@bigtablet/design-system 3.19.1 → 3.19.3
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/index.css +17 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +72 -8
- package/package.json +7 -6
package/dist/index.css
CHANGED
|
@@ -1068,6 +1068,10 @@ body:has(.bottom_nav) {
|
|
|
1068
1068
|
border-radius: 12px;
|
|
1069
1069
|
box-shadow: var(--bt-elevation-level2);
|
|
1070
1070
|
padding: 4px 0;
|
|
1071
|
+
box-sizing: border-box;
|
|
1072
|
+
overflow-y: auto;
|
|
1073
|
+
scrollbar-width: thin;
|
|
1074
|
+
scrollbar-color: var(--bt-color-border-hover) transparent;
|
|
1071
1075
|
will-change: transform, opacity;
|
|
1072
1076
|
}
|
|
1073
1077
|
[data-theme=dark] .menu {
|
|
@@ -1882,6 +1886,9 @@ body:has(.bottom_nav) {
|
|
|
1882
1886
|
box-sizing: border-box;
|
|
1883
1887
|
min-width: min(200px, 100%);
|
|
1884
1888
|
max-width: min(320px, 100%);
|
|
1889
|
+
overflow-y: auto;
|
|
1890
|
+
scrollbar-width: thin;
|
|
1891
|
+
scrollbar-color: var(--bt-color-border-hover) transparent;
|
|
1885
1892
|
color: var(--bt-color-text-body);
|
|
1886
1893
|
font-size: 14px;
|
|
1887
1894
|
font-weight: 400;
|
|
@@ -1929,6 +1936,10 @@ body:has(.bottom_nav) {
|
|
|
1929
1936
|
}
|
|
1930
1937
|
.tooltip {
|
|
1931
1938
|
display: block;
|
|
1939
|
+
box-sizing: border-box;
|
|
1940
|
+
overflow-y: auto;
|
|
1941
|
+
scrollbar-width: thin;
|
|
1942
|
+
scrollbar-color: var(--bt-color-border-hover) transparent;
|
|
1932
1943
|
background: var(--bt-color-accent-strong);
|
|
1933
1944
|
color: var(--bt-color-accent-on-surface);
|
|
1934
1945
|
padding: 6px 10px;
|
|
@@ -4783,6 +4794,8 @@ body:has(.bottom_nav) {
|
|
|
4783
4794
|
position: fixed;
|
|
4784
4795
|
z-index: 1000;
|
|
4785
4796
|
box-sizing: border-box;
|
|
4797
|
+
display: flex;
|
|
4798
|
+
flex-direction: column;
|
|
4786
4799
|
width: max-content;
|
|
4787
4800
|
background: var(--bt-color-bg-solid);
|
|
4788
4801
|
border: 1px solid var(--bt-color-border-default);
|
|
@@ -4800,6 +4813,7 @@ body:has(.bottom_nav) {
|
|
|
4800
4813
|
}
|
|
4801
4814
|
.combobox_list {
|
|
4802
4815
|
max-height: 18rem;
|
|
4816
|
+
min-height: 0;
|
|
4803
4817
|
overflow-y: auto;
|
|
4804
4818
|
scrollbar-width: thin;
|
|
4805
4819
|
scrollbar-color: var(--bt-color-border-hover) transparent;
|
|
@@ -5029,6 +5043,8 @@ body:has(.bottom_nav) {
|
|
|
5029
5043
|
position: fixed;
|
|
5030
5044
|
z-index: 1000;
|
|
5031
5045
|
box-sizing: border-box;
|
|
5046
|
+
display: flex;
|
|
5047
|
+
flex-direction: column;
|
|
5032
5048
|
width: max-content;
|
|
5033
5049
|
background: var(--bt-color-bg-solid);
|
|
5034
5050
|
border: 1px solid var(--bt-color-border-default);
|
|
@@ -5084,6 +5100,7 @@ body:has(.bottom_nav) {
|
|
|
5084
5100
|
}
|
|
5085
5101
|
.dropdown_options {
|
|
5086
5102
|
max-height: 18rem;
|
|
5103
|
+
min-height: 0;
|
|
5087
5104
|
overflow-y: auto;
|
|
5088
5105
|
scrollbar-width: thin;
|
|
5089
5106
|
scrollbar-color: var(--bt-color-border-hover) transparent;
|
package/dist/index.d.ts
CHANGED
|
@@ -181,6 +181,11 @@ interface UseListboxPopupResult {
|
|
|
181
181
|
* 이 값으로 좌표를 잡는데 패널 폭을 트리거 폭으로 두면 오른쪽이 잘린다.
|
|
182
182
|
*/
|
|
183
183
|
maxWidth: number;
|
|
184
|
+
/**
|
|
185
|
+
* 배치된 방향의 가용 높이 상한(px). 목록이 길고 뷰포트가 낮으면 이걸 안 걸었을 때
|
|
186
|
+
* 패널이 화면 밖으로 나간다(#621).
|
|
187
|
+
*/
|
|
188
|
+
maxHeight: number;
|
|
184
189
|
/** 최초 측정 전에는 false - 이때 숨겨 (0,0) 깜빡임을 막는다 */
|
|
185
190
|
ready: boolean;
|
|
186
191
|
};
|
package/dist/index.js
CHANGED
|
@@ -254,6 +254,22 @@ var mainAxisStart = (side, anchor, floating, gap) => {
|
|
|
254
254
|
return anchor.left + anchor.width + gap;
|
|
255
255
|
}
|
|
256
256
|
};
|
|
257
|
+
var mainAxisSpace = (side, anchor, viewport, gap, padding) => {
|
|
258
|
+
const top = clamp(anchor.top, 0, viewport.height);
|
|
259
|
+
const bottom = clamp(anchor.top + anchor.height, 0, viewport.height);
|
|
260
|
+
const left = clamp(anchor.left, 0, viewport.width);
|
|
261
|
+
const right = clamp(anchor.left + anchor.width, 0, viewport.width);
|
|
262
|
+
switch (side) {
|
|
263
|
+
case "top":
|
|
264
|
+
return top - gap - padding;
|
|
265
|
+
case "bottom":
|
|
266
|
+
return viewport.height - bottom - gap - padding;
|
|
267
|
+
case "left":
|
|
268
|
+
return left - gap - padding;
|
|
269
|
+
case "right":
|
|
270
|
+
return viewport.width - right - gap - padding;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
257
273
|
var fitsMainAxis = (side, anchor, floating, viewport, gap, padding) => {
|
|
258
274
|
const start = mainAxisStart(side, anchor, floating, gap);
|
|
259
275
|
switch (side) {
|
|
@@ -277,9 +293,14 @@ function computeAnchoredPosition(anchor, floating, viewport, options) {
|
|
|
277
293
|
height: floating.height
|
|
278
294
|
};
|
|
279
295
|
let side = options.placement;
|
|
280
|
-
if (!fitsMainAxis(side, anchor, sized, viewport, gap, padding)
|
|
281
|
-
|
|
296
|
+
if (!fitsMainAxis(side, anchor, sized, viewport, gap, padding)) {
|
|
297
|
+
const opposite = OPPOSITE[side];
|
|
298
|
+
if (fitsMainAxis(opposite, anchor, sized, viewport, gap, padding) || mainAxisSpace(opposite, anchor, viewport, gap, padding) > mainAxisSpace(side, anchor, viewport, gap, padding)) {
|
|
299
|
+
side = opposite;
|
|
300
|
+
}
|
|
282
301
|
}
|
|
302
|
+
const maxHeight = isVertical(side) ? Math.max(mainAxisSpace(side, anchor, viewport, gap, padding), 0) : viewport.height - padding * 2;
|
|
303
|
+
sized.height = Math.min(sized.height, maxHeight);
|
|
283
304
|
const align = options.align ?? "center";
|
|
284
305
|
let x;
|
|
285
306
|
let y;
|
|
@@ -287,12 +308,14 @@ function computeAnchoredPosition(anchor, floating, viewport, options) {
|
|
|
287
308
|
y = mainAxisStart(side, anchor, sized, gap);
|
|
288
309
|
x = align === "start" ? anchor.left : align === "end" ? anchor.left + anchor.width - sized.width : anchor.left + anchor.width / 2 - sized.width / 2;
|
|
289
310
|
x = clamp(x, padding, viewport.width - padding - sized.width);
|
|
311
|
+
y = clamp(y, padding, viewport.height - padding - floating.height);
|
|
290
312
|
} else {
|
|
291
313
|
x = mainAxisStart(side, anchor, sized, gap);
|
|
292
314
|
y = align === "start" ? anchor.top : align === "end" ? anchor.top + anchor.height - sized.height : anchor.top + anchor.height / 2 - sized.height / 2;
|
|
293
315
|
y = clamp(y, padding, viewport.height - padding - sized.height);
|
|
316
|
+
x = clamp(x, padding, viewport.width - padding - floating.width);
|
|
294
317
|
}
|
|
295
|
-
return { x, y, placement: side, maxWidth };
|
|
318
|
+
return { x, y, placement: side, maxWidth, maxHeight };
|
|
296
319
|
}
|
|
297
320
|
function useAnchoredPosition({
|
|
298
321
|
open,
|
|
@@ -308,12 +331,16 @@ function useAnchoredPosition({
|
|
|
308
331
|
y: 0,
|
|
309
332
|
placement,
|
|
310
333
|
maxWidth: 0,
|
|
334
|
+
maxHeight: 0,
|
|
311
335
|
ready: false,
|
|
312
|
-
anchorWidth: 0
|
|
336
|
+
anchorWidth: 0,
|
|
337
|
+
anchorHidden: false
|
|
313
338
|
});
|
|
314
339
|
useSafeLayoutEffect(() => {
|
|
315
340
|
if (!open) {
|
|
316
|
-
setState(
|
|
341
|
+
setState(
|
|
342
|
+
(prev) => prev.ready || prev.anchorHidden ? { ...prev, ready: false, anchorHidden: false } : prev
|
|
343
|
+
);
|
|
317
344
|
return;
|
|
318
345
|
}
|
|
319
346
|
const anchor = anchorRef.current;
|
|
@@ -328,10 +355,12 @@ function useAnchoredPosition({
|
|
|
328
355
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
329
356
|
{ placement, align, gap, padding }
|
|
330
357
|
);
|
|
358
|
+
const anchorHidden = a.width > 0 && a.height > 0 && (a.bottom <= 0 || a.top >= window.innerHeight || a.right <= 0 || a.left >= window.innerWidth);
|
|
331
359
|
setState({
|
|
332
360
|
...result,
|
|
333
361
|
ready: true,
|
|
334
|
-
anchorWidth: Math.min(a.width, result.maxWidth)
|
|
362
|
+
anchorWidth: Math.min(a.width, result.maxWidth),
|
|
363
|
+
anchorHidden
|
|
335
364
|
});
|
|
336
365
|
};
|
|
337
366
|
let frame = 0;
|
|
@@ -661,6 +690,12 @@ function useListboxPopup({
|
|
|
661
690
|
gap: LIST_GAP,
|
|
662
691
|
padding: LIST_PADDING
|
|
663
692
|
});
|
|
693
|
+
useEffect(() => {
|
|
694
|
+
if (!isOpen || !anchored.ready || !anchored.anchorHidden) return;
|
|
695
|
+
const focusInPanel = !!panelRef.current?.contains(document.activeElement);
|
|
696
|
+
setIsOpen(false);
|
|
697
|
+
if (focusInPanel && returnFocusOnClose) triggerRef.current?.focus({ preventScroll: true });
|
|
698
|
+
}, [isOpen, anchored.ready, anchored.anchorHidden, returnFocusOnClose]);
|
|
664
699
|
return {
|
|
665
700
|
isOpen,
|
|
666
701
|
setIsOpen,
|
|
@@ -670,6 +705,7 @@ function useListboxPopup({
|
|
|
670
705
|
y: anchored.y,
|
|
671
706
|
width: anchored.anchorWidth,
|
|
672
707
|
maxWidth: anchored.maxWidth,
|
|
708
|
+
maxHeight: anchored.maxHeight,
|
|
673
709
|
ready: anchored.ready
|
|
674
710
|
},
|
|
675
711
|
activeIndex,
|
|
@@ -1255,6 +1291,14 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1255
1291
|
gap: MENU_GAP,
|
|
1256
1292
|
padding: 8
|
|
1257
1293
|
});
|
|
1294
|
+
React11.useEffect(() => {
|
|
1295
|
+
if (!open || !pos.ready || !pos.anchorHidden) return;
|
|
1296
|
+
const focusInMenu = !!menuRef.current?.contains(document.activeElement);
|
|
1297
|
+
setOpen(false);
|
|
1298
|
+
if (focusInMenu) {
|
|
1299
|
+
wrapperRef.current?.querySelector("[aria-haspopup]")?.focus({ preventScroll: true });
|
|
1300
|
+
}
|
|
1301
|
+
}, [open, pos.ready, pos.anchorHidden]);
|
|
1258
1302
|
const style = useSpringPresence({
|
|
1259
1303
|
visible: open,
|
|
1260
1304
|
from: pos.placement === "top" ? `translateY(${MENU_GAP}px)` : `translateY(-${MENU_GAP}px)`
|
|
@@ -1346,6 +1390,9 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1346
1390
|
position: "fixed",
|
|
1347
1391
|
left: pos.x,
|
|
1348
1392
|
top: pos.y,
|
|
1393
|
+
// 배치된 방향에 남은 높이. 항목이 많은 메뉴가 낮은 뷰포트에서 화면 밖으로
|
|
1394
|
+
// 나가지 않게 한다(#621). 넘치는 만큼은 메뉴 안에서 스크롤한다.
|
|
1395
|
+
maxHeight: pos.ready ? pos.maxHeight : void 0,
|
|
1349
1396
|
visibility: pos.ready ? void 0 : "hidden"
|
|
1350
1397
|
},
|
|
1351
1398
|
className: cn("menu", `menu_align_${align}`),
|
|
@@ -2016,7 +2063,7 @@ var Popover = ({
|
|
|
2016
2063
|
tabIndex: -1,
|
|
2017
2064
|
"aria-label": ariaLabel,
|
|
2018
2065
|
"aria-labelledby": ariaLabelledby,
|
|
2019
|
-
style,
|
|
2066
|
+
style: { ...style, maxHeight: pos.ready ? pos.maxHeight : void 0 },
|
|
2020
2067
|
className: cn("popover", className),
|
|
2021
2068
|
children: content
|
|
2022
2069
|
}
|
|
@@ -2073,6 +2120,9 @@ var Tooltip = ({
|
|
|
2073
2120
|
gap: TOOLTIP_GAP,
|
|
2074
2121
|
padding: 8
|
|
2075
2122
|
});
|
|
2123
|
+
React11.useEffect(() => {
|
|
2124
|
+
if (open && pos.ready && pos.anchorHidden) hideNow();
|
|
2125
|
+
}, [open, pos.ready, pos.anchorHidden, hideNow]);
|
|
2076
2126
|
const fromTransform = (() => {
|
|
2077
2127
|
switch (pos.placement) {
|
|
2078
2128
|
case "top":
|
|
@@ -2132,7 +2182,16 @@ var Tooltip = ({
|
|
|
2132
2182
|
},
|
|
2133
2183
|
onMouseEnter: cancelHide,
|
|
2134
2184
|
onMouseLeave: hide,
|
|
2135
|
-
children: /* @__PURE__ */ jsx(
|
|
2185
|
+
children: /* @__PURE__ */ jsx(
|
|
2186
|
+
animated.span,
|
|
2187
|
+
{
|
|
2188
|
+
id: tooltipId,
|
|
2189
|
+
role: "tooltip",
|
|
2190
|
+
style: { ...style, maxHeight: pos.ready ? pos.maxHeight : void 0 },
|
|
2191
|
+
className: "tooltip",
|
|
2192
|
+
children: content
|
|
2193
|
+
}
|
|
2194
|
+
)
|
|
2136
2195
|
}
|
|
2137
2196
|
),
|
|
2138
2197
|
document.body
|
|
@@ -4303,6 +4362,8 @@ var Combobox = ({
|
|
|
4303
4362
|
minWidth: popup.position.width || void 0,
|
|
4304
4363
|
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4305
4364
|
maxWidth: popup.position.ready ? popup.position.maxWidth : void 0,
|
|
4365
|
+
// 배치된 방향에 남은 높이 - Dropdown 과 같은 이유(#621).
|
|
4366
|
+
maxHeight: popup.position.ready ? popup.position.maxHeight : void 0,
|
|
4306
4367
|
visibility: popup.position.ready ? void 0 : "hidden"
|
|
4307
4368
|
},
|
|
4308
4369
|
children: !hasList ? /* @__PURE__ */ jsx("p", { className: "combobox_message", role: "status", children: showIdle ? idleMessage : emptyMessage }) : /* @__PURE__ */ jsx(
|
|
@@ -4526,6 +4587,9 @@ var Dropdown = (props) => {
|
|
|
4526
4587
|
minWidth: position.width || void 0,
|
|
4527
4588
|
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4528
4589
|
maxWidth: position.ready ? position.maxWidth : void 0,
|
|
4590
|
+
// 배치된 방향에 남은 높이. 안 걸면 긴 목록이 낮은 뷰포트에서 화면 밖으로
|
|
4591
|
+
// 나간다(#621). 패널이 세로 flex 라 목록이 이 안에서 줄고 스크롤한다.
|
|
4592
|
+
maxHeight: position.ready ? position.maxHeight : void 0,
|
|
4529
4593
|
// 최초 측정 전에는 숨긴다 - (0,0) 에서 한 프레임 깜빡이는 것을 막는다.
|
|
4530
4594
|
visibility: position.ready ? void 0 : "hidden"
|
|
4531
4595
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bigtablet/design-system",
|
|
3
|
-
"version": "3.19.
|
|
3
|
+
"version": "3.19.3",
|
|
4
4
|
"description": "Bigtablet Design System UI Components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -125,17 +125,18 @@
|
|
|
125
125
|
"size-limit": [
|
|
126
126
|
{
|
|
127
127
|
"path": "dist/index.js",
|
|
128
|
-
"limit": "
|
|
129
|
-
"message": "Raised
|
|
128
|
+
"limit": "70 kB",
|
|
129
|
+
"message": "Raised 60 kB -> 70 kB on 2026-09-10: baseline was 59.82 kB on develop (59.91 kB with PR #625), leaving 0.09-0.18 kB (0.3%) under the old 60 kB limit. Of that baseline, 18.48 kB is @react-spring/web (a runtime dependency, so size-limit bundles it) and 40.55 kB is this package - measure `dist/index.js` alone before blaming the dependency. The current limit leaves 10.09 kB. Re-measure before raising again."
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"path": "dist/vanilla/bigtablet.min.js",
|
|
133
|
-
"limit": "
|
|
134
|
-
"message": "Raised
|
|
133
|
+
"limit": "8 kB",
|
|
134
|
+
"message": "Raised 7 kB -> 8 kB on 2026-09-10: baseline was 6.83 kB, leaving 0.17 kB (2.4%) under the old 7 kB limit. The current limit leaves 1.17 kB. Re-measure before raising again."
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
"path": "dist/index.css",
|
|
138
|
-
"limit": "
|
|
138
|
+
"limit": "18 kB",
|
|
139
|
+
"message": "Lowered 130 kB -> 18 kB on 2026-09-10: baseline is 14.56 kB brotlied, so the old limit sat 9x above reality and could never fire. The current limit leaves 3.44 kB. Raise it with a measurement, not to make a build pass."
|
|
139
140
|
}
|
|
140
141
|
],
|
|
141
142
|
"dependencies": {
|