@ihabdevteam/core 0.67.0 → 0.69.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/dist/components.cjs.js +16 -16
- package/dist/components.esm.css +1 -1
- package/dist/hooks.cjs.js +1 -1
- package/dist/i18n.cjs.js +5 -5
- package/dist/ihabdevteam-core.cjs.js +180 -180
- package/dist/ihabdevteam-core.d.ts +23 -4
- package/dist/packages/core/src/components/Table.css +22 -0
- package/dist/packages/core/src/components/Table.js +69 -7
- package/dist/packages/core/src/i18n/locales/en.json +3 -0
- package/dist/packages/core/src/i18n/locales/ko.json +3 -0
- package/dist/packages/core/src/version.d.ts +23 -4
- package/dist/packages/core/src/version.js +23 -4
- package/dist/utils.cjs.js +1 -1
- package/package.json +1 -1
|
@@ -1283,11 +1283,30 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1283
1283
|
* (락파일 검사는 서버가 뜰 때 한 번이다 — CONSUMING.md 2-0-1).
|
|
1284
1284
|
*
|
|
1285
1285
|
* 그래서 "올렸다고 믿으며 옛 코드를 검증하는" 일이 생긴다. 이 상수는 소비자가
|
|
1286
|
-
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
1287
|
-
* 콘솔에서 한 줄로 확인할 수 있다:
|
|
1286
|
+
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
1288
1287
|
*
|
|
1289
1288
|
* ```js
|
|
1290
|
-
*
|
|
1289
|
+
* // 앱 코드 안에서
|
|
1290
|
+
* import { CORE_VERSION } from '@ihabdevteam/core/components'
|
|
1291
|
+
* ```
|
|
1292
|
+
*
|
|
1293
|
+
* **브라우저 콘솔에서는 저 이름이 안 풀린다.** 맨 이름(bare specifier)은 번들러가
|
|
1294
|
+
* 푸는 것이라 콘솔에는 그 지도가 없다 — `Failed to resolve module specifier` 가 난다
|
|
1295
|
+
* (2026-09-09 실측. 이 글이 그렇게 적혀 있어 두 사람이 헛걸음했다). Vite dev
|
|
1296
|
+
* 서버에서는 `/@id/` 를 앞에 붙이면 콘솔에서도 풀린다:
|
|
1297
|
+
*
|
|
1298
|
+
* ```js
|
|
1299
|
+
* import('/@id/@ihabdevteam/core/components').then(m => console.log(m.CORE_VERSION))
|
|
1300
|
+
* ```
|
|
1301
|
+
*
|
|
1302
|
+
* CSS 만 궁금하다면 **화면에 실제로 걸린 규칙**을 CSSOM 에서 바로 읽는 편이 더
|
|
1303
|
+
* 확실하다 — 번들러와 무관하고, 판 번호가 아니라 그리는 데 쓰이는 값을 본다:
|
|
1304
|
+
*
|
|
1305
|
+
* ```js
|
|
1306
|
+
* for (const ss of document.styleSheets) {
|
|
1307
|
+
* try { for (const r of ss.cssRules) if (/\.button:disabled/.test(r.selectorText ?? '')) console.log(r.style.cssText) }
|
|
1308
|
+
* catch { } // 다른 오리진의 시트는 못 읽는다
|
|
1309
|
+
* }
|
|
1291
1310
|
* ```
|
|
1292
1311
|
*
|
|
1293
1312
|
* 네 갈래(index·components·hooks·utils) 모두에서 내보낸다 — 갈래마다 따로
|
|
@@ -1295,7 +1314,7 @@ export declare function copyText(text: string): Promise<boolean>;
|
|
|
1295
1314
|
*
|
|
1296
1315
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
1297
1316
|
*/
|
|
1298
|
-
export declare const CORE_VERSION = "0.
|
|
1317
|
+
export declare const CORE_VERSION = "0.69.0";
|
|
1299
1318
|
|
|
1300
1319
|
/** @public */
|
|
1301
1320
|
declare const coreDefault: Record<string, unknown>;
|
|
@@ -75,6 +75,28 @@
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/* 필드 라벨 행 — 정렬 클릭 가능한 컬럼 헤더. 높이는 size 스케일을 따름 */
|
|
78
|
+
/* 되돌리기 단추를 얹을 겉. **`.table-fields` 안에 넣으면 안 된다** — 그 자식은
|
|
79
|
+
곧 열이라 칸이 하나 늘어 몸통 줄과 어긋난다. 겉에서 자리를 안 차지하게 얹는다. */
|
|
80
|
+
.table-fields-strip { position: relative; }
|
|
81
|
+
|
|
82
|
+
/* 단추가 설 자리를 비운다. **머리와 몸통에 똑같이** 줘야 한다 — 한쪽만 주면
|
|
83
|
+
그쪽 격자만 좁아져 열이 어긋난다.
|
|
84
|
+
|
|
85
|
+
마지막 **열**에 안쪽 여백을 주는 길로 먼저 갔다가 되돌렸다: `1fr` 트랙의
|
|
86
|
+
자동 최소 크기가 min-content 라, 여백이 그 최소를 키워 **그 열이 다른 열의
|
|
87
|
+
자리를 빼앗았다**(실측: 머리 52/32/32/**56**, 몸통 52/40/40/40 로 어긋났다.
|
|
88
|
+
`min-width: 0` 을 얹어도 안 풀렸다). 담은 상자에 주면 둘 다 같은 만큼
|
|
89
|
+
좁아져 정렬이 지켜진다. */
|
|
90
|
+
.table-wrap--resizable .table-fields,
|
|
91
|
+
.table-wrap--resizable .table-row { padding-right: 40px; }
|
|
92
|
+
|
|
93
|
+
.table-fields__reset {
|
|
94
|
+
position: absolute;
|
|
95
|
+
right: 2px;
|
|
96
|
+
top: 50%;
|
|
97
|
+
transform: translateY(-50%);
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
.table-fields {
|
|
79
101
|
display: grid;
|
|
80
102
|
grid-template-columns: var(--table-grid-cols, repeat(auto-fit, minmax(80px, 1fr)));
|
|
@@ -67,6 +67,8 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
|
|
|
67
67
|
const MIN_COL_W = 48;
|
|
68
68
|
const storeKey = resizeStorageKey ? `table-cols:${resizeStorageKey}` : null;
|
|
69
69
|
const fieldsRef = useRef(null);
|
|
70
|
+
const isFixedPxRef = useRef(() => false);
|
|
71
|
+
const defaultColsRef = useRef([]);
|
|
70
72
|
// fit 모드에서는 이웃(next)에서 같은 만큼 덜어 오므로 그 컬럼의 시작 폭도 들고 있어야 한다
|
|
71
73
|
const dragRef = useRef(null);
|
|
72
74
|
const [colWidths, setColWidths] = useState(() => {
|
|
@@ -144,6 +146,63 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
|
|
|
144
146
|
document.body.style.cursor = 'col-resize';
|
|
145
147
|
document.body.style.userSelect = 'none';
|
|
146
148
|
}, [onResizeMove, onResizeEnd, resizeMode, fields]);
|
|
149
|
+
/**
|
|
150
|
+
* **칸 너비를 내용에 맞춰 다시 잡는다.**
|
|
151
|
+
*
|
|
152
|
+
* 손으로 끌어 놓은 폭을 그냥 지우면(=기본 격자로 되돌리면) 만든 사람이 적어 둔
|
|
153
|
+
* 틀로 갈 뿐, 지금 화면에 든 글자에는 안 맞는다. 그래서 **재서** 맞춘다 —
|
|
154
|
+
* 잠깐 `max-content` 로 두고 머리 한 줄과 몸통 모든 줄의 칸을 재어, 열마다
|
|
155
|
+
* 가장 넓은 값을 쓴다. 잰 뒤 원래 값으로 되돌리므로 화면에는 안 보인다.
|
|
156
|
+
*
|
|
157
|
+
* 고정 px 로 못 박은 열(아이콘·숫자 칸)은 그대로 둔다 — 내용에 맞추면 정해 둔
|
|
158
|
+
* 크기를 잃는다.
|
|
159
|
+
*/
|
|
160
|
+
const 자동맞춤 = useCallback(() => {
|
|
161
|
+
const el = fieldsRef.current;
|
|
162
|
+
const wrap = el?.closest('.table-wrap');
|
|
163
|
+
if (!el || !wrap)
|
|
164
|
+
return;
|
|
165
|
+
const 이전 = wrap.style.getPropertyValue('--table-grid-cols');
|
|
166
|
+
wrap.style.setProperty('--table-grid-cols', fields.map((_, i) => (isFixedPxRef.current(i) ? defaultColsRef.current[i] : 'max-content')).join(' '));
|
|
167
|
+
/* 재는 동안만 max-content 다. `.table-fields` 와 각 줄은 서로 다른 격자라
|
|
168
|
+
이때는 열이 어긋나 보이지만, 같은 함수 안에서 되돌리므로 화면에는 안 나온다. */
|
|
169
|
+
const 줄들 = Array.from(wrap.querySelectorAll('.table-row'))
|
|
170
|
+
.filter(r => !r.classList.contains('table-row--loading') && !r.classList.contains('table-row--empty'));
|
|
171
|
+
const 잰것 = fields.map((_, i) => {
|
|
172
|
+
let w = el.children[i]?.getBoundingClientRect().width ?? 0;
|
|
173
|
+
for (const 줄 of 줄들) {
|
|
174
|
+
const c = 줄.children[i];
|
|
175
|
+
if (c)
|
|
176
|
+
w = Math.max(w, c.getBoundingClientRect().width);
|
|
177
|
+
}
|
|
178
|
+
return Math.max(MIN_COL_W, Math.ceil(w));
|
|
179
|
+
});
|
|
180
|
+
const 그릇 = el.clientWidth;
|
|
181
|
+
if (이전)
|
|
182
|
+
wrap.style.setProperty('--table-grid-cols', 이전);
|
|
183
|
+
else
|
|
184
|
+
wrap.style.removeProperty('--table-grid-cols');
|
|
185
|
+
/* `fit` 은 이 값을 fr 가중치로 내보내므로 합이 늘 그릇 폭이 된다 — 넘칠 일이 없다.
|
|
186
|
+
`grow` 는 px 라 내용이 그릇보다 넓으면 가로로 구른다. 그때는 고정 열을 뺀
|
|
187
|
+
나머지를 같은 비율로 줄여 그릇에 담는다(최소 폭은 지킨다). */
|
|
188
|
+
let 최종 = 잰것;
|
|
189
|
+
if (resizeMode !== 'fit' && 그릇 > 0) {
|
|
190
|
+
const 고정 = 잰것.reduce((a, w, i) => a + (isFixedPxRef.current(i) ? w : 0), 0);
|
|
191
|
+
const 유동 = 잰것.reduce((a, w, i) => a + (isFixedPxRef.current(i) ? 0 : w), 0);
|
|
192
|
+
const 남은 = 그릇 - 고정;
|
|
193
|
+
if (유동 > 남은 && 남은 > 0) {
|
|
194
|
+
const 비 = 남은 / 유동;
|
|
195
|
+
최종 = 잰것.map((w, i) => (isFixedPxRef.current(i) ? w : Math.max(MIN_COL_W, Math.floor(w * 비))));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
setColWidths(최종);
|
|
199
|
+
if (storeKey) {
|
|
200
|
+
try {
|
|
201
|
+
window.localStorage.setItem(storeKey, JSON.stringify(최종));
|
|
202
|
+
}
|
|
203
|
+
catch { /* 저장 실패 무시 */ }
|
|
204
|
+
}
|
|
205
|
+
}, [fields, resizeMode, storeKey]);
|
|
147
206
|
// 컬럼 리사이즈 드래그 도중(pointerup 발생 전) Table이 언마운트되면(예: 필터링/페이지 전환으로
|
|
148
207
|
// 목록이 통째로 바뀜) startResize에서 window에 등록한 pointermove/pointerup 리스너가 정리되지
|
|
149
208
|
// 않고 남아 언마운트된 컴포넌트의 setColWidths를 계속 호출하고, body 커서/userSelect도 고정된
|
|
@@ -171,6 +230,9 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
|
|
|
171
230
|
// fr로 바꾸면 창이 넓어질 때 아이콘 칸까지 함께 늘어나 정해 둔 크기를 잃는다.
|
|
172
231
|
// px 열이 커진 만큼 남는 공간이 줄고 fr 가중치 합도 같이 줄어 총폭은 그대로다.
|
|
173
232
|
const isFixedPx = (i) => /^\d+(\.\d+)?px$/.test(defaultCols[i] ?? '');
|
|
233
|
+
/* `자동맞춤` 은 이 둘보다 위에서 정의되므로(콜백이라 부를 때 읽힌다) ref 로 건넨다 */
|
|
234
|
+
isFixedPxRef.current = isFixedPx;
|
|
235
|
+
defaultColsRef.current = defaultCols;
|
|
174
236
|
const widthUnit = (i) => (resizeMode === 'fit' && !isFixedPx(i)) ? 'fr' : 'px';
|
|
175
237
|
const canApplyWidths = colWidths && colWidths.length === fields.length
|
|
176
238
|
&& (resizeMode === 'fit' || items.length > 0);
|
|
@@ -181,15 +243,15 @@ export default function Table({ fields = [], headerGroups, items = [], renderRow
|
|
|
181
243
|
? { '--table-grid-cols': effectiveGrid }
|
|
182
244
|
: undefined;
|
|
183
245
|
const hasHeader = title !== undefined;
|
|
184
|
-
return (_jsx("div", { className: `table-wrap table-wrap--${size}${dragOver ? ' table-wrap--dragover' : ''}${collapsed ? ' table-wrap--collapsed' : ''}`, style: gridStyle, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, children: _jsxs("div", { className: "table body-2", children: [hasHeader && (_jsxs("div", { className: "table-header", onClick: onToggleCollapse, role: onToggleCollapse ? 'button' : undefined, tabIndex: onToggleCollapse ? 0 : undefined, "aria-expanded": onToggleCollapse ? !collapsed : undefined, onKeyDown: onToggleCollapse ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
|
|
246
|
+
return (_jsx("div", { className: `table-wrap table-wrap--${size}${resizable ? ' table-wrap--resizable' : ''}${dragOver ? ' table-wrap--dragover' : ''}${collapsed ? ' table-wrap--collapsed' : ''}`, style: gridStyle, onDragOver: onDragOver, onDragLeave: onDragLeave, onDrop: onDrop, children: _jsxs("div", { className: "table body-2", children: [hasHeader && (_jsxs("div", { className: "table-header", onClick: onToggleCollapse, role: onToggleCollapse ? 'button' : undefined, tabIndex: onToggleCollapse ? 0 : undefined, "aria-expanded": onToggleCollapse ? !collapsed : undefined, onKeyDown: onToggleCollapse ? (e) => { if (e.key === 'Enter' || e.key === ' ') {
|
|
185
247
|
e.preventDefault();
|
|
186
248
|
onToggleCollapse();
|
|
187
|
-
} } : undefined, children: [onToggleCollapse && (_jsx(Button, { size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-right' : 'chevron-down', ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), className: "table-header__chevron" })), icon && (_jsx(Icon, { icon: icon, label: typeof title === 'string' ? title : undefined, className: "table-header__icon" })), _jsx("span", { className: "table-header__title h5b", children: title }), count !== undefined && _jsx("span", { className: "table-header__count body-3b", children: count }), headerActions && (_jsx("span", { className: "table-header__actions", onClick: e => e.stopPropagation(), children: headerActions }))] })), !collapsed && (_jsxs(_Fragment, { children: [headerGroups && headerGroups.length > 0 && (_jsx("div", { className: "table-fields table-fields--groups", children: headerGroups.map((g, i) => (_jsx("div", { className: cx('table-field body-3', g.className), style: { gridColumn: `span ${g.span}` }, children: g.label }, i))) })), _jsx("div", { className: "table-fields", ref: fieldsRef, children: fields.map((f, i) => (_jsx(TableField, { onClick: f.onClick, className: f.className, sortDir: f.sortDir,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
249
|
+
} } : undefined, children: [onToggleCollapse && (_jsx(Button, { size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: collapsed ? 'chevron-right' : 'chevron-down', ariaLabel: collapsed ? t('common.expand', '펼치기') : t('common.collapse', '접기'), className: "table-header__chevron" })), icon && (_jsx(Icon, { icon: icon, label: typeof title === 'string' ? title : undefined, className: "table-header__icon" })), _jsx("span", { className: "table-header__title h5b", children: title }), count !== undefined && _jsx("span", { className: "table-header__count body-3b", children: count }), headerActions && (_jsx("span", { className: "table-header__actions", onClick: e => e.stopPropagation(), children: headerActions }))] })), !collapsed && (_jsxs(_Fragment, { children: [headerGroups && headerGroups.length > 0 && (_jsx("div", { className: "table-fields table-fields--groups", children: headerGroups.map((g, i) => (_jsx("div", { className: cx('table-field body-3', g.className), style: { gridColumn: `span ${g.span}` }, children: g.label }, i))) })), _jsxs("div", { className: cx('table-fields-strip', resizable && 'table-fields-strip--resizable'), children: [_jsx("div", { className: "table-fields", ref: fieldsRef, children: fields.map((f, i) => (_jsx(TableField, { onClick: f.onClick, className: f.className, sortDir: f.sortDir,
|
|
250
|
+
/* fit 모드는 오른쪽 이웃에서 폭을 덜어 오므로, 덜어 올 컬럼이 없는 자리에는
|
|
251
|
+
손잡이를 두지 않는다 — 두면 그 컬럼만 grow처럼 넓어져 표가 밀린다. */
|
|
252
|
+
onResizeStart: resizable && i < fields.length - 1 && f.resizable !== false
|
|
253
|
+
&& (resizeMode !== 'fit' || fields.some((g, j) => j > i && g.resizable !== false))
|
|
254
|
+
? (e) => startResize(i, e) : undefined, children: f.label }, i))) }), resizable && (_jsx(Button, { size: 40, type: "dot", variant: "transparent-gray", useBorder: false, icon: "rotate-ccw", ariaLabel: t('table.resetColumns', '칸 너비 초기화'), showTooltipInfo: t('table.resetColumns', '칸 너비 초기화'), className: "table-fields__reset", onClick: 자동맞춤 }))] }), _jsx("div", { className: "table-body", children: loading ? (_jsx(TableRow, { variant: "loading" })) : items.length === 0 && empty ? (_jsx(TableRow, { variant: "empty", children: empty })) : items.map((item, idx) => {
|
|
193
255
|
const isDragging = draggingId != null && item.id === draggingId;
|
|
194
256
|
const isExpanded = expandedId != null && (expandedId instanceof Set ? expandedId.has(item.id) : item.id === expandedId);
|
|
195
257
|
return (_jsx(TableRow, { className: `${onRowClick ? ' table-row--clickable' : ''}${onRowDragStart ? ' table-row--draggable' : ''}${isDragging ? ' table-row--dragging' : ''}`.trim(), draggable: !!onRowDragStart,
|
|
@@ -4005,6 +4005,9 @@
|
|
|
4005
4005
|
"avgDuration": "{{sec}}s avg",
|
|
4006
4006
|
"truncated": "Only the most recent {{n}} items were loaded — the counts below cover just those."
|
|
4007
4007
|
},
|
|
4008
|
+
"table": {
|
|
4009
|
+
"resetColumns": "Reset column widths"
|
|
4010
|
+
},
|
|
4008
4011
|
"wordScore": {
|
|
4009
4012
|
"panelTitle": "Word table scores",
|
|
4010
4013
|
"viewWords": "Words",
|
|
@@ -7,11 +7,30 @@
|
|
|
7
7
|
* (락파일 검사는 서버가 뜰 때 한 번이다 — CONSUMING.md 2-0-1).
|
|
8
8
|
*
|
|
9
9
|
* 그래서 "올렸다고 믿으며 옛 코드를 검증하는" 일이 생긴다. 이 상수는 소비자가
|
|
10
|
-
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
11
|
-
* 콘솔에서 한 줄로 확인할 수 있다:
|
|
10
|
+
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
12
11
|
*
|
|
13
12
|
* ```js
|
|
14
|
-
*
|
|
13
|
+
* // 앱 코드 안에서
|
|
14
|
+
* import { CORE_VERSION } from '@ihabdevteam/core/components'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* **브라우저 콘솔에서는 저 이름이 안 풀린다.** 맨 이름(bare specifier)은 번들러가
|
|
18
|
+
* 푸는 것이라 콘솔에는 그 지도가 없다 — `Failed to resolve module specifier` 가 난다
|
|
19
|
+
* (2026-09-09 실측. 이 글이 그렇게 적혀 있어 두 사람이 헛걸음했다). Vite dev
|
|
20
|
+
* 서버에서는 `/@id/` 를 앞에 붙이면 콘솔에서도 풀린다:
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* import('/@id/@ihabdevteam/core/components').then(m => console.log(m.CORE_VERSION))
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* CSS 만 궁금하다면 **화면에 실제로 걸린 규칙**을 CSSOM 에서 바로 읽는 편이 더
|
|
27
|
+
* 확실하다 — 번들러와 무관하고, 판 번호가 아니라 그리는 데 쓰이는 값을 본다:
|
|
28
|
+
*
|
|
29
|
+
* ```js
|
|
30
|
+
* for (const ss of document.styleSheets) {
|
|
31
|
+
* try { for (const r of ss.cssRules) if (/\.button:disabled/.test(r.selectorText ?? '')) console.log(r.style.cssText) }
|
|
32
|
+
* catch { } // 다른 오리진의 시트는 못 읽는다
|
|
33
|
+
* }
|
|
15
34
|
* ```
|
|
16
35
|
*
|
|
17
36
|
* 네 갈래(index·components·hooks·utils) 모두에서 내보낸다 — 갈래마다 따로
|
|
@@ -19,4 +38,4 @@
|
|
|
19
38
|
*
|
|
20
39
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
21
40
|
*/
|
|
22
|
-
export declare const CORE_VERSION = "0.
|
|
41
|
+
export declare const CORE_VERSION = "0.69.0";
|
|
@@ -7,11 +7,30 @@
|
|
|
7
7
|
* (락파일 검사는 서버가 뜰 때 한 번이다 — CONSUMING.md 2-0-1).
|
|
8
8
|
*
|
|
9
9
|
* 그래서 "올렸다고 믿으며 옛 코드를 검증하는" 일이 생긴다. 이 상수는 소비자가
|
|
10
|
-
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
11
|
-
* 콘솔에서 한 줄로 확인할 수 있다:
|
|
10
|
+
* 실제로 싣는 그 알맹이 안에 들어 있으므로 **같은 길을 지나온다.**
|
|
12
11
|
*
|
|
13
12
|
* ```js
|
|
14
|
-
*
|
|
13
|
+
* // 앱 코드 안에서
|
|
14
|
+
* import { CORE_VERSION } from '@ihabdevteam/core/components'
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* **브라우저 콘솔에서는 저 이름이 안 풀린다.** 맨 이름(bare specifier)은 번들러가
|
|
18
|
+
* 푸는 것이라 콘솔에는 그 지도가 없다 — `Failed to resolve module specifier` 가 난다
|
|
19
|
+
* (2026-09-09 실측. 이 글이 그렇게 적혀 있어 두 사람이 헛걸음했다). Vite dev
|
|
20
|
+
* 서버에서는 `/@id/` 를 앞에 붙이면 콘솔에서도 풀린다:
|
|
21
|
+
*
|
|
22
|
+
* ```js
|
|
23
|
+
* import('/@id/@ihabdevteam/core/components').then(m => console.log(m.CORE_VERSION))
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* CSS 만 궁금하다면 **화면에 실제로 걸린 규칙**을 CSSOM 에서 바로 읽는 편이 더
|
|
27
|
+
* 확실하다 — 번들러와 무관하고, 판 번호가 아니라 그리는 데 쓰이는 값을 본다:
|
|
28
|
+
*
|
|
29
|
+
* ```js
|
|
30
|
+
* for (const ss of document.styleSheets) {
|
|
31
|
+
* try { for (const r of ss.cssRules) if (/\.button:disabled/.test(r.selectorText ?? '')) console.log(r.style.cssText) }
|
|
32
|
+
* catch { } // 다른 오리진의 시트는 못 읽는다
|
|
33
|
+
* }
|
|
15
34
|
* ```
|
|
16
35
|
*
|
|
17
36
|
* 네 갈래(index·components·hooks·utils) 모두에서 내보낸다 — 갈래마다 따로
|
|
@@ -19,4 +38,4 @@
|
|
|
19
38
|
*
|
|
20
39
|
* 값은 손으로 적고 `test/coreVersion.test.ts` 가 package.json 과 어긋나면 운다.
|
|
21
40
|
*/
|
|
22
|
-
export const CORE_VERSION = '0.
|
|
41
|
+
export const CORE_VERSION = '0.69.0';
|