@ihabdevteam/core 0.205.0 → 0.207.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.
@@ -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.205.0";
1629
+ export declare const CORE_VERSION = "0.207.0";
1630
1630
 
1631
1631
  /** @public */
1632
1632
  declare const coreDefault: Record<string, unknown>;
@@ -2350,7 +2350,7 @@ export declare const FILTER_ALL_VALUE = "\uC804\uCCB4";
2350
2350
  export declare function filterContrastCandidates(answer: string, candidates: Iterable<string>, opts: ContrastFilterOpts): string[];
2351
2351
 
2352
2352
  /** @public */
2353
- export declare function FilterPanel({ title, headerAction, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
2353
+ export declare function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): JSX.Element;
2354
2354
 
2355
2355
  /** @public */
2356
2356
  export declare interface FilterPanelItem {
@@ -2372,7 +2372,25 @@ export declare interface FilterPanelItem {
2372
2372
  export declare interface FilterPanelProps {
2373
2373
  title?: ReactNode;
2374
2374
  headerAction?: ReactNode;
2375
- /** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다. */
2375
+ /** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
2376
+ *
2377
+ * `headerAction` 은 펼쳤을 때만 서고 접으면 사라진다(본문을 다루는 단추라 접힌 판에서는 할 일이 없다).
2378
+ * 그래서 접힌 판이 무엇을 담고 있는지 말할 자리가 `title` 밖에 없었고, 소비자가 제목 안에 요약을 싣고
2379
+ * CSS 로 오른쪽 끝까지 미는 억지를 썼다(말귀 단어표 만들기 2026-09-29).
2380
+ * **펼치면 그 자리를 `headerAction` 이 쓴다** — 둘은 같은 칸을 번갈아 쓴다.
2381
+ *
2382
+ * **안 주면 코어가 짓는다** — `절 이름 + 고른 값` 을 ` · ` 으로 이어 한 줄로 만든다
2383
+ * (`등급 전체 · 음절 수 2음절`). 이름표도 값도 코어 것이라 부르는 쪽이 옮겨 적으면 두 벌이 되고,
2384
+ * 표 설정 판처럼 값이 `WordTableBlock` 안에 있는 자리는 부르는 쪽이 지을 길조차 없다
2385
+ * (말귀 요청 ㉗, 2026-09-29). 셈은 `utils/filterPanelSummary` 에 있다.
2386
+ *
2387
+ * **주면 준 것이 이긴다** — 글월을 부르는 쪽이 쥐고 싶을 때 쓴다. 코어는 자리만 낸다 —
2388
+ * 여러 조각을 넣을 때는 묶음에 `role="group"` 과 한 줄짜리 `aria-label` 을 주고 낱개는 `aria-hidden` 으로
2389
+ * 숨기는 편이 낫다(낭독기가 조각마다 끊어 읽지 않게). */
2390
+ headerSummary?: ReactNode;
2391
+ /** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다.
2392
+ * **머리 줄 전체가 손잡이다** — 머리 아무 데나 눌러도 접힌다(사용자 지시 2026-09-29).
2393
+ * `headerAction`·`headerSummary` 안의 단추·입력칸을 누른 것은 접지 않는다. */
2376
2394
  collapsible?: boolean;
2377
2395
  /** collapsible일 때 초기 접힘 상태 */
2378
2396
  defaultCollapsed?: boolean;
@@ -7265,6 +7283,16 @@ export declare function TableFilterPanel(props: TableFilterPanelProps): JSX.Elem
7265
7283
  declare interface TableFilterPanelBaseProps {
7266
7284
  /** 패널 제목. 미지정 시 i18n(wordTable.filterTitle) 기본값 사용. */
7267
7285
  title?: ReactNode;
7286
+ /** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다(`FilterPanel` 의 것). */
7287
+ collapsible?: boolean;
7288
+ /** 처음에 접힌 채로 연다. */
7289
+ defaultCollapsed?: boolean;
7290
+ /** 접힘을 바깥에서 쥔다(controlled). */
7291
+ collapsed?: boolean;
7292
+ /** 접힘이 바뀔 때 알린다. */
7293
+ onCollapsedChange?: (collapsed: boolean) => void;
7294
+ /** 접혀도 남는 머리 오른쪽 자리 — 요약. */
7295
+ headerSummary?: ReactNode;
7268
7296
  className?: string;
7269
7297
  }
7270
7298
 
@@ -10223,7 +10251,7 @@ export declare interface WordRow {
10223
10251
  * [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
10224
10252
  * @public
10225
10253
  */
10226
- export declare function WordSearchFilterPanel({ title, sections, selected, onSelect, className }: WordSearchFilterPanelProps): JSX.Element;
10254
+ export declare function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }: WordSearchFilterPanelProps): JSX.Element;
10227
10255
 
10228
10256
  /** @public */
10229
10257
  export declare interface WordSearchFilterPanelProps {
@@ -10237,6 +10265,18 @@ export declare interface WordSearchFilterPanelProps {
10237
10265
  sections: FilterPanelSection[];
10238
10266
  selected: Record<string, string | string[]>;
10239
10267
  onSelect: (value: string, sectionLabel: string) => void;
10268
+ /** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다 — `FilterPanel` 의 것을 그대로 넘긴다. */
10269
+ collapsible?: boolean;
10270
+ /** 처음에 접힌 채로 연다(`collapsible` 과 함께). */
10271
+ defaultCollapsed?: boolean;
10272
+ /** 접힘을 바깥에서 쥔다(controlled). */
10273
+ collapsed?: boolean;
10274
+ /** 접힘이 바뀔 때 알린다. */
10275
+ onCollapsedChange?: (collapsed: boolean) => void;
10276
+ /** 접혀도 남는 머리 오른쪽 자리 — 접힌 판이 제 속을 말하는 요약(`FilterPanel headerSummary`). */
10277
+ headerSummary?: ReactNode;
10278
+ /** 펼쳤을 때 머리 오른쪽에 서는 조작부(`FilterPanel headerAction`). */
10279
+ headerAction?: ReactNode;
10240
10280
  className?: string;
10241
10281
  }
10242
10282
 
@@ -10326,12 +10366,24 @@ export declare interface WordSearchResultWord {
10326
10366
  */
10327
10367
  export declare function WordTable({ title, titleIcon, disc, className, orientation, contentTypes, cutLine, showOutline, showDivLine, items, editCells, count, activeCell, onActivateCell, onRemoveCell, onRemoveItem, onReorder, renderCellAction, showFillerAdd, contentRef, buttonSize, pageNumber, pageNumberPosition, fitHeight, scoring, marks, onMarkClick, scoreIndexOffset, }: WordTableProps): JSX.Element;
10328
10368
 
10329
- export declare function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): JSX.Element;
10369
+ export declare function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): JSX.Element;
10330
10370
 
10331
10371
  /** @public */
10332
10372
  export declare interface WordTableBlockProps {
10333
10373
  title?: ReactNode;
10334
10374
  titleIcon?: string;
10375
+ /** 안쪽 **표 설정 판의 제목** — 안 주면 코어 기본값(`wordTable.filterTitle`, '표 설정').
10376
+ *
10377
+ * 화면마다 달라야 한다(단어표는 '단어표 설정', 대립쌍표는 그 화면 말) — 그래서 코어 기본값을 바꾸는 것으로는 안 되고,
10378
+ * 앱 사전으로 코어 열쇠를 덮는 것은 말귀의 `guards/i18nOwnership` 이 막는다(옳은 규칙이다).
10379
+ * **부르는 쪽이 제 열쇠로 지어 넘긴다**(말귀 2026-09-29). */
10380
+ filterTitle?: ReactNode;
10381
+ /** 안쪽 표 설정 판을 접을 수 있게 한다 — 머리 줄 전체가 손잡이다. */
10382
+ filterCollapsible?: boolean;
10383
+ /** 그 판을 처음에 접힌 채로 연다(`filterCollapsible` 과 함께). */
10384
+ filterDefaultCollapsed?: boolean;
10385
+ /** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. */
10386
+ filterHeaderSummary?: ReactNode;
10335
10387
  disc?: ReactNode;
10336
10388
  /** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
10337
10389
  tableName?: string;
@@ -13,6 +13,9 @@
13
13
  display: flex;
14
14
  align-items: center;
15
15
  justify-content: space-between;
16
+ /* 제목과 요약이 맞닿지 않게 — 요약이 길면 `space-between` 의 빈 곳이 0이 되어
17
+ '표 설정구성 이미지…' 로 붙어 읽힌다(실측 2026-09-29). */
18
+ gap: var(--spacing-s);
16
19
  }
17
20
 
18
21
  /* ── 접기 가능한 패널 ─────────────────────────────────────────
@@ -26,6 +29,18 @@
26
29
  .filter-panel--collapsible .filter-panel__header {
27
30
  min-height: 60px;
28
31
  flex-shrink: 0;
32
+ /* 머리 줄 전체가 손잡이다(사용자 지시 2026-09-29) — 소비자가 감싸개에 onClick 을 달아
33
+ 머리 안을 눌렀는지 보던 자리다. 안의 단추·입력칸을 누른 것은 컴포넌트가 걸러 낸다. */
34
+ cursor: pointer;
35
+ }
36
+ /* 접기 손잡이는 **아이콘만** 보인다 — 단추의 바탕·네모 티를 지운다(사용자 지시).
37
+ 포커스 테두리는 남긴다: 키보드 쓰는 사람에게 그 자리가 어디인지 알리는 유일한 표시다. */
38
+ .filter-panel__collapse-toggle,
39
+ .filter-panel__collapse-toggle:hover,
40
+ .filter-panel__collapse-toggle:active {
41
+ background: transparent;
42
+ border: none;
43
+ box-shadow: none;
29
44
  }
30
45
  .filter-panel--collapsible .filter-panel__sections {
31
46
  padding-bottom: var(--spacing-m);
@@ -42,6 +57,10 @@
42
57
  .filter-panel__title {
43
58
  display: block;
44
59
  text-align: left;
60
+ /* 안 접힌다 — 넘치면 …로 자른다(title-row 의 `max-width` 에 닿았을 때) */
61
+ white-space: nowrap;
62
+ overflow: hidden;
63
+ text-overflow: ellipsis;
45
64
  }
46
65
 
47
66
  /* 헤더 좌측: 접기 버튼 + 타이틀 인라인 */
@@ -50,12 +69,29 @@
50
69
  align-items: center;
51
70
  gap: var(--spacing-xs);
52
71
  min-width: 0;
72
+ /* **제목이 먼저 자리를 얻고 요약이 양보한다.** 접힌 판의 요약은 길어서(표 설정은 절이 일곱이다)
73
+ 둘이 같이 줄면 짧은 제목이 두 줄로 접혀 60px 머리에 눌린다(실측 2026-09-29: '표 설정' 52px).
74
+ 그렇다고 제목이 머리를 다 먹으면 요약이 통째로 사라지므로 절반까지만 준다. */
75
+ flex-shrink: 0;
76
+ max-width: 50%;
77
+ }
78
+
79
+
80
+ /* 접힌 판이 제 속을 말하는 한 줄 — 길어지면 줄이지 말고 …로 자른다(머리는 한 줄 높이다).
81
+ `min-width: 0` 이 없으면 flex 칸이 제 속만큼 벌어져 제목을 밀어낸다. */
82
+ .filter-panel__header-summary {
83
+ color: var(--color-gray);
84
+ overflow: hidden;
85
+ text-overflow: ellipsis;
86
+ white-space: nowrap;
87
+ min-width: 0;
53
88
  }
54
89
 
55
90
  .filter-panel__header-actions {
56
91
  display: flex;
57
92
  align-items: center;
58
93
  gap: var(--spacing-xs);
94
+ min-width: 0;
59
95
  margin-left: auto;
60
96
  }
61
97
 
@@ -7,7 +7,25 @@ import type { FilterPanelSection } from '../types/filterPanel';
7
7
  export interface FilterPanelProps {
8
8
  title?: ReactNode;
9
9
  headerAction?: ReactNode;
10
- /** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다. */
10
+ /** 머리 오른쪽에 **접혀도 남는** 자리 — 접힌 판이 제 속을 한 줄로 말하게 한다("등급 전체 · 품사 전체" 같은 요약).
11
+ *
12
+ * `headerAction` 은 펼쳤을 때만 서고 접으면 사라진다(본문을 다루는 단추라 접힌 판에서는 할 일이 없다).
13
+ * 그래서 접힌 판이 무엇을 담고 있는지 말할 자리가 `title` 밖에 없었고, 소비자가 제목 안에 요약을 싣고
14
+ * CSS 로 오른쪽 끝까지 미는 억지를 썼다(말귀 단어표 만들기 2026-09-29).
15
+ * **펼치면 그 자리를 `headerAction` 이 쓴다** — 둘은 같은 칸을 번갈아 쓴다.
16
+ *
17
+ * **안 주면 코어가 짓는다** — `절 이름 + 고른 값` 을 ` · ` 으로 이어 한 줄로 만든다
18
+ * (`등급 전체 · 음절 수 2음절`). 이름표도 값도 코어 것이라 부르는 쪽이 옮겨 적으면 두 벌이 되고,
19
+ * 표 설정 판처럼 값이 `WordTableBlock` 안에 있는 자리는 부르는 쪽이 지을 길조차 없다
20
+ * (말귀 요청 ㉗, 2026-09-29). 셈은 `utils/filterPanelSummary` 에 있다.
21
+ *
22
+ * **주면 준 것이 이긴다** — 글월을 부르는 쪽이 쥐고 싶을 때 쓴다. 코어는 자리만 낸다 —
23
+ * 여러 조각을 넣을 때는 묶음에 `role="group"` 과 한 줄짜리 `aria-label` 을 주고 낱개는 `aria-hidden` 으로
24
+ * 숨기는 편이 낫다(낭독기가 조각마다 끊어 읽지 않게). */
25
+ headerSummary?: ReactNode;
26
+ /** 헤더 우측에 접기/펼치기 토글 버튼을 노출하고, 접으면 본문(섹션)을 숨긴다.
27
+ * **머리 줄 전체가 손잡이다** — 머리 아무 데나 눌러도 접힌다(사용자 지시 2026-09-29).
28
+ * `headerAction`·`headerSummary` 안의 단추·입력칸을 누른 것은 접지 않는다. */
11
29
  collapsible?: boolean;
12
30
  /** collapsible일 때 초기 접힘 상태 */
13
31
  defaultCollapsed?: boolean;
@@ -31,4 +49,4 @@ export interface FilterPanelProps {
31
49
  className?: string;
32
50
  }
33
51
  /** @public */
34
- export default function FilterPanel({ title, headerAction, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
52
+ export default function FilterPanel({ title, headerAction, headerSummary, collapsible, defaultCollapsed, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected, onSelect, align, sectionAlign, className, }: FilterPanelProps): import("react").JSX.Element;
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { cx } from '../utils/cx';
4
- import { Fragment, useState } from 'react';
4
+ import { Fragment, useMemo, useState } from 'react';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import './FilterPanel.css';
7
7
  import Button from './Button';
@@ -12,8 +12,9 @@ export { FILTER_ALL_VALUE } from '../types/filterPanel';
12
12
  import { FILTER_ALL_VALUE } from '../types/filterPanel';
13
13
  import FilterSectionInfo from './FilterSectionInfo';
14
14
  import { devWarnOnce } from '../utils/devWarn';
15
+ import { buildFilterSummary } from '../utils/filterPanelSummary';
15
16
  /** @public */
16
- export default function FilterPanel({ title, headerAction, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
17
+ export default function FilterPanel({ title, headerAction, headerSummary, collapsible = false, defaultCollapsed = false, collapsed: controlledCollapsed, onCollapsedChange, footer, sections, selected = {}, onSelect, align, sectionAlign, className, }) {
17
18
  // align 우선. 없으면 구 prop(sectionAlign) 매핑, 그것도 없으면 inline 기본.
18
19
  const isRow = (align ?? (sectionAlign === 'vertical' ? 'newline' : sectionAlign === 'horizontal' ? 'inline' : 'inline')) === 'inline';
19
20
  const { t } = useTranslation();
@@ -24,13 +25,34 @@ export default function FilterPanel({ title, headerAction, collapsible = false,
24
25
  setInternalCollapsed(next);
25
26
  onCollapsedChange?.(next);
26
27
  };
27
- const showHeader = title || headerAction || collapsible;
28
- return (_jsxs("div", { className: cx('filter-panel', collapsible ? 'filter-panel--collapsible' : '', collapsed ? 'filter-panel--collapsed' : '', className), children: [showHeader && (_jsxs("div", { className: "filter-panel__header", children: [_jsxs("div", { className: "filter-panel__title-row", children: [collapsible && (_jsx(Button, { htmlType: "button", size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-down' : 'chevron-up',
28
+ /* 머리 아무 데나 눌러도 접힌다. 다만 **그 안의 조작부를 누른 것은 아니다** —
29
+ `headerAction` 의 단추(초기화 등)를 누르면 그 일만 하고 판은 그대로 둔다.
30
+ chevron 단추는 제 `onClick` 으로 접으므로 여기서 한 번 더 접지 않는다(두 번 접히면 제자리다). */
31
+ const 머리누름 = (e) => {
32
+ if (!collapsible)
33
+ return;
34
+ const el = e.target;
35
+ if (!el || typeof el.closest !== 'function')
36
+ return;
37
+ if (el.closest('.filter-panel__collapse-toggle'))
38
+ return;
39
+ if (el.closest('button, a, input, select, textarea, label, [role="radiogroup"], [role="button"], [role="menu"]'))
40
+ return;
41
+ toggleCollapsed();
42
+ };
43
+ /* 안 주면 **코어가 짓는다** — 이름표도 값도 코어 것이라 부르는 쪽이 옮겨 적으면 두 벌이 되고,
44
+ 표 설정 판처럼 값이 아예 `WordTableBlock` 안에 있는 자리는 지을 길조차 없다(말귀 요청 ㉗).
45
+ `headerSummary` 를 주면 그것이 이긴다 — 글월을 부르는 쪽이 쥐던 길은 그대로 열려 있다.
46
+ 접혀 있을 때만 쓰이므로 펼친 동안은 셈하지 않는다. */
47
+ const 기본요약 = useMemo(() => (headerSummary !== undefined || !collapsed ? '' : buildFilterSummary(sections, selected, t('common.all', '전체'))), [headerSummary, collapsed, sections, selected, t]);
48
+ const summaryNode = headerSummary ?? (기본요약 ? _jsx("span", { className: "filter-panel__header-summary body-3", children: 기본요약 }) : null);
49
+ const showHeader = title || headerAction || headerSummary || collapsible;
50
+ return (_jsxs("div", { className: cx('filter-panel', collapsible ? 'filter-panel--collapsible' : '', collapsed ? 'filter-panel--collapsed' : '', className), children: [showHeader && (_jsxs("div", { className: "filter-panel__header", onClick: collapsible ? 머리누름 : undefined, children: [_jsx("div", { className: "filter-panel__title-row", children: title && _jsx("span", { className: "filter-panel__title h4", children: title }) }), _jsxs("div", { className: "filter-panel__header-actions", children: [collapsed ? summaryNode : headerAction, collapsible && (_jsx(Button, { htmlType: "button", size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-down' : 'chevron-up',
29
51
  /* Button은 aria-label을 자기 ariaLabel prop으로 덮어쓴다({...rest} 뒤에
30
52
  aria-label={ariaLabelString}을 다시 쓴다) — kebab으로 넘기면 undefined가
31
53
  되어 이름이 통째로 사라진다. 저장소의 다른 호출부는 모두 ariaLabel을 쓴다.
32
54
  문구도 이 파일만 한국어로 박혀 있었다(키는 이미 있다). */
33
- ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), "aria-expanded": !collapsed, onClick: toggleCollapsed })), title && _jsx("span", { className: "filter-panel__title h4", children: title })] }), _jsx("div", { className: "filter-panel__header-actions", children: !collapsed && headerAction })] })), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { className: cx('filter-panel__sections', isRow ? 'filter-panel__sections--row' : ''), children: sections.map(section => {
55
+ className: "filter-panel__collapse-toggle", ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), "aria-expanded": !collapsed, onClick: toggleCollapsed }))] })] })), !collapsed && (_jsxs(_Fragment, { children: [_jsx("div", { className: cx('filter-panel__sections', isRow ? 'filter-panel__sections--row' : ''), children: sections.map(section => {
34
56
  const sel = selected[section.label];
35
57
  const isAllSelected = sel === undefined || (Array.isArray(sel) ? sel.length === 0 : false);
36
58
  // section.items에 빈 항목(null/undefined)이 섞여 들어오면 item.value 접근에서
@@ -5,6 +5,16 @@ import { type TableSettingsSetters } from './wordTableSettings';
5
5
  interface TableFilterPanelBaseProps {
6
6
  /** 패널 제목. 미지정 시 i18n(wordTable.filterTitle) 기본값 사용. */
7
7
  title?: ReactNode;
8
+ /** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다(`FilterPanel` 의 것). */
9
+ collapsible?: boolean;
10
+ /** 처음에 접힌 채로 연다. */
11
+ defaultCollapsed?: boolean;
12
+ /** 접힘을 바깥에서 쥔다(controlled). */
13
+ collapsed?: boolean;
14
+ /** 접힘이 바뀔 때 알린다. */
15
+ onCollapsedChange?: (collapsed: boolean) => void;
16
+ /** 접혀도 남는 머리 오른쪽 자리 — 요약. */
17
+ headerSummary?: ReactNode;
8
18
  className?: string;
9
19
  }
10
20
  /**
@@ -16,9 +16,11 @@ function isShellProps(props) {
16
16
  */
17
17
  export default function TableFilterPanel(props) {
18
18
  const { t } = useTranslation();
19
- const { title, className } = props;
19
+ const { title, className, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary } = props;
20
+ /* 두 갈래(껍데기 모드·표 설정 모드)가 같은 접기 값을 쓴다 — 한쪽만 넘기면 화면마다 다르게 접힌다 */
21
+ const 접기 = { collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary };
20
22
  if (isShellProps(props)) {
21
- return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: props.sections, selected: props.selected, onSelect: props.onSelect, align: "inline" }));
23
+ return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: props.sections, selected: props.selected, onSelect: props.onSelect, ...접기, align: "inline" }));
22
24
  }
23
25
  const { hasImage, hasColloquial, contentTypes, orientation, showOutline, cutLine, showDivLine, setters, showCutLine = false, showDivLine2 = false, middleSections = [], extraSelected = {}, onExtraSelect, } = props;
24
26
  const sections = [
@@ -38,5 +40,5 @@ export default function TableFilterPanel(props) {
38
40
  return;
39
41
  onExtraSelect?.(value, sectionLabel);
40
42
  };
41
- return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: sections, selected: selected, onSelect: onSelect, sectionAlign: "horizontal" }));
43
+ return (_jsx(FilterPanel, { className: className, title: title ?? t('wordTable.filterTitle', '표 설정'), sections: sections, selected: selected, onSelect: onSelect, ...접기, sectionAlign: "horizontal" }));
42
44
  }
@@ -12,6 +12,18 @@ export interface WordSearchFilterPanelProps {
12
12
  sections: FilterPanelSection[];
13
13
  selected: Record<string, string | string[]>;
14
14
  onSelect: (value: string, sectionLabel: string) => void;
15
+ /** 머리에 접기 손잡이를 두고 머리 줄 전체를 손잡이로 만든다 — `FilterPanel` 의 것을 그대로 넘긴다. */
16
+ collapsible?: boolean;
17
+ /** 처음에 접힌 채로 연다(`collapsible` 과 함께). */
18
+ defaultCollapsed?: boolean;
19
+ /** 접힘을 바깥에서 쥔다(controlled). */
20
+ collapsed?: boolean;
21
+ /** 접힘이 바뀔 때 알린다. */
22
+ onCollapsedChange?: (collapsed: boolean) => void;
23
+ /** 접혀도 남는 머리 오른쪽 자리 — 접힌 판이 제 속을 말하는 요약(`FilterPanel headerSummary`). */
24
+ headerSummary?: ReactNode;
25
+ /** 펼쳤을 때 머리 오른쪽에 서는 조작부(`FilterPanel headerAction`). */
26
+ headerAction?: ReactNode;
15
27
  className?: string;
16
28
  }
17
29
  /**
@@ -20,4 +32,4 @@ export interface WordSearchFilterPanelProps {
20
32
  * [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
21
33
  * @public
22
34
  */
23
- export default function WordSearchFilterPanel({ title, sections, selected, onSelect, className }: WordSearchFilterPanelProps): import("react").JSX.Element;
35
+ export default function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }: WordSearchFilterPanelProps): import("react").JSX.Element;
@@ -2,13 +2,15 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import FilterPanel from '../FilterPanel';
5
+ /* 이 넷이 없어서 소비자가 **껍데기를 건너뛰고 `FilterPanel` 을 직접** 쓰고 있었다 —
6
+ 그러면 이 껍데기가 쥔 두 줄(i18n 제목·`align="inline"`)이 소비자 쪽에 복사된다(말귀 2026-09-29). */
5
7
  /**
6
8
  * 단어를 어떤 조건으로 좁힐지 고르는 FilterPanel 셸(shell) — 등급/품사/어종/음절 수/선택 섹션 자체는
7
9
  * 호출부가 구성해 넘긴다. WordSearchPage의 [단어 검색 설정] 패널과 WordTrainingFilterPanels의
8
10
  * [단어 선정] 패널이 원 출처(섹션 구성은 다르지만 같은 셸을 공용).
9
11
  * @public
10
12
  */
11
- export default function WordSearchFilterPanel({ title, sections, selected, onSelect, className }) {
13
+ export default function WordSearchFilterPanel({ title, sections, selected, onSelect, collapsible, defaultCollapsed, collapsed, onCollapsedChange, headerSummary, headerAction, className, }) {
12
14
  const { t } = useTranslation();
13
- return (_jsx(FilterPanel, { className: className, title: title ?? t('wordSearch.filterTitle', '단어 검색 설정'), sections: sections, selected: selected, onSelect: onSelect, align: "inline" }));
15
+ return (_jsx(FilterPanel, { className: className, title: title ?? t('wordSearch.filterTitle', '단어 검색 설정'), sections: sections, selected: selected, onSelect: onSelect, collapsible: collapsible, defaultCollapsed: defaultCollapsed, collapsed: collapsed, onCollapsedChange: onCollapsedChange, headerSummary: headerSummary, headerAction: headerAction, align: "inline" }));
14
16
  }
@@ -20,7 +20,46 @@
20
20
  border: 1px solid var(--color-darkWhite);
21
21
  background: var(--color-white);
22
22
  border-radius: var(--radius-m);
23
- padding: var(--spacing-content);
23
+ /* 옆에 서는 `FilterPanel` 과 같은 여백(사용자 지시 2026-09-29) */
24
+ padding: var(--spacing-l);
25
+ display: flex;
26
+ flex-direction: column;
27
+ gap: var(--spacing-m);
28
+ }
29
+
30
+ /* 상자 안 머리 — `FilterPanel` 의 머리와 같은 결(제목 h4 · 부제 body-3 회색).
31
+ 설명은 한 줄을 통째로 쓴다(제목 옆에 두면 조작부를 다음 줄로 밀어낸다). */
32
+ .word-search-result-block__head {
33
+ display: flex;
34
+ align-items: center;
35
+ flex-wrap: wrap;
36
+ gap: var(--spacing-xs);
37
+ }
38
+ .word-search-result-block__head-main {
39
+ display: flex;
40
+ align-items: center;
41
+ gap: var(--spacing-xs);
42
+ min-width: 0;
43
+ }
44
+ .word-search-result-block__head-icon {
45
+ flex-shrink: 0;
46
+ color: var(--color-gray);
47
+ }
48
+ .word-search-result-block__title {
49
+ text-align: left;
50
+ min-width: 0;
51
+ }
52
+ .word-search-result-block__head-actions {
53
+ display: flex;
54
+ align-items: center;
55
+ gap: var(--spacing-xs);
56
+ margin-left: auto;
57
+ }
58
+ .word-search-result-block__head-disc {
59
+ flex: 0 0 100%;
60
+ min-width: 0;
61
+ color: var(--color-gray);
62
+ margin: 0;
24
63
  }
25
64
 
26
65
  /* 박스↔액션 간격은 '내용이 있는' 액션(더보기/선택초기화/인쇄)이 뒤에 올 때만 준다.
@@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next';
7
7
  import './WordSearchResultBlock.css';
8
8
  import Button from '../Button';
9
9
  import Section from '../Section';
10
+ import Icon from '../Icon';
10
11
  import Dropdown from '../Dropdown';
11
12
  import Pagination from '../Pagination';
12
13
  import WordImageTooltip from '../Tooltip/WordImageTooltip';
@@ -158,7 +159,16 @@ export default function WordSearchResultBlock({ titleIcon, resultCount, resultUn
158
159
  const discNode = disc ?? (notice
159
160
  ? _jsxs(_Fragment, { children: [_jsx("span", { className: "word-search-result-block__notice", children: notice }), discLabel ? _jsx("span", { className: "word-search-result-block__disc", children: discLabel }) : null] })
160
161
  : discLabel);
161
- return (_jsxs(Section, { type: sectionType, icon: titleIcon, title: titleLabel, action: toolbar, disc: discNode, className: cx('word-search-result-block', className), children: [_jsxs("div", { className: "word-search-result-block__inner", children: [(groups ? groups.length === 0 : items.length === 0) && (_jsxs("div", { className: "word-search-result-block__empty-wrap", children: [_jsx("p", { className: "word-search-result-block__empty body-3", children: t('wordSearch.emptyResult', '검색 결과가 없습니다.') }), emptyHint && _jsx("p", { className: "word-search-result-block__empty-hint body-3", children: emptyHint })] })), groups ? ((pagedGroups ?? []).map((group) => (_jsx("div", { className: "word-search-result-block__group", children: _jsx("div", { className: "word-search-result-block__items", children: group.items.map((item) => {
162
+ return (
163
+ /* 제목·부제·조작부가 **상자 안** 에 선다(사용자 지시 2026-09-29) — 전에는 `Section` 의 머리가
164
+ 상자 밖에 서서 제목이 테두리 위에 뜨고 좌우 여백도 옆의 `FilterPanel` 과 달라 보였다.
165
+ 이제 여백(--spacing-l)·부제(body-3 회색)를 그 판과 맞춘다.
166
+
167
+ **제목만 한 급 위(h3)다**(말귀 요청 ㉘, 2026-09-29). 0.206.0 에서는 곁의 `FilterPanel`
168
+ 과 아주 같은 h4 로 맞췄는데, 그러니 결과 덩이의 머리가 그 위 판 제목들(`검색 옵션` ·
169
+ `단어 검색 설정`)과 같은 급이 되어 **무엇이 무엇의 머리인지 안 보였다.** 거르개 판은
170
+ 결과를 고르는 도구고 이 덩이는 그 결과라, 나란한 것이 아니라 아래 딸린 것이다. */
171
+ _jsxs(Section, { type: sectionType, className: cx('word-search-result-block', className), children: [_jsxs("div", { className: "word-search-result-block__inner", children: [(titleLabel || toolbar || discNode) && (_jsxs("div", { className: "word-search-result-block__head", children: [_jsxs("div", { className: "word-search-result-block__head-main", children: [titleIcon && _jsx(Icon, { icon: titleIcon, className: "word-search-result-block__head-icon" }), titleLabel && _jsx("span", { className: "word-search-result-block__title h3", children: titleLabel })] }), toolbar && _jsx("div", { className: "word-search-result-block__head-actions", children: toolbar }), discNode && _jsx("div", { className: "word-search-result-block__head-disc body-3", children: discNode })] })), (groups ? groups.length === 0 : items.length === 0) && (_jsxs("div", { className: "word-search-result-block__empty-wrap", children: [_jsx("p", { className: "word-search-result-block__empty body-3", children: t('wordSearch.emptyResult', '검색 결과가 없습니다.') }), emptyHint && _jsx("p", { className: "word-search-result-block__empty-hint body-3", children: emptyHint })] })), groups ? ((pagedGroups ?? []).map((group) => (_jsx("div", { className: "word-search-result-block__group", children: _jsx("div", { className: "word-search-result-block__items", children: group.items.map((item) => {
162
172
  const isSelected = selected.includes(item.id);
163
173
  return (_jsx("div", { onMouseEnter: e => {
164
174
  openHover(e.currentTarget);
@@ -1,4 +1,4 @@
1
1
  import './WordTableBlock.css';
2
2
  import type { WordTableBlockProps } from './wordTableBlockTypes';
3
3
  export type { WordEntry, WordTableBlockProps } from './wordTableBlockTypes';
4
- export default function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): import("react").JSX.Element;
4
+ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }: WordTableBlockProps): import("react").JSX.Element;
@@ -45,7 +45,7 @@ function 겹치지않는id(list) {
45
45
  return id === it.id ? it : { ...it, id };
46
46
  });
47
47
  }
48
- export default function WordTableBlock({ title, titleIcon, disc, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }) {
48
+ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterHeaderSummary, items, onSaveComplete, onLoad, onRemoveItem, onReset, resetLabel, saveButtonLabel, secondarySaveButtonLabel, onReorder, showCellCount, onAddItem, className, renderSavePopup, renderWordSearch, hideControls, saveOnly, tableName, nameValue, onNameChange, descriptionValue, onDescriptionChange, namePlaceholder, descriptionPlaceholder, existingNames, editingName, onCancel, initialItemsPerPage, renderCellAction, actionsLeftExtra }) {
49
49
  const { t } = useTranslation();
50
50
  const resolvedResetLabel = resetLabel ?? t('wordPrint.clearAllSelection', '모두 선택 해제');
51
51
  const isEditable = items === undefined;
@@ -318,7 +318,7 @@ export default function WordTableBlock({ title, titleIcon, disc, items, onSaveCo
318
318
  setActiveCell(null);
319
319
  },
320
320
  onClose: handleCloseWordSearch,
321
- }), !hideControls && (_jsx(TableFilterPanel, { hasImage: hasImage, hasColloquial: hasColloquial, contentTypes: contentTypes, orientation: orientation, showOutline: showOutline, cutLine: cutLine, showDivLine: showDivLine, setters: { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine }, showCutLine: (pagesEnabled ? itemsPerPage : count) > 1, showDivLine2: hasPairs, middleSections: middleSections, extraSelected: middleSelected, onExtraSelect: handleMiddleSelect })), isPaginated && pages ? (_jsx("div", { className: "word-table-block__pages", children: pages.map((page, i) => (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: itemsPerPage, items: page, onRemoveItem: onRemoveItem,
321
+ }), !hideControls && (_jsx(TableFilterPanel, { title: filterTitle, collapsible: filterCollapsible, defaultCollapsed: filterDefaultCollapsed, headerSummary: filterHeaderSummary, hasImage: hasImage, hasColloquial: hasColloquial, contentTypes: contentTypes, orientation: orientation, showOutline: showOutline, cutLine: cutLine, showDivLine: showDivLine, setters: { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine }, showCutLine: (pagesEnabled ? itemsPerPage : count) > 1, showDivLine2: hasPairs, middleSections: middleSections, extraSelected: middleSelected, onExtraSelect: handleMiddleSelect })), isPaginated && pages ? (_jsx("div", { className: "word-table-block__pages", children: pages.map((page, i) => (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: itemsPerPage, items: page, onRemoveItem: onRemoveItem,
322
322
  /* 쪽이 나뉘면 끌어 옮기기가 통째로 꺼졌다(말귀 단어 찾기: 9개부터). 쪽 안의 자리를 전체 자리로 옮겨 넘긴다 —
323
323
  쪽을 넘는 이동은 하지 않는다. */
324
324
  onReorder: onReorder && ((from, to) => onReorder(from + i * itemsPerPage, to + i * itemsPerPage)),
@@ -18,6 +18,18 @@ export interface WordEntry {
18
18
  export interface WordTableBlockProps {
19
19
  title?: ReactNode;
20
20
  titleIcon?: string;
21
+ /** 안쪽 **표 설정 판의 제목** — 안 주면 코어 기본값(`wordTable.filterTitle`, '표 설정').
22
+ *
23
+ * 화면마다 달라야 한다(단어표는 '단어표 설정', 대립쌍표는 그 화면 말) — 그래서 코어 기본값을 바꾸는 것으로는 안 되고,
24
+ * 앱 사전으로 코어 열쇠를 덮는 것은 말귀의 `guards/i18nOwnership` 이 막는다(옳은 규칙이다).
25
+ * **부르는 쪽이 제 열쇠로 지어 넘긴다**(말귀 2026-09-29). */
26
+ filterTitle?: ReactNode;
27
+ /** 안쪽 표 설정 판을 접을 수 있게 한다 — 머리 줄 전체가 손잡이다. */
28
+ filterCollapsible?: boolean;
29
+ /** 그 판을 처음에 접힌 채로 연다(`filterCollapsible` 과 함께). */
30
+ filterDefaultCollapsed?: boolean;
31
+ /** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. */
32
+ filterHeaderSummary?: ReactNode;
21
33
  disc?: ReactNode;
22
34
  /** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
23
35
  tableName?: string;
@@ -0,0 +1,14 @@
1
+ import type { FilterPanelSection } from '../types/filterPanel';
2
+ /**
3
+ * **접힌 거르개 판이 제 속을 한 줄로 말하는 글월**을 절과 고른 값에서 짓는다
4
+ * (`등급 전체 · 음절 수 2음절 · 품사 전체`).
5
+ *
6
+ * 왜 코어가 짓나: 이름표도 값도 코어 것이라 **부르는 쪽이 옮겨 적으면 두 벌이 된다.**
7
+ * 말귀가 등급·음절 수 이름표를 두 화면에 다르게 적어 한 번 어긋났고(말귀 요청 ㉗,
8
+ * 2026-09-29), 표 설정 판은 아예 값이 `WordTableBlock` 안에 있어 부르는 쪽이 글월을
9
+ * 지을 길조차 없었다. 그래서 **기본값은 코어가 짓고, 덮고 싶을 때만 `headerSummary`** 다.
10
+ *
11
+ * 안 담는 절: `note`(고르는 절이 아니다) · `input`(빈 칸을 '전체'라 말하면 거짓이다) ·
12
+ * 고른 것이 없는데 '전체' 보기도 없는 절(말할 것이 없다).
13
+ */
14
+ export declare function buildFilterSummary(sections: readonly FilterPanelSection[], selected: Record<string, string | string[]> | undefined, 전체말: string): string;
@@ -0,0 +1,57 @@
1
+ import { FILTER_ALL_VALUE } from '../types/filterPanel';
2
+ /**
3
+ * **접힌 거르개 판이 제 속을 한 줄로 말하는 글월**을 절과 고른 값에서 짓는다
4
+ * (`등급 전체 · 음절 수 2음절 · 품사 전체`).
5
+ *
6
+ * 왜 코어가 짓나: 이름표도 값도 코어 것이라 **부르는 쪽이 옮겨 적으면 두 벌이 된다.**
7
+ * 말귀가 등급·음절 수 이름표를 두 화면에 다르게 적어 한 번 어긋났고(말귀 요청 ㉗,
8
+ * 2026-09-29), 표 설정 판은 아예 값이 `WordTableBlock` 안에 있어 부르는 쪽이 글월을
9
+ * 지을 길조차 없었다. 그래서 **기본값은 코어가 짓고, 덮고 싶을 때만 `headerSummary`** 다.
10
+ *
11
+ * 안 담는 절: `note`(고르는 절이 아니다) · `input`(빈 칸을 '전체'라 말하면 거짓이다) ·
12
+ * 고른 것이 없는데 '전체' 보기도 없는 절(말할 것이 없다).
13
+ */
14
+ export function buildFilterSummary(sections, selected, 전체말) {
15
+ const 조각들 = [];
16
+ for (const section of sections ?? []) {
17
+ if (section.type === 'note' || section.type === 'input')
18
+ continue;
19
+ const 이름 = section.displayLabel ?? section.label;
20
+ const 값 = 값말(section, selected?.[section.label], 전체말);
21
+ if (값 === null)
22
+ continue;
23
+ 조각들.push(`${이름} ${값}`);
24
+ }
25
+ return 조각들.join(' · ');
26
+ }
27
+ /** 한 절이 무엇으로 정해져 있는지 — 말할 것이 없으면 `null` */
28
+ function 값말(section, sel, 전체말) {
29
+ const 고른것 = Array.isArray(sel) ? sel : sel === undefined ? [] : [sel];
30
+ /* '전체' 는 두 꼴로 온다 — 아무것도 안 고른 것과 '전체' 보기를 고른 것. 같은 말로 읽는다. */
31
+ const 실제 = 고른것.filter(v => v !== FILTER_ALL_VALUE);
32
+ if (실제.length === 0) {
33
+ return section.allOption ? (section.allLabel ?? 전체말) : null;
34
+ }
35
+ return 실제.map(v => 보기말(section, v)).join(', ');
36
+ }
37
+ /** 고른 값 하나를 사람이 읽는 말로 — 보기 목록에 없으면 값 그대로 */
38
+ function 보기말(section, value) {
39
+ const 보기들 = [
40
+ ...(section.items ?? []),
41
+ ...(section.groups ?? []).flat(),
42
+ ];
43
+ for (const item of 보기들) {
44
+ if (item == null)
45
+ continue;
46
+ if (typeof item === 'string') {
47
+ if (item === value)
48
+ return item;
49
+ continue;
50
+ }
51
+ if (item.value !== value)
52
+ continue;
53
+ /* 단추에 쓰는 차례 그대로 — `labels` 는 여러 줄로 쪼갠 것이라 한 줄로 이어 붙인다 */
54
+ return item.displayLabel ?? item.labels?.join(' ') ?? item.value;
55
+ }
56
+ return value;
57
+ }
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export declare const CORE_VERSION = "0.205.0";
49
+ export declare const CORE_VERSION = "0.207.0";
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export const CORE_VERSION = '0.205.0';
49
+ export const CORE_VERSION = '0.207.0';