@ihabdevteam/core 0.199.0 → 0.201.0
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/CHANGELOG.md +205 -0
- package/dist/components.cjs.js +177 -177
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +3 -3
- package/dist/i18n.cjs.js +3 -3
- package/dist/ihabdevteam-core.cjs.js +186 -186
- package/dist/ihabdevteam-core.d.ts +304 -18
- package/dist/packages/core/src/components/Header.css +143 -73
- package/dist/packages/core/src/components/Header.js +8 -2
- package/dist/packages/core/src/components/ItemActions.css +12 -0
- package/dist/packages/core/src/components/ItemActions.d.ts +95 -0
- package/dist/packages/core/src/components/ItemActions.js +82 -0
- package/dist/packages/core/src/components/Popup/useDiscardGuard.d.ts +48 -0
- package/dist/packages/core/src/components/Popup/useDiscardGuard.js +56 -0
- package/dist/packages/core/src/components/SideNav.css +13 -1
- package/dist/packages/core/src/components.d.ts +4 -0
- package/dist/packages/core/src/components.js +2 -0
- package/dist/packages/core/src/hooks/useListControls.d.ts +66 -0
- package/dist/packages/core/src/hooks/useListControls.js +52 -0
- package/dist/packages/core/src/hooks.d.ts +1 -0
- package/dist/packages/core/src/hooks.js +1 -0
- package/dist/packages/core/src/i18n/locales/en.json +5 -0
- package/dist/packages/core/src/i18n/locales/ko.json +5 -0
- package/dist/packages/core/src/utils/chunkSkew.d.ts +27 -0
- package/dist/packages/core/src/utils/chunkSkew.js +44 -0
- package/dist/packages/core/src/utils/format.d.ts +14 -14
- package/dist/packages/core/src/utils/format.js +27 -1
- package/dist/packages/core/src/utils/sessionTab.d.ts +36 -0
- package/dist/packages/core/src/utils/sessionTab.js +36 -0
- package/dist/packages/core/src/utils.d.ts +4 -2
- package/dist/packages/core/src/utils.js +3 -2
- package/dist/packages/core/src/version.d.ts +1 -1
- package/dist/packages/core/src/version.js +1 -1
- package/dist/utils.cjs.js +2 -2
- package/package.json +1 -1
|
@@ -1626,7 +1626,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1626
1626
|
*
|
|
1627
1627
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1628
1628
|
*/
|
|
1629
|
-
export declare const CORE_VERSION = "0.
|
|
1629
|
+
export declare const CORE_VERSION = "0.201.0";
|
|
1630
1630
|
|
|
1631
1631
|
/** @public */
|
|
1632
1632
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -2042,6 +2042,23 @@ export declare type DialogSize = 'sm' | 'md' | 'lg' | 'full';
|
|
|
2042
2042
|
*/
|
|
2043
2043
|
declare type DifficultyLevel = 'random' | 'jong' | 'jung' | 'cho' | 'count';
|
|
2044
2044
|
|
|
2045
|
+
/** @public */
|
|
2046
|
+
export declare interface DiscardGuard {
|
|
2047
|
+
/** `Popup` 의 `beforeClose` 로 넘긴다 — `false` 면 안 닫힌다 */
|
|
2048
|
+
beforeClose: () => boolean;
|
|
2049
|
+
/** 물음창. JSX 어디에 두든 된다(아래 글 참고) */
|
|
2050
|
+
discardPopup: ReactNode;
|
|
2051
|
+
/** **다음 한 번은 안 묻는다** — 저장이 끝나고 스스로 닫을 때 부른다 */
|
|
2052
|
+
allowClose: () => void;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/** @public */
|
|
2056
|
+
export declare interface DiscardGuardLabels {
|
|
2057
|
+
title?: string;
|
|
2058
|
+
message?: string;
|
|
2059
|
+
confirmLabel?: string;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2045
2062
|
/** @public */
|
|
2046
2063
|
export declare function Divider({ orientation, label, className }: DividerProps): JSX.Element;
|
|
2047
2064
|
|
|
@@ -2980,6 +2997,34 @@ export declare function injectPassCardBgTheme(el: HTMLElement | null, theme: Pas
|
|
|
2980
2997
|
/** theme 객체를 부모 요소에 CSS var로 주입 */
|
|
2981
2998
|
export declare function injectPromoBannerBgTheme(el: HTMLElement | null, theme: PromoBannerBgTheme): void;
|
|
2982
2999
|
|
|
3000
|
+
/**
|
|
3001
|
+
* **위 복구를 창에 매단다** — 듣는 자리 둘을 한 번에 달고, 떼는 함수를 돌려준다.
|
|
3002
|
+
*
|
|
3003
|
+
* 복구하는 일(`recoverFromSkew`)은 코어에 있었는데 **어디에 어떻게 매다는지**가 소비자마다
|
|
3004
|
+
* 한 벌씩이었다(말귀 회원앱·어드민의 `main.tsx`, 글자까지 같았다 — 2026-09-27). 그 열몇 줄에
|
|
3005
|
+
* 조용히 틀어지는 조각이 둘 있다:
|
|
3006
|
+
*
|
|
3007
|
+
* 1. **`error` 는 캡처 단계로 들어야 한다**(셋째 인자 `true`). `link`·`style` 의 `error` 는
|
|
3008
|
+
* **버블링하지 않아** 창까지 올라오지 않는다. 한쪽에서 그 `true` 를 빠뜨리면 그 앱만 CSS
|
|
3009
|
+
* 청크 실패를 못 듣고, **화면은 뜨는데 스타일만 전부 빠진 채** 남는다(말귀 실제 QA 건 —
|
|
3010
|
+
* 매니저 화면이 기본 스타일로 무너졌다). 스크립트는 멀쩡히 도니 오류도 안 난다.
|
|
3011
|
+
* 2. **`vite:preloadError` 는 막아야(`preventDefault`)** Vite 헬퍼가 오류를 안 던진다.
|
|
3012
|
+
* 막지 않으면 새로고침과 오류 화면이 함께 난다(이 파일 머리말).
|
|
3013
|
+
*
|
|
3014
|
+
* CSS 쪽은 `preventDefault` 를 하지 않는다 — 막을 기본 동작이 없고, 그 오류는 다른
|
|
3015
|
+
* 듣는 이(오류 보고 등)에게도 그대로 가는 편이 맞다.
|
|
3016
|
+
*
|
|
3017
|
+
* ```ts
|
|
3018
|
+
* // main.tsx — 한 줄이면 된다
|
|
3019
|
+
* installSkewRecovery()
|
|
3020
|
+
* ```
|
|
3021
|
+
*
|
|
3022
|
+
* 창이 없는 곳(SSR·시험)에서는 아무것도 안 달고 빈 함수를 돌려준다.
|
|
3023
|
+
* @returns 떼는 함수. 앱 시작점에서는 뗄 일이 없지만, 시험이 뒷정리할 수 있어야 한다.
|
|
3024
|
+
* @public
|
|
3025
|
+
*/
|
|
3026
|
+
export declare function installSkewRecovery(): () => void;
|
|
3027
|
+
|
|
2983
3028
|
/** @public 검색어가 초성으로만 이루어졌는지 (예: 'ㅎㅈ' → true, '한' → false) */
|
|
2984
3029
|
export declare function isAllChoseong(search: string): boolean;
|
|
2985
3030
|
|
|
@@ -3005,6 +3050,13 @@ export declare function isManualOnlyType(typeKey: WordTrainingTypeKey): boolean;
|
|
|
3005
3050
|
*/
|
|
3006
3051
|
export declare function isNetworkErrorMessage(msg: string | null | undefined): boolean;
|
|
3007
3052
|
|
|
3053
|
+
/**
|
|
3054
|
+
* 연락처가 **사람에게 거는 번호**의 모양인가 — 대표번호(15xx~19xx)는 안 받는다.
|
|
3055
|
+
* 규칙은 `PERSONAL_PHONE_REGEX`, 대표번호까지 받으려면 `isValidPhone`.
|
|
3056
|
+
* @public
|
|
3057
|
+
*/
|
|
3058
|
+
export declare function isPersonalPhone(phone: string): boolean;
|
|
3059
|
+
|
|
3008
3060
|
/** @public */
|
|
3009
3061
|
export declare function isSelectionCorrect({ payload, selected, choices, correctAnswers }: IsSelectionCorrectParams): boolean | undefined;
|
|
3010
3062
|
|
|
@@ -3015,6 +3067,39 @@ declare interface IsSelectionCorrectParams {
|
|
|
3015
3067
|
correctAnswers?: unknown[];
|
|
3016
3068
|
}
|
|
3017
3069
|
|
|
3070
|
+
/**
|
|
3071
|
+
* **이 이벤트가 이 탭에 실제로 반영된 것인가.**
|
|
3072
|
+
*
|
|
3073
|
+
* 로그인을 탭마다 가르려고 `auth.storage` 를 `sessionStorage` 로 두어도, GoTrue 의
|
|
3074
|
+
* `BroadcastChannel` 은 **저장소 종류와 무관하게** 세션 이벤트를 같은 브라우저의 모든 탭에
|
|
3075
|
+
* 뿌린다(여러 탭을 맞추라고 만든 길이다). 그래서 받은 세션을 그대로 믿으면 **다른 탭에서
|
|
3076
|
+
* 로그인한 세션이 이 탭 화면에 얹힌다** — 로그인된 것처럼 그려지는데 이 탭의 저장소는 비어
|
|
3077
|
+
* 있어 API 호출은 전부 미인증으로 실패한다. 반대로 이 탭에 제 세션이 있는데 다른 탭의
|
|
3078
|
+
* 로그아웃이 날아와 뒤섞이기도 한다. 말귀 어드민에서는 그렇게 얹히는 것이 `ROLE_ADMIN`
|
|
3079
|
+
* 세션이다.
|
|
3080
|
+
*
|
|
3081
|
+
* 가르는 법은 하나다 — `getSession()` 으로 **이 탭 저장소를 다시 읽어** 받은 것과 맞대 본다.
|
|
3082
|
+
* 그 한 줄과 여덟 줄짜리 까닭이 말귀 회원앱·어드민에 한 벌씩 있었다(2026-09-27).
|
|
3083
|
+
*
|
|
3084
|
+
* **로그아웃(세션 없음)은 늘 받는다.** 지울 것이 없다는 말은 어느 탭에서 왔든 참이고,
|
|
3085
|
+
* 여기서 막으면 이 탭이 로그인된 채로 남는다 — 막는 것보다 나쁜 쪽이다.
|
|
3086
|
+
*
|
|
3087
|
+
* ```ts
|
|
3088
|
+
* onAuthStateChange((event, 받은것) => {
|
|
3089
|
+
* getSession().then(({ data }) => {
|
|
3090
|
+
* const 이탭것 = data.session
|
|
3091
|
+
* if (!isSessionForThisTab(받은것, 이탭것)) return // 다른 탭에서 온 것 — 무시
|
|
3092
|
+
* …이탭것 으로 상태를 맞춘다
|
|
3093
|
+
* })
|
|
3094
|
+
* })
|
|
3095
|
+
* ```
|
|
3096
|
+
*
|
|
3097
|
+
* @param 받은것 이벤트가 실어 온 세션(없을 수 있다)
|
|
3098
|
+
* @param 이탭것 `getSession()` 이 이 탭 저장소에서 다시 읽은 세션
|
|
3099
|
+
* @public
|
|
3100
|
+
*/
|
|
3101
|
+
export declare function isSessionForThisTab(받은것: TabSessionLike | null | undefined, 이탭것: TabSessionLike | null | undefined): boolean;
|
|
3102
|
+
|
|
3018
3103
|
/**
|
|
3019
3104
|
* @public 문자열이 음절 패턴 DSL 문법인지 — 쉼표(,) 포함 여부로 판별한다.
|
|
3020
3105
|
* 일반 단어 검색어엔 쉼표가 쓰이지 않으므로 충돌 없이 자동 분기할 수 있다.
|
|
@@ -3047,6 +3132,90 @@ export declare function isValidPhone(phone: string): boolean;
|
|
|
3047
3132
|
/** @public 단어가 해당 타입의 문제(정답) 단어로 구조적으로 유효한가. 제약 없는 타입은 항상 true. */
|
|
3048
3133
|
export declare function isValidQuestionWord(typeKey: WordTrainingTypeKey, word: string): boolean;
|
|
3049
3134
|
|
|
3135
|
+
/** @public */
|
|
3136
|
+
export declare interface ItemAction {
|
|
3137
|
+
/**
|
|
3138
|
+
* 읽어 줄 이름. 아이콘뿐인 단추라 **이름과 말풍선에 같은 말**이 붙는다 —
|
|
3139
|
+
* 한쪽만 달면 눈으로 본 말과 낭독기가 읽는 말이 갈린다.
|
|
3140
|
+
*/
|
|
3141
|
+
label: string;
|
|
3142
|
+
onClick: () => void;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
/**
|
|
3146
|
+
* **보고 있는 한 항목에 하는 일들** — 창 바닥 왼쪽에 서는 아이콘 단추 줄.
|
|
3147
|
+
*
|
|
3148
|
+
* 무언가를 **보여 주는 창**이 동시에 **그것을 손대는 자리**이기도 한 곳에 선다. 말귀에 그런
|
|
3149
|
+
* 창이 셋 있었고(훈련 묶음 실행 · 단어훈련 카드 실행 · 단어표 미리보기), 세 곳이 같은 다섯
|
|
3150
|
+
* 단추를 같은 차례로 세우면서 규칙을 한 벌씩 베껴 적고 있었다(2026-09-26).
|
|
3151
|
+
*
|
|
3152
|
+
* 벌리는 일은 이미 코어 몫이다(`Popup` 의 `footerStart`/`footerEnd`, 0.190.0). 남아 있던 것은
|
|
3153
|
+
* **무엇을 어떤 차례로 세우고 무엇을 닫고 하는가**인데, 그것이 베껴져 있으면 한 곳만 고쳐도
|
|
3154
|
+
* 아무 데서도 안 터진다 — 같은 모양의 창들이 조용히 서로 다르게 움직인다. 실제로 **링크 단추의
|
|
3155
|
+
* 조건이 세 곳에서 달랐다**(둘은 길이 있을 때만, 하나는 늘 세움).
|
|
3156
|
+
*
|
|
3157
|
+
* 이 조각이 쥐는 것 —
|
|
3158
|
+
* · **차례와 꼴** — 고치기 · 복제 · 발행 · 링크 · 지우기, 모두 `size={40} type="dot"`.
|
|
3159
|
+
* · **닫고 할 것과 안 닫고 할 것** — 고치기 · 지우기는 `onClose` 를 먼저 부른다. 둘 다 **다른 창을
|
|
3160
|
+
* 여는** 일이라 안 닫으면 창 위에 창이 겹치고, 지운 것을 계속 보고 있게 된다. 복제 · 발행 ·
|
|
3161
|
+
* 링크는 안 닫는다 — 보던 것을 그대로 두고 곁에서 일어나는 일이다.
|
|
3162
|
+
* · **안 준 것은 안 세운다** — 부르는 쪽이 손으로 `&&` 하던 자리다. 갈라진 그 자리가 여기다.
|
|
3163
|
+
* · **뒤집히는 단추는 하나** — `publish.published` 가 어느 쪽인지 고른다.
|
|
3164
|
+
*
|
|
3165
|
+
* 새 갈래가 필요하면 **여기에 칸을 만든다.** 아무거나 끼울 구멍(`children` 같은 것)을 두지 않는
|
|
3166
|
+
* 까닭은, 그 구멍으로 들어온 단추는 닫고 할지 말지를 또 부르는 쪽이 정하게 되어 이 조각이
|
|
3167
|
+
* 막으려던 갈라짐이 그대로 돌아오기 때문이다.
|
|
3168
|
+
*
|
|
3169
|
+
* `Popup` 의 `footerStart` 안에서도, 손수 지은 바닥 줄 안에서도 같게 선다 — 제 간격을 스스로
|
|
3170
|
+
* 든다(`--spacing-xs`).
|
|
3171
|
+
*
|
|
3172
|
+
* 이름표는 부르는 쪽의 말이다 — 아래 예의 글자 자리에 그쪽 사전에서 꺼낸 말을 넣는다.
|
|
3173
|
+
* (여기에 `t('…')` 을 적지 않는 까닭은, 소스를 훑는 시험이 **주석 속 예시도 실제로 부르는
|
|
3174
|
+
* 열쇠로 읽기** 때문이다. 글이 시험을 깨뜨릴 수 있으면 안 된다.)
|
|
3175
|
+
*
|
|
3176
|
+
* @example
|
|
3177
|
+
* <Popup
|
|
3178
|
+
* title={표.name}
|
|
3179
|
+
* footerStart={({ closeWithAnimation }) => (
|
|
3180
|
+
* <ItemActions
|
|
3181
|
+
* onClose={closeWithAnimation}
|
|
3182
|
+
* edit={{ label: '수정', onClick: () => openEdit(표) }}
|
|
3183
|
+
* duplicate={{ label: '복제', onClick: () => duplicate(표) }}
|
|
3184
|
+
* publish={{
|
|
3185
|
+
* published: 표.isPublished,
|
|
3186
|
+
* publish: { label: '말귀자료실에 올리기', onClick: () => publish(표) },
|
|
3187
|
+
* unpublish: { label: '말귀자료실에서 내리기', onClick: () => unpublish(표) },
|
|
3188
|
+
* }}
|
|
3189
|
+
* copyLink={링크길있나 ? { label: '링크 복사', onClick: () => copyLink(표) } : undefined}
|
|
3190
|
+
* remove={{ label: '삭제', onClick: () => askDelete(표) }} />
|
|
3191
|
+
* )} />
|
|
3192
|
+
* @public
|
|
3193
|
+
*/
|
|
3194
|
+
export declare function ItemActions(props: ItemActionsProps): JSX.Element;
|
|
3195
|
+
|
|
3196
|
+
/** @public */
|
|
3197
|
+
export declare interface ItemActionsProps {
|
|
3198
|
+
/**
|
|
3199
|
+
* 이 창을 닫는 길. **닫고 하는 것**(고치기 · 지우기)이 제 일을 하기 전에 이것을 먼저 부른다.
|
|
3200
|
+
*
|
|
3201
|
+
* 줄 것이 없어 보여도 비우지 않는다 — 안 닫으면 고칠 창이 보던 창 **위에 겹쳐** 서고,
|
|
3202
|
+
* 지운 것을 계속 보고 있는 창이 남는다. 그 사고를 막는 것이 이 줄의 규칙이라
|
|
3203
|
+
* 타입으로 못박아 둔다.
|
|
3204
|
+
*/
|
|
3205
|
+
onClose: () => void;
|
|
3206
|
+
/** 고치기 — **닫고 한다**(다른 창을 연다) */
|
|
3207
|
+
edit?: ItemAction;
|
|
3208
|
+
/** 복제 — 안 닫는다(보던 것을 계속 본다) */
|
|
3209
|
+
duplicate?: ItemAction;
|
|
3210
|
+
/** 발행 ↔ 발행취소 — 안 닫는다. 올리고 내릴 길이 없으면 아예 주지 않는다 */
|
|
3211
|
+
publish?: ItemPublishAction;
|
|
3212
|
+
/** 링크 복사 — 안 닫는다. 손잡이를 안 준 화면에서는 주지 않는다 */
|
|
3213
|
+
copyLink?: ItemAction;
|
|
3214
|
+
/** 지우기 — **닫고 한다** */
|
|
3215
|
+
remove?: ItemAction;
|
|
3216
|
+
className?: string;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3050
3219
|
/**
|
|
3051
3220
|
* 맞았는지 — 훈련 유형마다 칸 이름이 다르다.
|
|
3052
3221
|
*
|
|
@@ -3056,6 +3225,20 @@ export declare function isValidQuestionWord(typeKey: WordTrainingTypeKey, word:
|
|
|
3056
3225
|
*/
|
|
3057
3226
|
export declare function itemCorrectness(r: RawResult): boolean | null;
|
|
3058
3227
|
|
|
3228
|
+
/**
|
|
3229
|
+
* **한 단추가 뒤집히는 자리**(올리기 ↔ 내리기).
|
|
3230
|
+
*
|
|
3231
|
+
* 둘을 나란히 세우지 않는 까닭은 자리가 아니라 읽힘이다 — 나란히 두면 **지금 어느 쪽인지**를
|
|
3232
|
+
* 화면이 말해 주지 않는다. 아이콘은 그대로 두고 하는 일과 이름만 갈린다.
|
|
3233
|
+
* @public
|
|
3234
|
+
*/
|
|
3235
|
+
export declare interface ItemPublishAction {
|
|
3236
|
+
/** 지금 올라가 있나 — 참이면 `unpublish` 쪽이 선다 */
|
|
3237
|
+
published: boolean;
|
|
3238
|
+
publish: ItemAction;
|
|
3239
|
+
unpublish: ItemAction;
|
|
3240
|
+
}
|
|
3241
|
+
|
|
3059
3242
|
/**
|
|
3060
3243
|
* @public TTS 전용 변환 — 문장 속 따옴표로 감싼 낱개 자모("ㄱ")를 낱자 이름("기역")으로 치환하고 뒤 조사를 맞춘다.
|
|
3061
3244
|
* 표시 텍스트는 자모 그대로 두고(교육용 표기), 합성·캐시 키에만 이 변환을 적용한다. 자음은 명칭(ㄱ→기역),
|
|
@@ -3335,6 +3518,37 @@ export declare interface LineChartScales {
|
|
|
3335
3518
|
};
|
|
3336
3519
|
}
|
|
3337
3520
|
|
|
3521
|
+
/** @public */
|
|
3522
|
+
export declare interface ListControls {
|
|
3523
|
+
/** 찾는 말. 무엇을 어떻게 거를지는 **부르는 쪽**이 정한다 — 이 훅은 그 말만 쥔다 */
|
|
3524
|
+
query: string;
|
|
3525
|
+
setQuery: (v: string) => void;
|
|
3526
|
+
/** 고르개에 그대로 주는 글 */
|
|
3527
|
+
pageSize: string;
|
|
3528
|
+
setPageSize: (v: string) => void;
|
|
3529
|
+
/** `usePagedList` 에 그대로 주는 수 — `Number(pageSize)` 가 못 읽히면 기본값이다 */
|
|
3530
|
+
perPage: number;
|
|
3531
|
+
page: number;
|
|
3532
|
+
setPage: (n: number) => void;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
/** @public */
|
|
3536
|
+
export declare interface ListControlsOptions {
|
|
3537
|
+
/**
|
|
3538
|
+
* 쪽크기 기본값. 글인 것은 고르개(`Dropdown`)가 글을 다루기 때문이다 — 숫자로 바꾸는 일은
|
|
3539
|
+
* 이 훅이 한다(`perPage`). 안 주면 `'25'`.
|
|
3540
|
+
*/
|
|
3541
|
+
defaultPageSize?: string;
|
|
3542
|
+
/**
|
|
3543
|
+
* 검색어·쪽크기 **말고 더** 바뀌면 첫 쪽으로 돌아가야 하는 것들 — 갈래(`type`) · 보기(`view`) 처럼
|
|
3544
|
+
* 목록을 거르는 값이다. 검색어와 쪽크기는 이 훅이 스스로 넣으므로 **여기 또 적지 않는다.**
|
|
3545
|
+
*
|
|
3546
|
+
* 값은 글·수·참거짓만 받는다. 객체를 넣으면 `String()` 이 전부 `[object Object]` 로 만들어
|
|
3547
|
+
* **무엇이 바뀌어도 안 바뀐 것이 되고, 그래도 아무 데서도 안 터진다** — 타입으로 막는다.
|
|
3548
|
+
*/
|
|
3549
|
+
resetOn?: readonly ListResetValue[];
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3338
3552
|
/** @public */
|
|
3339
3553
|
export declare function ListenStep({ _stepIndex, _totalSteps, ...rest }: StepWrapperProps): JSX.Element;
|
|
3340
3554
|
|
|
@@ -3343,6 +3557,9 @@ export declare function ListPopup<T extends {
|
|
|
3343
3557
|
id?: string | number;
|
|
3344
3558
|
}>(props: PopupListProps<T>): JSX.Element;
|
|
3345
3559
|
|
|
3560
|
+
/** 첫 쪽으로 돌아갈 까닭을 잇는 값 — **글로 적을 수 있는 것**만 받는다(아래 `resetOn` 참고) @public */
|
|
3561
|
+
export declare type ListResetValue = string | number | boolean | null | undefined;
|
|
3562
|
+
|
|
3346
3563
|
/** @public */
|
|
3347
3564
|
export declare function Loader(props: LoaderProps): JSX.Element;
|
|
3348
3565
|
|
|
@@ -4428,6 +4645,18 @@ export declare interface PaymentPlanTier {
|
|
|
4428
4645
|
/** @public */
|
|
4429
4646
|
export declare const pctToLevel: (pct: number) => number;
|
|
4430
4647
|
|
|
4648
|
+
/**
|
|
4649
|
+
* **대표번호를 뺀 것만** 받는 자 — `PHONE_REGEX` 에서 대표번호 갈래를 뺀 것이다.
|
|
4650
|
+
*
|
|
4651
|
+
* 말귀 회원앱·어드민이 `isValidPhone(phone) && phone.startsWith('0')` 를 한 벌씩 들고
|
|
4652
|
+
* 있었다(2026-09-27). 그 칸들은 **사람에게 거는 번호**를 받는 자리라 `1588-1234` 를
|
|
4653
|
+
* 거절해 왔다 — 뜻은 맞는데 **`'0'` 으로 시작하는가**로 적혀 있었다. 그것이 지금 같은
|
|
4654
|
+
* 뜻인 것은 `PHONE_REGEX` 의 비-0 갈래가 대표번호 하나뿐이라서일 뿐이고, 0 으로 시작하지
|
|
4655
|
+
* 않는 대역이 하나 더 생기는 날 **두 앱이 조용히 그것을 받게 된다.** 뜻대로 적어 둔다.
|
|
4656
|
+
* @public
|
|
4657
|
+
*/
|
|
4658
|
+
export declare const PERSONAL_PHONE_REGEX: RegExp;
|
|
4659
|
+
|
|
4431
4660
|
/**
|
|
4432
4661
|
* 인물 소개 카드 — 원형 사진 위에 이름·직함, 아래에 경력 목록을 세로로 쌓는다.
|
|
4433
4662
|
* 회사 소개의 운영진 섹션, 연구진 목록 등에 사용.
|
|
@@ -4455,23 +4684,6 @@ export declare interface PersonCardProps {
|
|
|
4455
4684
|
className?: string;
|
|
4456
4685
|
}
|
|
4457
4686
|
|
|
4458
|
-
/**
|
|
4459
|
-
* 연락처가 **`formatPhone` 이 끝까지 채워 낸 모양** 인가.
|
|
4460
|
-
*
|
|
4461
|
-
* 만드는 쪽(`formatPhone`)은 코어인데 받는 쪽(형식 검사)은 소비자마다 한 벌씩 두고 있었다 —
|
|
4462
|
-
* 말귀 회원앱·어드민에 같은 정규식이 한 벌씩 있었고, 둘이 같은지는 짝 시험이 지켰지만
|
|
4463
|
-
* **코어가 만든 모양과 맞는지는 아무도 안 쟀다.** 실제로 코어가 050X·0303(앞자리 네 자리)을
|
|
4464
|
-
* 받았을 때 그 두 벌도 손으로 함께 고쳐야 했다. 그래서 짝을 여기 둔다 — 아래 시험이
|
|
4465
|
-
* "formatPhone 이 끝까지 채운 값은 늘 통과한다" 를 못 박는다.
|
|
4466
|
-
*
|
|
4467
|
-
* `formatPhone` 은 **덜 채운 입력을 그대로 돌려준다**(`010-12`). 그래서 빈값 검사만으로는
|
|
4468
|
-
* 모자라고, 이 함수가 그 자리를 맡는다.
|
|
4469
|
-
*
|
|
4470
|
-
* 받는 꼴 — 휴대폰·지역번호(02·0NN)·050X 안심번호·0303 착신전환·**1588 등 대표번호**.
|
|
4471
|
-
* 대표번호는 `formatPhone` 이 `1588-1234` 로 만들어 내므로 여기서도 받는다
|
|
4472
|
-
* (0 으로 시작하는 것만 받던 소비자 정규식은 그 번호를 거절했다 — 코어가 만든 모양을 코어가 거절하는 꼴).
|
|
4473
|
-
* @public
|
|
4474
|
-
*/
|
|
4475
4687
|
export declare const PHONE_REGEX: RegExp;
|
|
4476
4688
|
|
|
4477
4689
|
/**
|
|
@@ -7257,6 +7469,11 @@ export declare function tableTrends(records: readonly ScoreRecord[]): TableTrend
|
|
|
7257
7469
|
*/
|
|
7258
7470
|
export declare function Tabs({ items, value, onChange, className, size }: TabsProps): JSX.Element;
|
|
7259
7471
|
|
|
7472
|
+
/** 이 자가 보는 것은 **이것뿐이다** — 세션의 나머지는 알 필요가 없다 @public */
|
|
7473
|
+
export declare interface TabSessionLike {
|
|
7474
|
+
access_token: string;
|
|
7475
|
+
}
|
|
7476
|
+
|
|
7260
7477
|
/** @public */
|
|
7261
7478
|
export declare interface TabsProps {
|
|
7262
7479
|
items?: TabItem[];
|
|
@@ -8972,6 +9189,39 @@ export declare function useDeviceManagement(): {
|
|
|
8972
9189
|
handleSelectFromList: (form: DeviceForm) => void;
|
|
8973
9190
|
};
|
|
8974
9191
|
|
|
9192
|
+
/**
|
|
9193
|
+
* **쓰던 것을 버리고 닫으려 할 때 한 번 더 묻는다.**
|
|
9194
|
+
*
|
|
9195
|
+
* 코어 0.128.0 부터 창은 어두운 바탕을 눌러도 닫힌다(사장님 지시 2026-09-14 "기본값으로").
|
|
9196
|
+
* 긴 글을 받는 창에서 바깥을 한 번 잘못 누르면 쓰던 것이 통째로 사라진다 — 그 자리를 막는다.
|
|
9197
|
+
*
|
|
9198
|
+
* 말귀 회원앱과 어드민에 한 벌씩 있었고(2026-09-27) **어드민 쪽이 윗집합**이었다. 갈라져
|
|
9199
|
+
* 있던 탓에 어드민에서 찾은 고침(`allowClose`)이 회원앱으로 건너오지 못했다 — 회원앱
|
|
9200
|
+
* 고객 추가 창이 **저장을 끝내고도 "저장했는데 버릴까요?" 를 물었다.** 여기 한 벌로 둔다.
|
|
9201
|
+
*
|
|
9202
|
+
* **`allowClose` 가 그 고침이다.** 저장이 끝나고 창이 스스로 닫을 때(`closeWithAnimation`)도
|
|
9203
|
+
* 그 길은 `beforeClose` 를 거치는데, 폼은 방금 채워 달라진 채라 "버릴까요" 가 뜬다. 저장에
|
|
9204
|
+
* 성공한 직후 `allowClose()` 를 부르면 **다음 한 번만** 안 묻고 닫는다. 같은 틱에 닫으므로
|
|
9205
|
+
* 상태가 아니라 ref 로 든다 — 상태로 들면 다시 그려지기 전에 닫혀 안 읽힌다.
|
|
9206
|
+
*
|
|
9207
|
+
* **물음창은 JSX 어디에 두든 된다.** 창은 `document.body` 로 옮겨 붙고(`createPortal`)
|
|
9208
|
+
* 그 차례는 코드 자리가 아니라 **뜬 시각**으로 정해지므로, 나중에 뜨는 이 물음창이 늘 위다.
|
|
9209
|
+
*
|
|
9210
|
+
* 문구는 `labels` 로 바꾼다 — 안 주면 사전(`common.discardConfirm.*`)을 따르고, 사전이
|
|
9211
|
+
* 없으면 한국어 기본값으로 그린다(번역을 안 쓰는 소비자도 그대로 읽힌다).
|
|
9212
|
+
*
|
|
9213
|
+
* ```tsx
|
|
9214
|
+
* const { beforeClose, discardPopup, allowClose } = useDiscardGuard(달라졌나, 닫기)
|
|
9215
|
+
* const 저장 = async () => { await 보내기(); allowClose(); closeWithAnimation() }
|
|
9216
|
+
* return <Popup title={제목} beforeClose={beforeClose} …>{…}{discardPopup}</Popup>
|
|
9217
|
+
* ```
|
|
9218
|
+
*
|
|
9219
|
+
* @param isDirty 지금 닫으면 잃을 것이 있는가
|
|
9220
|
+
* @param onClose 버리기로 했을 때 실제로 닫는 일
|
|
9221
|
+
* @public
|
|
9222
|
+
*/
|
|
9223
|
+
export declare function useDiscardGuard(isDirty: boolean, onClose: () => void, labels?: DiscardGuardLabels): DiscardGuard;
|
|
9224
|
+
|
|
8975
9225
|
/**
|
|
8976
9226
|
* 현재 화면의 제목을 `document.title`에 반영한다 — `'<제목> · <앱 이름>'`.
|
|
8977
9227
|
*
|
|
@@ -9061,6 +9311,42 @@ export declare function useHoverAnchor(): {
|
|
|
9061
9311
|
/** @public */
|
|
9062
9312
|
export declare function useI18n(): I18nContextValue;
|
|
9063
9313
|
|
|
9314
|
+
/**
|
|
9315
|
+
* **목록 화면의 조작부 한 벌** — 찾는 말 · 쪽크기 · 쪽 번호, 그리고 **언제 첫 쪽으로 돌아가는가.**
|
|
9316
|
+
*
|
|
9317
|
+
* 화면 셋이 같은 다섯 줄을 손으로 엮고 있었다(어드민 문장 자료 · 어드민 낱말 자료 · 회원앱 낱말
|
|
9318
|
+
* 관리 — 2026-09-27). `usePagedList` 를 코어로 올린 까닭이 한 겹 위에서 되풀이된 자리다.
|
|
9319
|
+
*
|
|
9320
|
+
* **이 훅이 막는 것은 조용히 틀어지는 쪽이다.** `usePageResetOnChange` 의 열쇠에서 한 조각만
|
|
9321
|
+
* 빠뜨리면 — 이를테면 쪽크기 — 25에서 100으로 바꿔도 `page` 가 7에 머문다. 총 쪽수가 2로 줄어
|
|
9322
|
+
* `usePagedList` 가 마지막 쪽으로 조이니 **2쪽이 뜨는데 사람은 1쪽을 본다고 믿는다.** 오류도
|
|
9323
|
+
* 빈 화면도 아니고 **엉뚱한 쪽**이라 아무도 못 알아챈다. 그래서 검색어와 쪽크기는 부르는 쪽이
|
|
9324
|
+
* 적는 것이 아니라 **이 훅이 스스로 넣는다.** 적지 않은 것은 빠뜨릴 수도 없다.
|
|
9325
|
+
*
|
|
9326
|
+
* 쪽크기 기본값(`'25'`)도 여기가 쥔다 — 세 화면에 `Number(pageSize) || 25` 가 한 벌씩 있었다.
|
|
9327
|
+
* (코어 `Pagination` 에는 쪽크기 고르개가 없어 맞춰야 할 기본값이 따로 있지는 않다. 겹친 것은
|
|
9328
|
+
* 화면 셋의 `25` 뿐이다.)
|
|
9329
|
+
*
|
|
9330
|
+
* **거르고 세우는 일은 안 맡는다.** 세 화면의 잣대가 서로 다르고(`묶음에서찾기` ·
|
|
9331
|
+
* `searchAndRankWords` · 보기별 거르기 + 낱말 순위), 거르기는 화면이 아는 일이다. 무엇보다
|
|
9332
|
+
* 그 일을 이 훅 안에 넣으면 **부르는 쪽의 `useMemo` 가 사라진다** — 낱말 자료 화면은 23,000개를
|
|
9333
|
+
* 세우므로 매 그림마다 다시 세우게 된다.
|
|
9334
|
+
*
|
|
9335
|
+
* 그래서 두 걸음이다. 거르는 일이 가운데 있고, 그 자리가 곧 `useMemo` 가 설 자리다:
|
|
9336
|
+
*
|
|
9337
|
+
* ```tsx
|
|
9338
|
+
* const 목록 = useListControls({ resetOn: [보기] })
|
|
9339
|
+
* const 걸러낸것 = useMemo(() => 거르기(줄들, 목록.query, 보기), [줄들, 목록.query, 보기])
|
|
9340
|
+
* const { pageItems, totalPages, curPage } = usePagedList(걸러낸것, 목록.perPage, 목록.page)
|
|
9341
|
+
* ```
|
|
9342
|
+
*
|
|
9343
|
+
* 찾는 말을 이 훅이 쥐는 것은 **첫 쪽으로 돌아갈 까닭**이기 때문이다. 쥐고 있으니 열쇠에 넣는
|
|
9344
|
+
* 일도 여기서 끝난다 — `목록.query` 는 위처럼 거를 때 그대로 쓴다.
|
|
9345
|
+
*
|
|
9346
|
+
* @public
|
|
9347
|
+
*/
|
|
9348
|
+
export declare function useListControls(opts?: ListControlsOptions): ListControls;
|
|
9349
|
+
|
|
9064
9350
|
/** @public */
|
|
9065
9351
|
export declare function useMediaQuery(query: string): boolean;
|
|
9066
9352
|
|