@bigtablet/design-system 3.17.3 → 3.18.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/index.css +6 -33
- package/dist/index.d.ts +48 -19
- package/dist/index.js +328 -184
- package/dist/vanilla/bigtablet.min.css +1 -1
- package/dist/vanilla/bigtablet.min.js +12 -12
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -104,7 +104,7 @@ var over = (top, bottom) => [
|
|
|
104
104
|
Math.round(top[2] * top[3] + bottom[2] * (1 - top[3])),
|
|
105
105
|
1
|
|
106
106
|
];
|
|
107
|
-
var
|
|
107
|
+
var measureCanvasColors = (html, body) => {
|
|
108
108
|
const raw = window.getComputedStyle(html).getPropertyValue(DIM_VAR).trim() || DIM_FALLBACK;
|
|
109
109
|
const dim = normalizeColor(raw);
|
|
110
110
|
if (!dim || dim[3] <= 0) return null;
|
|
@@ -113,8 +113,7 @@ var compositeCanvasDim = (html, body) => {
|
|
|
113
113
|
parseRgb(window.getComputedStyle(body).backgroundColor)
|
|
114
114
|
];
|
|
115
115
|
const base = candidates.find((color) => color !== null && color[3] > 0) ?? WHITE;
|
|
116
|
-
|
|
117
|
-
return `rgb(${r}, ${g}, ${b})`;
|
|
116
|
+
return [dim, over(base, WHITE)];
|
|
118
117
|
};
|
|
119
118
|
var measureViewportInset = () => {
|
|
120
119
|
const probe = document.createElement("div");
|
|
@@ -125,6 +124,33 @@ var measureViewportInset = () => {
|
|
|
125
124
|
if (width <= 0) return 0;
|
|
126
125
|
return Math.max(0, window.innerWidth - width);
|
|
127
126
|
};
|
|
127
|
+
var dimProgress = /* @__PURE__ */ new Map();
|
|
128
|
+
var canvasBase = null;
|
|
129
|
+
var canvasDim = null;
|
|
130
|
+
var combinedDimAlpha = (alpha) => {
|
|
131
|
+
if (dimProgress.size === 0) return alpha;
|
|
132
|
+
let transmitted = 1;
|
|
133
|
+
for (const progress of dimProgress.values()) {
|
|
134
|
+
transmitted *= 1 - alpha * progress;
|
|
135
|
+
}
|
|
136
|
+
return 1 - transmitted;
|
|
137
|
+
};
|
|
138
|
+
var paintCanvas = (html) => {
|
|
139
|
+
if (!canvasBase || !canvasDim) return;
|
|
140
|
+
const alpha = combinedDimAlpha(canvasDim[3]);
|
|
141
|
+
const [r, g, b] = over([canvasDim[0], canvasDim[1], canvasDim[2], alpha], canvasBase);
|
|
142
|
+
html.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
|
|
143
|
+
};
|
|
144
|
+
function reportOverlayDim(owner, progress) {
|
|
145
|
+
if (typeof document === "undefined") return;
|
|
146
|
+
dimProgress.set(owner, Math.min(1, Math.max(0, progress)));
|
|
147
|
+
paintCanvas(document.documentElement);
|
|
148
|
+
}
|
|
149
|
+
function unregisterOverlayDim(owner) {
|
|
150
|
+
if (typeof document === "undefined") return;
|
|
151
|
+
if (!dimProgress.delete(owner)) return;
|
|
152
|
+
paintCanvas(document.documentElement);
|
|
153
|
+
}
|
|
128
154
|
function lockBodyScroll() {
|
|
129
155
|
if (typeof document === "undefined") return;
|
|
130
156
|
const body = document.body;
|
|
@@ -152,9 +178,10 @@ function lockBodyScroll() {
|
|
|
152
178
|
}
|
|
153
179
|
}
|
|
154
180
|
if (scrollbarWidth > 0 && canReserveGutter) {
|
|
155
|
-
const
|
|
156
|
-
if (
|
|
157
|
-
|
|
181
|
+
const measured = measureCanvasColors(html, body);
|
|
182
|
+
if (measured) {
|
|
183
|
+
[canvasDim, canvasBase] = measured;
|
|
184
|
+
paintCanvas(html);
|
|
158
185
|
}
|
|
159
186
|
}
|
|
160
187
|
html.setAttribute(LOCKED_ATTR, "");
|
|
@@ -185,6 +212,9 @@ function unlockBodyScroll() {
|
|
|
185
212
|
delete body.dataset[PREV_PADDING_RIGHT];
|
|
186
213
|
delete body.dataset[PREV_SCROLLBAR_WIDTH_VAR];
|
|
187
214
|
delete body.dataset[PREV_BACKGROUND_COLOR];
|
|
215
|
+
dimProgress.clear();
|
|
216
|
+
canvasBase = null;
|
|
217
|
+
canvasDim = null;
|
|
188
218
|
} else {
|
|
189
219
|
body.dataset[COUNTER] = String(remaining);
|
|
190
220
|
}
|
|
@@ -250,15 +280,16 @@ function computeAnchoredPosition(anchor, floating, viewport, options) {
|
|
|
250
280
|
if (!fitsMainAxis(side, anchor, sized, viewport, gap, padding) && fitsMainAxis(OPPOSITE[side], anchor, sized, viewport, gap, padding)) {
|
|
251
281
|
side = OPPOSITE[side];
|
|
252
282
|
}
|
|
283
|
+
const align = options.align ?? "center";
|
|
253
284
|
let x;
|
|
254
285
|
let y;
|
|
255
286
|
if (isVertical(side)) {
|
|
256
287
|
y = mainAxisStart(side, anchor, sized, gap);
|
|
257
|
-
x = anchor.left + anchor.width / 2 - sized.width / 2;
|
|
288
|
+
x = align === "start" ? anchor.left : align === "end" ? anchor.left + anchor.width - sized.width : anchor.left + anchor.width / 2 - sized.width / 2;
|
|
258
289
|
x = clamp(x, padding, viewport.width - padding - sized.width);
|
|
259
290
|
} else {
|
|
260
291
|
x = mainAxisStart(side, anchor, sized, gap);
|
|
261
|
-
y = anchor.top + anchor.height / 2 - sized.height / 2;
|
|
292
|
+
y = align === "start" ? anchor.top : align === "end" ? anchor.top + anchor.height - sized.height : anchor.top + anchor.height / 2 - sized.height / 2;
|
|
262
293
|
y = clamp(y, padding, viewport.height - padding - sized.height);
|
|
263
294
|
}
|
|
264
295
|
return { x, y, placement: side, maxWidth };
|
|
@@ -268,6 +299,7 @@ function useAnchoredPosition({
|
|
|
268
299
|
anchorRef,
|
|
269
300
|
floatingRef,
|
|
270
301
|
placement,
|
|
302
|
+
align,
|
|
271
303
|
gap,
|
|
272
304
|
padding
|
|
273
305
|
}) {
|
|
@@ -276,7 +308,8 @@ function useAnchoredPosition({
|
|
|
276
308
|
y: 0,
|
|
277
309
|
placement,
|
|
278
310
|
maxWidth: 0,
|
|
279
|
-
ready: false
|
|
311
|
+
ready: false,
|
|
312
|
+
anchorWidth: 0
|
|
280
313
|
});
|
|
281
314
|
useSafeLayoutEffect(() => {
|
|
282
315
|
if (!open) {
|
|
@@ -293,9 +326,9 @@ function useAnchoredPosition({
|
|
|
293
326
|
{ top: a.top, left: a.left, width: a.width, height: a.height },
|
|
294
327
|
{ width: f.width, height: f.height },
|
|
295
328
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
296
|
-
{ placement, gap, padding }
|
|
329
|
+
{ placement, align, gap, padding }
|
|
297
330
|
);
|
|
298
|
-
setState({ ...result, ready: true });
|
|
331
|
+
setState({ ...result, ready: true, anchorWidth: a.width });
|
|
299
332
|
};
|
|
300
333
|
let frame = 0;
|
|
301
334
|
const schedule = () => {
|
|
@@ -317,7 +350,7 @@ function useAnchoredPosition({
|
|
|
317
350
|
window.removeEventListener("resize", schedule);
|
|
318
351
|
observer?.disconnect();
|
|
319
352
|
};
|
|
320
|
-
}, [open, placement, gap, padding, anchorRef, floatingRef]);
|
|
353
|
+
}, [open, placement, align, gap, padding, anchorRef, floatingRef]);
|
|
321
354
|
return state;
|
|
322
355
|
}
|
|
323
356
|
var SKIP_AUTOFOCUS_ATTR = "data-focus-trap-skip-autofocus";
|
|
@@ -426,7 +459,8 @@ function useSpringHover({
|
|
|
426
459
|
const reduced = useReducedMotion();
|
|
427
460
|
const style = useSpring({
|
|
428
461
|
transform: hovered ? `translateY(${lift}px) scale(${scale})` : "translateY(0px) scale(1)",
|
|
429
|
-
// reduced-motion: lift/scale
|
|
462
|
+
// reduced-motion: 보간을 없애 움직임을 지운다. 최종 상태(lift/scale)는 남는다 - 강조가
|
|
463
|
+
// 사라지면 hover/focus 를 알 수 없다. WCAG 2.3.3 이 막는 것은 모션이다.
|
|
430
464
|
immediate: reduced,
|
|
431
465
|
config: { tension: 320, friction: 22 }
|
|
432
466
|
});
|
|
@@ -441,7 +475,8 @@ function useSpringHover({
|
|
|
441
475
|
function useSpringPresence({
|
|
442
476
|
visible,
|
|
443
477
|
from = "translateY(8px)",
|
|
444
|
-
onExitComplete
|
|
478
|
+
onExitComplete,
|
|
479
|
+
onProgress
|
|
445
480
|
}) {
|
|
446
481
|
const reduced = useReducedMotion();
|
|
447
482
|
return useSpring({
|
|
@@ -462,6 +497,7 @@ function useSpringPresence({
|
|
|
462
497
|
clamp: !visible
|
|
463
498
|
// 사라질 땐 진동 없이 빠르게
|
|
464
499
|
},
|
|
500
|
+
onChange: (result) => onProgress?.(Number(result.value.opacity)),
|
|
465
501
|
onRest: (result) => {
|
|
466
502
|
if (!visible && result.finished && onExitComplete) {
|
|
467
503
|
onExitComplete();
|
|
@@ -469,7 +505,8 @@ function useSpringPresence({
|
|
|
469
505
|
}
|
|
470
506
|
});
|
|
471
507
|
}
|
|
472
|
-
var
|
|
508
|
+
var LIST_GAP = 4;
|
|
509
|
+
var LIST_PADDING = 8;
|
|
473
510
|
function useListboxPopup({
|
|
474
511
|
items,
|
|
475
512
|
onCommit,
|
|
@@ -479,17 +516,19 @@ function useListboxPopup({
|
|
|
479
516
|
}) {
|
|
480
517
|
const [isOpen, setIsOpen] = useState(false);
|
|
481
518
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
482
|
-
const [dropUp, setDropUp] = useState(false);
|
|
483
519
|
const wrapperRef = useRef(null);
|
|
484
520
|
const triggerRef = useRef(null);
|
|
485
521
|
const listRef = useRef(null);
|
|
522
|
+
const panelRef = useRef(null);
|
|
486
523
|
const close = useCallback(() => {
|
|
487
524
|
setIsOpen(false);
|
|
488
525
|
if (returnFocusOnClose) triggerRef.current?.focus();
|
|
489
526
|
}, [returnFocusOnClose]);
|
|
490
527
|
useEffect(() => {
|
|
491
528
|
const handleOutsideClick = (event) => {
|
|
492
|
-
|
|
529
|
+
const target = event.target;
|
|
530
|
+
if (wrapperRef.current?.contains(target) || panelRef.current?.contains(target)) return;
|
|
531
|
+
setIsOpen(false);
|
|
493
532
|
};
|
|
494
533
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
495
534
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
@@ -608,22 +647,33 @@ function useListboxPopup({
|
|
|
608
647
|
const option = list.querySelectorAll('[role="option"]')[activeIndex];
|
|
609
648
|
option?.scrollIntoView?.({ block: "nearest" });
|
|
610
649
|
}, [isOpen, activeIndex, items]);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
650
|
+
const anchored = useAnchoredPosition({
|
|
651
|
+
open: isOpen,
|
|
652
|
+
anchorRef: wrapperRef,
|
|
653
|
+
floatingRef: panelRef,
|
|
654
|
+
placement: "bottom",
|
|
655
|
+
// 트리거 왼쪽 변에 맞춘다 - 목록이 컨트롤의 연장으로 읽혀야 한다.
|
|
656
|
+
align: "start",
|
|
657
|
+
gap: LIST_GAP,
|
|
658
|
+
padding: LIST_PADDING
|
|
659
|
+
});
|
|
618
660
|
return {
|
|
619
661
|
isOpen,
|
|
620
662
|
setIsOpen,
|
|
621
|
-
dropUp,
|
|
663
|
+
dropUp: anchored.placement === "top",
|
|
664
|
+
position: {
|
|
665
|
+
x: anchored.x,
|
|
666
|
+
y: anchored.y,
|
|
667
|
+
width: anchored.anchorWidth,
|
|
668
|
+
maxWidth: anchored.maxWidth,
|
|
669
|
+
ready: anchored.ready
|
|
670
|
+
},
|
|
622
671
|
activeIndex,
|
|
623
672
|
setActiveIndex,
|
|
624
673
|
wrapperRef,
|
|
625
674
|
triggerRef,
|
|
626
675
|
listRef,
|
|
676
|
+
panelRef,
|
|
627
677
|
close,
|
|
628
678
|
moveActive,
|
|
629
679
|
commitActive,
|
|
@@ -1171,25 +1221,46 @@ var Breadcrumb = ({
|
|
|
1171
1221
|
const sep = separator ?? /* @__PURE__ */ jsx(ChevronRight, { size: iconSize.xs, "aria-hidden": "true" });
|
|
1172
1222
|
return /* @__PURE__ */ jsx("nav", { "aria-label": navLabel, className: cn("breadcrumb", className), ...props, children: /* @__PURE__ */ jsx("ol", { className: "breadcrumb_list", children: items.map((item, idx) => {
|
|
1173
1223
|
const isLast = idx === items.length - 1;
|
|
1224
|
+
const Tag = item.as ?? "a";
|
|
1174
1225
|
return (
|
|
1175
1226
|
// biome-ignore lint/suspicious/noArrayIndexKey: breadcrumb items have stable order
|
|
1176
1227
|
/* @__PURE__ */ jsxs("li", { className: "breadcrumb_item", children: [
|
|
1177
|
-
isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ?
|
|
1228
|
+
isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ? (
|
|
1229
|
+
// `href`·`className`·`onClick` 을 그대로 넘긴다 - 라우터 `Link` 가 수정자
|
|
1230
|
+
// 클릭·프리페치를 자기 방식대로 처리한다.
|
|
1231
|
+
/* @__PURE__ */ jsx(Tag, { className: "breadcrumb_link", href: item.href, onClick: item.onClick, children: item.label })
|
|
1232
|
+
) : /* @__PURE__ */ jsx("button", { type: "button", className: "breadcrumb_link", onClick: item.onClick, children: item.label }),
|
|
1178
1233
|
!isLast && /* @__PURE__ */ jsx("span", { className: "breadcrumb_separator", "aria-hidden": "true", children: sep })
|
|
1179
1234
|
] }, idx)
|
|
1180
1235
|
);
|
|
1181
1236
|
}) }) });
|
|
1182
1237
|
};
|
|
1238
|
+
var MENU_GAP = 4;
|
|
1183
1239
|
var Menu = ({ items, trigger, align = "start" }) => {
|
|
1184
1240
|
const [open, setOpen] = React11.useState(false);
|
|
1185
1241
|
const wrapperRef = React11.useRef(null);
|
|
1242
|
+
const menuRef = React11.useRef(null);
|
|
1186
1243
|
const itemRefs = React11.useRef([]);
|
|
1187
1244
|
const menuId = React11.useId();
|
|
1188
|
-
const
|
|
1245
|
+
const pos = useAnchoredPosition({
|
|
1246
|
+
open,
|
|
1247
|
+
anchorRef: wrapperRef,
|
|
1248
|
+
floatingRef: menuRef,
|
|
1249
|
+
placement: "bottom",
|
|
1250
|
+
align,
|
|
1251
|
+
gap: MENU_GAP,
|
|
1252
|
+
padding: 8
|
|
1253
|
+
});
|
|
1254
|
+
const style = useSpringPresence({
|
|
1255
|
+
visible: open,
|
|
1256
|
+
from: pos.placement === "top" ? `translateY(${MENU_GAP}px)` : `translateY(-${MENU_GAP}px)`
|
|
1257
|
+
});
|
|
1189
1258
|
React11.useEffect(() => {
|
|
1190
1259
|
if (!open) return;
|
|
1191
1260
|
const handleClick = (e) => {
|
|
1192
|
-
|
|
1261
|
+
const target = e.target;
|
|
1262
|
+
if (wrapperRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
1263
|
+
setOpen(false);
|
|
1193
1264
|
};
|
|
1194
1265
|
const handleEsc = (e) => {
|
|
1195
1266
|
if (e.key === "Escape") setOpen(false);
|
|
@@ -1212,8 +1283,8 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1212
1283
|
if (enabled.length === 0) return;
|
|
1213
1284
|
if (dir === "first") return void itemRefs.current[enabled[0]]?.focus();
|
|
1214
1285
|
if (dir === "last") return void itemRefs.current[enabled[enabled.length - 1]]?.focus();
|
|
1215
|
-
const
|
|
1216
|
-
const next =
|
|
1286
|
+
const pos2 = enabled.findIndex((i) => itemRefs.current[i] === document.activeElement);
|
|
1287
|
+
const next = pos2 < 0 ? dir === 1 ? 0 : enabled.length - 1 : (pos2 + dir + enabled.length) % enabled.length;
|
|
1217
1288
|
itemRefs.current[enabled[next]]?.focus();
|
|
1218
1289
|
};
|
|
1219
1290
|
const handleMenuKeyDown = (e) => {
|
|
@@ -1243,6 +1314,7 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1243
1314
|
wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
|
|
1244
1315
|
break;
|
|
1245
1316
|
case "Tab":
|
|
1317
|
+
wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
|
|
1246
1318
|
setOpen(false);
|
|
1247
1319
|
break;
|
|
1248
1320
|
}
|
|
@@ -1258,42 +1330,52 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1258
1330
|
);
|
|
1259
1331
|
return /* @__PURE__ */ jsxs("span", { className: "menu_wrapper", ref: wrapperRef, children: [
|
|
1260
1332
|
triggerWithProps,
|
|
1261
|
-
open &&
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
},
|
|
1275
|
-
type: "button",
|
|
1276
|
-
role: "menuitem",
|
|
1277
|
-
tabIndex: -1,
|
|
1278
|
-
disabled: item.disabled,
|
|
1279
|
-
className: cn(
|
|
1280
|
-
"menu_item",
|
|
1281
|
-
item.destructive && "menu_item_destructive",
|
|
1282
|
-
item.disabled && "menu_item_disabled"
|
|
1283
|
-
),
|
|
1284
|
-
onClick: () => {
|
|
1285
|
-
if (item.disabled) return;
|
|
1286
|
-
item.onSelect?.();
|
|
1287
|
-
setOpen(false);
|
|
1288
|
-
},
|
|
1289
|
-
children: [
|
|
1290
|
-
item.icon && /* @__PURE__ */ jsx("span", { className: "menu_item_icon", "aria-hidden": "true", children: item.icon }),
|
|
1291
|
-
/* @__PURE__ */ jsx("span", { className: "menu_item_label", children: item.label })
|
|
1292
|
-
]
|
|
1333
|
+
open && typeof document !== "undefined" && createPortal(
|
|
1334
|
+
/* @__PURE__ */ jsx(
|
|
1335
|
+
animated.div,
|
|
1336
|
+
{
|
|
1337
|
+
id: menuId,
|
|
1338
|
+
ref: menuRef,
|
|
1339
|
+
role: "menu",
|
|
1340
|
+
style: {
|
|
1341
|
+
...style,
|
|
1342
|
+
position: "fixed",
|
|
1343
|
+
left: pos.x,
|
|
1344
|
+
top: pos.y,
|
|
1345
|
+
visibility: pos.ready ? void 0 : "hidden"
|
|
1293
1346
|
},
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1347
|
+
className: cn("menu", `menu_align_${align}`),
|
|
1348
|
+
onKeyDown: handleMenuKeyDown,
|
|
1349
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
1350
|
+
"button",
|
|
1351
|
+
{
|
|
1352
|
+
ref: (el) => {
|
|
1353
|
+
itemRefs.current[index] = el;
|
|
1354
|
+
},
|
|
1355
|
+
type: "button",
|
|
1356
|
+
role: "menuitem",
|
|
1357
|
+
tabIndex: -1,
|
|
1358
|
+
disabled: item.disabled,
|
|
1359
|
+
className: cn(
|
|
1360
|
+
"menu_item",
|
|
1361
|
+
item.destructive && "menu_item_destructive",
|
|
1362
|
+
item.disabled && "menu_item_disabled"
|
|
1363
|
+
),
|
|
1364
|
+
onClick: () => {
|
|
1365
|
+
if (item.disabled) return;
|
|
1366
|
+
item.onSelect?.();
|
|
1367
|
+
setOpen(false);
|
|
1368
|
+
},
|
|
1369
|
+
children: [
|
|
1370
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "menu_item_icon", "aria-hidden": "true", children: item.icon }),
|
|
1371
|
+
/* @__PURE__ */ jsx("span", { className: "menu_item_label", children: item.label })
|
|
1372
|
+
]
|
|
1373
|
+
},
|
|
1374
|
+
item.key
|
|
1375
|
+
))
|
|
1376
|
+
}
|
|
1377
|
+
),
|
|
1378
|
+
document.body
|
|
1297
1379
|
)
|
|
1298
1380
|
] });
|
|
1299
1381
|
};
|
|
@@ -1489,13 +1571,16 @@ var LocaleSwitcher = ({ locale }) => {
|
|
|
1489
1571
|
) }, opt.value)) })
|
|
1490
1572
|
] });
|
|
1491
1573
|
};
|
|
1492
|
-
var NavLink = (
|
|
1574
|
+
var NavLink = (props) => {
|
|
1575
|
+
const { active, className, children, as, ref, ...rest } = props;
|
|
1576
|
+
const Tag = as ?? "a";
|
|
1493
1577
|
return /* @__PURE__ */ jsx(
|
|
1494
|
-
|
|
1578
|
+
Tag,
|
|
1495
1579
|
{
|
|
1496
|
-
|
|
1580
|
+
ref,
|
|
1581
|
+
className: cn("nav_bar_link", active && "nav_bar_link_active", className),
|
|
1497
1582
|
"aria-current": active ? "page" : void 0,
|
|
1498
|
-
...
|
|
1583
|
+
...rest,
|
|
1499
1584
|
children
|
|
1500
1585
|
}
|
|
1501
1586
|
);
|
|
@@ -3754,11 +3839,13 @@ var AlertModal = ({
|
|
|
3754
3839
|
useOverlayEscape(isOpen, () => dismiss());
|
|
3755
3840
|
if (isOpen && !shouldRender) setShouldRender(true);
|
|
3756
3841
|
const reduced = useReducedMotion();
|
|
3842
|
+
const dimOwner = React11.useRef({}).current;
|
|
3757
3843
|
const overlayStyle = useSpring({
|
|
3758
3844
|
...springEnterFrom(reduced),
|
|
3759
3845
|
to: { opacity: isOpen ? 1 : 0 },
|
|
3760
3846
|
immediate: reduced,
|
|
3761
3847
|
config: OVERLAY_SPRING_CONFIG,
|
|
3848
|
+
onChange: (result) => reportOverlayDim(dimOwner, Number(result.value.opacity)),
|
|
3762
3849
|
onRest: (result) => {
|
|
3763
3850
|
if (!isOpen && result.finished) setShouldRender(false);
|
|
3764
3851
|
}
|
|
@@ -3774,8 +3861,12 @@ var AlertModal = ({
|
|
|
3774
3861
|
});
|
|
3775
3862
|
React11.useEffect(() => {
|
|
3776
3863
|
if (!shouldRender) return;
|
|
3864
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
3777
3865
|
lockBodyScroll();
|
|
3778
|
-
return
|
|
3866
|
+
return () => {
|
|
3867
|
+
unlockBodyScroll();
|
|
3868
|
+
unregisterOverlayDim(dimOwner);
|
|
3869
|
+
};
|
|
3779
3870
|
}, [shouldRender]);
|
|
3780
3871
|
if (!isOpen && !shouldRender) return null;
|
|
3781
3872
|
const confirmVariant = "filled";
|
|
@@ -4185,42 +4276,57 @@ var Combobox = ({
|
|
|
4185
4276
|
}
|
|
4186
4277
|
)
|
|
4187
4278
|
] }),
|
|
4188
|
-
isOpen &&
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
{
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4279
|
+
isOpen && typeof document !== "undefined" && createPortal(
|
|
4280
|
+
// Dropdown 과 같은 이유로 포탈이다 - 트리거 옆에 두면 `overflow: hidden` 조상이
|
|
4281
|
+
// 잘라낸다(#586). 좌표·폭은 배치 훅이 트리거를 재서 준다.
|
|
4282
|
+
/* @__PURE__ */ jsx(
|
|
4283
|
+
animated.div,
|
|
4284
|
+
{
|
|
4285
|
+
ref: popup.panelRef,
|
|
4286
|
+
className: cn("combobox_panel", { combobox_panel_up: popup.dropUp }),
|
|
4287
|
+
style: {
|
|
4288
|
+
...panelStyle,
|
|
4289
|
+
position: "fixed",
|
|
4290
|
+
left: popup.position.x,
|
|
4291
|
+
top: popup.position.y,
|
|
4292
|
+
width: popup.position.width || void 0,
|
|
4293
|
+
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4294
|
+
maxWidth: popup.position.ready ? popup.position.maxWidth : void 0,
|
|
4295
|
+
visibility: popup.position.ready ? void 0 : "hidden"
|
|
4296
|
+
},
|
|
4297
|
+
children: !hasList ? /* @__PURE__ */ jsx("p", { className: "combobox_message", role: "status", children: showIdle ? idleMessage : emptyMessage }) : /* @__PURE__ */ jsx(
|
|
4298
|
+
"div",
|
|
4299
|
+
{
|
|
4300
|
+
ref: popup.listRef,
|
|
4301
|
+
id: listId,
|
|
4302
|
+
className: "combobox_list",
|
|
4303
|
+
role: "listbox",
|
|
4304
|
+
children: options.map((option, index) => (
|
|
4305
|
+
/* biome-ignore lint/a11y/useKeyWithClickEvents: 키보드는 입력의 onKeyDown 이 담당한다 - option 은 aria-activedescendant 로 가리키는 비포커스 요소다 (APG Combobox) */
|
|
4306
|
+
/* @__PURE__ */ jsx(
|
|
4307
|
+
"div",
|
|
4308
|
+
{
|
|
4309
|
+
id: `${listId}-${option.value}`,
|
|
4310
|
+
role: "option",
|
|
4311
|
+
tabIndex: -1,
|
|
4312
|
+
"aria-selected": value?.value === option.value,
|
|
4313
|
+
"aria-disabled": option.disabled || void 0,
|
|
4314
|
+
className: cn("combobox_option", {
|
|
4315
|
+
is_active: index === activeIndex,
|
|
4316
|
+
is_disabled: option.disabled
|
|
4317
|
+
}),
|
|
4318
|
+
onMouseEnter: () => !option.disabled && setActiveIndex(index),
|
|
4319
|
+
onClick: () => !option.disabled && commit(option),
|
|
4320
|
+
children: renderOption ? renderOption(option) : option.label
|
|
4321
|
+
},
|
|
4322
|
+
option.value
|
|
4323
|
+
)
|
|
4324
|
+
))
|
|
4325
|
+
}
|
|
4326
|
+
)
|
|
4327
|
+
}
|
|
4328
|
+
),
|
|
4329
|
+
document.body
|
|
4224
4330
|
)
|
|
4225
4331
|
] });
|
|
4226
4332
|
};
|
|
@@ -4320,6 +4426,8 @@ var Dropdown = (props) => {
|
|
|
4320
4426
|
wrapperRef,
|
|
4321
4427
|
triggerRef: controlRef,
|
|
4322
4428
|
listRef,
|
|
4429
|
+
panelRef,
|
|
4430
|
+
position,
|
|
4323
4431
|
close: closePanel,
|
|
4324
4432
|
onTriggerKeyDown: onControlKeyDown,
|
|
4325
4433
|
onInputKeyDown: onSearchKeyDown
|
|
@@ -4387,86 +4495,110 @@ var Dropdown = (props) => {
|
|
|
4387
4495
|
}
|
|
4388
4496
|
) }),
|
|
4389
4497
|
name && (multiple ? selectedValues.map((v) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: v, disabled }, v)) : /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selectedValues[0] ?? "", disabled })),
|
|
4390
|
-
isOpen &&
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
ref: searchRef,
|
|
4397
|
-
type: "text",
|
|
4398
|
-
className: "dropdown_search_input",
|
|
4399
|
-
placeholder: searchPlaceholder,
|
|
4400
|
-
"aria-label": searchPlaceholder,
|
|
4401
|
-
autoComplete: "off",
|
|
4402
|
-
role: "combobox",
|
|
4403
|
-
"aria-autocomplete": "list",
|
|
4404
|
-
"aria-expanded": isOpen,
|
|
4405
|
-
"aria-controls": `${dropdownId}_listbox`,
|
|
4406
|
-
"aria-activedescendant": activeIndex >= 0 && visibleOptions[activeIndex] ? `${dropdownId}_option_${activeIndex}` : void 0,
|
|
4407
|
-
value: searchText,
|
|
4408
|
-
onChange: (e) => {
|
|
4409
|
-
const v = e.target.value;
|
|
4410
|
-
setSearchText(v);
|
|
4411
|
-
if (!isComposingRef.current) setCommittedQuery(v);
|
|
4412
|
-
},
|
|
4413
|
-
onCompositionStart: () => {
|
|
4414
|
-
isComposingRef.current = true;
|
|
4415
|
-
},
|
|
4416
|
-
onCompositionEnd: (e) => {
|
|
4417
|
-
isComposingRef.current = false;
|
|
4418
|
-
const v = e.currentTarget.value;
|
|
4419
|
-
setSearchText(v);
|
|
4420
|
-
setCommittedQuery(v);
|
|
4421
|
-
},
|
|
4422
|
-
onKeyDown: onSearchKeyDown
|
|
4423
|
-
}
|
|
4424
|
-
)
|
|
4425
|
-
] }),
|
|
4426
|
-
/* @__PURE__ */ jsx(
|
|
4427
|
-
"div",
|
|
4498
|
+
isOpen && typeof document !== "undefined" && createPortal(
|
|
4499
|
+
// 포탈로 body 에 띄운다. 트리거 옆에 두면 `overflow: hidden` 인 조상(카드·표
|
|
4500
|
+
// 래퍼)이 목록을 잘라내고 `z-index` 로는 넘지 못한다(#586 - 170px 중 46px 만
|
|
4501
|
+
// 보였다). 좌표·폭은 훅이 트리거를 재서 준다.
|
|
4502
|
+
/* @__PURE__ */ jsxs(
|
|
4503
|
+
animated.div,
|
|
4428
4504
|
{
|
|
4429
|
-
ref:
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4505
|
+
ref: panelRef,
|
|
4506
|
+
className: listClassName,
|
|
4507
|
+
style: {
|
|
4508
|
+
...listStyle,
|
|
4509
|
+
position: "fixed",
|
|
4510
|
+
left: position.x,
|
|
4511
|
+
top: position.y,
|
|
4512
|
+
width: position.width || void 0,
|
|
4513
|
+
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4514
|
+
maxWidth: position.ready ? position.maxWidth : void 0,
|
|
4515
|
+
// 최초 측정 전에는 숨긴다 - (0,0) 에서 한 프레임 깜빡이는 것을 막는다.
|
|
4516
|
+
visibility: position.ready ? void 0 : "hidden"
|
|
4517
|
+
},
|
|
4518
|
+
children: [
|
|
4519
|
+
searchable && /* @__PURE__ */ jsxs("div", { className: "dropdown_search", children: [
|
|
4520
|
+
/* @__PURE__ */ jsx("span", { className: "dropdown_search_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Search, { size: iconSize.sm }) }),
|
|
4521
|
+
/* @__PURE__ */ jsx(
|
|
4522
|
+
"input",
|
|
4440
4523
|
{
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
"aria-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4524
|
+
ref: searchRef,
|
|
4525
|
+
type: "text",
|
|
4526
|
+
className: "dropdown_search_input",
|
|
4527
|
+
placeholder: searchPlaceholder,
|
|
4528
|
+
"aria-label": searchPlaceholder,
|
|
4529
|
+
autoComplete: "off",
|
|
4530
|
+
role: "combobox",
|
|
4531
|
+
"aria-autocomplete": "list",
|
|
4532
|
+
"aria-expanded": isOpen,
|
|
4533
|
+
"aria-controls": `${dropdownId}_listbox`,
|
|
4534
|
+
"aria-activedescendant": activeIndex >= 0 && visibleOptions[activeIndex] ? `${dropdownId}_option_${activeIndex}` : void 0,
|
|
4535
|
+
value: searchText,
|
|
4536
|
+
onChange: (e) => {
|
|
4537
|
+
const v = e.target.value;
|
|
4538
|
+
setSearchText(v);
|
|
4539
|
+
if (!isComposingRef.current) setCommittedQuery(v);
|
|
4540
|
+
},
|
|
4541
|
+
onCompositionStart: () => {
|
|
4542
|
+
isComposingRef.current = true;
|
|
4543
|
+
},
|
|
4544
|
+
onCompositionEnd: (e) => {
|
|
4545
|
+
isComposingRef.current = false;
|
|
4546
|
+
const v = e.currentTarget.value;
|
|
4547
|
+
setSearchText(v);
|
|
4548
|
+
setCommittedQuery(v);
|
|
4549
|
+
},
|
|
4550
|
+
onKeyDown: onSearchKeyDown
|
|
4462
4551
|
}
|
|
4463
|
-
)
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4552
|
+
)
|
|
4553
|
+
] }),
|
|
4554
|
+
/* @__PURE__ */ jsx(
|
|
4555
|
+
"div",
|
|
4556
|
+
{
|
|
4557
|
+
ref: listRef,
|
|
4558
|
+
id: `${dropdownId}_listbox`,
|
|
4559
|
+
role: "listbox",
|
|
4560
|
+
className: "dropdown_options",
|
|
4561
|
+
"aria-multiselectable": multiple || void 0,
|
|
4562
|
+
children: visibleOptions.length === 0 ? searchable && /* @__PURE__ */ jsx("div", { className: "dropdown_empty", children: emptyText }) : visibleOptions.map((opt, i) => {
|
|
4563
|
+
const selected = selectedValues.includes(opt.value);
|
|
4564
|
+
const active = i === activeIndex;
|
|
4565
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4566
|
+
/* @__PURE__ */ jsxs(
|
|
4567
|
+
"div",
|
|
4568
|
+
{
|
|
4569
|
+
id: `${dropdownId}_option_${i}`,
|
|
4570
|
+
role: "option",
|
|
4571
|
+
tabIndex: -1,
|
|
4572
|
+
"aria-selected": selected,
|
|
4573
|
+
"aria-disabled": opt.disabled ? true : void 0,
|
|
4574
|
+
className: cn("dropdown_option", {
|
|
4575
|
+
is_selected: selected,
|
|
4576
|
+
is_active: active,
|
|
4577
|
+
is_disabled: opt.disabled
|
|
4578
|
+
}),
|
|
4579
|
+
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
4580
|
+
onClick: () => selectOption(opt),
|
|
4581
|
+
children: [
|
|
4582
|
+
multiple && /* @__PURE__ */ jsx("span", { className: "dropdown_option_check", "aria-hidden": "true", children: selected && /* @__PURE__ */ jsx(Check, { size: iconSize.sm }) }),
|
|
4583
|
+
opt.leadingIcon && /* @__PURE__ */ jsx("span", { className: "dropdown_option_icon", children: opt.leadingIcon }),
|
|
4584
|
+
/* @__PURE__ */ jsxs("span", { className: "dropdown_option_content", children: [
|
|
4585
|
+
/* @__PURE__ */ jsx("span", { className: "dropdown_option_label", children: opt.label }),
|
|
4586
|
+
opt.supportingText && /* @__PURE__ */ jsx("span", { className: "dropdown_option_supporting", children: opt.supportingText })
|
|
4587
|
+
] }),
|
|
4588
|
+
(opt.trailingIcon || !multiple && selected) && /* @__PURE__ */ jsx("span", { className: "dropdown_option_trailing", children: opt.trailingIcon ?? /* @__PURE__ */ jsx(Check, { size: iconSize.sm, "aria-hidden": "true" }) })
|
|
4589
|
+
]
|
|
4590
|
+
}
|
|
4591
|
+
),
|
|
4592
|
+
opt.showDivider && /* @__PURE__ */ jsx("hr", { className: "dropdown_option_divider" })
|
|
4593
|
+
] }, opt.value);
|
|
4594
|
+
})
|
|
4595
|
+
}
|
|
4596
|
+
)
|
|
4597
|
+
]
|
|
4467
4598
|
}
|
|
4468
|
-
)
|
|
4469
|
-
|
|
4599
|
+
),
|
|
4600
|
+
document.body
|
|
4601
|
+
)
|
|
4470
4602
|
] });
|
|
4471
4603
|
};
|
|
4472
4604
|
var pad = (n) => String(n).padStart(2, "0");
|
|
@@ -5966,9 +6098,11 @@ var Drawer = ({
|
|
|
5966
6098
|
if (escapeDismissible) onClose?.();
|
|
5967
6099
|
});
|
|
5968
6100
|
if (open && !shouldRender) setShouldRender(true);
|
|
6101
|
+
const dimOwner = React11.useRef({}).current;
|
|
5969
6102
|
const overlayStyle = useSpringPresence({
|
|
5970
6103
|
visible: open,
|
|
5971
6104
|
from: "translateY(0px)",
|
|
6105
|
+
onProgress: (progress) => reportOverlayDim(dimOwner, progress),
|
|
5972
6106
|
onExitComplete: () => {
|
|
5973
6107
|
setShouldRender(false);
|
|
5974
6108
|
onExited?.();
|
|
@@ -5984,8 +6118,12 @@ var Drawer = ({
|
|
|
5984
6118
|
});
|
|
5985
6119
|
React11.useEffect(() => {
|
|
5986
6120
|
if (!shouldRender) return;
|
|
6121
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
5987
6122
|
lockBodyScroll();
|
|
5988
|
-
return
|
|
6123
|
+
return () => {
|
|
6124
|
+
unlockBodyScroll();
|
|
6125
|
+
unregisterOverlayDim(dimOwner);
|
|
6126
|
+
};
|
|
5989
6127
|
}, [shouldRender]);
|
|
5990
6128
|
if (!open && !shouldRender) return null;
|
|
5991
6129
|
if (typeof document === "undefined" || !isMounted) return null;
|
|
@@ -6074,11 +6212,13 @@ var Modal = ({
|
|
|
6074
6212
|
});
|
|
6075
6213
|
if (open && !shouldRender) setShouldRender(true);
|
|
6076
6214
|
const reduced = useReducedMotion();
|
|
6215
|
+
const dimOwner = React11.useRef({}).current;
|
|
6077
6216
|
const overlayStyle = useSpring({
|
|
6078
6217
|
...springEnterFrom(reduced),
|
|
6079
6218
|
to: { opacity: open ? 1 : 0 },
|
|
6080
6219
|
immediate: reduced,
|
|
6081
6220
|
config: OVERLAY_SPRING_CONFIG,
|
|
6221
|
+
onChange: (result) => reportOverlayDim(dimOwner, Number(result.value.opacity)),
|
|
6082
6222
|
onRest: (result) => {
|
|
6083
6223
|
if (open || !result.finished) return;
|
|
6084
6224
|
setShouldRender(false);
|
|
@@ -6096,8 +6236,12 @@ var Modal = ({
|
|
|
6096
6236
|
});
|
|
6097
6237
|
React11.useEffect(() => {
|
|
6098
6238
|
if (!shouldRender) return;
|
|
6239
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
6099
6240
|
lockBodyScroll();
|
|
6100
|
-
return
|
|
6241
|
+
return () => {
|
|
6242
|
+
unlockBodyScroll();
|
|
6243
|
+
unregisterOverlayDim(dimOwner);
|
|
6244
|
+
};
|
|
6101
6245
|
}, [shouldRender]);
|
|
6102
6246
|
if (!open && !shouldRender) return null;
|
|
6103
6247
|
if (typeof document === "undefined" || !isMounted) return null;
|