@bigtablet/design-system 3.17.3 → 3.18.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.
- package/dist/index.css +9 -33
- package/dist/index.d.ts +52 -19
- package/dist/index.js +337 -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,13 @@ 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({
|
|
331
|
+
setState({
|
|
332
|
+
...result,
|
|
333
|
+
ready: true,
|
|
334
|
+
anchorWidth: Math.min(a.width, result.maxWidth)
|
|
335
|
+
});
|
|
299
336
|
};
|
|
300
337
|
let frame = 0;
|
|
301
338
|
const schedule = () => {
|
|
@@ -317,7 +354,7 @@ function useAnchoredPosition({
|
|
|
317
354
|
window.removeEventListener("resize", schedule);
|
|
318
355
|
observer?.disconnect();
|
|
319
356
|
};
|
|
320
|
-
}, [open, placement, gap, padding, anchorRef, floatingRef]);
|
|
357
|
+
}, [open, placement, align, gap, padding, anchorRef, floatingRef]);
|
|
321
358
|
return state;
|
|
322
359
|
}
|
|
323
360
|
var SKIP_AUTOFOCUS_ATTR = "data-focus-trap-skip-autofocus";
|
|
@@ -426,7 +463,8 @@ function useSpringHover({
|
|
|
426
463
|
const reduced = useReducedMotion();
|
|
427
464
|
const style = useSpring({
|
|
428
465
|
transform: hovered ? `translateY(${lift}px) scale(${scale})` : "translateY(0px) scale(1)",
|
|
429
|
-
// reduced-motion: lift/scale
|
|
466
|
+
// reduced-motion: 보간을 없애 움직임을 지운다. 최종 상태(lift/scale)는 남는다 - 강조가
|
|
467
|
+
// 사라지면 hover/focus 를 알 수 없다. WCAG 2.3.3 이 막는 것은 모션이다.
|
|
430
468
|
immediate: reduced,
|
|
431
469
|
config: { tension: 320, friction: 22 }
|
|
432
470
|
});
|
|
@@ -441,7 +479,8 @@ function useSpringHover({
|
|
|
441
479
|
function useSpringPresence({
|
|
442
480
|
visible,
|
|
443
481
|
from = "translateY(8px)",
|
|
444
|
-
onExitComplete
|
|
482
|
+
onExitComplete,
|
|
483
|
+
onProgress
|
|
445
484
|
}) {
|
|
446
485
|
const reduced = useReducedMotion();
|
|
447
486
|
return useSpring({
|
|
@@ -462,6 +501,7 @@ function useSpringPresence({
|
|
|
462
501
|
clamp: !visible
|
|
463
502
|
// 사라질 땐 진동 없이 빠르게
|
|
464
503
|
},
|
|
504
|
+
onChange: (result) => onProgress?.(Number(result.value.opacity)),
|
|
465
505
|
onRest: (result) => {
|
|
466
506
|
if (!visible && result.finished && onExitComplete) {
|
|
467
507
|
onExitComplete();
|
|
@@ -469,7 +509,8 @@ function useSpringPresence({
|
|
|
469
509
|
}
|
|
470
510
|
});
|
|
471
511
|
}
|
|
472
|
-
var
|
|
512
|
+
var LIST_GAP = 4;
|
|
513
|
+
var LIST_PADDING = 8;
|
|
473
514
|
function useListboxPopup({
|
|
474
515
|
items,
|
|
475
516
|
onCommit,
|
|
@@ -479,17 +520,19 @@ function useListboxPopup({
|
|
|
479
520
|
}) {
|
|
480
521
|
const [isOpen, setIsOpen] = useState(false);
|
|
481
522
|
const [activeIndex, setActiveIndex] = useState(-1);
|
|
482
|
-
const [dropUp, setDropUp] = useState(false);
|
|
483
523
|
const wrapperRef = useRef(null);
|
|
484
524
|
const triggerRef = useRef(null);
|
|
485
525
|
const listRef = useRef(null);
|
|
526
|
+
const panelRef = useRef(null);
|
|
486
527
|
const close = useCallback(() => {
|
|
487
528
|
setIsOpen(false);
|
|
488
529
|
if (returnFocusOnClose) triggerRef.current?.focus();
|
|
489
530
|
}, [returnFocusOnClose]);
|
|
490
531
|
useEffect(() => {
|
|
491
532
|
const handleOutsideClick = (event) => {
|
|
492
|
-
|
|
533
|
+
const target = event.target;
|
|
534
|
+
if (wrapperRef.current?.contains(target) || panelRef.current?.contains(target)) return;
|
|
535
|
+
setIsOpen(false);
|
|
493
536
|
};
|
|
494
537
|
document.addEventListener("mousedown", handleOutsideClick);
|
|
495
538
|
return () => document.removeEventListener("mousedown", handleOutsideClick);
|
|
@@ -608,22 +651,33 @@ function useListboxPopup({
|
|
|
608
651
|
const option = list.querySelectorAll('[role="option"]')[activeIndex];
|
|
609
652
|
option?.scrollIntoView?.({ block: "nearest" });
|
|
610
653
|
}, [isOpen, activeIndex, items]);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
654
|
+
const anchored = useAnchoredPosition({
|
|
655
|
+
open: isOpen,
|
|
656
|
+
anchorRef: wrapperRef,
|
|
657
|
+
floatingRef: panelRef,
|
|
658
|
+
placement: "bottom",
|
|
659
|
+
// 트리거 왼쪽 변에 맞춘다 - 목록이 컨트롤의 연장으로 읽혀야 한다.
|
|
660
|
+
align: "start",
|
|
661
|
+
gap: LIST_GAP,
|
|
662
|
+
padding: LIST_PADDING
|
|
663
|
+
});
|
|
618
664
|
return {
|
|
619
665
|
isOpen,
|
|
620
666
|
setIsOpen,
|
|
621
|
-
dropUp,
|
|
667
|
+
dropUp: anchored.placement === "top",
|
|
668
|
+
position: {
|
|
669
|
+
x: anchored.x,
|
|
670
|
+
y: anchored.y,
|
|
671
|
+
width: anchored.anchorWidth,
|
|
672
|
+
maxWidth: anchored.maxWidth,
|
|
673
|
+
ready: anchored.ready
|
|
674
|
+
},
|
|
622
675
|
activeIndex,
|
|
623
676
|
setActiveIndex,
|
|
624
677
|
wrapperRef,
|
|
625
678
|
triggerRef,
|
|
626
679
|
listRef,
|
|
680
|
+
panelRef,
|
|
627
681
|
close,
|
|
628
682
|
moveActive,
|
|
629
683
|
commitActive,
|
|
@@ -1171,25 +1225,46 @@ var Breadcrumb = ({
|
|
|
1171
1225
|
const sep = separator ?? /* @__PURE__ */ jsx(ChevronRight, { size: iconSize.xs, "aria-hidden": "true" });
|
|
1172
1226
|
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
1227
|
const isLast = idx === items.length - 1;
|
|
1228
|
+
const Tag = item.as ?? "a";
|
|
1174
1229
|
return (
|
|
1175
1230
|
// biome-ignore lint/suspicious/noArrayIndexKey: breadcrumb items have stable order
|
|
1176
1231
|
/* @__PURE__ */ jsxs("li", { className: "breadcrumb_item", children: [
|
|
1177
|
-
isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ?
|
|
1232
|
+
isLast ? /* @__PURE__ */ jsx("span", { className: "breadcrumb_current", "aria-current": "page", children: item.label }) : item.href ? (
|
|
1233
|
+
// `href`·`className`·`onClick` 을 그대로 넘긴다 - 라우터 `Link` 가 수정자
|
|
1234
|
+
// 클릭·프리페치를 자기 방식대로 처리한다.
|
|
1235
|
+
/* @__PURE__ */ jsx(Tag, { className: "breadcrumb_link", href: item.href, onClick: item.onClick, children: item.label })
|
|
1236
|
+
) : /* @__PURE__ */ jsx("button", { type: "button", className: "breadcrumb_link", onClick: item.onClick, children: item.label }),
|
|
1178
1237
|
!isLast && /* @__PURE__ */ jsx("span", { className: "breadcrumb_separator", "aria-hidden": "true", children: sep })
|
|
1179
1238
|
] }, idx)
|
|
1180
1239
|
);
|
|
1181
1240
|
}) }) });
|
|
1182
1241
|
};
|
|
1242
|
+
var MENU_GAP = 4;
|
|
1183
1243
|
var Menu = ({ items, trigger, align = "start" }) => {
|
|
1184
1244
|
const [open, setOpen] = React11.useState(false);
|
|
1185
1245
|
const wrapperRef = React11.useRef(null);
|
|
1246
|
+
const menuRef = React11.useRef(null);
|
|
1186
1247
|
const itemRefs = React11.useRef([]);
|
|
1187
1248
|
const menuId = React11.useId();
|
|
1188
|
-
const
|
|
1249
|
+
const pos = useAnchoredPosition({
|
|
1250
|
+
open,
|
|
1251
|
+
anchorRef: wrapperRef,
|
|
1252
|
+
floatingRef: menuRef,
|
|
1253
|
+
placement: "bottom",
|
|
1254
|
+
align,
|
|
1255
|
+
gap: MENU_GAP,
|
|
1256
|
+
padding: 8
|
|
1257
|
+
});
|
|
1258
|
+
const style = useSpringPresence({
|
|
1259
|
+
visible: open,
|
|
1260
|
+
from: pos.placement === "top" ? `translateY(${MENU_GAP}px)` : `translateY(-${MENU_GAP}px)`
|
|
1261
|
+
});
|
|
1189
1262
|
React11.useEffect(() => {
|
|
1190
1263
|
if (!open) return;
|
|
1191
1264
|
const handleClick = (e) => {
|
|
1192
|
-
|
|
1265
|
+
const target = e.target;
|
|
1266
|
+
if (wrapperRef.current?.contains(target) || menuRef.current?.contains(target)) return;
|
|
1267
|
+
setOpen(false);
|
|
1193
1268
|
};
|
|
1194
1269
|
const handleEsc = (e) => {
|
|
1195
1270
|
if (e.key === "Escape") setOpen(false);
|
|
@@ -1212,8 +1287,8 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1212
1287
|
if (enabled.length === 0) return;
|
|
1213
1288
|
if (dir === "first") return void itemRefs.current[enabled[0]]?.focus();
|
|
1214
1289
|
if (dir === "last") return void itemRefs.current[enabled[enabled.length - 1]]?.focus();
|
|
1215
|
-
const
|
|
1216
|
-
const next =
|
|
1290
|
+
const pos2 = enabled.findIndex((i) => itemRefs.current[i] === document.activeElement);
|
|
1291
|
+
const next = pos2 < 0 ? dir === 1 ? 0 : enabled.length - 1 : (pos2 + dir + enabled.length) % enabled.length;
|
|
1217
1292
|
itemRefs.current[enabled[next]]?.focus();
|
|
1218
1293
|
};
|
|
1219
1294
|
const handleMenuKeyDown = (e) => {
|
|
@@ -1243,6 +1318,7 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1243
1318
|
wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
|
|
1244
1319
|
break;
|
|
1245
1320
|
case "Tab":
|
|
1321
|
+
wrapperRef.current?.querySelector("[aria-haspopup]")?.focus();
|
|
1246
1322
|
setOpen(false);
|
|
1247
1323
|
break;
|
|
1248
1324
|
}
|
|
@@ -1258,42 +1334,52 @@ var Menu = ({ items, trigger, align = "start" }) => {
|
|
|
1258
1334
|
);
|
|
1259
1335
|
return /* @__PURE__ */ jsxs("span", { className: "menu_wrapper", ref: wrapperRef, children: [
|
|
1260
1336
|
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
|
-
]
|
|
1337
|
+
open && typeof document !== "undefined" && createPortal(
|
|
1338
|
+
/* @__PURE__ */ jsx(
|
|
1339
|
+
animated.div,
|
|
1340
|
+
{
|
|
1341
|
+
id: menuId,
|
|
1342
|
+
ref: menuRef,
|
|
1343
|
+
role: "menu",
|
|
1344
|
+
style: {
|
|
1345
|
+
...style,
|
|
1346
|
+
position: "fixed",
|
|
1347
|
+
left: pos.x,
|
|
1348
|
+
top: pos.y,
|
|
1349
|
+
visibility: pos.ready ? void 0 : "hidden"
|
|
1293
1350
|
},
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1351
|
+
className: cn("menu", `menu_align_${align}`),
|
|
1352
|
+
onKeyDown: handleMenuKeyDown,
|
|
1353
|
+
children: items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
1354
|
+
"button",
|
|
1355
|
+
{
|
|
1356
|
+
ref: (el) => {
|
|
1357
|
+
itemRefs.current[index] = el;
|
|
1358
|
+
},
|
|
1359
|
+
type: "button",
|
|
1360
|
+
role: "menuitem",
|
|
1361
|
+
tabIndex: -1,
|
|
1362
|
+
disabled: item.disabled,
|
|
1363
|
+
className: cn(
|
|
1364
|
+
"menu_item",
|
|
1365
|
+
item.destructive && "menu_item_destructive",
|
|
1366
|
+
item.disabled && "menu_item_disabled"
|
|
1367
|
+
),
|
|
1368
|
+
onClick: () => {
|
|
1369
|
+
if (item.disabled) return;
|
|
1370
|
+
item.onSelect?.();
|
|
1371
|
+
setOpen(false);
|
|
1372
|
+
},
|
|
1373
|
+
children: [
|
|
1374
|
+
item.icon && /* @__PURE__ */ jsx("span", { className: "menu_item_icon", "aria-hidden": "true", children: item.icon }),
|
|
1375
|
+
/* @__PURE__ */ jsx("span", { className: "menu_item_label", children: item.label })
|
|
1376
|
+
]
|
|
1377
|
+
},
|
|
1378
|
+
item.key
|
|
1379
|
+
))
|
|
1380
|
+
}
|
|
1381
|
+
),
|
|
1382
|
+
document.body
|
|
1297
1383
|
)
|
|
1298
1384
|
] });
|
|
1299
1385
|
};
|
|
@@ -1489,13 +1575,16 @@ var LocaleSwitcher = ({ locale }) => {
|
|
|
1489
1575
|
) }, opt.value)) })
|
|
1490
1576
|
] });
|
|
1491
1577
|
};
|
|
1492
|
-
var NavLink = (
|
|
1578
|
+
var NavLink = (props) => {
|
|
1579
|
+
const { active, className, children, as, ref, ...rest } = props;
|
|
1580
|
+
const Tag = as ?? "a";
|
|
1493
1581
|
return /* @__PURE__ */ jsx(
|
|
1494
|
-
|
|
1582
|
+
Tag,
|
|
1495
1583
|
{
|
|
1496
|
-
|
|
1584
|
+
ref,
|
|
1585
|
+
className: cn("nav_bar_link", active && "nav_bar_link_active", className),
|
|
1497
1586
|
"aria-current": active ? "page" : void 0,
|
|
1498
|
-
...
|
|
1587
|
+
...rest,
|
|
1499
1588
|
children
|
|
1500
1589
|
}
|
|
1501
1590
|
);
|
|
@@ -3754,11 +3843,13 @@ var AlertModal = ({
|
|
|
3754
3843
|
useOverlayEscape(isOpen, () => dismiss());
|
|
3755
3844
|
if (isOpen && !shouldRender) setShouldRender(true);
|
|
3756
3845
|
const reduced = useReducedMotion();
|
|
3846
|
+
const dimOwner = React11.useRef({}).current;
|
|
3757
3847
|
const overlayStyle = useSpring({
|
|
3758
3848
|
...springEnterFrom(reduced),
|
|
3759
3849
|
to: { opacity: isOpen ? 1 : 0 },
|
|
3760
3850
|
immediate: reduced,
|
|
3761
3851
|
config: OVERLAY_SPRING_CONFIG,
|
|
3852
|
+
onChange: (result) => reportOverlayDim(dimOwner, Number(result.value.opacity)),
|
|
3762
3853
|
onRest: (result) => {
|
|
3763
3854
|
if (!isOpen && result.finished) setShouldRender(false);
|
|
3764
3855
|
}
|
|
@@ -3774,8 +3865,12 @@ var AlertModal = ({
|
|
|
3774
3865
|
});
|
|
3775
3866
|
React11.useEffect(() => {
|
|
3776
3867
|
if (!shouldRender) return;
|
|
3868
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
3777
3869
|
lockBodyScroll();
|
|
3778
|
-
return
|
|
3870
|
+
return () => {
|
|
3871
|
+
unlockBodyScroll();
|
|
3872
|
+
unregisterOverlayDim(dimOwner);
|
|
3873
|
+
};
|
|
3779
3874
|
}, [shouldRender]);
|
|
3780
3875
|
if (!isOpen && !shouldRender) return null;
|
|
3781
3876
|
const confirmVariant = "filled";
|
|
@@ -4185,42 +4280,59 @@ var Combobox = ({
|
|
|
4185
4280
|
}
|
|
4186
4281
|
)
|
|
4187
4282
|
] }),
|
|
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
|
-
|
|
4283
|
+
isOpen && typeof document !== "undefined" && createPortal(
|
|
4284
|
+
// Dropdown 과 같은 이유로 포탈이다 - 트리거 옆에 두면 `overflow: hidden` 조상이
|
|
4285
|
+
// 잘라낸다(#586). 좌표·폭은 배치 훅이 트리거를 재서 준다.
|
|
4286
|
+
/* @__PURE__ */ jsx(
|
|
4287
|
+
animated.div,
|
|
4288
|
+
{
|
|
4289
|
+
ref: popup.panelRef,
|
|
4290
|
+
className: cn("combobox_panel", { combobox_panel_up: popup.dropUp }),
|
|
4291
|
+
style: {
|
|
4292
|
+
...panelStyle,
|
|
4293
|
+
position: "fixed",
|
|
4294
|
+
left: popup.position.x,
|
|
4295
|
+
top: popup.position.y,
|
|
4296
|
+
// 트리거 폭은 하한 - 못박으면 좁은 트리거에서 옵션 라벨이 잘린다(#596).
|
|
4297
|
+
// 실제 폭은 `width: max-content`(style.scss)가 내용 기준으로 정한다.
|
|
4298
|
+
minWidth: popup.position.width || void 0,
|
|
4299
|
+
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4300
|
+
maxWidth: popup.position.ready ? popup.position.maxWidth : void 0,
|
|
4301
|
+
visibility: popup.position.ready ? void 0 : "hidden"
|
|
4302
|
+
},
|
|
4303
|
+
children: !hasList ? /* @__PURE__ */ jsx("p", { className: "combobox_message", role: "status", children: showIdle ? idleMessage : emptyMessage }) : /* @__PURE__ */ jsx(
|
|
4304
|
+
"div",
|
|
4305
|
+
{
|
|
4306
|
+
ref: popup.listRef,
|
|
4307
|
+
id: listId,
|
|
4308
|
+
className: "combobox_list",
|
|
4309
|
+
role: "listbox",
|
|
4310
|
+
children: options.map((option, index) => (
|
|
4311
|
+
/* biome-ignore lint/a11y/useKeyWithClickEvents: 키보드는 입력의 onKeyDown 이 담당한다 - option 은 aria-activedescendant 로 가리키는 비포커스 요소다 (APG Combobox) */
|
|
4312
|
+
/* @__PURE__ */ jsx(
|
|
4313
|
+
"div",
|
|
4314
|
+
{
|
|
4315
|
+
id: `${listId}-${option.value}`,
|
|
4316
|
+
role: "option",
|
|
4317
|
+
tabIndex: -1,
|
|
4318
|
+
"aria-selected": value?.value === option.value,
|
|
4319
|
+
"aria-disabled": option.disabled || void 0,
|
|
4320
|
+
className: cn("combobox_option", {
|
|
4321
|
+
is_active: index === activeIndex,
|
|
4322
|
+
is_disabled: option.disabled
|
|
4323
|
+
}),
|
|
4324
|
+
onMouseEnter: () => !option.disabled && setActiveIndex(index),
|
|
4325
|
+
onClick: () => !option.disabled && commit(option),
|
|
4326
|
+
children: renderOption ? renderOption(option) : option.label
|
|
4327
|
+
},
|
|
4328
|
+
option.value
|
|
4329
|
+
)
|
|
4330
|
+
))
|
|
4331
|
+
}
|
|
4332
|
+
)
|
|
4333
|
+
}
|
|
4334
|
+
),
|
|
4335
|
+
document.body
|
|
4224
4336
|
)
|
|
4225
4337
|
] });
|
|
4226
4338
|
};
|
|
@@ -4320,6 +4432,8 @@ var Dropdown = (props) => {
|
|
|
4320
4432
|
wrapperRef,
|
|
4321
4433
|
triggerRef: controlRef,
|
|
4322
4434
|
listRef,
|
|
4435
|
+
panelRef,
|
|
4436
|
+
position,
|
|
4323
4437
|
close: closePanel,
|
|
4324
4438
|
onTriggerKeyDown: onControlKeyDown,
|
|
4325
4439
|
onInputKeyDown: onSearchKeyDown
|
|
@@ -4387,86 +4501,113 @@ var Dropdown = (props) => {
|
|
|
4387
4501
|
}
|
|
4388
4502
|
) }),
|
|
4389
4503
|
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",
|
|
4504
|
+
isOpen && typeof document !== "undefined" && createPortal(
|
|
4505
|
+
// 포탈로 body 에 띄운다. 트리거 옆에 두면 `overflow: hidden` 인 조상(카드·표
|
|
4506
|
+
// 래퍼)이 목록을 잘라내고 `z-index` 로는 넘지 못한다(#586 - 170px 중 46px 만
|
|
4507
|
+
// 보였다). 좌표·폭은 훅이 트리거를 재서 준다.
|
|
4508
|
+
/* @__PURE__ */ jsxs(
|
|
4509
|
+
animated.div,
|
|
4428
4510
|
{
|
|
4429
|
-
ref:
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4511
|
+
ref: panelRef,
|
|
4512
|
+
className: listClassName,
|
|
4513
|
+
style: {
|
|
4514
|
+
...listStyle,
|
|
4515
|
+
position: "fixed",
|
|
4516
|
+
left: position.x,
|
|
4517
|
+
top: position.y,
|
|
4518
|
+
// 트리거 폭은 **하한**이다 - 못박으면 트리거가 좁을 때 목록이 자기 옵션
|
|
4519
|
+
// 라벨을 ellipsis 로 접는다(#596 - 48px 트리거에서 `02` 가 `0.`). 실제 폭은
|
|
4520
|
+
// `width: max-content`(style.scss)가 내용 기준으로 정하고 maxWidth 가 막는다.
|
|
4521
|
+
minWidth: position.width || void 0,
|
|
4522
|
+
// 트리거가 뷰포트보다 넓으면 좌표만 줄어들고 패널은 그대로 넘친다.
|
|
4523
|
+
maxWidth: position.ready ? position.maxWidth : void 0,
|
|
4524
|
+
// 최초 측정 전에는 숨긴다 - (0,0) 에서 한 프레임 깜빡이는 것을 막는다.
|
|
4525
|
+
visibility: position.ready ? void 0 : "hidden"
|
|
4526
|
+
},
|
|
4527
|
+
children: [
|
|
4528
|
+
searchable && /* @__PURE__ */ jsxs("div", { className: "dropdown_search", children: [
|
|
4529
|
+
/* @__PURE__ */ jsx("span", { className: "dropdown_search_icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Search, { size: iconSize.sm }) }),
|
|
4530
|
+
/* @__PURE__ */ jsx(
|
|
4531
|
+
"input",
|
|
4440
4532
|
{
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
"aria-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
}
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4533
|
+
ref: searchRef,
|
|
4534
|
+
type: "text",
|
|
4535
|
+
className: "dropdown_search_input",
|
|
4536
|
+
placeholder: searchPlaceholder,
|
|
4537
|
+
"aria-label": searchPlaceholder,
|
|
4538
|
+
autoComplete: "off",
|
|
4539
|
+
role: "combobox",
|
|
4540
|
+
"aria-autocomplete": "list",
|
|
4541
|
+
"aria-expanded": isOpen,
|
|
4542
|
+
"aria-controls": `${dropdownId}_listbox`,
|
|
4543
|
+
"aria-activedescendant": activeIndex >= 0 && visibleOptions[activeIndex] ? `${dropdownId}_option_${activeIndex}` : void 0,
|
|
4544
|
+
value: searchText,
|
|
4545
|
+
onChange: (e) => {
|
|
4546
|
+
const v = e.target.value;
|
|
4547
|
+
setSearchText(v);
|
|
4548
|
+
if (!isComposingRef.current) setCommittedQuery(v);
|
|
4549
|
+
},
|
|
4550
|
+
onCompositionStart: () => {
|
|
4551
|
+
isComposingRef.current = true;
|
|
4552
|
+
},
|
|
4553
|
+
onCompositionEnd: (e) => {
|
|
4554
|
+
isComposingRef.current = false;
|
|
4555
|
+
const v = e.currentTarget.value;
|
|
4556
|
+
setSearchText(v);
|
|
4557
|
+
setCommittedQuery(v);
|
|
4558
|
+
},
|
|
4559
|
+
onKeyDown: onSearchKeyDown
|
|
4462
4560
|
}
|
|
4463
|
-
)
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4561
|
+
)
|
|
4562
|
+
] }),
|
|
4563
|
+
/* @__PURE__ */ jsx(
|
|
4564
|
+
"div",
|
|
4565
|
+
{
|
|
4566
|
+
ref: listRef,
|
|
4567
|
+
id: `${dropdownId}_listbox`,
|
|
4568
|
+
role: "listbox",
|
|
4569
|
+
className: "dropdown_options",
|
|
4570
|
+
"aria-multiselectable": multiple || void 0,
|
|
4571
|
+
children: visibleOptions.length === 0 ? searchable && /* @__PURE__ */ jsx("div", { className: "dropdown_empty", children: emptyText }) : visibleOptions.map((opt, i) => {
|
|
4572
|
+
const selected = selectedValues.includes(opt.value);
|
|
4573
|
+
const active = i === activeIndex;
|
|
4574
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4575
|
+
/* @__PURE__ */ jsxs(
|
|
4576
|
+
"div",
|
|
4577
|
+
{
|
|
4578
|
+
id: `${dropdownId}_option_${i}`,
|
|
4579
|
+
role: "option",
|
|
4580
|
+
tabIndex: -1,
|
|
4581
|
+
"aria-selected": selected,
|
|
4582
|
+
"aria-disabled": opt.disabled ? true : void 0,
|
|
4583
|
+
className: cn("dropdown_option", {
|
|
4584
|
+
is_selected: selected,
|
|
4585
|
+
is_active: active,
|
|
4586
|
+
is_disabled: opt.disabled
|
|
4587
|
+
}),
|
|
4588
|
+
onMouseEnter: () => !opt.disabled && setActiveIndex(i),
|
|
4589
|
+
onClick: () => selectOption(opt),
|
|
4590
|
+
children: [
|
|
4591
|
+
multiple && /* @__PURE__ */ jsx("span", { className: "dropdown_option_check", "aria-hidden": "true", children: selected && /* @__PURE__ */ jsx(Check, { size: iconSize.sm }) }),
|
|
4592
|
+
opt.leadingIcon && /* @__PURE__ */ jsx("span", { className: "dropdown_option_icon", children: opt.leadingIcon }),
|
|
4593
|
+
/* @__PURE__ */ jsxs("span", { className: "dropdown_option_content", children: [
|
|
4594
|
+
/* @__PURE__ */ jsx("span", { className: "dropdown_option_label", children: opt.label }),
|
|
4595
|
+
opt.supportingText && /* @__PURE__ */ jsx("span", { className: "dropdown_option_supporting", children: opt.supportingText })
|
|
4596
|
+
] }),
|
|
4597
|
+
(opt.trailingIcon || !multiple && selected) && /* @__PURE__ */ jsx("span", { className: "dropdown_option_trailing", children: opt.trailingIcon ?? /* @__PURE__ */ jsx(Check, { size: iconSize.sm, "aria-hidden": "true" }) })
|
|
4598
|
+
]
|
|
4599
|
+
}
|
|
4600
|
+
),
|
|
4601
|
+
opt.showDivider && /* @__PURE__ */ jsx("hr", { className: "dropdown_option_divider" })
|
|
4602
|
+
] }, opt.value);
|
|
4603
|
+
})
|
|
4604
|
+
}
|
|
4605
|
+
)
|
|
4606
|
+
]
|
|
4467
4607
|
}
|
|
4468
|
-
)
|
|
4469
|
-
|
|
4608
|
+
),
|
|
4609
|
+
document.body
|
|
4610
|
+
)
|
|
4470
4611
|
] });
|
|
4471
4612
|
};
|
|
4472
4613
|
var pad = (n) => String(n).padStart(2, "0");
|
|
@@ -5966,9 +6107,11 @@ var Drawer = ({
|
|
|
5966
6107
|
if (escapeDismissible) onClose?.();
|
|
5967
6108
|
});
|
|
5968
6109
|
if (open && !shouldRender) setShouldRender(true);
|
|
6110
|
+
const dimOwner = React11.useRef({}).current;
|
|
5969
6111
|
const overlayStyle = useSpringPresence({
|
|
5970
6112
|
visible: open,
|
|
5971
6113
|
from: "translateY(0px)",
|
|
6114
|
+
onProgress: (progress) => reportOverlayDim(dimOwner, progress),
|
|
5972
6115
|
onExitComplete: () => {
|
|
5973
6116
|
setShouldRender(false);
|
|
5974
6117
|
onExited?.();
|
|
@@ -5984,8 +6127,12 @@ var Drawer = ({
|
|
|
5984
6127
|
});
|
|
5985
6128
|
React11.useEffect(() => {
|
|
5986
6129
|
if (!shouldRender) return;
|
|
6130
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
5987
6131
|
lockBodyScroll();
|
|
5988
|
-
return
|
|
6132
|
+
return () => {
|
|
6133
|
+
unlockBodyScroll();
|
|
6134
|
+
unregisterOverlayDim(dimOwner);
|
|
6135
|
+
};
|
|
5989
6136
|
}, [shouldRender]);
|
|
5990
6137
|
if (!open && !shouldRender) return null;
|
|
5991
6138
|
if (typeof document === "undefined" || !isMounted) return null;
|
|
@@ -6074,11 +6221,13 @@ var Modal = ({
|
|
|
6074
6221
|
});
|
|
6075
6222
|
if (open && !shouldRender) setShouldRender(true);
|
|
6076
6223
|
const reduced = useReducedMotion();
|
|
6224
|
+
const dimOwner = React11.useRef({}).current;
|
|
6077
6225
|
const overlayStyle = useSpring({
|
|
6078
6226
|
...springEnterFrom(reduced),
|
|
6079
6227
|
to: { opacity: open ? 1 : 0 },
|
|
6080
6228
|
immediate: reduced,
|
|
6081
6229
|
config: OVERLAY_SPRING_CONFIG,
|
|
6230
|
+
onChange: (result) => reportOverlayDim(dimOwner, Number(result.value.opacity)),
|
|
6082
6231
|
onRest: (result) => {
|
|
6083
6232
|
if (open || !result.finished) return;
|
|
6084
6233
|
setShouldRender(false);
|
|
@@ -6096,8 +6245,12 @@ var Modal = ({
|
|
|
6096
6245
|
});
|
|
6097
6246
|
React11.useEffect(() => {
|
|
6098
6247
|
if (!shouldRender) return;
|
|
6248
|
+
reportOverlayDim(dimOwner, reduced ? 1 : 0);
|
|
6099
6249
|
lockBodyScroll();
|
|
6100
|
-
return
|
|
6250
|
+
return () => {
|
|
6251
|
+
unlockBodyScroll();
|
|
6252
|
+
unregisterOverlayDim(dimOwner);
|
|
6253
|
+
};
|
|
6101
6254
|
}, [shouldRender]);
|
|
6102
6255
|
if (!open && !shouldRender) return null;
|
|
6103
6256
|
if (typeof document === "undefined" || !isMounted) return null;
|