@ihabdevteam/core 0.9.6 → 0.9.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.
- package/dist/components.cjs.js +33 -33
- package/dist/components.esm.js +23 -23
- package/dist/ihabdevteam-core.cjs.js +2 -2
- package/dist/ihabdevteam-core.d.ts +18 -2
- package/dist/ihabdevteam-core.esm.js +12 -12
- package/dist/packages/core/src/components/Tooltip/DebugTooltip.d.ts +18 -2
- package/dist/packages/core/src/components/Tooltip/DebugTooltip.js +18 -5
- package/dist/packages/core/src/components/Training/TrainingStepConfirm.js +37 -37
- package/package.json +1 -1
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import './DebugTooltip.css';
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* 결과 팝업을 실제 훈련 없이 열어보는 개발용 단추 넷.
|
|
5
|
+
*
|
|
6
|
+
* 예전에는 파일 안의 `const DEBUG = false` 로만 켤 수 있었다. 모노레포 시절에는
|
|
7
|
+
* packages/core 를 그 자리에서 고칠 수 있었으니 말이 됐는데, **패키지로 분리되면서
|
|
8
|
+
* 켤 방법이 사라졌다** — 소비자가 node_modules 를 고치지 않는 한 언제나 null 이다.
|
|
9
|
+
* 실제로 speechear-v2 가 이것을 렌더하고 있고, 아무것도 그려지지 않는다.
|
|
10
|
+
*
|
|
11
|
+
* 그래서 스위치를 밖으로 낸다. 기본값은 그대로 꺼짐이라 지금 쓰는 곳은 안 바뀐다.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* <DebugResultLauncher enabled={import.meta.env.DEV} renderResultPopup={…} />
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export default function DebugResultLauncher({ enabled, renderResultPopup }?: {
|
|
19
|
+
/** 켜야 단추가 보인다. 기본은 꺼짐 — 배포 화면에 실수로 남지 않게. */
|
|
20
|
+
enabled?: boolean;
|
|
5
21
|
renderResultPopup?: (props: {
|
|
6
22
|
score?: number | null;
|
|
7
23
|
contextsLength?: number | null;
|
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from 'react';
|
|
4
4
|
import './DebugTooltip.css';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/**
|
|
6
|
+
* 결과 팝업을 실제 훈련 없이 열어보는 개발용 단추 넷.
|
|
7
|
+
*
|
|
8
|
+
* 예전에는 파일 안의 `const DEBUG = false` 로만 켤 수 있었다. 모노레포 시절에는
|
|
9
|
+
* packages/core 를 그 자리에서 고칠 수 있었으니 말이 됐는데, **패키지로 분리되면서
|
|
10
|
+
* 켤 방법이 사라졌다** — 소비자가 node_modules 를 고치지 않는 한 언제나 null 이다.
|
|
11
|
+
* 실제로 speechear-v2 가 이것을 렌더하고 있고, 아무것도 그려지지 않는다.
|
|
12
|
+
*
|
|
13
|
+
* 그래서 스위치를 밖으로 낸다. 기본값은 그대로 꺼짐이라 지금 쓰는 곳은 안 바뀐다.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <DebugResultLauncher enabled={import.meta.env.DEV} renderResultPopup={…} />
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export default function DebugResultLauncher({ enabled = false, renderResultPopup } = {}) {
|
|
8
21
|
const [openType, setOpenType] = useState(null);
|
|
9
22
|
const [payload, setPayload] = useState(null);
|
|
10
23
|
const openPopup = (type) => {
|
|
@@ -66,8 +79,8 @@ export default function DebugResultLauncher({ renderResultPopup } = {}) {
|
|
|
66
79
|
const handleShortPopup = useCallback(() => openPopup('short'), []);
|
|
67
80
|
const handleLongPopup = useCallback(() => openPopup('long'), []);
|
|
68
81
|
const handleOrderPopup = useCallback(() => openPopup('order'), []);
|
|
69
|
-
// 훅 규칙 준수를 위해 모든 훅 호출 뒤에서
|
|
70
|
-
if (!
|
|
82
|
+
// 훅 규칙 준수를 위해 모든 훅 호출 뒤에서 분기한다 — 위 훅들은 언제나 같은 수만큼 돈다.
|
|
83
|
+
if (!enabled)
|
|
71
84
|
return null;
|
|
72
85
|
return (_jsxs("div", { className: "debug-result-launcher", children: [_jsxs("div", { className: "debug-buttons", children: [_jsx("button", { className: "debug-button", onClick: handleNoisePopup, children: "Noise \uACB0\uACFC\uBCF4\uAE30" }), _jsx("button", { className: "debug-button", onClick: handleShortPopup, children: "ShortStory \uACB0\uACFC\uBCF4\uAE30" }), _jsx("button", { className: "debug-button", onClick: handleLongPopup, children: "LongStory \uACB0\uACFC\uBCF4\uAE30" }), _jsx("button", { className: "debug-button", onClick: handleOrderPopup, children: "Order \uACB0\uACFC\uBCF4\uAE30" })] }), openType && payload ? renderResultPopup?.({
|
|
73
86
|
score: payload.score,
|
|
@@ -61,31 +61,34 @@ export default function TrainingStepConfirm({ index, stepIndex, isLast = false,
|
|
|
61
61
|
/* 여기 오면 늘 active 다 — canNotify 는 아래 흐름을 그대로 두기 위해 남긴다. */
|
|
62
62
|
const canNotify = currentState === 'active' || currentState === 'done';
|
|
63
63
|
if (multiSelect) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
64
|
+
/* 다음 선택 집합을 **updater 밖에서** 만든다. 예전에는 updater 안에서 만들면서
|
|
65
|
+
거기서 onChange/onDone 까지 불렀는데, updater 는 순수해야 하고 React 가 렌더
|
|
66
|
+
단계에 부를 수 있다 — 그래서 "렌더 중에 다른 컴포넌트를 갱신한다" 는 경고가
|
|
67
|
+
났다(실측). 클릭은 discrete event 라 처리 시점에 상태가 이미 커밋돼 있으므로
|
|
68
|
+
여기서 selectedSet 을 읽어도 어긋나지 않는다. */
|
|
69
|
+
const next = new Set(selectedSet);
|
|
70
|
+
const wasSelected = next.has(val);
|
|
71
|
+
if (wasSelected)
|
|
72
|
+
next.delete(val);
|
|
73
|
+
else
|
|
74
|
+
next.add(val);
|
|
75
|
+
setSelectedSet(next);
|
|
76
|
+
if (typeof onChange === 'function') {
|
|
77
|
+
onChange({ selectedValues: [...next], __ts: Date.now() });
|
|
78
|
+
}
|
|
79
|
+
if (canNotify) {
|
|
80
|
+
// correctCount가 지정된 문제는 그 개수를 다 채워야 제출된다 — 지정 안 됐을 때만
|
|
81
|
+
// (예: 정답이 정해지지 않은 단일 선택형) 아무거나 하나 고르면 바로 제출한다.
|
|
82
|
+
// 이전엔 "|| !wasSelected"로 묶여 있어 correctCount 지정 여부와 무관하게
|
|
83
|
+
// 추가 선택마다 항상 true가 되어, 목표 개수를 채우기 전에도 매번 제출됐다.
|
|
84
|
+
const hasCorrectCount = typeof correctCount === 'number' && correctCount > 0;
|
|
85
|
+
const shouldSubmitNow = hasCorrectCount ? next.size >= correctCount : !wasSelected;
|
|
86
|
+
if (shouldSubmitNow && typeof onDone === 'function') {
|
|
87
|
+
if (currentState === 'active') {
|
|
88
|
+
onDone({ selectedValues: [...next], __ts: Date.now() });
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
|
-
|
|
88
|
-
});
|
|
91
|
+
}
|
|
89
92
|
}
|
|
90
93
|
else {
|
|
91
94
|
setSelectedAnswer(val);
|
|
@@ -98,20 +101,17 @@ export default function TrainingStepConfirm({ index, stepIndex, isLast = false,
|
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
|
-
}, [choices, currentState, multiSelect, onChange, onDone, correctCount]);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}, [selectedSet, multiSelect, onChange, correctCount, currentState, onDone]);
|
|
104
|
+
}, [choices, currentState, multiSelect, onChange, onDone, correctCount, selectedSet]);
|
|
105
|
+
/* 여기 있던 effect 는 selectedSet 이 바뀔 때마다 onChange/onDone 을 **한 번 더**
|
|
106
|
+
불렀다. 클릭 핸들러가 이미 같은 일을 하고 있어 한 번 고를 때마다 두 번 보고됐다.
|
|
107
|
+
게다가 콜백이 그 effect 의 의존성에 있어서, 부르는 쪽이 인라인 화살표를 주고
|
|
108
|
+
받은 payload 를 담으면(payload 에 Date.now() 가 있어 담는 값이 매번 다르다)
|
|
109
|
+
onChange → 부모 리렌더 → 새 화살표 → effect → onChange … 로 끝나지 않았다.
|
|
110
|
+
실측: 고르기 한 번에 40회(세운 상한). 상한을 빼면 10분이 지나도 안 끝났다.
|
|
111
|
+
|
|
112
|
+
지울 수 있는 까닭은 selectedSet 을 실제로 채우는 곳이 클릭 핸들러 하나뿐이기
|
|
113
|
+
때문이다 — 나머지 둘(choices 바뀜·페이지 전환)은 빈 Set 으로 되돌리는 자리라
|
|
114
|
+
저 effect 의 `size === 0` 가드에 걸려 어차피 아무것도 안 했다. */
|
|
115
115
|
const btnsClassName = `training-step__buttons${Array.isArray(choices) && choices.length > 3 ? ' training-step__buttons--wrap-3' : ''}`;
|
|
116
116
|
return (_jsx(TrainingStep, { index: index, stepIndex: stepIndex, isLast: isLast, title: resolvedTitle, useTitleFaded: useTitleFaded, children: _jsx("div", { className: btnsClassName, children: Array.isArray(choices) && choices.length > 0 ? (choices.map((c, i) => (_jsx(Button, { size: 56, type: "bar", onClick: () => handleSelect(i), ariaLabel: String(c), label: c, useLabel: true, icon: choiceIcons ? choiceIcons[String(c)] : undefined, variant: 'default', disabled: currentState === 'disabled', selected: multiSelect ? selectedSet.has(c) : selectedAnswer === c, useDisabledCovered: useDisabledCovered, minWidth: 0 }, String(c))))) : null }) }));
|
|
117
117
|
}
|