@ihabdevteam/core 0.185.0 → 0.186.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -809,6 +809,12 @@ export declare interface ButtonProps {
809
809
  * 크기. 숫자는 CSS 의 크기 사다리(.button24 … .button72)와 짝을 이룬다 — **사다리에
810
810
  * 없는 숫자를 넣으면 그 버튼은 크기·모서리 규칙을 하나도 못 받는다.**
811
811
  *
812
+ * **숫자 size 는 좁은 화면에서 한 급씩 줄어든다.** 1024px 이하에서 56→50 · 48→44 · 40→36 · 32→30,
813
+ * 768px 이하에서 56→46 · 48→40 · 40→34 · **32→28** 이다. `'Keyboard'`(32px)·`'Word'`(72px) 는
814
+ * **줄어들지 않는다** — 그 둘에는 반응형 규칙이 없다. 그래서 폰에서 `size={32}` 와 `size="Keyboard"` 를
815
+ * 한 줄에 세우면 28 과 32 로 4px 어긋난다(말귀 '표 설정' 패널 실측 2026-09-22). 나란히 세워야 하면
816
+ * 양쪽 다 숫자 size 를 쓰거나 양쪽 다 Keyboard 를 쓴다.
817
+ *
812
818
  * 목록이 사실과 두 군데 어긋나 있었다. 72 는 CSS 에 있는데 목록에 없었고, 52 는
813
819
  * 목록에도 CSS 에도 없는데 BottomNav 가 쓰고 있었다(그쪽은 자체 클래스가 !important
814
820
  * 로 크기를 다시 잡아 겉으로는 멀쩡했다). 둘 다 사실에 맞춘다 — 52 는 CSS 에 칸을
@@ -1600,7 +1606,7 @@ export declare function copyText(text: string): Promise<boolean>;
1600
1606
  *
1601
1607
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
1602
1608
  */
1603
- export declare const CORE_VERSION = "0.185.0";
1609
+ export declare const CORE_VERSION = "0.186.1";
1604
1610
 
1605
1611
  /** @public */
1606
1612
  declare const coreDefault: Record<string, unknown>;
@@ -5880,6 +5886,11 @@ export declare interface SegmentedControlProps {
5880
5886
  /** 버튼 높이 — Button 과 같은 숫자 size (24 / 32 / 40 / 48). **기본 40.** **겉높이가 곧 size 다**(0.159.0 부터 판 여백 없음) —
5881
5887
  * 옆에 `Button size={32}` 를 세우면 셈 없이 나란히 선다.
5882
5888
  *
5889
+ * **다만 768px 이하에서는 숫자 size 가 한 급 줄어든다**(Button 의 반응형 사다리: 48→40 · 40→34 · **32→28**; 1024px 이하에서는 32→30).
5890
+ * 고르개는 그 Button 을 그대로 쓰므로 함께 줄어든다. 그래서 폰에서 `size={32}` 는 **28px** 로 선다.
5891
+ * 같은 줄에 `size="Keyboard"`(32px 고정, 줄어들지 않는다) 단추를 세우면 폰에서만 4px 어긋난다 —
5892
+ * 나란히 세워야 하면 양쪽 다 숫자 size 를 쓰거나 양쪽 다 Keyboard 를 쓴다(말귀 '표 설정' 패널 실측 2026-09-22).
5893
+ *
5883
5894
  * 기본이 48 이던 것을 40 으로 내렸다(사용자 지시 2026-09-21). 코어가 제 안에서 세우는 고르개는 이미 **전부 40**
5884
5895
  * 이었고(`Toolbar`·`FilterPanel`·`BarChart`·`PanelRow`…), 옆에 서는 `Dropdown`·`TextField` 도 40 이다 —
5885
5896
  * 기본값만 혼자 48 이라 안 주고 쓴 자리가 한 급 크게 섰다. */
@@ -6854,9 +6865,12 @@ export declare type TableProps<T extends {
6854
6865
  draggingId?: string | number | null;
6855
6866
  gridTemplateColumns?: string;
6856
6867
  /**
6857
- * 좁은 화면(767px 이하)에서 쓸 열 틀. 주면 그 폭에서는 이 틀을 쓰고 **끌어 둔 폭(저장값)은 무시**한다 —
6858
- * 폭 조절 손잡이와 '칸 너비 초기화' 단추도 숨겨, 좁은 화면에서 끈 값이 넓은 화면의 저장값을 덮지 않게 한다.
6859
- * 넓은 화면으로 돌아오면 저장값이 그대로 돌아온다. 미지정이면 좁은 화면도 넓은 화면과 같다(기본).
6868
+ * 좁은 화면(767px 이하)에서 쓸 열 틀. 주면 그 폭에서는 이 틀을 쓴다.
6869
+ *
6870
+ * **안 줘도 좁은 화면에서는 끌어 둔 폭(저장값)을 안 쓰고, 손잡이와 '칸 너비 초기화' 단추도 숨긴다** —
6871
+ * 이 prop 과 무관하게 늘 그렇다. 그래서 안 주면 좁은 화면에서는 `gridTemplateColumns` 가 그대로 서고,
6872
+ * 그 틀의 `minmax(…px, …fr)` 밑값이 지켜지며 넘치는 만큼은 표가 제 가로 스크롤로 보여 준다.
6873
+ * 넓은 화면으로 돌아오면 저장값이 그대로 돌아온다.
6860
6874
  *
6861
6875
  * 열 틀은 인라인 CSS 변수로 들어가므로 앱 CSS 로는 `!important` 없이 못 이긴다 — 그래서 prop 이다.
6862
6876
  * 숨길 칸은 `renderRow` 의 `className`·`fields[].className` 으로 앱이 가린다.
@@ -81,7 +81,13 @@ export default function FilterPanel({ title, headerAction, collapsible = false,
81
81
  /* 같은 패널의 항목 단추(size "keyboard" = 32)와 같은 높이다(사용자 지시 2026-09-22).
82
82
  전에는 40 이었는데, 그 까닭으로 적힌 "옆 Dropdown·TextField(40)와 맞춘다" 는
83
83
  실제와 달랐다 — 세그먼트를 쓰는 패널(단어표 '표 설정')에는 Dropdown 도 TextField 도
84
- 없고 32 짜리 항목 단추만 있어, 한 줄에 32 와 40 이 섞여 세그먼트만 커 보였다. */
84
+ 없고 32 짜리 항목 단추만 있어, 한 줄에 32 와 40 이 섞여 세그먼트만 커 보였다.
85
+
86
+ **폰에서는 28 로 선다 — 그대로 두기로 했다(사용자 판단 2026-09-22).** 숫자 size 는
87
+ 768px 이하에서 한 급 줄고(32→28) `size="Keyboard"`(32)는 안 줄어, 폰에서만 4px 이
88
+ 어긋난다. 맞추려면 항목 단추를 숫자 32 로 내려야 하는데 그러면 손가락 자리가 28 이
89
+ 된다 — 같은 날 채점 단추를 33 까지 끌어올린 것과 반대 방향이라 어긋남 쪽을 택했다.
90
+ 사다리는 `test/buttonSizeLadder0922.test.ts` 에 못 박혀 있다. */
85
91
  size: 32 })) : (!section.multiple && section.display === 'dropdown') ? (_jsx(Dropdown, { className: "filter-panel__dropdown", size: 40, options: [
86
92
  ...(section.allOption ? [{ value: FILTER_ALL_VALUE, label: section.allLabel ?? t('common.all', '전체'), disabled: section.allOptionDisabled }] : []),
87
93
  ...items.map(item => {
@@ -98,7 +98,13 @@ export default function ListPopup(props) {
98
98
  sortDir: col.sortable !== false && sortKey === col.key ? (sortAsc ? 'asc' : 'desc') : null,
99
99
  })), [columns, sortKey, sortAsc, handleSort]);
100
100
  const pageSizeOpts = useMemo(() => pageSizeOptions.map(n => ({ value: String(n), label: t('common.pageSizeLabel', { n }) })), [pageSizeOptions, t]);
101
- return (_jsxs(Popup, { className: `popup-list ${className}`, title: title, onClose: onClose, children: [topContent, 'label' in countParts && _jsx("p", { className: "toolbar__count body-1", children: countParts.label }), _jsx(Toolbar, { count: 'label' in countParts ? undefined : { value: filtered.length, prefix: countParts.prefix, suffix: countParts.suffix }, search: searchEnabled ? { value: search, onChange: setSearch, placeholder: resolvedSearchPlaceholder } : undefined, pageSize: { options: pageSizeOpts, value: pageSize, onChange: setPageSize }, pagination: { page: 이쪽, total: totalPages, onChange: setPage } }), _jsx(Table, { size: "medium", gridTemplateColumns: gridTemplateColumns, mobileGridTemplateColumns: mobileGridTemplateColumns, resizable: true, fields: tableFields, items: paged, onRowClick: onSelect, renderRow: (it) => {
101
+ return (_jsxs(Popup, { className: `popup-list ${className}`, title: title, onClose: onClose, children: [topContent, 'label' in countParts && _jsx("p", { className: "toolbar__count body-1", children: countParts.label }), _jsx(Toolbar, { count: 'label' in countParts ? undefined : { value: filtered.length, prefix: countParts.prefix, suffix: countParts.suffix }, search: searchEnabled ? { value: search, onChange: setSearch, placeholder: resolvedSearchPlaceholder } : undefined, pageSize: { options: pageSizeOpts, value: pageSize, onChange: setPageSize }, pagination: { page: 이쪽, total: totalPages, onChange: setPage } }), _jsx(Table, { size: "medium", gridTemplateColumns: gridTemplateColumns, mobileGridTemplateColumns: mobileGridTemplateColumns,
102
+ /* 폭 조절은 켜되 **저장 열쇠는 안 준다** — 창은 열 때마다 새로 짓는 것이라 폭이 따라다니면
103
+ 엉뚱하다. 그래서 이 창의 표는 끌어도 localStorage 에 아무 자국이 없고, 끈 폭은 창이 닫히면
104
+ 사라진다. 폰에서 '오늘의 듣기 현황 상세보기' 가 `48px 48px …` 로 눌렸는데 저장소가
105
+ 비어 있어 한동안 원인을 못 찾았던 자리다(2026-09-22) — 손잡이 띠에 스친 손짓이 메모리에만
106
+ 폭을 박은 것이었다. 0.186.0 부터 좁은 화면에서는 손잡이도 없고 끈 폭도 안 쓴다. */
107
+ resizable: true, fields: tableFields, items: paged, onRowClick: onSelect, renderRow: (it) => {
102
108
  const isSelected = selectedIds != null
103
109
  ? (it.id != null && selectedIds.has(it.id))
104
110
  : (selectedId != null && it.id === selectedId);
@@ -20,6 +20,11 @@ export interface SegmentedControlProps {
20
20
  /** 버튼 높이 — Button 과 같은 숫자 size (24 / 32 / 40 / 48). **기본 40.** **겉높이가 곧 size 다**(0.159.0 부터 판 여백 없음) —
21
21
  * 옆에 `Button size={32}` 를 세우면 셈 없이 나란히 선다.
22
22
  *
23
+ * **다만 768px 이하에서는 숫자 size 가 한 급 줄어든다**(Button 의 반응형 사다리: 48→40 · 40→34 · **32→28**; 1024px 이하에서는 32→30).
24
+ * 고르개는 그 Button 을 그대로 쓰므로 함께 줄어든다. 그래서 폰에서 `size={32}` 는 **28px** 로 선다.
25
+ * 같은 줄에 `size="Keyboard"`(32px 고정, 줄어들지 않는다) 단추를 세우면 폰에서만 4px 어긋난다 —
26
+ * 나란히 세워야 하면 양쪽 다 숫자 size 를 쓰거나 양쪽 다 Keyboard 를 쓴다(말귀 '표 설정' 패널 실측 2026-09-22).
27
+ *
23
28
  * 기본이 48 이던 것을 40 으로 내렸다(사용자 지시 2026-09-21). 코어가 제 안에서 세우는 고르개는 이미 **전부 40**
24
29
  * 이었고(`Toolbar`·`FilterPanel`·`BarChart`·`PanelRow`…), 옆에 서는 `Dropdown`·`TextField` 도 40 이다 —
25
30
  * 기본값만 혼자 48 이라 안 주고 쓴 자리가 한 급 크게 섰다. */
@@ -54,9 +54,12 @@ export type TableProps<T extends {
54
54
  draggingId?: string | number | null;
55
55
  gridTemplateColumns?: string;
56
56
  /**
57
- * 좁은 화면(767px 이하)에서 쓸 열 틀. 주면 그 폭에서는 이 틀을 쓰고 **끌어 둔 폭(저장값)은 무시**한다 —
58
- * 폭 조절 손잡이와 '칸 너비 초기화' 단추도 숨겨, 좁은 화면에서 끈 값이 넓은 화면의 저장값을 덮지 않게 한다.
59
- * 넓은 화면으로 돌아오면 저장값이 그대로 돌아온다. 미지정이면 좁은 화면도 넓은 화면과 같다(기본).
57
+ * 좁은 화면(767px 이하)에서 쓸 열 틀. 주면 그 폭에서는 이 틀을 쓴다.
58
+ *
59
+ * **안 줘도 좁은 화면에서는 끌어 둔 폭(저장값)을 안 쓰고, 손잡이와 '칸 너비 초기화' 단추도 숨긴다** —
60
+ * 이 prop 과 무관하게 늘 그렇다. 그래서 안 주면 좁은 화면에서는 `gridTemplateColumns` 가 그대로 서고,
61
+ * 그 틀의 `minmax(…px, …fr)` 밑값이 지켜지며 넘치는 만큼은 표가 제 가로 스크롤로 보여 준다.
62
+ * 넓은 화면으로 돌아오면 저장값이 그대로 돌아온다.
60
63
  *
61
64
  * 열 틀은 인라인 CSS 변수로 들어가므로 앱 CSS 로는 `!important` 없이 못 이긴다 — 그래서 prop 이다.
62
65
  * 숨길 칸은 `renderRow` 의 `className`·`fields[].className` 으로 앱이 가린다.
@@ -76,12 +76,44 @@ function TableField({ children, onClick, className, sortDir, ariaSort, onResizeS
76
76
  /** @public */
77
77
  export default function Table({ fields = [], headerGroups, items = [], renderRow, onRowClick, isRowClickable, onRowMouseEnter, onRowMouseLeave, onRowDragStart, onRowDragEnd, draggingId, gridTemplateColumns, mobileGridTemplateColumns, resizable = false, resizeMode = 'grow', resizeStorageKey, size = 'small', title, icon, count, collapsed = false, onToggleCollapse, headerActions, empty, loading, dragOver = false, onDragOver, onDragLeave, onDrop, renderExpandedRow, expandedId, }) {
78
78
  const { t } = useTranslation();
79
- /* 좁은 화면 틀이 걸리면 폭 조절을 통째로 끈다 — 손잡이·초기화 단추·저장. 훅은 늘 부른다(순서 고정). */
79
+ /**
80
+ * **좁은 화면에서는 폭 조절을 통째로 끈다** — 손잡이·초기화 단추·저장값.
81
+ *
82
+ * 예전엔 `mobileGridTemplateColumns` 를 준 표에서만 껐다. 그런데 손잡이는 열마다
83
+ * 오른쪽 끝 10px 을 `touch-action: none` 으로 먹는 띠다 — 폰에서 표를 옆으로 밀려고
84
+ * 짚은 손가락이 그 띠에 닿으면 스크롤이 아니라 **폭 조절**이 되고, 손을 떼는 순간
85
+ * 그 값이 저장소에 박힌다. 폰에서는 칸 하나가 밑값(48px)까지 눌리기 쉬워, 그 뒤로는
86
+ * 열어도 열어도 눌린 표가 나온다(말귀 '오늘의 듣기 현황': 앱이 준
87
+ * `minmax(130px,0.9fr) minmax(140px,1.1fr) 150px 110px 160px` 인데 `48px 48px …` 로 서고
88
+ * 줄 높이가 161px 이 됐다 — 2026-09-22 실측).
89
+ *
90
+ * 게다가 저장값은 화면 폭마다 따로 두지 않는다. 넓은 화면에서 끌어 둔 px 폭이 폰에도
91
+ * 그대로 씌워지므로, 좁은 화면에서는 **만든 사람이 적어 둔 틀**로 돌아가는 편이 맞다 —
92
+ * 그 틀의 `minmax(…px, …fr)` 밑값이 살아나 칸이 제 폭을 갖고, 넘치는 만큼은 표가
93
+ * 제 가로 스크롤로 보여 준다. 넓은 화면으로 돌아오면 저장값이 그대로 돌아온다.
94
+ *
95
+ * **좁은 화면의 답은 그 가로 스크롤이다 — 접는 꼴(카드형)은 넣지 않는다(사용자 판단 2026-09-22).**
96
+ * 한 줄을 두 줄 카드로 접는 안과 중요한 칸만 두고 펼치는 안을 함께 올렸으나, 표는 표로 두고
97
+ * 옆으로 미는 쪽을 골랐다. 그러니 담을 수 없을 만큼 좁을 때 칸을 눌러 담으려 드는 코드를
98
+ * 다시 넣지 말 것 — 그게 위에 적은 48px 사건이었다.
99
+ */
80
100
  const 좁은화면 = useMediaQuery('(max-width: 767px)');
81
101
  const 좁은틀 = mobileGridTemplateColumns && 좁은화면 ? mobileGridTemplateColumns : null;
82
- const 폭조절 = resizable && !좁은틀;
102
+ const 폭조절 = resizable && !좁은화면;
83
103
  // ── 컬럼 폭 드래그 리사이즈 (resizable) ──
104
+ /** 만든 사람이 밑값을 안 적은 칸에 쓰는 바닥 — 이보다 좁으면 손잡이를 다시 잡을 수도 없다 */
84
105
  const MIN_COL_W = 48;
106
+ /**
107
+ * **그 칸의 가장 좁은 폭** — `minmax(130px, 0.9fr)` 의 130, 못 박은 `150px` 의 150.
108
+ *
109
+ * 밑값을 한 값(48px)으로 두면 만든 사람이 적어 둔 바닥을 넘어 깎인다. 고정 칸 합이
110
+ * 자리보다 넓을 때 유동 칸을 눌러 버리는 쪽인데, **밑값이 있으면 지키고 넘치는 만큼
111
+ * 굴리는 것**이 맞다(가로 스크롤). `test/tableColumnMinWidth.test.ts` 가 요구하는
112
+ * `minmax(120px, 1fr)` 도 그래야 뜻이 있다.
113
+ *
114
+ * 자동맞춤은 이 값보다 위에서 정의되므로 ref 로 건넨다(isFixedPxRef 와 같은 이유).
115
+ */
116
+ const 밑값Ref = useRef(() => MIN_COL_W);
85
117
  const storeKey = resizeStorageKey ? `table-cols:${resizeStorageKey}` : null;
86
118
  const fieldsRef = useRef(null);
87
119
  const isFixedPxRef = useRef(() => false);
@@ -127,12 +159,12 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
127
159
  // fit — 이동량을 두 컬럼이 나눠 갖는다. 둘 다 최소폭 밑으로 내려가지 않도록
128
160
  // 이동량 자체를 먼저 자른다(한쪽만 자르면 합이 달라져 표 폭이 흔들린다).
129
161
  const raw = e.clientX - d.startX;
130
- const delta = Math.max(MIN_COL_W - d.startW, Math.min(d.startWNext - MIN_COL_W, raw));
162
+ const delta = Math.max(밑값Ref.current(d.index) - d.startW, Math.min(d.startWNext - 밑값Ref.current(d.next), raw));
131
163
  copy[d.index] = d.startW + delta;
132
164
  copy[d.next] = d.startWNext - delta;
133
165
  }
134
166
  else {
135
- copy[d.index] = Math.max(MIN_COL_W, d.startW + (e.clientX - d.startX));
167
+ copy[d.index] = Math.max(밑값Ref.current(d.index), d.startW + (e.clientX - d.startX));
136
168
  }
137
169
  colWidthsRef.current = copy;
138
170
  setColWidths(copy);
@@ -213,7 +245,7 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
213
245
  /* 고정 px 열은 정해 둔 값 그대로 — 최소 폭(MIN_COL_W)으로 올린 값을 고정 합에 넣으면 격자에는 원래 값(40px)으로 나가
214
246
  그만큼(8px) 표 오른쪽이 비었다(말귀 어드민 버그 탭·쿠폰) */
215
247
  const 고정값 = isFixedPxRef.current(i) ? parseFloat(defaultColsRef.current[i]) : NaN;
216
- return Number.isFinite(고정값) ? 고정값 : Math.max(MIN_COL_W, Math.ceil(w));
248
+ return Number.isFinite(고정값) ? 고정값 : Math.max(밑값Ref.current(i), Math.ceil(w));
217
249
  });
218
250
  /* 격자가 실제로 채우는 폭 — `clientWidth` 는 안쪽 여백을 포함하므로 뺀다
219
251
  (되돌리기 단추 자리로 40px 을 비워 두었다). */
@@ -237,7 +269,7 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
237
269
  const 남은 = 그릇 - 고정;
238
270
  if (그릇 > 0 && 유동 > 0 && 남은 > 0) {
239
271
  const 비 = 남은 / 유동;
240
- 최종 = 잰것.map((w, i) => (isFixedPxRef.current(i) ? w : Math.max(MIN_COL_W, Math.round(w * 비))));
272
+ 최종 = 잰것.map((w, i) => (isFixedPxRef.current(i) ? w : Math.max(밑값Ref.current(i), Math.round(w * 비))));
241
273
  /* 반올림으로 남은 몇 px 은 가장 넓은 유동 열에 얹는다 — 안 그러면 늘 몇 px 이
242
274
  비거나 넘친다. 최소 폭에 걸린 열이 있으면 넘칠 수 있는데, 그건 담을 수
243
275
  없을 만큼 좁다는 뜻이라 가로 스크롤로 두는 편이 맞다. */
@@ -247,7 +279,7 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
247
279
  최종.forEach((w, i) => { if (!isFixedPxRef.current(i) && (넓은 < 0 || w > 최종[넓은]))
248
280
  넓은 = i; });
249
281
  if (넓은 >= 0)
250
- 최종[넓은] = Math.max(MIN_COL_W, 최종[넓은] + 차);
282
+ 최종[넓은] = Math.max(밑값Ref.current(넓은), 최종[넓은] + 차);
251
283
  }
252
284
  }
253
285
  setColWidths(최종);
@@ -325,11 +357,20 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
325
357
  return fields[i].onClick ? 'none' : undefined;
326
358
  };
327
359
  const isFixedPx = (i) => /^\d+(\.\d+)?px$/.test(defaultCols[i] ?? '');
328
- /* `자동맞춤` 은 이 둘보다 위에서 정의되므로(콜백이라 부를 때 읽힌다) ref 로 건넨다 */
360
+ /** 그 칸에 적힌 밑값 — `minmax(130px, …)` 의 130, 못 박은 `150px` 의 150, 없으면 MIN_COL_W */
361
+ const 선언밑값 = (i) => {
362
+ const t = defaultCols[i] ?? '';
363
+ const m = /^minmax\(\s*(\d+(?:\.\d+)?)px\s*,/.exec(t) ?? /^(\d+(?:\.\d+)?)px$/.exec(t);
364
+ return m ? parseFloat(m[1]) : MIN_COL_W;
365
+ };
366
+ /* `자동맞춤` 은 이 셋보다 위에서 정의되므로(콜백이라 부를 때 읽힌다) ref 로 건넨다 */
329
367
  isFixedPxRef.current = isFixedPx;
368
+ 밑값Ref.current = 선언밑값;
330
369
  defaultColsRef.current = defaultCols;
331
370
  const widthUnit = (i) => (resizeMode === 'fit' && !isFixedPx(i)) ? 'fr' : 'px';
371
+ /* 좁은 화면에서는 저장값을 아예 안 쓴다 — 위 `폭조절` 의 글을 보라(넓은 화면에서 끈 폭이 폰에 씌워진다) */
332
372
  const canApplyWidths = colWidths && colWidths.length === fields.length
373
+ && !좁은화면
333
374
  && (resizeMode === 'fit' || items.length > 0);
334
375
  const effectiveGrid = 좁은틀 ?? (canApplyWidths
335
376
  ? fields.map((f, i) => (f.resizable === false && defaultCols[i]) ? defaultCols[i] : `${colWidths[i]}${widthUnit(i)}`).join(' ')
@@ -16,6 +16,12 @@ export interface ButtonProps {
16
16
  * 크기. 숫자는 CSS 의 크기 사다리(.button24 … .button72)와 짝을 이룬다 — **사다리에
17
17
  * 없는 숫자를 넣으면 그 버튼은 크기·모서리 규칙을 하나도 못 받는다.**
18
18
  *
19
+ * **숫자 size 는 좁은 화면에서 한 급씩 줄어든다.** 1024px 이하에서 56→50 · 48→44 · 40→36 · 32→30,
20
+ * 768px 이하에서 56→46 · 48→40 · 40→34 · **32→28** 이다. `'Keyboard'`(32px)·`'Word'`(72px) 는
21
+ * **줄어들지 않는다** — 그 둘에는 반응형 규칙이 없다. 그래서 폰에서 `size={32}` 와 `size="Keyboard"` 를
22
+ * 한 줄에 세우면 28 과 32 로 4px 어긋난다(말귀 '표 설정' 패널 실측 2026-09-22). 나란히 세워야 하면
23
+ * 양쪽 다 숫자 size 를 쓰거나 양쪽 다 Keyboard 를 쓴다.
24
+ *
19
25
  * 목록이 사실과 두 군데 어긋나 있었다. 72 는 CSS 에 있는데 목록에 없었고, 52 는
20
26
  * 목록에도 CSS 에도 없는데 BottomNav 가 쓰고 있었다(그쪽은 자체 클래스가 !important
21
27
  * 로 크기를 다시 잡아 겉으로는 멀쩡했다). 둘 다 사실에 맞춘다 — 52 는 CSS 에 칸을
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export declare const CORE_VERSION = "0.185.0";
49
+ export declare const CORE_VERSION = "0.186.1";
@@ -46,4 +46,4 @@
46
46
  *
47
47
  * 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
48
48
  */
49
- export const CORE_VERSION = '0.185.0';
49
+ export const CORE_VERSION = '0.186.1';