@ihabdevteam/core 0.215.0 → 0.216.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.215.0";
1629
+ export declare const CORE_VERSION = "0.216.0";
1630
1630
 
1631
1631
  /** @public */
1632
1632
  declare const coreDefault: Record<string, unknown>;
@@ -10373,7 +10373,7 @@ export declare interface WordSearchResultWord {
10373
10373
  */
10374
10374
  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, brand, code, pageNumber, pageNumberPosition, fitHeight, scoring, marks, onMarkClick, scoreIndexOffset, }: WordTableProps): JSX.Element;
10375
10375
 
10376
- export declare function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, 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;
10376
+ export declare function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, filterHeaderSummary, brand, code, 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;
10377
10377
 
10378
10378
  /** @public */
10379
10379
  export declare interface WordTableBlockProps {
@@ -10410,6 +10410,16 @@ export declare interface WordTableBlockProps {
10410
10410
  onFilterCollapsedChange?: (collapsed: boolean) => void;
10411
10411
  /** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. 안 주면 코어가 짓는다(`FilterPanel`). */
10412
10412
  filterHeaderSummary?: ReactNode;
10413
+ /**
10414
+ * 종이 머리·바닥 띠 — 안쪽 `WordTable` 로 그대로 간다.
10415
+ *
10416
+ * **`show` 가 켜지면 표 설정에 `상단 정보`·`하단 정보` 고르개가 저절로 선다** — 스위치를
10417
+ * 둘로 나누면 부르는 쪽이 어긋나게 줄 수 있어서다(말귀 요청 ㉛, 2026-09-30). 그 고르개로
10418
+ * 켜고 끈 값은 `top`·`bottom` 을 **덮어쓴다** — 화면에서 사람이 고른 것이 마지막 말이다.
10419
+ */
10420
+ brand?: WordTableProps['brand'];
10421
+ /** 표 번호 — 띠 위 오른쪽에 선다. 안쪽 `WordTable` 로 그대로 간다. */
10422
+ code?: string;
10413
10423
  disc?: ReactNode;
10414
10424
  /** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
10415
10425
  tableName?: string;
@@ -10622,7 +10632,7 @@ export declare type WordTableOrientation = 'landscape' | 'portrait';
10622
10632
  * 표 내부에는 제목을 두지 않아 팝업 헤더 제목과 중복되지 않는다.
10623
10633
  * @public
10624
10634
  */
10625
- export declare function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring, onScoreChange, printHeader, beforeClose }: WordTablePopupProps): JSX.Element;
10635
+ export declare function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring, onScoreChange, printHeader, brand, code, beforeClose }: WordTablePopupProps): JSX.Element;
10626
10636
 
10627
10637
  /** @public */
10628
10638
  export declare interface WordTablePopupActions {
@@ -10669,6 +10679,14 @@ export declare interface WordTablePopupProps {
10669
10679
  /** 종이 맨 위에 찍을 한 줄(누구의·언제·무슨 표). 인쇄되는 것은 격자뿐이라
10670
10680
  * 이것이 없으면 뽑아 둔 종이에 아무 단서도 안 남는다. */
10671
10681
  printHeader?: string;
10682
+ /**
10683
+ * 종이 머리·바닥 띠 — 안쪽 `WordTable` 의 같은 이름 프롭으로 그대로 간다.
10684
+ * **사람이 실제로 인쇄하는 화면은 이 창을 지난다**(미리보기 · 채점지 · 고객 상세 ·
10685
+ * 훈련 더보기) — 여기가 안 흘리면 앱에서 띠를 켤 길이 없다(말귀 요청 ㉛, 2026-09-30).
10686
+ */
10687
+ brand?: WordTableProps['brand'];
10688
+ /** 표 번호 — 띠 위 오른쪽에 선다. 안쪽 `WordTable` 로 그대로 간다. */
10689
+ code?: string;
10672
10690
  /** 닫기 직전에 불린다. false 를 돌려주면 닫지 않는다(채점을 버릴지 묻는 용). */
10673
10691
  beforeClose?: PopupProps['beforeClose'];
10674
10692
  /** true면 팝업이 열리고 표가 렌더된 직후 인쇄를 1회 자동 시작한다(메뉴 '인쇄하기' 진입용). */
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type PopupProps } from '../Popup/Popup';
3
+ import type { WordTableProps } from '../WordTable';
3
4
  import type { WordSearchResultItem } from '../../types/wordSearch';
4
5
  import { type ScoreMark } from '../Word/wordTableScoring';
5
6
  import './WordTablePopup.css';
@@ -47,6 +48,14 @@ export interface WordTablePopupProps {
47
48
  /** 종이 맨 위에 찍을 한 줄(누구의·언제·무슨 표). 인쇄되는 것은 격자뿐이라
48
49
  * 이것이 없으면 뽑아 둔 종이에 아무 단서도 안 남는다. */
49
50
  printHeader?: string;
51
+ /**
52
+ * 종이 머리·바닥 띠 — 안쪽 `WordTable` 의 같은 이름 프롭으로 그대로 간다.
53
+ * **사람이 실제로 인쇄하는 화면은 이 창을 지난다**(미리보기 · 채점지 · 고객 상세 ·
54
+ * 훈련 더보기) — 여기가 안 흘리면 앱에서 띠를 켤 길이 없다(말귀 요청 ㉛, 2026-09-30).
55
+ */
56
+ brand?: WordTableProps['brand'];
57
+ /** 표 번호 — 띠 위 오른쪽에 선다. 안쪽 `WordTable` 로 그대로 간다. */
58
+ code?: string;
50
59
  /** 닫기 직전에 불린다. false 를 돌려주면 닫지 않는다(채점을 버릴지 묻는 용). */
51
60
  beforeClose?: PopupProps['beforeClose'];
52
61
  /** true면 팝업이 열리고 표가 렌더된 직후 인쇄를 1회 자동 시작한다(메뉴 '인쇄하기' 진입용). */
@@ -62,4 +71,4 @@ export interface WordTablePopupProps {
62
71
  * 표 내부에는 제목을 두지 않아 팝업 헤더 제목과 중복되지 않는다.
63
72
  * @public
64
73
  */
65
- export default function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring, onScoreChange, printHeader, beforeClose }: WordTablePopupProps): import("react").JSX.Element;
74
+ export default function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring, onScoreChange, printHeader, brand, code, beforeClose }: WordTablePopupProps): import("react").JSX.Element;
@@ -19,7 +19,7 @@ import { useToastState } from '../../hooks/useToastState';
19
19
  * 표 내부에는 제목을 두지 않아 팝업 헤더 제목과 중복되지 않는다.
20
20
  * @public
21
21
  */
22
- export default function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring = false, onScoreChange, printHeader, beforeClose }) {
22
+ export default function WordTablePopup({ items, words, title, subtitle, emptyText, buttons, renderButtons, renderFooter, footerClassName, footerLayout, printFileName, showSettings, autoPrint, className, onClose, scoring = false, onScoreChange, printHeader, brand, code, beforeClose }) {
23
23
  const { t } = useTranslation();
24
24
  // items 우선, 없으면 문자열 words → 한 단어=한 칸으로 매핑
25
25
  const tableItems = useMemo(() => {
@@ -30,7 +30,7 @@ export default function WordTablePopup({ items, words, title, subtitle, emptyTex
30
30
  .map((w, i) => ({ id: `${w}-${i}`, words: [{ label: w }] }));
31
31
  }, [items, words]);
32
32
  // 표 설정(구성·용지 방향·외곽선·페이지당 개수 등) — showSettings일 때만 FilterPanel로 노출하고, 값은 항상 표에 반영한다.
33
- const { settings, filterSections, filterSelected, handleSelect, pages, isPaginated, pageNumberPosition } = useWordTableDisplaySettings(tableItems);
33
+ const { settings, filterSections, filterSelected, handleSelect, pages, isPaginated, pageNumberPosition } = useWordTableDisplaySettings(tableItems, { paperBands: Boolean(brand?.show) });
34
34
  /* 쪽마다 첫 칸이 표 전체에서 몇 번째인지 — 쪽 안에서 0부터 다시 세는 번호를
35
35
  표 전체 번호로 옮긴다. */
36
36
  const pageStart = useMemo(() => pageStartIndexes(pages.map(p => p.length)), [pages]);
@@ -115,5 +115,5 @@ export default function WordTablePopup({ items, words, title, subtitle, emptyTex
115
115
  }, 100);
116
116
  return () => window.clearTimeout(id);
117
117
  }, [autoPrint]);
118
- return (_jsxs(Popup, { title: title ?? t('keyWords.title', '주요 단어'), subtitle: subtitle, size: "large", className: cx('word-table-popup', className), buttons: footerButtons, footer: customFooter, footerClassName: footerClassName, footerLayout: footerLayout, beforeClose: beforeClose, onClose: onClose, children: [tableItems.length > 0 ? (_jsxs("div", { className: "word-table-popup__body", children: [showSettings && (_jsx(TableFilterPanel, { sections: filterSections, selected: filterSelected, onSelect: handleSelect })), isPaginated ? (_jsx("div", { className: "word-table-popup__pages", children: pages.map((page, i) => (_jsx(WordTable, { items: page, orientation: settings.orientation, contentTypes: settings.contentTypes, cutLine: settings.cutLine, showOutline: settings.showOutline, showDivLine: settings.showDivLine, count: settings.count, contentRef: el => { pageRefs.current[i] = el; }, pageNumber: { current: i + 1, total: pages.length }, pageNumberPosition: pageNumberPosition, scoring: scoring, scoreIndexOffset: pageStart[i], marks: 표에줄채점, onMarkClick: handleMarkClick }, i))) })) : (_jsx("div", { className: "word-table-popup__table", children: _jsx(WordTable, { items: tableItems, orientation: settings.orientation, contentTypes: settings.contentTypes, cutLine: settings.cutLine, showOutline: settings.showOutline, showDivLine: settings.showDivLine, count: settings.count, contentRef: contentRef, fitHeight: true, scoring: scoring, marks: 표에줄채점, onMarkClick: handleMarkClick }) }))] })) : (_jsx("p", { className: "word-table-popup__empty body-3", children: emptyText ?? t('keyWords.empty', '추출된 주요 단어가 없습니다.') })), _jsx(Toast, { message: toastMsg, position: "top", onClose: handleToastClose }, toastKey)] }));
118
+ return (_jsxs(Popup, { title: title ?? t('keyWords.title', '주요 단어'), subtitle: subtitle, size: "large", className: cx('word-table-popup', className), buttons: footerButtons, footer: customFooter, footerClassName: footerClassName, footerLayout: footerLayout, beforeClose: beforeClose, onClose: onClose, children: [tableItems.length > 0 ? (_jsxs("div", { className: "word-table-popup__body", children: [showSettings && (_jsx(TableFilterPanel, { sections: filterSections, selected: filterSelected, onSelect: handleSelect })), isPaginated ? (_jsx("div", { className: "word-table-popup__pages", children: pages.map((page, i) => (_jsx(WordTable, { items: page, orientation: settings.orientation, contentTypes: settings.contentTypes, cutLine: settings.cutLine, showOutline: settings.showOutline, showDivLine: settings.showDivLine, count: settings.count, contentRef: el => { pageRefs.current[i] = el; }, pageNumber: { current: i + 1, total: pages.length }, pageNumberPosition: pageNumberPosition, brand: brand && { ...brand, top: settings.showTopInfo, bottom: settings.showBottomInfo }, code: code, scoring: scoring, scoreIndexOffset: pageStart[i], marks: 표에줄채점, onMarkClick: handleMarkClick }, i))) })) : (_jsx("div", { className: "word-table-popup__table", children: _jsx(WordTable, { items: tableItems, orientation: settings.orientation, contentTypes: settings.contentTypes, cutLine: settings.cutLine, showOutline: settings.showOutline, showDivLine: settings.showDivLine, count: settings.count, contentRef: contentRef, fitHeight: true, scoring: scoring, marks: 표에줄채점, onMarkClick: handleMarkClick, brand: brand && { ...brand, top: settings.showTopInfo, bottom: settings.showBottomInfo }, code: code }) }))] })) : (_jsx("p", { className: "word-table-popup__empty body-3", children: emptyText ?? t('keyWords.empty', '추출된 주요 단어가 없습니다.') })), _jsx(Toast, { message: toastMsg, position: "top", onClose: handleToastClose }, toastKey)] }));
119
119
  }
@@ -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, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, 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;
4
+ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, filterHeaderSummary, brand, code, 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, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, 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 }) {
48
+ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, filterCollapsible, filterDefaultCollapsed, filterCollapsed, onFilterCollapsedChange, filterHeaderSummary, brand, code, 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;
@@ -76,6 +76,12 @@ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, fi
76
76
  const [cellCount, setCellCount] = useState(4);
77
77
  const [activeCell, setActiveCell] = useState(null);
78
78
  const visibleCells = cells.slice(0, cellCount);
79
+ /* 띠를 켠 표면 설정 판에 '상단 정보'·'하단 정보' 고르개가 선다 — 스위치를 따로 두지
80
+ 않는다(부르는 쪽이 둘을 어긋나게 줄 일이 없게). 고른 값은 `brand.top`·`bottom` 을 덮는다. */
81
+ const 띠쓰나 = Boolean(brand?.show);
82
+ const [showTopInfo, setShowTopInfo] = useState(true);
83
+ const [showBottomInfo, setShowBottomInfo] = useState(true);
84
+ const 띠 = brand && { ...brand, top: showTopInfo, bottom: showBottomInfo };
79
85
  // 공통 단어 표 설정 상태
80
86
  const [orientation, setOrientation] = useState('landscape');
81
87
  const [contentTypes, setContentTypes] = useState(['이미지', '단어', '표준 발음']);
@@ -320,15 +326,15 @@ export default function WordTableBlock({ title, titleIcon, disc, filterTitle, fi
320
326
  setActiveCell(null);
321
327
  },
322
328
  onClose: handleCloseWordSearch,
323
- }), !hideControls && (_jsx(TableFilterPanel, { title: filterTitle, collapsible: filterCollapsible, defaultCollapsed: filterDefaultCollapsed, collapsed: filterCollapsed, onCollapsedChange: onFilterCollapsedChange, 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,
329
+ }), !hideControls && (_jsx(TableFilterPanel, { title: filterTitle, collapsible: filterCollapsible, defaultCollapsed: filterDefaultCollapsed, collapsed: filterCollapsed, onCollapsedChange: onFilterCollapsedChange, headerSummary: filterHeaderSummary, hasImage: hasImage, hasColloquial: hasColloquial, contentTypes: contentTypes, orientation: orientation, showOutline: showOutline, cutLine: cutLine, showDivLine: showDivLine, setters: { setContentTypes, setOrientation, setShowOutline, setCutLine, setShowDivLine, setShowTopInfo, setShowBottomInfo }, showCutLine: (pagesEnabled ? itemsPerPage : count) > 1, showDivLine2: hasPairs, middleSections: middleSections, extraSelected: middleSelected, onExtraSelect: handleMiddleSelect, showPaperBands: 띠쓰나, showTopInfo: showTopInfo, showBottomInfo: showBottomInfo })), 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,
324
330
  /* 쪽이 나뉘면 끌어 옮기기가 통째로 꺼졌다(말귀 단어 찾기: 9개부터). 쪽 안의 자리를 전체 자리로 옮겨 넘긴다 —
325
331
  쪽을 넘는 이동은 하지 않는다. */
326
332
  onReorder: onReorder && ((from, to) => onReorder(from + i * itemsPerPage, to + i * itemsPerPage)),
327
333
  /* 안쪽 표는 제 쪽 안의 번호를 준다 — 부르는 쪽에는 **표 전체 기준**으로 넘긴다
328
334
  (위 `onReorder` 와 같은 셈이다. 쪽이 몇으로 나뉘었는지는 부르는 쪽 일이 아니다). */
329
- renderCellAction: renderCellAction && ((n, item) => renderCellAction(n + i * itemsPerPage, item)), contentRef: el => { pageRefs.current[i] = el; }, pageNumber: { current: i + 1, total: pages.length }, pageNumberPosition: pageNumberPosition }, i))) })) : (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: count, items: isEditable ? undefined : (items ?? []), editCells: isEditable ? visibleCells : undefined, activeCell: activeCell, onActivateCell: (isEditable || onAddItem) ? setActiveCell : undefined, onRemoveCell: isEditable ? handleRemove : undefined, onRemoveItem: !isEditable ? onRemoveItem : undefined, onReorder: !isEditable ? onReorder : undefined,
335
+ renderCellAction: renderCellAction && ((n, item) => renderCellAction(n + i * itemsPerPage, item)), brand: 띠, code: code, contentRef: el => { pageRefs.current[i] = el; }, pageNumber: { current: i + 1, total: pages.length }, pageNumberPosition: pageNumberPosition }, i))) })) : (_jsx(WordTable, { orientation: orientation, contentTypes: contentTypes, cutLine: cutLine, showOutline: showOutline, showDivLine: showDivLine, count: count, items: isEditable ? undefined : (items ?? []), editCells: isEditable ? visibleCells : undefined, activeCell: activeCell, onActivateCell: (isEditable || onAddItem) ? setActiveCell : undefined, onRemoveCell: isEditable ? handleRemove : undefined, onRemoveItem: !isEditable ? onRemoveItem : undefined, onReorder: !isEditable ? onReorder : undefined,
330
336
  /* 편집 모드에는 얹을 항목이 없다 — 칸을 손으로 채우는 꼴이다 */
331
- renderCellAction: !isEditable ? renderCellAction : undefined, showFillerAdd: !!onAddItem, contentRef: contentRef })), inlineNameFields && !hideControls && (_jsxs(Panel, { size: "medium", labelWidth: 64, useBorder: false, useRowBorder: false, className: "word-table-block__meta", children: [_jsx(PanelRow, { variant: "custom", label: t('wordTable.save.nameLabel', '이름'), required: true, align: "vertical", useFullWidth: true, children: _jsx(TextField, { required: true, value: nameValue ?? '', onChange: onNameChange, placeholder: namePlaceholder ? namePlaceholder + t('wordTable.save.autoFillHint', ' (Tab 또는 space 입력시 자동 입력)') : t('wordTable.save.placeholder', '이 단어표에 대한 제목을 입력하세요.'),
337
+ renderCellAction: !isEditable ? renderCellAction : undefined, brand: 띠, code: code, showFillerAdd: !!onAddItem, contentRef: contentRef })), inlineNameFields && !hideControls && (_jsxs(Panel, { size: "medium", labelWidth: 64, useBorder: false, useRowBorder: false, className: "word-table-block__meta", children: [_jsx(PanelRow, { variant: "custom", label: t('wordTable.save.nameLabel', '이름'), required: true, align: "vertical", useFullWidth: true, children: _jsx(TextField, { required: true, value: nameValue ?? '', onChange: onNameChange, placeholder: namePlaceholder ? namePlaceholder + t('wordTable.save.autoFillHint', ' (Tab 또는 space 입력시 자동 입력)') : t('wordTable.save.placeholder', '이 단어표에 대한 제목을 입력하세요.'),
332
338
  /* PanelRow의 라벨은 시각적으로만 붙어 있고 입력과 연결돼 있지 않아, 낭독기에는
333
339
  이름 없는 입력으로 읽혔다. placeholder는 자동완성 안내문("Tab 또는 space…")이
334
340
  섞여 있어 이름으로 부적절하므로 같은 라벨 문구를 넘긴다. */
@@ -3,7 +3,7 @@ import { useMemo, useState, useCallback, useEffect } from 'react';
3
3
  import { useTranslation } from 'react-i18next';
4
4
  import { chunkItems } from '../wordTableLayout';
5
5
  import { getImageResolver, checkImageExists } from '../../utils/imageConfig';
6
- import { buildCompositionSection, buildOrientationSection, buildBorderSection, buildCutLineSection, buildDivLineSection, buildPageNumberSection, buildTopInfoSection, buildBottomInfoSection, buildItemsPerPageSection, buildTableSettingsSelected, applyTableSettingsSelect, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition, itemsPerPageFromChoice, } from './wordTableSettings';
6
+ import { buildCompositionSection, buildOrientationSection, buildBorderSection, buildCutLineSection, buildDivLineSection, buildTopInfoSection, buildBottomInfoSection, buildItemsPerPageSection, buildTableSettingsSelected, applyTableSettingsSelect, PAGE_NUM_POSITION_VALUE, parsePageNumberPosition, itemsPerPageFromChoice, } from './wordTableSettings';
7
7
  /**
8
8
  * 표시 전용 단어표(편집 없음)의 '표 설정' FilterPanel 상태·섹션을 제공하는 훅.
9
9
  * 구성·용지 방향·외곽선, 내용이 있으면 페이지당 개수, 2페이지 이상이면 페이지 번호, 항목이 2개 이상이면
@@ -61,11 +61,13 @@ opts) {
61
61
  buildOrientationSection(t),
62
62
  // 내용이 있는(1개 이상) 표면 페이지당 개수를 항상 노출.
63
63
  ...(itemCount > 0 ? [buildItemsPerPageSection(t, { hasPairs, itemCount })] : []),
64
- /* '페이지 번호' 자리 고르개는 **띠를 켠 화면에서만** 걷는다(사용자 지시 2026-09-30) —
65
- 거기서는 쪽 번호가 바닥 띠 오른쪽에 고정으로 서므로 고를 자리가 없다. 띠를 안 쓰는
66
- 화면은 전과 똑같아야 한다: 한때 조건 없이 걷었는데, 그러면 이 기능을 쓰던 화면에서
67
- 절이 통째로 사라진다(말귀가 깔기 전에 짚었다, 2026-09-30). */
68
- ...(!띠쓰나 && isPaginated ? [buildPageNumberSection(t)] : []),
64
+ /* **'페이지 번호' 자리 고르개는 없앤다** — 띠를 쓰든 안 쓰든 아예 안 세운다.
65
+ 사용자 지시다(2026-09-30). `pageNumberPosition` 프롭은 남아 있어 부르는 쪽이 코드로
66
+ 자리를 정할 수는 있고, 다만 화면에서 고르게 하지 않는다.
67
+
68
+ **한 번 되돌렸다가 다시 걷었다.** 0.215.0 에서 '띠를 안 쓰는 화면의 회귀' 로 보고
69
+ 조건을 달아 되살렸는데, 그 판단의 바탕이 된 보고가 틀렸다(한 쪽짜리 표라 안 보였던
70
+ 것을 사라진 것으로 읽었다). 없애는 것이 본래 지시였다 — 다시 되살리지 말 것. */
69
71
  buildBorderSection(t),
70
72
  ...(count > 1 ? [buildCutLineSection(t)] : []),
71
73
  ...(hasPairs ? [buildDivLineSection(t)] : []),
@@ -3,6 +3,7 @@
3
3
  * WordTableBlock의 순수 타입 및 인터페이스 정의.
4
4
  */
5
5
  import type { ReactNode } from 'react';
6
+ import type { WordTableProps } from '../WordTable';
6
7
  import type { WordSearchResultItem } from './WordSearchResultBlock';
7
8
  export interface WordEntry {
8
9
  word: string;
@@ -49,6 +50,16 @@ export interface WordTableBlockProps {
49
50
  onFilterCollapsedChange?: (collapsed: boolean) => void;
50
51
  /** 그 판이 접혀도 머리 오른쪽에 남는 자리 — 요약. 안 주면 코어가 짓는다(`FilterPanel`). */
51
52
  filterHeaderSummary?: ReactNode;
53
+ /**
54
+ * 종이 머리·바닥 띠 — 안쪽 `WordTable` 로 그대로 간다.
55
+ *
56
+ * **`show` 가 켜지면 표 설정에 `상단 정보`·`하단 정보` 고르개가 저절로 선다** — 스위치를
57
+ * 둘로 나누면 부르는 쪽이 어긋나게 줄 수 있어서다(말귀 요청 ㉛, 2026-09-30). 그 고르개로
58
+ * 켜고 끈 값은 `top`·`bottom` 을 **덮어쓴다** — 화면에서 사람이 고른 것이 마지막 말이다.
59
+ */
60
+ brand?: WordTableProps['brand'];
61
+ /** 표 번호 — 띠 위 오른쪽에 선다. 안쪽 `WordTable` 로 그대로 간다. */
62
+ code?: string;
52
63
  disc?: ReactNode;
53
64
  /** 현재 표의 저장된 이름(불러온 표 등). 있으면 인쇄/PDF 저장 시 파일명으로 사용, 없으면 "오늘 날짜 + 단어표/대립쌍표" */
54
65
  tableName?: string;
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export declare const CORE_VERSION = "0.215.0";
49
+ export declare const CORE_VERSION = "0.216.0";
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export const CORE_VERSION = '0.215.0';
49
+ export const CORE_VERSION = '0.216.0';