sellmate-design-system-react 9.0.0-beta.90 → 9.0.0-beta.92
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/AGENTS.md +40 -13
- package/dist/components/SChatInput/README.md +8 -2
- package/dist/components/SChatInput/SChatInput.d.ts +54 -4
- package/dist/components/SDatePicker/README.md +1 -0
- package/dist/components/SDatePicker/SDatePicker.d.ts +9 -1
- package/dist/components/SDateRangePicker/README.md +1 -0
- package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +9 -1
- package/dist/components/SExpansionItem/README.md +1 -0
- package/dist/components/SExpansionItem/SExpansionItem.d.ts +10 -0
- package/dist/components/SGhostButton/README.md +3 -1
- package/dist/components/SGhostButton/SGhostButton.d.ts +18 -3
- package/dist/components/SGnbSystem/README.md +6 -2
- package/dist/components/SGnbSystem/SGnbSystem.d.ts +3 -3
- package/dist/components/SGnbSystem/gnbSystem.config.d.ts +5 -3
- package/dist/components/STimePicker/README.md +1 -1
- package/dist/components/STimePicker/STimePicker.d.ts +3 -7
- package/dist/components/STimePicker/TimeListbox.d.ts +21 -0
- package/dist/components/STimeRangePicker/README.md +2 -2
- package/dist/components/STimeRangePicker/STimeRangePicker.d.ts +5 -1
- package/dist/index.cjs +644 -626
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +644 -626
- package/dist/index.js.map +1 -1
- package/dist/lib/date.d.ts +9 -0
- package/dist/lib/mode-listbox.d.ts +10 -0
- package/dist/llms-full.txt +62 -21
- package/dist/llms.txt +40 -13
- package/dist/styles.css +6 -35
- package/dist/theme.css +0 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -4515,6 +4515,8 @@ var SGhostButton = /* @__PURE__ */ react.forwardRef(function SGhostButton2({
|
|
|
4515
4515
|
ariaExpanded,
|
|
4516
4516
|
tooltipText,
|
|
4517
4517
|
disabled = false,
|
|
4518
|
+
href,
|
|
4519
|
+
target,
|
|
4518
4520
|
onClick,
|
|
4519
4521
|
anchorClassName,
|
|
4520
4522
|
anchorStyle,
|
|
@@ -4538,51 +4540,74 @@ var SGhostButton = /* @__PURE__ */ react.forwardRef(function SGhostButton2({
|
|
|
4538
4540
|
cancelHide();
|
|
4539
4541
|
hideTimer.current = setTimeout(() => setOpen(false), TOOLTIP_HIDE_DELAY_MS);
|
|
4540
4542
|
};
|
|
4541
|
-
const s = SIZE[size];
|
|
4542
|
-
const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
|
|
4543
4543
|
const text = (tooltipText ?? "").trim();
|
|
4544
4544
|
const hasTooltip = text !== "";
|
|
4545
|
-
const
|
|
4545
|
+
const [prevHasTooltip, setPrevHasTooltip] = react.useState(hasTooltip);
|
|
4546
|
+
if (prevHasTooltip !== hasTooltip) {
|
|
4547
|
+
setPrevHasTooltip(hasTooltip);
|
|
4548
|
+
if (!hasTooltip) setOpen(false);
|
|
4549
|
+
}
|
|
4550
|
+
const s = SIZE[size];
|
|
4551
|
+
const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
|
|
4552
|
+
const shared = {
|
|
4553
|
+
"aria-disabled": disabled ? "true" : void 0,
|
|
4554
|
+
"aria-label": ariaLabel.trim() || void 0,
|
|
4555
|
+
"tabIndex": disabled ? -1 : void 0,
|
|
4556
|
+
"onClick": (e) => {
|
|
4557
|
+
if (disabled) {
|
|
4558
|
+
e.preventDefault();
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
onClick?.(e);
|
|
4562
|
+
},
|
|
4563
|
+
"className": cn(
|
|
4564
|
+
"group/ghost-button relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
|
|
4565
|
+
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
4566
|
+
"focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
|
|
4567
|
+
className
|
|
4568
|
+
),
|
|
4569
|
+
"style": {
|
|
4570
|
+
width: `var(--cmp-ghostButton-${size}-size)`,
|
|
4571
|
+
height: `var(--cmp-ghostButton-${size}-size)`,
|
|
4572
|
+
borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
|
|
4573
|
+
...style
|
|
4574
|
+
}
|
|
4575
|
+
};
|
|
4576
|
+
const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4577
|
+
!disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4578
|
+
"span",
|
|
4579
|
+
{
|
|
4580
|
+
"aria-hidden": true,
|
|
4581
|
+
className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/ghost-button:opacity-5",
|
|
4582
|
+
style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
|
|
4583
|
+
}
|
|
4584
|
+
),
|
|
4585
|
+
/* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
|
|
4586
|
+
] });
|
|
4587
|
+
const btn2 = href != null ? (
|
|
4588
|
+
// 비활성 링크는 주소를 뗀다 — `<a>` 에는 disabled 가 없어서 주소가 남으면 가운데 클릭으로 열린다.
|
|
4589
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4590
|
+
"a",
|
|
4591
|
+
{
|
|
4592
|
+
ref,
|
|
4593
|
+
href: disabled ? void 0 : href,
|
|
4594
|
+
target,
|
|
4595
|
+
rel: target === "_blank" ? "noopener noreferrer" : void 0,
|
|
4596
|
+
role: disabled ? "link" : void 0,
|
|
4597
|
+
...shared,
|
|
4598
|
+
children: content
|
|
4599
|
+
}
|
|
4600
|
+
)
|
|
4601
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4546
4602
|
"button",
|
|
4547
4603
|
{
|
|
4548
4604
|
ref,
|
|
4549
4605
|
type: "button",
|
|
4550
4606
|
disabled,
|
|
4551
|
-
"aria-disabled": disabled ? "true" : void 0,
|
|
4552
|
-
"aria-label": ariaLabel.trim() || void 0,
|
|
4553
4607
|
"aria-pressed": ariaPressed,
|
|
4554
4608
|
"aria-expanded": ariaExpanded,
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
if (disabled) {
|
|
4558
|
-
e.preventDefault();
|
|
4559
|
-
return;
|
|
4560
|
-
}
|
|
4561
|
-
onClick?.(e);
|
|
4562
|
-
},
|
|
4563
|
-
className: cn(
|
|
4564
|
-
"group/ghost-button relative box-border inline-flex items-center justify-center border-none bg-transparent p-0 transition-shadow",
|
|
4565
|
-
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
|
4566
|
-
"focus-visible:outline-none focus-visible:shadow-[0_0_0_2px_var(--cmp-ghostButton-action-hover-bg)]",
|
|
4567
|
-
className
|
|
4568
|
-
),
|
|
4569
|
-
style: {
|
|
4570
|
-
width: `var(--cmp-ghostButton-${size}-size)`,
|
|
4571
|
-
height: `var(--cmp-ghostButton-${size}-size)`,
|
|
4572
|
-
borderRadius: `var(--cmp-ghostButton-radius-${s.radius})`,
|
|
4573
|
-
...style
|
|
4574
|
-
},
|
|
4575
|
-
children: [
|
|
4576
|
-
!disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4577
|
-
"span",
|
|
4578
|
-
{
|
|
4579
|
-
"aria-hidden": true,
|
|
4580
|
-
className: "pointer-events-none absolute inset-0 rounded-[inherit] opacity-0 transition-opacity duration-200 group-hover/ghost-button:opacity-5",
|
|
4581
|
-
style: { background: `var(--cmp-ghostButton-${intent}-hover-bg)` }
|
|
4582
|
-
}
|
|
4583
|
-
),
|
|
4584
|
-
/* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: icon, size: s.icon, color: contentColor })
|
|
4585
|
-
]
|
|
4609
|
+
...shared,
|
|
4610
|
+
children: content
|
|
4586
4611
|
}
|
|
4587
4612
|
);
|
|
4588
4613
|
if (!hasTooltip) return btn2;
|
|
@@ -5310,6 +5335,9 @@ var FIELD_BORDER = 1;
|
|
|
5310
5335
|
var TEXTAREA_MIN_HEIGHT = `calc(var(--cmp-chatInput-buttonFrame-height) - ${FIELD_BORDER * 2}px)`;
|
|
5311
5336
|
var BUTTON_BOTTOM_INSET = `calc((var(--cmp-chatInput-buttonFrame-height) - var(--cmp-ghostButton-md-size)) / 2)`;
|
|
5312
5337
|
var DEFAULT_MAX_ROWS = 5;
|
|
5338
|
+
function BarButton(props) {
|
|
5339
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SGhostButton, { ...props, size: "md", style: { marginBottom: BUTTON_BOTTOM_INSET } });
|
|
5340
|
+
}
|
|
5313
5341
|
var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
5314
5342
|
value = "",
|
|
5315
5343
|
onValueChange,
|
|
@@ -5317,7 +5345,13 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
5317
5345
|
disabled = false,
|
|
5318
5346
|
onSend,
|
|
5319
5347
|
hasAttachments = false,
|
|
5348
|
+
onAttachFiles,
|
|
5349
|
+
attachAccept,
|
|
5350
|
+
attachMultiple = true,
|
|
5320
5351
|
onAttach,
|
|
5352
|
+
attachDisabled = false,
|
|
5353
|
+
attachTooltip,
|
|
5354
|
+
sendDisabled = false,
|
|
5321
5355
|
onPasteFiles,
|
|
5322
5356
|
busy = false,
|
|
5323
5357
|
onStop,
|
|
@@ -5329,11 +5363,23 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
5329
5363
|
...rest
|
|
5330
5364
|
}, ref) {
|
|
5331
5365
|
const hasText = value.trim() !== "";
|
|
5332
|
-
const canSend = !disabled && !busy && (hasText || hasAttachments);
|
|
5333
|
-
const send =
|
|
5366
|
+
const canSend = !disabled && !busy && !sendDisabled && (hasText || hasAttachments);
|
|
5367
|
+
const send = () => {
|
|
5334
5368
|
if (!canSend) return;
|
|
5335
5369
|
onSend?.(hasText ? value : "");
|
|
5336
|
-
}
|
|
5370
|
+
};
|
|
5371
|
+
const fileInputRef = react.useRef(null);
|
|
5372
|
+
const hasAttachButton = onAttachFiles != null || onAttach != null;
|
|
5373
|
+
const attachLocked = disabled || attachDisabled;
|
|
5374
|
+
const handleAttachClick = () => {
|
|
5375
|
+
if (onAttachFiles != null) fileInputRef.current?.click();
|
|
5376
|
+
else onAttach?.();
|
|
5377
|
+
};
|
|
5378
|
+
const handleFilesPicked = (e) => {
|
|
5379
|
+
const files = Array.from(e.target.files ?? []);
|
|
5380
|
+
e.target.value = "";
|
|
5381
|
+
if (files.length > 0) onAttachFiles?.(files);
|
|
5382
|
+
};
|
|
5337
5383
|
const handleKeyDown = (e) => {
|
|
5338
5384
|
rest.onKeyDown?.(e);
|
|
5339
5385
|
if (e.key !== "Enter" || e.shiftKey || e.nativeEvent.isComposing) return;
|
|
@@ -5346,19 +5392,31 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
5346
5392
|
const files = Array.from(e.clipboardData.files);
|
|
5347
5393
|
if (files.length === 0) return;
|
|
5348
5394
|
e.preventDefault();
|
|
5395
|
+
if (attachDisabled) return;
|
|
5349
5396
|
onPasteFiles(files);
|
|
5350
5397
|
};
|
|
5351
5398
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col bg-(--cmp-chatInput-bg)", className), style, children: [
|
|
5352
5399
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-end gap-(--cmp-chatInput-gap) p-(--cmp-chatInput-padding)", children: [
|
|
5353
|
-
|
|
5354
|
-
|
|
5400
|
+
hasAttachButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5401
|
+
BarButton,
|
|
5355
5402
|
{
|
|
5356
5403
|
icon: "attachFile",
|
|
5357
|
-
size: "md",
|
|
5358
5404
|
ariaLabel: "\uD30C\uC77C \uCCA8\uBD80",
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5405
|
+
tooltipText: attachTooltip,
|
|
5406
|
+
disabled: attachLocked,
|
|
5407
|
+
onClick: handleAttachClick
|
|
5408
|
+
}
|
|
5409
|
+
),
|
|
5410
|
+
onAttachFiles != null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
5411
|
+
"input",
|
|
5412
|
+
{
|
|
5413
|
+
ref: fileInputRef,
|
|
5414
|
+
type: "file",
|
|
5415
|
+
className: "hidden",
|
|
5416
|
+
accept: attachAccept,
|
|
5417
|
+
multiple: attachMultiple,
|
|
5418
|
+
disabled: attachLocked,
|
|
5419
|
+
onChange: handleFilesPicked
|
|
5362
5420
|
}
|
|
5363
5421
|
),
|
|
5364
5422
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -5382,26 +5440,26 @@ var SChatInputImpl = /* @__PURE__ */ react.forwardRef(function SChatInput({
|
|
|
5382
5440
|
onPaste: handlePaste
|
|
5383
5441
|
}
|
|
5384
5442
|
),
|
|
5385
|
-
busy ?
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5443
|
+
busy ? (
|
|
5444
|
+
// disabled 는 모두 잠근다 — 응답 도중 대화가 끝나면 정지도 할 수 없다
|
|
5445
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5446
|
+
BarButton,
|
|
5447
|
+
{
|
|
5448
|
+
icon: "stop",
|
|
5449
|
+
intent: "danger",
|
|
5450
|
+
ariaLabel: "\uC751\uB2F5 \uC911\uC9C0",
|
|
5451
|
+
disabled,
|
|
5452
|
+
onClick: onStop
|
|
5453
|
+
}
|
|
5454
|
+
)
|
|
5395
5455
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
5396
|
-
|
|
5456
|
+
BarButton,
|
|
5397
5457
|
{
|
|
5398
5458
|
icon: "send",
|
|
5399
|
-
size: "md",
|
|
5400
5459
|
intent: "subAction",
|
|
5401
5460
|
ariaLabel: "\uC804\uC1A1",
|
|
5402
5461
|
disabled: !canSend,
|
|
5403
|
-
onClick: send
|
|
5404
|
-
style: { marginBottom: BUTTON_BOTTOM_INSET }
|
|
5462
|
+
onClick: send
|
|
5405
5463
|
}
|
|
5406
5464
|
)
|
|
5407
5465
|
] }),
|
|
@@ -6642,6 +6700,7 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
|
|
|
6642
6700
|
depth = 1,
|
|
6643
6701
|
leading,
|
|
6644
6702
|
trailing,
|
|
6703
|
+
headerAction,
|
|
6645
6704
|
bodyPadding = true,
|
|
6646
6705
|
bordered = false,
|
|
6647
6706
|
expanded,
|
|
@@ -6681,12 +6740,92 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
|
|
|
6681
6740
|
);
|
|
6682
6741
|
const leadingNode = typeof leading === "function" ? leading({ expanded: isExpanded, selected: isSelected, disabled }) : leading;
|
|
6683
6742
|
const hasSupportingText = supportingText != null && supportingText !== "";
|
|
6743
|
+
const hasHeaderAction = headerAction != null && headerAction !== false;
|
|
6684
6744
|
const id = react.useId();
|
|
6745
|
+
const surfaceClass = cn(
|
|
6746
|
+
isExpanded && "border-b border-(--cmp-listExpansionItem-title-border)",
|
|
6747
|
+
hasChevronInteraction ? "hover:bg-(--sys-color-bg-neutralBright)" : !isExpanded && "hover:bg-(--cmp-listExpansionItem-title-bg-hover)",
|
|
6748
|
+
// 펼침 배경(파랑)은 bordered 일 때만. 카드처럼 테두리로 경계가 잡힌
|
|
6749
|
+
// 아이템에서만 배경이 선택 상태를 나타낸다.
|
|
6750
|
+
// 테두리 없는 리스트에서는 accentStripe 가 그 역할을 하므로,
|
|
6751
|
+
// 배경까지 깔면 항목 경계가 없는 목록에서 선택 표시가 두 겹이 된다.
|
|
6752
|
+
isExpanded && isSelected && bordered && "bg-(--cmp-listExpansionItem-title-bg-expanded)"
|
|
6753
|
+
);
|
|
6754
|
+
const hoverHandlers = {
|
|
6755
|
+
onMouseEnter: () => setHovered(true),
|
|
6756
|
+
onMouseLeave: () => setHovered(false)
|
|
6757
|
+
};
|
|
6685
6758
|
react.useEffect(() => {
|
|
6686
6759
|
if (!isExpanded) return;
|
|
6687
6760
|
parentDescendantContext?.reportExpandedDescendant(1);
|
|
6688
6761
|
return () => parentDescendantContext?.reportExpandedDescendant(-1);
|
|
6689
6762
|
}, [isExpanded, parentDescendantContext]);
|
|
6763
|
+
const toggleButton = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6764
|
+
"button",
|
|
6765
|
+
{
|
|
6766
|
+
type: "button",
|
|
6767
|
+
disabled,
|
|
6768
|
+
"aria-expanded": isExpanded,
|
|
6769
|
+
"aria-controls": id,
|
|
6770
|
+
...hasHeaderAction ? {} : hoverHandlers,
|
|
6771
|
+
onClick: (event) => {
|
|
6772
|
+
if (disabled) return;
|
|
6773
|
+
const next = !isExpanded;
|
|
6774
|
+
if (expanded === void 0) setInternalExpanded(next);
|
|
6775
|
+
onToggle?.(next, event);
|
|
6776
|
+
},
|
|
6777
|
+
className: cn(
|
|
6778
|
+
"group/expansion-item-button flex w-full cursor-pointer items-center justify-between border-0 text-left text-(--cmp-listExpansionItem-title-text-default) focus-visible:outline focus-visible:outline-2 focus-visible:outline-(--sys-color-border-accent) disabled:cursor-not-allowed disabled:text-(--sys-color-disabled-text)",
|
|
6779
|
+
titleTypo,
|
|
6780
|
+
// 헤더 액션이 있으면 면은 바깥 줄이 그린다 — 버튼은 남은 폭만 차지한다
|
|
6781
|
+
hasHeaderAction ? "min-w-0 flex-1" : surfaceClass,
|
|
6782
|
+
// 높이는 세로 패딩과 내용이 정한다 — 제목 줄에 높이를 못 박지 않는다 (SListItem 과 동일)
|
|
6783
|
+
dense ? "py-(--cmp-listExpansionItem-title-paddingY-dense)" : "py-(--cmp-listExpansionItem-title-paddingY-normal)",
|
|
6784
|
+
"gap-(--cmp-listExpansionItem-title-gap)",
|
|
6785
|
+
// 헤더 액션이 있으면 오른쪽 끝 여백은 액션 칸이 갖고, 버튼은 액션과의 간격만 남긴다
|
|
6786
|
+
hasHeaderAction ? "pr-(--cmp-listExpansionItem-title-gap)" : "pr-(--cmp-listExpansionItem-title-paddingX)",
|
|
6787
|
+
depth === 2 ? "pl-[36px]" : depth === 3 ? "pl-[56px]" : "pl-[16px]",
|
|
6788
|
+
isExpanded && "text-(--cmp-listExpansionItem-title-text-expanded)"
|
|
6789
|
+
),
|
|
6790
|
+
children: [
|
|
6791
|
+
!leading && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6792
|
+
"span",
|
|
6793
|
+
{
|
|
6794
|
+
className: cn(
|
|
6795
|
+
"flex transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
6796
|
+
isExpanded && "rotate-180"
|
|
6797
|
+
),
|
|
6798
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "caretDown", size: 12, color: "var(--sys-color-fg-quaternary)" })
|
|
6799
|
+
}
|
|
6800
|
+
),
|
|
6801
|
+
leadingNode,
|
|
6802
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6803
|
+
"span",
|
|
6804
|
+
{
|
|
6805
|
+
className: cn(
|
|
6806
|
+
"min-w-0 flex-1",
|
|
6807
|
+
supportingTextPosition === "bottom" ? "flex flex-col" : "flex items-center gap-[8px] whitespace-nowrap"
|
|
6808
|
+
),
|
|
6809
|
+
children: [
|
|
6810
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: title }),
|
|
6811
|
+
hasSupportingText && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6812
|
+
"span",
|
|
6813
|
+
{
|
|
6814
|
+
className: cn(
|
|
6815
|
+
"typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
|
|
6816
|
+
supportingTextPosition === "right" && "flex-1"
|
|
6817
|
+
),
|
|
6818
|
+
children: supportingText
|
|
6819
|
+
}
|
|
6820
|
+
)
|
|
6821
|
+
]
|
|
6822
|
+
}
|
|
6823
|
+
),
|
|
6824
|
+
trailing,
|
|
6825
|
+
showInteractionChevron && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "chevronRight", size: 12, color: interactionChevronColor })
|
|
6826
|
+
]
|
|
6827
|
+
}
|
|
6828
|
+
);
|
|
6690
6829
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
6691
6830
|
"div",
|
|
6692
6831
|
{
|
|
@@ -6709,79 +6848,13 @@ var SExpansionItem = /* @__PURE__ */ react.forwardRef(
|
|
|
6709
6848
|
className: "pointer-events-none absolute bottom-[-1px] left-0 top-0 z-[1] w-(--cmp-listItem-accentStripe-width) bg-(--cmp-listItem-accentStripe-color)"
|
|
6710
6849
|
}
|
|
6711
6850
|
),
|
|
6712
|
-
|
|
6713
|
-
|
|
6714
|
-
{
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
|
|
6718
|
-
|
|
6719
|
-
onMouseEnter: () => setHovered(true),
|
|
6720
|
-
onMouseLeave: () => setHovered(false),
|
|
6721
|
-
onClick: (event) => {
|
|
6722
|
-
if (disabled) return;
|
|
6723
|
-
const next = !isExpanded;
|
|
6724
|
-
if (expanded === void 0) setInternalExpanded(next);
|
|
6725
|
-
onToggle?.(next, event);
|
|
6726
|
-
},
|
|
6727
|
-
className: cn(
|
|
6728
|
-
"group/expansion-item-button flex w-full cursor-pointer items-center justify-between border-0 text-left text-(--cmp-listExpansionItem-title-text-default) focus-visible:outline focus-visible:outline-2 focus-visible:outline-(--sys-color-border-accent) disabled:cursor-not-allowed disabled:text-(--sys-color-disabled-text)",
|
|
6729
|
-
titleTypo,
|
|
6730
|
-
isExpanded && "border-b border-(--cmp-listExpansionItem-title-border)",
|
|
6731
|
-
hasChevronInteraction ? "hover:bg-(--sys-color-bg-neutralBright)" : !isExpanded && "hover:bg-(--cmp-listExpansionItem-title-bg-hover)",
|
|
6732
|
-
// 높이는 세로 패딩과 내용이 정한다 — 제목 줄에 높이를 못 박지 않는다 (SListItem 과 동일)
|
|
6733
|
-
dense ? "py-(--cmp-listExpansionItem-title-paddingY-dense)" : "py-(--cmp-listExpansionItem-title-paddingY-normal)",
|
|
6734
|
-
"gap-(--cmp-listExpansionItem-title-gap)",
|
|
6735
|
-
"pr-(--cmp-listExpansionItem-title-paddingX)",
|
|
6736
|
-
depth === 2 ? "pl-[36px]" : depth === 3 ? "pl-[56px]" : "pl-[16px]",
|
|
6737
|
-
isExpanded && cn(
|
|
6738
|
-
"text-(--cmp-listExpansionItem-title-text-expanded)",
|
|
6739
|
-
// 펼침 배경(파랑)은 bordered 일 때만. 카드처럼 테두리로 경계가 잡힌
|
|
6740
|
-
// 아이템에서만 배경이 선택 상태를 나타낸다.
|
|
6741
|
-
// 테두리 없는 리스트에서는 accentStripe 가 그 역할을 하므로,
|
|
6742
|
-
// 배경까지 깔면 항목 경계가 없는 목록에서 선택 표시가 두 겹이 된다.
|
|
6743
|
-
isSelected && bordered && "bg-(--cmp-listExpansionItem-title-bg-expanded)"
|
|
6744
|
-
)
|
|
6745
|
-
),
|
|
6746
|
-
children: [
|
|
6747
|
-
!leading && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6748
|
-
"span",
|
|
6749
|
-
{
|
|
6750
|
-
className: cn(
|
|
6751
|
-
"flex transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
6752
|
-
isExpanded && "rotate-180"
|
|
6753
|
-
),
|
|
6754
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "caretDown", size: 12, color: "var(--sys-color-fg-quaternary)" })
|
|
6755
|
-
}
|
|
6756
|
-
),
|
|
6757
|
-
leadingNode,
|
|
6758
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
6759
|
-
"span",
|
|
6760
|
-
{
|
|
6761
|
-
className: cn(
|
|
6762
|
-
"min-w-0 flex-1",
|
|
6763
|
-
supportingTextPosition === "bottom" ? "flex flex-col" : "flex items-center gap-[8px] whitespace-nowrap"
|
|
6764
|
-
),
|
|
6765
|
-
children: [
|
|
6766
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 truncate", children: title }),
|
|
6767
|
-
hasSupportingText && /* @__PURE__ */ jsxRuntime.jsx(
|
|
6768
|
-
"span",
|
|
6769
|
-
{
|
|
6770
|
-
className: cn(
|
|
6771
|
-
"typo-body-sm-default min-w-0 truncate text-(--sys-color-fg-secondary)",
|
|
6772
|
-
supportingTextPosition === "right" && "flex-1"
|
|
6773
|
-
),
|
|
6774
|
-
children: supportingText
|
|
6775
|
-
}
|
|
6776
|
-
)
|
|
6777
|
-
]
|
|
6778
|
-
}
|
|
6779
|
-
),
|
|
6780
|
-
trailing,
|
|
6781
|
-
showInteractionChevron && /* @__PURE__ */ jsxRuntime.jsx(SIcon, { name: "chevronRight", size: 12, color: interactionChevronColor })
|
|
6782
|
-
]
|
|
6783
|
-
}
|
|
6784
|
-
),
|
|
6851
|
+
hasHeaderAction ? (
|
|
6852
|
+
// 헤더 액션은 여닫는 버튼의 형제다 — 안에 넣으면 버튼이 중첩되고 누를 때마다 여닫힌다
|
|
6853
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center", surfaceClass), ...hoverHandlers, children: [
|
|
6854
|
+
toggleButton,
|
|
6855
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center gap-(--cmp-listExpansionItem-title-gap) pr-(--cmp-listExpansionItem-title-paddingX)", children: headerAction })
|
|
6856
|
+
] })
|
|
6857
|
+
) : toggleButton,
|
|
6785
6858
|
/* @__PURE__ */ jsxRuntime.jsx(SExpansionItemDescendantContext.Provider, { value: descendantContextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
6786
6859
|
"div",
|
|
6787
6860
|
{
|
|
@@ -13350,9 +13423,20 @@ var SystemButton = /* @__PURE__ */ react.forwardRef(function SystemButton2({
|
|
|
13350
13423
|
}
|
|
13351
13424
|
),
|
|
13352
13425
|
label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "typo-body-sm-semibold min-w-0 truncate", children: label }),
|
|
13353
|
-
count != null && count > 0 && //
|
|
13426
|
+
count != null && count > 0 && // 개수는 채운 배지로 단다 — 배경·글자색·치수는 xs deep 태그 토큰이 쥔다(시안).
|
|
13427
|
+
// 배지 색은 고정이다: 흰 면은 파랑, 어두운 면(dark·darker)은 노랑 바탕에 검은 글자.
|
|
13428
|
+
// 배지 오른쪽은 버튼 여백(alertButton.paddingX)만으로는 좁다 — 시안이 4px 을 더 준다.
|
|
13354
13429
|
// 대응 토큰이 없어 디자인 값을 그대로 쓴다.
|
|
13355
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13430
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
13431
|
+
STag,
|
|
13432
|
+
{
|
|
13433
|
+
size: "xs",
|
|
13434
|
+
deep: true,
|
|
13435
|
+
color: color === "light" ? "blue" : "yellow",
|
|
13436
|
+
label: count.toLocaleString(),
|
|
13437
|
+
className: "me-[4px] shrink-0"
|
|
13438
|
+
}
|
|
13439
|
+
)
|
|
13356
13440
|
]
|
|
13357
13441
|
}
|
|
13358
13442
|
);
|
|
@@ -14231,7 +14315,7 @@ var SStepper = /* @__PURE__ */ react.forwardRef(function SStepper2({
|
|
|
14231
14315
|
items.findIndex((item4, index) => getItemValue(item4, index) === value)
|
|
14232
14316
|
);
|
|
14233
14317
|
const lastCompletedIndex = items.reduce(
|
|
14234
|
-
(
|
|
14318
|
+
(lastIndex2, item4, index) => item4.completed === true ? index : lastIndex2,
|
|
14235
14319
|
-1
|
|
14236
14320
|
);
|
|
14237
14321
|
const resolvedClickableIndex = lastCompletedIndex + 1;
|
|
@@ -14673,12 +14757,25 @@ function monthCells(year, month) {
|
|
|
14673
14757
|
return cells;
|
|
14674
14758
|
}
|
|
14675
14759
|
var WEEK_LABELS = ["\uC77C", "\uC6D4", "\uD654", "\uC218", "\uBAA9", "\uAE08", "\uD1A0"];
|
|
14760
|
+
var WEEKDAY_SUFFIX_EM = 0.25 + 3 * 1;
|
|
14761
|
+
var weekdaySuffixWidth = (fontSize) => Math.ceil(fontSize * WEEKDAY_SUFFIX_EM);
|
|
14762
|
+
function withWeekday(value) {
|
|
14763
|
+
const [date = "", ...rest] = value.split(" ");
|
|
14764
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date);
|
|
14765
|
+
if (!match) return value;
|
|
14766
|
+
const day = new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3])).getDay();
|
|
14767
|
+
if (Number.isNaN(day)) return value;
|
|
14768
|
+
return [date, `${WEEK_LABELS[day]}\uC694\uC77C`, ...rest].join(" ");
|
|
14769
|
+
}
|
|
14676
14770
|
|
|
14677
14771
|
// src/components/SCalendarBoard/calendar-board-layout.ts
|
|
14678
14772
|
function eventEndDate(event) {
|
|
14679
14773
|
const end = event.endDate;
|
|
14680
14774
|
return end != null && end > event.date ? end : event.date;
|
|
14681
14775
|
}
|
|
14776
|
+
function compareDate(a, b) {
|
|
14777
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
14778
|
+
}
|
|
14682
14779
|
function layoutCalendarBoard({
|
|
14683
14780
|
cells,
|
|
14684
14781
|
events,
|
|
@@ -14724,7 +14821,7 @@ function layoutCalendarBoard({
|
|
|
14724
14821
|
flush(to);
|
|
14725
14822
|
});
|
|
14726
14823
|
runs.sort(
|
|
14727
|
-
(a, b) => a.startIndex - b.startIndex ||
|
|
14824
|
+
(a, b) => a.startIndex - b.startIndex || compareDate(a.event.date, b.event.date) || compareDate(eventEndDate(b.event), eventEndDate(a.event)) || a.order - b.order
|
|
14728
14825
|
);
|
|
14729
14826
|
const weekLanes = Array.from({ length: weekCount }, () => []);
|
|
14730
14827
|
for (const run of runs) {
|
|
@@ -17997,7 +18094,8 @@ var STableBar = /* @__PURE__ */ react.forwardRef(function STableBar2({ title, to
|
|
|
17997
18094
|
);
|
|
17998
18095
|
});
|
|
17999
18096
|
var NO_KEYS = /* @__PURE__ */ new Set();
|
|
18000
|
-
var
|
|
18097
|
+
var EXPAND_MOTION = { duration: 220, easing: "ease-out" };
|
|
18098
|
+
var COLLAPSE_MOTION = { duration: 140, easing: "ease-in" };
|
|
18001
18099
|
function STreeTable({
|
|
18002
18100
|
rows = [],
|
|
18003
18101
|
columns = [],
|
|
@@ -18044,32 +18142,88 @@ function STreeTable({
|
|
|
18044
18142
|
if (expandedKeys === void 0) setInnerExpandedKeys(next);
|
|
18045
18143
|
onExpandedChange?.(next, row);
|
|
18046
18144
|
};
|
|
18047
|
-
const visibleRows = react.useMemo(
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
|
|
18055
|
-
|
|
18056
|
-
|
|
18057
|
-
|
|
18058
|
-
|
|
18145
|
+
const visibleRows = react.useMemo(
|
|
18146
|
+
() => flattenRows(rows, (row) => expandedSet.has(keyOf(row)), childrenOf),
|
|
18147
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18148
|
+
[rows, expandedSet, rowKey, childrenKey]
|
|
18149
|
+
);
|
|
18150
|
+
const tableRef = react.useRef(null);
|
|
18151
|
+
const [closingKeys, setClosingKeys] = react.useState(NO_KEYS);
|
|
18152
|
+
const [prevExpand, setPrevExpand] = react.useState({ rows, expandedSet });
|
|
18153
|
+
if (prevExpand.rows !== rows || prevExpand.expandedSet !== expandedSet) {
|
|
18154
|
+
setPrevExpand({ rows, expandedSet });
|
|
18155
|
+
let nextClosing = NO_KEYS;
|
|
18156
|
+
if (prevExpand.rows === rows && motionAllowed()) {
|
|
18157
|
+
const collapsed = [...prevExpand.expandedSet].filter((key) => !expandedSet.has(key));
|
|
18158
|
+
const expanded = [...expandedSet].filter((key) => !prevExpand.expandedSet.has(key));
|
|
18159
|
+
if (collapsed.length > 0 && expanded.length === 0) nextClosing = new Set(collapsed);
|
|
18160
|
+
}
|
|
18161
|
+
if (nextClosing !== closingKeys) setClosingKeys(nextClosing);
|
|
18162
|
+
}
|
|
18163
|
+
const renderedRows = react.useMemo(
|
|
18164
|
+
() => closingKeys.size === 0 ? visibleRows : flattenRows(
|
|
18165
|
+
rows,
|
|
18166
|
+
(row) => expandedSet.has(keyOf(row)) || closingKeys.has(keyOf(row)),
|
|
18167
|
+
childrenOf
|
|
18168
|
+
),
|
|
18169
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18170
|
+
[visibleRows, closingKeys]
|
|
18171
|
+
);
|
|
18172
|
+
const visibleKeys = react.useMemo(
|
|
18173
|
+
() => new Set(visibleRows.map(keyOf)),
|
|
18174
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
18175
|
+
[visibleRows]
|
|
18176
|
+
);
|
|
18177
|
+
const motionRef = react.useRef(null);
|
|
18059
18178
|
const prevVisibleRef = react.useRef(null);
|
|
18060
18179
|
useIsomorphicLayoutEffect(() => {
|
|
18061
|
-
const keys = new Set(visibleRows.map(keyOf));
|
|
18062
18180
|
const prev = prevVisibleRef.current;
|
|
18063
|
-
prevVisibleRef.current = { rows, keys };
|
|
18064
|
-
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
if (
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
|
|
18181
|
+
prevVisibleRef.current = { rows, keys: visibleKeys };
|
|
18182
|
+
const running = motionRef.current;
|
|
18183
|
+
if (running?.done) {
|
|
18184
|
+
running.cancel();
|
|
18185
|
+
motionRef.current = null;
|
|
18186
|
+
}
|
|
18187
|
+
if (!prev || prev.rows !== rows) {
|
|
18188
|
+
motionRef.current?.cancel();
|
|
18189
|
+
motionRef.current = null;
|
|
18190
|
+
return;
|
|
18191
|
+
}
|
|
18192
|
+
const added = new Set([...visibleKeys].filter((key) => !prev.keys.has(key)));
|
|
18193
|
+
const removed = new Set([...prev.keys].filter((key) => !visibleKeys.has(key)));
|
|
18194
|
+
if (added.size === 0 && removed.size === 0) return;
|
|
18195
|
+
let from = added.size > 0 ? 0 : 1;
|
|
18196
|
+
if (motionRef.current) {
|
|
18197
|
+
const current = motionRef.current;
|
|
18198
|
+
const reversing = current.to === 0 && sameKeys(added, current.keys) || current.to === 1 && sameKeys(removed, current.keys);
|
|
18199
|
+
if (reversing) from = current.openness();
|
|
18200
|
+
current.cancel();
|
|
18201
|
+
motionRef.current = null;
|
|
18202
|
+
}
|
|
18203
|
+
const scrollEl = tableRef.current?.getScrollElement();
|
|
18204
|
+
const keys = added.size > 0 ? added : removed;
|
|
18205
|
+
const canAnimate = scrollEl != null && motionAllowed() && (added.size === 0 || removed.size === 0);
|
|
18206
|
+
const motion = canAnimate ? startRowMotion({
|
|
18207
|
+
scrollEl,
|
|
18208
|
+
rowKeys: renderedRows.map(keyOf),
|
|
18209
|
+
blockKeys: keys,
|
|
18210
|
+
from,
|
|
18211
|
+
to: added.size > 0 ? 1 : 0
|
|
18212
|
+
}) : null;
|
|
18213
|
+
if (motion) {
|
|
18214
|
+
motionRef.current = motion;
|
|
18215
|
+
if (motion.to === 0) {
|
|
18216
|
+
void motion.finished.then((completed) => {
|
|
18217
|
+
if (!completed || motionRef.current !== motion) return;
|
|
18218
|
+
motion.done = true;
|
|
18219
|
+
setClosingKeys(NO_KEYS);
|
|
18220
|
+
});
|
|
18221
|
+
}
|
|
18222
|
+
} else if (closingKeys.size > 0) {
|
|
18223
|
+
setClosingKeys(NO_KEYS);
|
|
18224
|
+
}
|
|
18225
|
+
}, [renderedRows]);
|
|
18226
|
+
react.useEffect(() => () => motionRef.current?.cancel(), []);
|
|
18073
18227
|
const collectSelectable = (row) => canSelectRow(row) ? [row, ...childrenOf(row).flatMap(collectSelectable)] : [];
|
|
18074
18228
|
const selectionOf = (keys) => {
|
|
18075
18229
|
const state2 = /* @__PURE__ */ new Map();
|
|
@@ -18165,8 +18319,9 @@ function STreeTable({
|
|
|
18165
18319
|
STable2,
|
|
18166
18320
|
{
|
|
18167
18321
|
...rest,
|
|
18322
|
+
ref: tableRef,
|
|
18168
18323
|
columns: tableColumns,
|
|
18169
|
-
rows:
|
|
18324
|
+
rows: renderedRows,
|
|
18170
18325
|
rowKey,
|
|
18171
18326
|
resizable,
|
|
18172
18327
|
selectable,
|
|
@@ -18174,14 +18329,113 @@ function STreeTable({
|
|
|
18174
18329
|
isRowIndeterminate: selectionState ? (row) => !!selectionState.get(keyOf(row))?.indeterminate : void 0,
|
|
18175
18330
|
rowClassName: (row) => cn(
|
|
18176
18331
|
rowClassName?.(row),
|
|
18177
|
-
//
|
|
18178
|
-
|
|
18332
|
+
// 접히는 중인 행은 이미 접힌 행이다 — 사라지는 동안 눌리지 않게 한다
|
|
18333
|
+
!visibleKeys.has(keyOf(row)) && "pointer-events-none"
|
|
18179
18334
|
) || void 0,
|
|
18180
18335
|
selected: tableSelected,
|
|
18181
18336
|
onSelectedChange: handleSelectedChange
|
|
18182
18337
|
}
|
|
18183
18338
|
);
|
|
18184
18339
|
}
|
|
18340
|
+
function flattenRows(nodes, isOpen, childrenOf) {
|
|
18341
|
+
const out = [];
|
|
18342
|
+
const walk = (list) => {
|
|
18343
|
+
list.forEach((row) => {
|
|
18344
|
+
out.push(row);
|
|
18345
|
+
if (isOpen(row)) walk(childrenOf(row));
|
|
18346
|
+
});
|
|
18347
|
+
};
|
|
18348
|
+
walk(nodes);
|
|
18349
|
+
return out;
|
|
18350
|
+
}
|
|
18351
|
+
function sameKeys(a, b) {
|
|
18352
|
+
return a.size === b.size && [...a].every((key) => b.has(key));
|
|
18353
|
+
}
|
|
18354
|
+
function motionAllowed() {
|
|
18355
|
+
if (typeof window === "undefined" || typeof Element.prototype.animate !== "function")
|
|
18356
|
+
return false;
|
|
18357
|
+
return !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
18358
|
+
}
|
|
18359
|
+
function startRowMotion({
|
|
18360
|
+
scrollEl,
|
|
18361
|
+
rowKeys,
|
|
18362
|
+
blockKeys,
|
|
18363
|
+
from,
|
|
18364
|
+
to
|
|
18365
|
+
}) {
|
|
18366
|
+
const spec = to === 1 ? EXPAND_MOTION : COLLAPSE_MOTION;
|
|
18367
|
+
const duration = spec.duration * Math.abs(to - from);
|
|
18368
|
+
if (duration < 1) return null;
|
|
18369
|
+
const trs = [...scrollEl.querySelectorAll("tr[data-row-index]")].map((tr) => {
|
|
18370
|
+
const key = rowKeys[Number(tr.dataset.rowIndex)];
|
|
18371
|
+
return {
|
|
18372
|
+
tr,
|
|
18373
|
+
inBlock: key !== void 0 && blockKeys.has(key),
|
|
18374
|
+
height: tr.getBoundingClientRect().height
|
|
18375
|
+
};
|
|
18376
|
+
});
|
|
18377
|
+
const viewBottom = scrollEl.scrollTop + scrollEl.clientHeight;
|
|
18378
|
+
const openAt = (offset) => from + (to - from) * offset;
|
|
18379
|
+
const translate = (shift2, s) => `translateY(${-shift2 * (1 - s)}px)`;
|
|
18380
|
+
const timing = {
|
|
18381
|
+
duration,
|
|
18382
|
+
easing: spec.easing,
|
|
18383
|
+
// 접힘은 남겨 둔 행이 빠질 때까지 끝 위치를 붙잡는다 — 놓으면 그 사이 한 프레임 제자리로 튄다
|
|
18384
|
+
fill: to === 0 ? "forwards" : "none"
|
|
18385
|
+
};
|
|
18386
|
+
const animations = [];
|
|
18387
|
+
let shift = 0;
|
|
18388
|
+
let blockHeight = 0;
|
|
18389
|
+
let y = 0;
|
|
18390
|
+
trs.forEach(({ tr, inBlock, height }, i) => {
|
|
18391
|
+
if (inBlock && !trs[i - 1]?.inBlock) {
|
|
18392
|
+
blockHeight = 0;
|
|
18393
|
+
for (let j = i; j < trs.length && trs[j].inBlock; j++) blockHeight += trs[j].height;
|
|
18394
|
+
shift += blockHeight;
|
|
18395
|
+
y = 0;
|
|
18396
|
+
}
|
|
18397
|
+
if (shift === 0 || tr.offsetTop - shift > viewBottom) return;
|
|
18398
|
+
if (!inBlock) {
|
|
18399
|
+
animations.push(
|
|
18400
|
+
tr.animate(
|
|
18401
|
+
[{ transform: translate(shift, from) }, { transform: translate(shift, to) }],
|
|
18402
|
+
timing
|
|
18403
|
+
)
|
|
18404
|
+
);
|
|
18405
|
+
return;
|
|
18406
|
+
}
|
|
18407
|
+
const rowY = y;
|
|
18408
|
+
const clipAt = (s) => Math.min(height, Math.max(0, blockHeight * (1 - s) - rowY));
|
|
18409
|
+
const bends = [1 - rowY / blockHeight, 1 - (rowY + height) / blockHeight].map((s) => (s - from) / (to - from)).filter((offset) => offset > 0 && offset < 1);
|
|
18410
|
+
const offsets = [0, ...bends, 1].sort((a, b) => a - b);
|
|
18411
|
+
animations.push(
|
|
18412
|
+
tr.animate(
|
|
18413
|
+
offsets.map((offset) => ({
|
|
18414
|
+
offset,
|
|
18415
|
+
transform: translate(shift, openAt(offset)),
|
|
18416
|
+
clipPath: `inset(${clipAt(openAt(offset))}px 0 0 0)`
|
|
18417
|
+
})),
|
|
18418
|
+
timing
|
|
18419
|
+
)
|
|
18420
|
+
);
|
|
18421
|
+
y += height;
|
|
18422
|
+
});
|
|
18423
|
+
if (animations.length === 0) return null;
|
|
18424
|
+
return {
|
|
18425
|
+
keys: blockKeys,
|
|
18426
|
+
to,
|
|
18427
|
+
done: false,
|
|
18428
|
+
finished: Promise.all(animations.map((animation) => animation.finished)).then(
|
|
18429
|
+
() => true,
|
|
18430
|
+
() => false
|
|
18431
|
+
),
|
|
18432
|
+
openness: () => {
|
|
18433
|
+
const progress = animations[0].effect?.getComputedTiming().progress;
|
|
18434
|
+
return progress == null ? to : openAt(progress);
|
|
18435
|
+
},
|
|
18436
|
+
cancel: () => animations.forEach((animation) => animation.cancel())
|
|
18437
|
+
};
|
|
18438
|
+
}
|
|
18185
18439
|
var OWN_CONTROL_SELECTOR = 'a, button, input, select, textarea, label, [role="button"], [role="checkbox"], [contenteditable="true"]';
|
|
18186
18440
|
function isOwnControl(target, cell) {
|
|
18187
18441
|
if (!(target instanceof Element)) return false;
|
|
@@ -19160,13 +19414,21 @@ function SCalendar({
|
|
|
19160
19414
|
}
|
|
19161
19415
|
);
|
|
19162
19416
|
}
|
|
19417
|
+
|
|
19418
|
+
// src/lib/mode-listbox.ts
|
|
19419
|
+
var MODE_LIST_HEIGHT = 240;
|
|
19420
|
+
var MODE_ITEM_HEIGHT = 28;
|
|
19421
|
+
var modeListItemClass = (selected, disabled) => cn(
|
|
19422
|
+
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19423
|
+
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19424
|
+
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19425
|
+
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19426
|
+
);
|
|
19163
19427
|
var MODE_NOUN = {
|
|
19164
19428
|
date: "\uB0A0\uC9DC",
|
|
19165
19429
|
month: "\uC6D4",
|
|
19166
19430
|
year: "\uC5F0\uB3C4"
|
|
19167
19431
|
};
|
|
19168
|
-
var DATEPICKER_MODE_LIST_HEIGHT = 240;
|
|
19169
|
-
var DATEPICKER_MODE_ITEM_HEIGHT = 28;
|
|
19170
19432
|
var YEAR_LIST_EDGE_BUFFER = 24;
|
|
19171
19433
|
var YEAR_LIST_EXPAND_SIZE = 80;
|
|
19172
19434
|
var MONTHS = Array.from({ length: 12 }, (_, i) => i + 1);
|
|
@@ -19217,7 +19479,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19217
19479
|
if (!list) return;
|
|
19218
19480
|
const index = anchorYearRef.current - rangeStartRef.current;
|
|
19219
19481
|
syncingScrollRef.current = true;
|
|
19220
|
-
list.scrollTop = index *
|
|
19482
|
+
list.scrollTop = index * MODE_ITEM_HEIGHT;
|
|
19221
19483
|
requestAnimationFrame(() => {
|
|
19222
19484
|
syncingScrollRef.current = false;
|
|
19223
19485
|
});
|
|
@@ -19226,7 +19488,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19226
19488
|
const list = listRef.current;
|
|
19227
19489
|
if (!list) return;
|
|
19228
19490
|
if (syncingScrollRef.current) return;
|
|
19229
|
-
if (list.scrollTop <
|
|
19491
|
+
if (list.scrollTop < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
|
|
19230
19492
|
const previousHeight = list.scrollHeight;
|
|
19231
19493
|
setRange((current) => ({
|
|
19232
19494
|
start: current.start - YEAR_LIST_EXPAND_SIZE,
|
|
@@ -19242,7 +19504,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19242
19504
|
});
|
|
19243
19505
|
return;
|
|
19244
19506
|
}
|
|
19245
|
-
if (list.scrollHeight - list.scrollTop - list.clientHeight <
|
|
19507
|
+
if (list.scrollHeight - list.scrollTop - list.clientHeight < MODE_ITEM_HEIGHT * YEAR_LIST_EDGE_BUFFER) {
|
|
19246
19508
|
setRange((current) => ({
|
|
19247
19509
|
start: current.start,
|
|
19248
19510
|
end: current.end + YEAR_LIST_EXPAND_SIZE
|
|
@@ -19257,8 +19519,8 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19257
19519
|
"aria-label": "year",
|
|
19258
19520
|
className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
19259
19521
|
style: {
|
|
19260
|
-
paddingTop: `calc((${
|
|
19261
|
-
paddingBottom: `calc((${
|
|
19522
|
+
paddingTop: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`,
|
|
19523
|
+
paddingBottom: `calc((${MODE_LIST_HEIGHT}px - ${MODE_ITEM_HEIGHT}px) / 2)`
|
|
19262
19524
|
},
|
|
19263
19525
|
onScroll: handleScroll,
|
|
19264
19526
|
children: years.map((year) => {
|
|
@@ -19271,12 +19533,7 @@ function YearWheel({ value, anchorYear, selectable, onCommit }) {
|
|
|
19271
19533
|
role: "option",
|
|
19272
19534
|
"aria-selected": selected,
|
|
19273
19535
|
disabled,
|
|
19274
|
-
className:
|
|
19275
|
-
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19276
|
-
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19277
|
-
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19278
|
-
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19279
|
-
),
|
|
19536
|
+
className: modeListItemClass(selected, disabled),
|
|
19280
19537
|
onClick: () => !disabled && onCommit(year),
|
|
19281
19538
|
children: year
|
|
19282
19539
|
},
|
|
@@ -19332,7 +19589,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
|
|
|
19332
19589
|
if (!list) return;
|
|
19333
19590
|
list.scrollTop = Math.max(
|
|
19334
19591
|
0,
|
|
19335
|
-
(initialValueRef.current - 1) *
|
|
19592
|
+
(initialValueRef.current - 1) * MODE_ITEM_HEIGHT - (list.clientHeight - MODE_ITEM_HEIGHT) / 2
|
|
19336
19593
|
);
|
|
19337
19594
|
}, []);
|
|
19338
19595
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19352,12 +19609,7 @@ function MonthWheel({ year, value, selectable, onCommit }) {
|
|
|
19352
19609
|
role: "option",
|
|
19353
19610
|
"aria-selected": selected,
|
|
19354
19611
|
disabled,
|
|
19355
|
-
className:
|
|
19356
|
-
"flex w-full items-center justify-center gap-[var(--cmp-datepicker-mode-item-gap)] border-0 px-[var(--cmp-datepicker-mode-item-paddingX)] py-[var(--cmp-datepicker-mode-item-paddingY)] text-center font-base text-12 leading-[20px]",
|
|
19357
|
-
disabled && "cursor-not-allowed bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-disabled)]",
|
|
19358
|
-
!disabled && !selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-medium text-[color:var(--cmp-datepicker-mode-item-content-default)] hover:bg-[var(--cmp-datepicker-mode-item-bg-hover)] hover:text-[color:var(--cmp-datepicker-mode-item-content-hover)]",
|
|
19359
|
-
!disabled && selected && "cursor-pointer bg-[var(--cmp-datepicker-mode-item-bg-default)] font-bold text-[color:var(--cmp-datepicker-mode-item-content-selected)]"
|
|
19360
|
-
),
|
|
19612
|
+
className: modeListItemClass(selected, disabled),
|
|
19361
19613
|
onClick: () => !disabled && onCommit(month),
|
|
19362
19614
|
children: [
|
|
19363
19615
|
month,
|
|
@@ -19436,6 +19688,7 @@ function SDatePicker({
|
|
|
19436
19688
|
placeholder,
|
|
19437
19689
|
selectable,
|
|
19438
19690
|
disabled = false,
|
|
19691
|
+
showWeekday = false,
|
|
19439
19692
|
clearable = false,
|
|
19440
19693
|
width,
|
|
19441
19694
|
maxWidth,
|
|
@@ -19465,6 +19718,7 @@ function SDatePicker({
|
|
|
19465
19718
|
const [focused, setFocused] = react.useState(false);
|
|
19466
19719
|
const [ruleError, setRuleError] = react.useState("");
|
|
19467
19720
|
const hasValue = value != null && value !== "";
|
|
19721
|
+
const weekday = showWeekday && mode === "date";
|
|
19468
19722
|
const resolvedPlaceholder = placeholder ?? (mode === "year" ? "YYYY" : mode === "month" ? "YYYY-MM" : "YYYY-MM-DD");
|
|
19469
19723
|
useFormField({
|
|
19470
19724
|
name,
|
|
@@ -19477,7 +19731,7 @@ function SDatePicker({
|
|
|
19477
19731
|
});
|
|
19478
19732
|
const resolvedError = error || ruleError !== "";
|
|
19479
19733
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
19480
|
-
const
|
|
19734
|
+
const baseDims = size === "md" ? {
|
|
19481
19735
|
padX: 16,
|
|
19482
19736
|
gap: 12,
|
|
19483
19737
|
icon: 20,
|
|
@@ -19502,6 +19756,13 @@ function SDatePicker({
|
|
|
19502
19756
|
lineHeight: 20,
|
|
19503
19757
|
radius: 4
|
|
19504
19758
|
};
|
|
19759
|
+
const weekdayW = weekday ? weekdaySuffixWidth(baseDims.fontSize) : 0;
|
|
19760
|
+
const dims = {
|
|
19761
|
+
...baseDims,
|
|
19762
|
+
minW: baseDims.minW + weekdayW,
|
|
19763
|
+
fieldMinW: baseDims.fieldMinW + weekdayW,
|
|
19764
|
+
fieldMaxW: weekdayW ? `calc(${fieldWidthValue(baseDims.fieldMaxW)} + ${weekdayW}px)` : baseDims.fieldMaxW
|
|
19765
|
+
};
|
|
19505
19766
|
const select = (date) => {
|
|
19506
19767
|
onValueChange?.(date);
|
|
19507
19768
|
if (rules && rules.length > 0) setRuleError(runRules(date, rules));
|
|
@@ -19586,7 +19847,7 @@ function SDatePicker({
|
|
|
19586
19847
|
lineHeight: `${dims.lineHeight}px`,
|
|
19587
19848
|
color: disabled ? "var(--cmp-datepicker-text-disabled)" : hasValue ? "var(--cmp-datepicker-text-default)" : "var(--cmp-field-hint-color)"
|
|
19588
19849
|
},
|
|
19589
|
-
children: hasValue ? value : resolvedPlaceholder
|
|
19850
|
+
children: hasValue ? weekday ? withWeekday(value) : value : resolvedPlaceholder
|
|
19590
19851
|
}
|
|
19591
19852
|
),
|
|
19592
19853
|
clearable && hasValue && !disabled && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -19980,6 +20241,7 @@ function SDateRangePicker({
|
|
|
19980
20241
|
selectable,
|
|
19981
20242
|
maxRange,
|
|
19982
20243
|
showTimePicker = false,
|
|
20244
|
+
showWeekday = false,
|
|
19983
20245
|
disabled = false,
|
|
19984
20246
|
clearable = false,
|
|
19985
20247
|
width,
|
|
@@ -20011,8 +20273,10 @@ function SDateRangePicker({
|
|
|
20011
20273
|
const [pendingStart, setPendingStart] = react.useState(null);
|
|
20012
20274
|
const hasValue = value != null && value[0] !== "" && value[1] !== "";
|
|
20013
20275
|
const hasDisplay = pendingStart != null || hasValue;
|
|
20014
|
-
const
|
|
20015
|
-
const
|
|
20276
|
+
const rawStart = pendingStart ?? (hasValue ? value[0] : "");
|
|
20277
|
+
const rawEnd = pendingStart ? "" : hasValue ? value[1] : "";
|
|
20278
|
+
const startText = showWeekday && rawStart ? withWeekday(rawStart) : rawStart;
|
|
20279
|
+
const endText = showWeekday && rawEnd ? withWeekday(rawEnd) : rawEnd;
|
|
20016
20280
|
useFormField({
|
|
20017
20281
|
name,
|
|
20018
20282
|
validate: () => {
|
|
@@ -20024,7 +20288,7 @@ function SDateRangePicker({
|
|
|
20024
20288
|
});
|
|
20025
20289
|
const resolvedError = error || ruleError !== "";
|
|
20026
20290
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
20027
|
-
const
|
|
20291
|
+
const baseDims = size === "md" ? {
|
|
20028
20292
|
padX: 16,
|
|
20029
20293
|
gap: 12,
|
|
20030
20294
|
icon: 20,
|
|
@@ -20048,6 +20312,13 @@ function SDateRangePicker({
|
|
|
20048
20312
|
lineHeight: 20,
|
|
20049
20313
|
radius: 4
|
|
20050
20314
|
};
|
|
20315
|
+
const weekdayW = showWeekday ? weekdaySuffixWidth(baseDims.fontSize) * 2 : 0;
|
|
20316
|
+
const dims = {
|
|
20317
|
+
...baseDims,
|
|
20318
|
+
minW: baseDims.minW + weekdayW,
|
|
20319
|
+
fieldMinW: baseDims.fieldMinW + weekdayW,
|
|
20320
|
+
fieldMaxW: weekdayW ? `calc(${fieldWidthValue(baseDims.fieldMaxW)} + ${weekdayW}px)` : baseDims.fieldMaxW
|
|
20321
|
+
};
|
|
20051
20322
|
const select = (r3) => {
|
|
20052
20323
|
setPendingStart(null);
|
|
20053
20324
|
onValueChange?.(r3);
|
|
@@ -26049,7 +26320,7 @@ var isAtResetTarget = (field, value) => {
|
|
|
26049
26320
|
var isInlineField = (field) => isInlineDateField(field) || field.type === "custom";
|
|
26050
26321
|
var isFixedField = (field) => field.fixed === true || field.required === true;
|
|
26051
26322
|
var isAddableField = (field) => !isFixedField(field);
|
|
26052
|
-
var
|
|
26323
|
+
var sameKeys2 = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
|
|
26053
26324
|
var isGroupSatisfied = (group, values) => {
|
|
26054
26325
|
if (!group.rule) return true;
|
|
26055
26326
|
const keys = group.fields.map((f) => f.key);
|
|
@@ -26232,7 +26503,7 @@ var SChipFilter = /* @__PURE__ */ react.forwardRef(function SChipFilter2({
|
|
|
26232
26503
|
const kept = current.filter((key) => known.has(key));
|
|
26233
26504
|
const additions = fields.filter((field) => isFixedField(field) && !kept.includes(field.key)).map((field) => field.key);
|
|
26234
26505
|
const next = [...kept, ...additions];
|
|
26235
|
-
return
|
|
26506
|
+
return sameKeys2(current, next) ? current : next;
|
|
26236
26507
|
});
|
|
26237
26508
|
}, [fields, values]);
|
|
26238
26509
|
react.useEffect(() => {
|
|
@@ -28425,212 +28696,171 @@ function formatTimeDisplay(value, type) {
|
|
|
28425
28696
|
const displayHour = hour % 12 === 0 ? 12 : hour % 12;
|
|
28426
28697
|
return `${meridiem} ${padTimePart(displayHour)}:${padTimePart(minute)}`;
|
|
28427
28698
|
}
|
|
28428
|
-
|
|
28429
|
-
|
|
28430
|
-
|
|
28699
|
+
function scrollSelectedIntoView(list, index, center) {
|
|
28700
|
+
const top = index * MODE_ITEM_HEIGHT;
|
|
28701
|
+
if (center) {
|
|
28702
|
+
list.scrollTop = Math.max(0, top - (list.clientHeight - MODE_ITEM_HEIGHT) / 2);
|
|
28703
|
+
return;
|
|
28704
|
+
}
|
|
28705
|
+
if (top < list.scrollTop) list.scrollTop = top;
|
|
28706
|
+
else if (top + MODE_ITEM_HEIGHT > list.scrollTop + list.clientHeight)
|
|
28707
|
+
list.scrollTop = top + MODE_ITEM_HEIGHT - list.clientHeight;
|
|
28708
|
+
}
|
|
28709
|
+
function lastIndex(arr, pred) {
|
|
28710
|
+
for (let i = arr.length - 1; i >= 0; i--) if (pred(arr[i], i)) return i;
|
|
28711
|
+
return -1;
|
|
28712
|
+
}
|
|
28713
|
+
function ListColumn({ ariaLabel, items }) {
|
|
28714
|
+
const listRef = react.useRef(null);
|
|
28715
|
+
const mountedRef = react.useRef(false);
|
|
28716
|
+
const selectedIndex = items.findIndex((item4) => item4.selected);
|
|
28717
|
+
const tabIndexTarget = selectedIndex >= 0 ? selectedIndex : items.findIndex((item4) => !item4.disabled);
|
|
28718
|
+
react.useLayoutEffect(() => {
|
|
28719
|
+
const list = listRef.current;
|
|
28720
|
+
if (!list || selectedIndex < 0) return;
|
|
28721
|
+
scrollSelectedIntoView(list, selectedIndex, !mountedRef.current);
|
|
28722
|
+
mountedRef.current = true;
|
|
28723
|
+
}, [selectedIndex]);
|
|
28724
|
+
const onKeyDown = (event) => {
|
|
28725
|
+
const buttons = [...listRef.current?.querySelectorAll("button") ?? []];
|
|
28726
|
+
const current = buttons.indexOf(document.activeElement);
|
|
28727
|
+
if (current < 0) return;
|
|
28728
|
+
const enabled = (item4, i) => !item4.disabled && i !== current;
|
|
28729
|
+
let next;
|
|
28730
|
+
if (event.key === "ArrowDown") next = items.findIndex((it, i) => i > current && enabled(it, i));
|
|
28731
|
+
else if (event.key === "ArrowUp")
|
|
28732
|
+
next = lastIndex(items, (it, i) => i < current && enabled(it, i));
|
|
28733
|
+
else if (event.key === "Home") next = items.findIndex((it) => !it.disabled);
|
|
28734
|
+
else if (event.key === "End") next = lastIndex(items, (it) => !it.disabled);
|
|
28735
|
+
else return;
|
|
28736
|
+
event.preventDefault();
|
|
28737
|
+
if (next < 0) return;
|
|
28738
|
+
buttons[next]?.focus({ preventScroll: true });
|
|
28739
|
+
if (listRef.current) scrollSelectedIntoView(listRef.current, next, false);
|
|
28740
|
+
};
|
|
28741
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-[var(--cmp-datepicker-mode-yearMonth-column-width)] min-w-0 overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28742
|
+
"div",
|
|
28743
|
+
{
|
|
28744
|
+
ref: listRef,
|
|
28745
|
+
role: "listbox",
|
|
28746
|
+
"aria-label": ariaLabel,
|
|
28747
|
+
tabIndex: -1,
|
|
28748
|
+
className: "h-full overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
28749
|
+
onKeyDown,
|
|
28750
|
+
children: items.map((item4, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
28751
|
+
"button",
|
|
28752
|
+
{
|
|
28753
|
+
type: "button",
|
|
28754
|
+
role: "option",
|
|
28755
|
+
"aria-selected": item4.selected,
|
|
28756
|
+
disabled: item4.disabled,
|
|
28757
|
+
tabIndex: index === tabIndexTarget ? 0 : -1,
|
|
28758
|
+
className: cn(modeListItemClass(item4.selected, item4.disabled), "outline-offset-[-2px]"),
|
|
28759
|
+
onClick: () => !item4.disabled && item4.onSelect(),
|
|
28760
|
+
children: item4.label
|
|
28761
|
+
},
|
|
28762
|
+
item4.key
|
|
28763
|
+
))
|
|
28764
|
+
}
|
|
28765
|
+
) });
|
|
28766
|
+
}
|
|
28767
|
+
var HOURS_24 = Array.from({ length: 24 }, (_, i) => i);
|
|
28768
|
+
var HOURS_12 = [12, ...Array.from({ length: 11 }, (_, i) => i + 1)];
|
|
28769
|
+
function TimeListbox({
|
|
28431
28770
|
value,
|
|
28432
28771
|
showMeridiem,
|
|
28433
28772
|
minuteStep,
|
|
28434
|
-
|
|
28773
|
+
isDisabled,
|
|
28774
|
+
onChange,
|
|
28775
|
+
labelPrefix,
|
|
28776
|
+
elevated = true
|
|
28435
28777
|
}) {
|
|
28436
|
-
const
|
|
28437
|
-
const [
|
|
28438
|
-
const [minute, setMinute] = react.useState(parsed.minute);
|
|
28439
|
-
const [editingPart, setEditingPart] = react.useState(null);
|
|
28440
|
-
const [inputDraft, setInputDraft] = react.useState({});
|
|
28778
|
+
const toState = (v) => v ? parseTimeValue(v) : null;
|
|
28779
|
+
const [time, setTime] = react.useState(() => toState(value));
|
|
28441
28780
|
react.useEffect(() => {
|
|
28442
|
-
|
|
28443
|
-
|
|
28444
|
-
|
|
28445
|
-
const
|
|
28446
|
-
const
|
|
28447
|
-
const
|
|
28448
|
-
|
|
28449
|
-
|
|
28450
|
-
|
|
28451
|
-
|
|
28452
|
-
|
|
28453
|
-
|
|
28454
|
-
|
|
28455
|
-
if (next
|
|
28456
|
-
|
|
28457
|
-
|
|
28458
|
-
const changeMinute = (delta) => {
|
|
28459
|
-
const next = minute + delta;
|
|
28460
|
-
if (next > 59) {
|
|
28461
|
-
commit((hour + 1) % 24, next - 60);
|
|
28462
|
-
return;
|
|
28463
|
-
}
|
|
28464
|
-
if (next < 0) {
|
|
28465
|
-
commit((hour + 23) % 24, next + 60);
|
|
28466
|
-
return;
|
|
28467
|
-
}
|
|
28468
|
-
commit(hour, next);
|
|
28469
|
-
};
|
|
28470
|
-
const changeHour = (delta) => commit((hour + delta + 24) % 24, minute);
|
|
28471
|
-
const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
|
|
28472
|
-
const beginEdit = (part, currentValue) => {
|
|
28473
|
-
setEditingPart(part);
|
|
28474
|
-
setInputDraft((prev) => ({ ...prev, [part]: String(currentValue).padStart(2, "0") }));
|
|
28475
|
-
};
|
|
28476
|
-
const commitHourDraft = (draft) => {
|
|
28477
|
-
const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
|
|
28478
|
-
const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
|
|
28479
|
-
commit(nextHour, minute);
|
|
28480
|
-
return clamped;
|
|
28481
|
-
};
|
|
28482
|
-
const commitMinuteDraft = (draft) => {
|
|
28483
|
-
const clamped = clampTimePart(Number(draft), 0, 59);
|
|
28484
|
-
commit(hour, clamped);
|
|
28485
|
-
return clamped;
|
|
28486
|
-
};
|
|
28487
|
-
const endEdit = (part) => {
|
|
28488
|
-
const draft = inputDraft[part];
|
|
28489
|
-
if (part === "hour" && draft != null && draft !== "") {
|
|
28490
|
-
commitHourDraft(draft);
|
|
28491
|
-
}
|
|
28492
|
-
if (part === "minute" && draft != null && draft !== "") {
|
|
28493
|
-
commitMinuteDraft(draft);
|
|
28494
|
-
}
|
|
28495
|
-
setEditingPart(null);
|
|
28496
|
-
setInputDraft({});
|
|
28497
|
-
};
|
|
28498
|
-
const inputStyle = {
|
|
28499
|
-
width: "var(--cmp-timepicker-selector-time-input-width)",
|
|
28500
|
-
height: "var(--cmp-textinput-sm-height)",
|
|
28501
|
-
borderRadius: "var(--cmp-textinput-sm-radius)",
|
|
28502
|
-
border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
|
|
28503
|
-
color: "var(--cmp-textinput-text-default)",
|
|
28504
|
-
fontSize: 12,
|
|
28505
|
-
lineHeight: "20px"
|
|
28781
|
+
setTime(toState(value));
|
|
28782
|
+
}, [value]);
|
|
28783
|
+
const step = Math.max(1, Math.floor(minuteStep));
|
|
28784
|
+
const minutes = Array.from({ length: Math.ceil(60 / step) }, (_, i) => i * step);
|
|
28785
|
+
const blocked = (h, m) => isDisabled?.(h * 60 + m) ?? false;
|
|
28786
|
+
const hourBlocked = (h) => minutes.every((m) => blocked(h, m));
|
|
28787
|
+
const commit = (hours, preferred, part) => {
|
|
28788
|
+
let next = blocked(preferred.hour, preferred.minute) ? null : preferred;
|
|
28789
|
+
for (const h of hours) {
|
|
28790
|
+
if (next) break;
|
|
28791
|
+
const m = minutes.find((mm) => !blocked(h, mm));
|
|
28792
|
+
if (m !== void 0) next = { hour: h, minute: m };
|
|
28793
|
+
}
|
|
28794
|
+
if (!next) return;
|
|
28795
|
+
setTime(next);
|
|
28796
|
+
onChange(formatTimeValue(next.hour, next.minute), part);
|
|
28506
28797
|
};
|
|
28507
|
-
const
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
|
|
28515
|
-
|
|
28516
|
-
|
|
28517
|
-
|
|
28518
|
-
|
|
28519
|
-
|
|
28520
|
-
|
|
28521
|
-
|
|
28522
|
-
|
|
28523
|
-
onClick: () => isHour ? changeHour(1) : changeMinute(minuteStep)
|
|
28524
|
-
}
|
|
28525
|
-
),
|
|
28526
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28527
|
-
"input",
|
|
28528
|
-
{
|
|
28529
|
-
"aria-label": isHour ? "\uC2DC" : "\uBD84",
|
|
28530
|
-
inputMode: "numeric",
|
|
28531
|
-
value: displayValue2,
|
|
28532
|
-
placeholder: "-",
|
|
28533
|
-
onFocus: () => beginEdit(type, val),
|
|
28534
|
-
onBlur: () => endEdit(type),
|
|
28535
|
-
onChange: (event) => {
|
|
28536
|
-
const draft = sanitizeInput(event.target.value);
|
|
28537
|
-
setInputDraft((prev) => ({ ...prev, [type]: draft }));
|
|
28538
|
-
if (draft === "") return;
|
|
28539
|
-
const raw = Number(draft);
|
|
28540
|
-
if (isHour) {
|
|
28541
|
-
if (draft.length < 2) return;
|
|
28542
|
-
const clamped2 = commitHourDraft(draft);
|
|
28543
|
-
const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
|
|
28544
|
-
setInputDraft((prev) => ({ ...prev, [type]: displayDraft2 }));
|
|
28545
|
-
return;
|
|
28546
|
-
}
|
|
28547
|
-
if (draft.length < 2) return;
|
|
28548
|
-
const clamped = commitMinuteDraft(draft);
|
|
28549
|
-
const displayDraft = raw === clamped ? draft : String(clamped);
|
|
28550
|
-
setInputDraft((prev) => ({ ...prev, [type]: displayDraft }));
|
|
28551
|
-
},
|
|
28552
|
-
className: "box-border bg-white text-center outline-none",
|
|
28553
|
-
style: inputStyle
|
|
28554
|
-
}
|
|
28555
|
-
),
|
|
28556
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28557
|
-
SGhostButton,
|
|
28558
|
-
{
|
|
28559
|
-
icon: "chevronDown",
|
|
28560
|
-
size: "xs",
|
|
28561
|
-
ariaLabel: `${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
|
|
28562
|
-
onClick: () => isHour ? changeHour(-1) : changeMinute(-minuteStep)
|
|
28563
|
-
}
|
|
28564
|
-
)
|
|
28565
|
-
]
|
|
28798
|
+
const hour = time?.hour;
|
|
28799
|
+
const minute = time?.minute ?? 0;
|
|
28800
|
+
const meridiem = hour === void 0 ? void 0 : hour >= 12 ? "PM" : "AM";
|
|
28801
|
+
const prefix = labelPrefix ? `${labelPrefix} ` : "";
|
|
28802
|
+
const meridiemItems = ["AM", "PM"].map((item4) => {
|
|
28803
|
+
const base = item4 === "AM" ? 0 : 12;
|
|
28804
|
+
const range = HOURS_24.slice(base, base + 12);
|
|
28805
|
+
return {
|
|
28806
|
+
key: item4,
|
|
28807
|
+
label: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4",
|
|
28808
|
+
selected: meridiem === item4,
|
|
28809
|
+
disabled: range.every(hourBlocked),
|
|
28810
|
+
onSelect: () => {
|
|
28811
|
+
if (meridiem === item4) return;
|
|
28812
|
+
const h = hour === void 0 ? base : hour % 12 + base;
|
|
28813
|
+
commit(range, { hour: h, minute }, "meridiem");
|
|
28566
28814
|
}
|
|
28567
|
-
|
|
28568
|
-
};
|
|
28815
|
+
};
|
|
28816
|
+
});
|
|
28817
|
+
const hourItems = showMeridiem ? HOURS_12.map((display) => {
|
|
28818
|
+
const h = display % 12 + (meridiem === "PM" ? 12 : 0);
|
|
28819
|
+
return {
|
|
28820
|
+
key: String(display),
|
|
28821
|
+
label: `${padTimePart(display)}\uC2DC`,
|
|
28822
|
+
selected: hour === h,
|
|
28823
|
+
disabled: hourBlocked(h),
|
|
28824
|
+
onSelect: () => commit([h], { hour: h, minute }, "hour")
|
|
28825
|
+
};
|
|
28826
|
+
}) : HOURS_24.map((h) => ({
|
|
28827
|
+
key: String(h),
|
|
28828
|
+
label: `${padTimePart(h)}\uC2DC`,
|
|
28829
|
+
selected: hour === h,
|
|
28830
|
+
disabled: hourBlocked(h),
|
|
28831
|
+
onSelect: () => commit([h], { hour: h, minute }, "hour")
|
|
28832
|
+
}));
|
|
28833
|
+
const minuteHour = hour ?? (meridiem === "PM" ? 12 : 0);
|
|
28834
|
+
const minuteItems = minutes.map((m) => ({
|
|
28835
|
+
key: String(m),
|
|
28836
|
+
label: `${padTimePart(m)}\uBD84`,
|
|
28837
|
+
selected: time != null && minute === m,
|
|
28838
|
+
disabled: blocked(minuteHour, m),
|
|
28839
|
+
onSelect: () => commit([minuteHour], { hour: minuteHour, minute: m }, "minute")
|
|
28840
|
+
}));
|
|
28841
|
+
const divider = /* @__PURE__ */ jsxRuntime.jsx(
|
|
28842
|
+
"div",
|
|
28843
|
+
{
|
|
28844
|
+
"aria-hidden": true,
|
|
28845
|
+
className: "w-[var(--border-width-1)] self-stretch bg-[var(--sys-color-divider-default)]"
|
|
28846
|
+
}
|
|
28847
|
+
);
|
|
28569
28848
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28570
28849
|
"div",
|
|
28571
28850
|
{
|
|
28572
|
-
className:
|
|
28573
|
-
|
|
28574
|
-
|
|
28575
|
-
|
|
28576
|
-
|
|
28577
|
-
},
|
|
28851
|
+
className: cn(
|
|
28852
|
+
"inline-flex gap-[var(--cmp-datepicker-mode-yearMonth-gap)] overflow-hidden bg-[var(--cmp-datepicker-mode-bg)]",
|
|
28853
|
+
elevated && "rounded-[var(--cmp-datepicker-mode-radius)] shadow-normal-sm"
|
|
28854
|
+
),
|
|
28855
|
+
style: { height: MODE_LIST_HEIGHT },
|
|
28578
28856
|
children: [
|
|
28579
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.
|
|
28580
|
-
|
|
28581
|
-
|
|
28582
|
-
|
|
28583
|
-
|
|
28584
|
-
|
|
28585
|
-
|
|
28586
|
-
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
|
|
28587
|
-
const selected = item4 === meridiem;
|
|
28588
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28589
|
-
"button",
|
|
28590
|
-
{
|
|
28591
|
-
type: "button",
|
|
28592
|
-
onClick: () => setMeridiem(item4),
|
|
28593
|
-
className: cn(
|
|
28594
|
-
"box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
|
|
28595
|
-
index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)]" : "rounded-r-[var(--cmp-select-sm-radius)]",
|
|
28596
|
-
index === 0 ? "-mr-px" : "",
|
|
28597
|
-
selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
|
|
28598
|
-
),
|
|
28599
|
-
children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
|
|
28600
|
-
},
|
|
28601
|
-
item4
|
|
28602
|
-
);
|
|
28603
|
-
}) })
|
|
28604
|
-
}
|
|
28605
|
-
),
|
|
28606
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px w-full bg-[var(--sys-color-divider-default)]" }),
|
|
28607
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
28608
|
-
"div",
|
|
28609
|
-
{
|
|
28610
|
-
className: "flex items-center justify-center",
|
|
28611
|
-
style: {
|
|
28612
|
-
gap: "var(--cmp-timepicker-selector-time-gap)",
|
|
28613
|
-
padding: "var(--cmp-timepicker-selector-section-paddingY) var(--cmp-timepicker-selector-section-paddingX)"
|
|
28614
|
-
},
|
|
28615
|
-
children: [
|
|
28616
|
-
renderColumn("hour"),
|
|
28617
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28618
|
-
"span",
|
|
28619
|
-
{
|
|
28620
|
-
className: "text-center",
|
|
28621
|
-
style: {
|
|
28622
|
-
width: "var(--cmp-timepicker-selector-colon-size)",
|
|
28623
|
-
color: "var(--cmp-field-hint-color)",
|
|
28624
|
-
fontSize: "var(--cmp-timepicker-selector-colon-size)",
|
|
28625
|
-
lineHeight: "var(--cmp-timepicker-selector-colon-size)"
|
|
28626
|
-
},
|
|
28627
|
-
children: ":"
|
|
28628
|
-
}
|
|
28629
|
-
),
|
|
28630
|
-
renderColumn("minute")
|
|
28631
|
-
]
|
|
28632
|
-
}
|
|
28633
|
-
)
|
|
28857
|
+
showMeridiem && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
28858
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uC624\uC804/\uC624\uD6C4`, items: meridiemItems }),
|
|
28859
|
+
divider
|
|
28860
|
+
] }),
|
|
28861
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uC2DC`, items: hourItems }),
|
|
28862
|
+
divider,
|
|
28863
|
+
/* @__PURE__ */ jsxRuntime.jsx(ListColumn, { ariaLabel: `${prefix}\uBD84`, items: minuteItems })
|
|
28634
28864
|
]
|
|
28635
28865
|
}
|
|
28636
28866
|
);
|
|
@@ -28668,11 +28898,9 @@ function STimePicker({
|
|
|
28668
28898
|
style
|
|
28669
28899
|
}) {
|
|
28670
28900
|
const portalContainer = usePortalContainer();
|
|
28671
|
-
const triggerRef = react.useRef(null);
|
|
28672
28901
|
const [open, setOpen] = react.useState(false);
|
|
28673
28902
|
const triggerId = useFieldControlId();
|
|
28674
28903
|
const triggerTextId = `${triggerId}-text`;
|
|
28675
|
-
const [popoverAlignOffset, setPopoverAlignOffset] = react.useState(0);
|
|
28676
28904
|
const [focused, setFocused] = react.useState(false);
|
|
28677
28905
|
const [ruleError, setRuleError] = react.useState("");
|
|
28678
28906
|
const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
|
|
@@ -28693,11 +28921,6 @@ function STimePicker({
|
|
|
28693
28921
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
28694
28922
|
const commitOpen = (next) => {
|
|
28695
28923
|
if (disabled) return;
|
|
28696
|
-
if (next) {
|
|
28697
|
-
const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
|
|
28698
|
-
const selectorWidth = effectiveShowMeridiem ? MERIDIEM_TIME_SELECTOR_WIDTH : TIME_SELECTOR_WIDTH;
|
|
28699
|
-
setPopoverAlignOffset(Math.max(0, (triggerWidth - selectorWidth) / 2));
|
|
28700
|
-
}
|
|
28701
28924
|
setOpen(next);
|
|
28702
28925
|
onOpenChange?.(next);
|
|
28703
28926
|
if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
|
|
@@ -28736,7 +28959,6 @@ function STimePicker({
|
|
|
28736
28959
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover13__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28737
28960
|
"div",
|
|
28738
28961
|
{
|
|
28739
|
-
ref: triggerRef,
|
|
28740
28962
|
id: triggerId,
|
|
28741
28963
|
role: "button",
|
|
28742
28964
|
"aria-disabled": disabled,
|
|
@@ -28800,20 +29022,22 @@ function STimePicker({
|
|
|
28800
29022
|
RPopover13__namespace.Content,
|
|
28801
29023
|
{
|
|
28802
29024
|
side: "bottom",
|
|
28803
|
-
align: "
|
|
29025
|
+
align: "center",
|
|
28804
29026
|
sideOffset: 4,
|
|
28805
|
-
alignOffset: popoverAlignOffset,
|
|
28806
29027
|
className: cn(
|
|
28807
29028
|
"z-50 max-w-[var(--radix-popover-content-available-width)]",
|
|
28808
29029
|
FLOATING_SLIDE_ANIM
|
|
28809
29030
|
),
|
|
28810
29031
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28811
|
-
|
|
29032
|
+
TimeListbox,
|
|
28812
29033
|
{
|
|
28813
29034
|
value: normalized,
|
|
28814
29035
|
showMeridiem: effectiveShowMeridiem,
|
|
28815
29036
|
minuteStep,
|
|
28816
|
-
onChange:
|
|
29037
|
+
onChange: (time, part) => {
|
|
29038
|
+
commitValue(time);
|
|
29039
|
+
if (part === "minute") commitOpen(false);
|
|
29040
|
+
}
|
|
28817
29041
|
}
|
|
28818
29042
|
)
|
|
28819
29043
|
}
|
|
@@ -28843,7 +29067,6 @@ function formatTimeRangeDisplay(value, type) {
|
|
|
28843
29067
|
if (value == null || value[0] === "" || value[1] === "") return "";
|
|
28844
29068
|
return `${formatTimeDisplay(value[0], type)} ~ ${formatTimeDisplay(value[1], type)}`;
|
|
28845
29069
|
}
|
|
28846
|
-
var TIME_RANGE_SELECTOR_WIDTH = 345;
|
|
28847
29070
|
var MIDDAY_FIELD_MIN_WIDTH = {
|
|
28848
29071
|
sm: 178,
|
|
28849
29072
|
md: 216
|
|
@@ -28852,6 +29075,10 @@ var MIDDAY_FIELD_MAX_WIDTH = {
|
|
|
28852
29075
|
sm: "lg",
|
|
28853
29076
|
md: "lg"
|
|
28854
29077
|
};
|
|
29078
|
+
var toMinutes = (time) => {
|
|
29079
|
+
const { hour, minute } = parseTimeValue(time);
|
|
29080
|
+
return hour * 60 + minute;
|
|
29081
|
+
};
|
|
28855
29082
|
function RangeTimeSelector({
|
|
28856
29083
|
value,
|
|
28857
29084
|
showMeridiem,
|
|
@@ -28859,233 +29086,32 @@ function RangeTimeSelector({
|
|
|
28859
29086
|
rangeOrder,
|
|
28860
29087
|
onChange
|
|
28861
29088
|
}) {
|
|
28862
|
-
const range =
|
|
28863
|
-
const
|
|
28864
|
-
const
|
|
28865
|
-
|
|
28866
|
-
|
|
28867
|
-
const getMeridiem = (side) => getParts(side).hour >= 12 ? "PM" : "AM";
|
|
28868
|
-
const getDisplayHour = (side) => {
|
|
28869
|
-
const { hour } = getParts(side);
|
|
28870
|
-
if (!showMeridiem) return hour;
|
|
28871
|
-
const displayHour = hour % 12;
|
|
28872
|
-
return displayHour === 0 ? 12 : displayHour;
|
|
28873
|
-
};
|
|
28874
|
-
const commit = (side, hour, minute, direction = "unknown") => {
|
|
28875
|
-
const next = formatTimeValue(hour, minute);
|
|
28876
|
-
onChange(
|
|
28877
|
-
normalizeNextRange(side === "start" ? [next, range[1]] : [range[0], next], side, direction)
|
|
28878
|
-
);
|
|
28879
|
-
};
|
|
28880
|
-
const getTimeMinutes2 = (time) => {
|
|
28881
|
-
const { hour, minute } = parseTimeValue(time);
|
|
28882
|
-
return hour * 60 + minute;
|
|
28883
|
-
};
|
|
28884
|
-
const normalizeNextRange = (nextRange, changedSide, direction) => {
|
|
28885
|
-
if (rangeOrder === "allow-cross-day") return nextRange;
|
|
28886
|
-
const [start, end] = nextRange;
|
|
28887
|
-
const [currentStart, currentEnd] = range;
|
|
28888
|
-
if (getTimeMinutes2(start) <= getTimeMinutes2(end)) {
|
|
28889
|
-
const shouldKeepSynced = autoAdjustedRef.current?.side === changedSide && currentStart === currentEnd && start !== end && (direction === "unknown" || autoAdjustedRef.current.direction === direction);
|
|
28890
|
-
if (shouldKeepSynced) {
|
|
28891
|
-
return changedSide === "start" ? [start, start] : [end, end];
|
|
28892
|
-
}
|
|
28893
|
-
autoAdjustedRef.current = null;
|
|
28894
|
-
return nextRange;
|
|
28895
|
-
}
|
|
28896
|
-
autoAdjustedRef.current = {
|
|
28897
|
-
side: changedSide,
|
|
28898
|
-
direction: changedSide === "start" ? "increase" : "decrease"
|
|
28899
|
-
};
|
|
28900
|
-
return changedSide === "start" ? [start, start] : [end, end];
|
|
28901
|
-
};
|
|
28902
|
-
const changeHour = (side, delta) => {
|
|
28903
|
-
const { hour, minute } = getParts(side);
|
|
28904
|
-
commit(side, (hour + delta + 24) % 24, minute, delta > 0 ? "increase" : "decrease");
|
|
28905
|
-
};
|
|
28906
|
-
const changeMinute = (side, delta) => {
|
|
28907
|
-
const { hour, minute } = getParts(side);
|
|
28908
|
-
const next = minute + delta;
|
|
28909
|
-
if (next > 59) {
|
|
28910
|
-
commit(side, (hour + 1) % 24, next - 60, delta > 0 ? "increase" : "decrease");
|
|
28911
|
-
return;
|
|
28912
|
-
}
|
|
28913
|
-
if (next < 0) {
|
|
28914
|
-
commit(side, (hour + 23) % 24, next + 60, delta > 0 ? "increase" : "decrease");
|
|
29089
|
+
const range = value ?? ["00:00", "23:59"];
|
|
29090
|
+
const strict = rangeOrder !== "allow-cross-day";
|
|
29091
|
+
const change = (side, time) => {
|
|
29092
|
+
if (side === "end") {
|
|
29093
|
+
onChange([range[0], time]);
|
|
28915
29094
|
return;
|
|
28916
29095
|
}
|
|
28917
|
-
|
|
28918
|
-
};
|
|
28919
|
-
const setMeridiem = (side, next) => {
|
|
28920
|
-
const current = getMeridiem(side);
|
|
28921
|
-
if (current === next) return;
|
|
28922
|
-
const { hour, minute } = getParts(side);
|
|
28923
|
-
commit(side, next === "AM" ? hour - 12 : hour + 12, minute);
|
|
28924
|
-
};
|
|
28925
|
-
const getEditingKey = (side, part) => `${side}-${part}`;
|
|
28926
|
-
const sanitizeInput = (next) => next.replace(/\D/g, "").slice(0, 2);
|
|
28927
|
-
const beginEdit = (side, part, currentValue) => {
|
|
28928
|
-
const key = getEditingKey(side, part);
|
|
28929
|
-
setEditingKey(key);
|
|
28930
|
-
setInputDraft((prev) => ({ ...prev, [key]: String(currentValue).padStart(2, "0") }));
|
|
28931
|
-
};
|
|
28932
|
-
const commitHourDraft = (side, draft) => {
|
|
28933
|
-
const clamped = clampTimePart(Number(draft), showMeridiem ? 1 : 0, showMeridiem ? 12 : 23);
|
|
28934
|
-
const parts = getParts(side);
|
|
28935
|
-
const meridiem = getMeridiem(side);
|
|
28936
|
-
const nextHour = showMeridiem ? meridiem === "PM" ? clamped === 12 ? 12 : clamped + 12 : clamped === 12 ? 0 : clamped : clamped;
|
|
28937
|
-
commit(side, nextHour, parts.minute);
|
|
28938
|
-
return clamped;
|
|
28939
|
-
};
|
|
28940
|
-
const commitMinuteDraft = (side, draft) => {
|
|
28941
|
-
const { hour } = getParts(side);
|
|
28942
|
-
const clamped = clampTimePart(Number(draft), 0, 59);
|
|
28943
|
-
commit(side, hour, clamped);
|
|
28944
|
-
return clamped;
|
|
28945
|
-
};
|
|
28946
|
-
const endEdit = (side, part) => {
|
|
28947
|
-
const key = getEditingKey(side, part);
|
|
28948
|
-
const draft = inputDraft[key];
|
|
28949
|
-
if (part === "hour" && draft != null && draft !== "") {
|
|
28950
|
-
commitHourDraft(side, draft);
|
|
28951
|
-
}
|
|
28952
|
-
if (part === "minute" && draft != null && draft !== "") {
|
|
28953
|
-
commitMinuteDraft(side, draft);
|
|
28954
|
-
}
|
|
28955
|
-
setEditingKey(null);
|
|
28956
|
-
setInputDraft({});
|
|
28957
|
-
};
|
|
28958
|
-
const inputStyle = {
|
|
28959
|
-
width: "var(--cmp-timepicker-selector-time-input-width)",
|
|
28960
|
-
height: "var(--cmp-textinput-sm-height)",
|
|
28961
|
-
borderRadius: "var(--cmp-textinput-sm-radius)",
|
|
28962
|
-
border: "var(--cmp-textinput-borderWidth) solid var(--cmp-textinput-border-default)",
|
|
28963
|
-
color: "var(--cmp-textinput-text-default)",
|
|
28964
|
-
fontSize: 12,
|
|
28965
|
-
lineHeight: "20px"
|
|
28966
|
-
};
|
|
28967
|
-
const renderColumn = (side, kind) => {
|
|
28968
|
-
const isHour = kind === "hour";
|
|
28969
|
-
const { minute } = getParts(side);
|
|
28970
|
-
const val = isHour ? getDisplayHour(side) : minute;
|
|
28971
|
-
const currentEditingKey = getEditingKey(side, kind);
|
|
28972
|
-
const displayValue2 = editingKey === currentEditingKey ? inputDraft[currentEditingKey] ?? "" : String(val).padStart(2, "0");
|
|
28973
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28974
|
-
"div",
|
|
28975
|
-
{
|
|
28976
|
-
className: "flex flex-col items-center justify-center",
|
|
28977
|
-
style: { gap: "var(--cmp-timepicker-selector-time-column-gap)" },
|
|
28978
|
-
children: [
|
|
28979
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28980
|
-
SGhostButton,
|
|
28981
|
-
{
|
|
28982
|
-
icon: "chevronUp",
|
|
28983
|
-
size: "xs",
|
|
28984
|
-
ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uC99D\uAC00`,
|
|
28985
|
-
onClick: () => isHour ? changeHour(side, 1) : changeMinute(side, minuteStep)
|
|
28986
|
-
}
|
|
28987
|
-
),
|
|
28988
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28989
|
-
"input",
|
|
28990
|
-
{
|
|
28991
|
-
"aria-label": `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"}`,
|
|
28992
|
-
inputMode: "numeric",
|
|
28993
|
-
value: displayValue2,
|
|
28994
|
-
placeholder: "-",
|
|
28995
|
-
onFocus: () => beginEdit(side, kind, val),
|
|
28996
|
-
onBlur: () => endEdit(side, kind),
|
|
28997
|
-
onChange: (event) => {
|
|
28998
|
-
const draft = sanitizeInput(event.target.value);
|
|
28999
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: draft }));
|
|
29000
|
-
if (draft === "") return;
|
|
29001
|
-
const raw = Number(draft);
|
|
29002
|
-
if (isHour) {
|
|
29003
|
-
if (draft.length < 2) return;
|
|
29004
|
-
const clamped2 = commitHourDraft(side, draft);
|
|
29005
|
-
const displayDraft2 = raw === clamped2 ? draft : String(clamped2);
|
|
29006
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft2 }));
|
|
29007
|
-
return;
|
|
29008
|
-
}
|
|
29009
|
-
if (draft.length < 2) return;
|
|
29010
|
-
const clamped = commitMinuteDraft(side, draft);
|
|
29011
|
-
const displayDraft = raw === clamped ? draft : String(clamped);
|
|
29012
|
-
setInputDraft((prev) => ({ ...prev, [currentEditingKey]: displayDraft }));
|
|
29013
|
-
},
|
|
29014
|
-
className: "box-border bg-white text-center outline-none",
|
|
29015
|
-
style: inputStyle
|
|
29016
|
-
}
|
|
29017
|
-
),
|
|
29018
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29019
|
-
SGhostButton,
|
|
29020
|
-
{
|
|
29021
|
-
icon: "chevronDown",
|
|
29022
|
-
size: "xs",
|
|
29023
|
-
ariaLabel: `${side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC"} ${isHour ? "\uC2DC" : "\uBD84"} \uAC10\uC18C`,
|
|
29024
|
-
onClick: () => isHour ? changeHour(side, -1) : changeMinute(side, -minuteStep)
|
|
29025
|
-
}
|
|
29026
|
-
)
|
|
29027
|
-
]
|
|
29028
|
-
}
|
|
29029
|
-
);
|
|
29096
|
+
onChange([time, strict && toMinutes(time) > toMinutes(range[1]) ? time : range[1]]);
|
|
29030
29097
|
};
|
|
29031
29098
|
const renderPanel = (side, title) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29032
29099
|
"div",
|
|
29033
29100
|
{
|
|
29034
|
-
className: "flex flex-col items-center
|
|
29035
|
-
style: { gap: "var(--cmp-timepicker-selector-range-
|
|
29101
|
+
className: "flex flex-col items-center",
|
|
29102
|
+
style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
|
|
29036
29103
|
children: [
|
|
29037
|
-
/* @__PURE__ */ jsxRuntime.
|
|
29038
|
-
|
|
29039
|
-
|
|
29040
|
-
className: "flex flex-col items-center",
|
|
29041
|
-
style: { gap: "var(--cmp-timepicker-selector-range-heading-gap)" },
|
|
29042
|
-
children: [
|
|
29043
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
|
|
29044
|
-
showMeridiem && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "isolate flex", children: ["AM", "PM"].map((item4, index) => {
|
|
29045
|
-
const selected = item4 === getMeridiem(side);
|
|
29046
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
29047
|
-
"button",
|
|
29048
|
-
{
|
|
29049
|
-
type: "button",
|
|
29050
|
-
onClick: () => setMeridiem(side, item4),
|
|
29051
|
-
className: cn(
|
|
29052
|
-
"box-border h-[var(--cmp-textinput-sm-height)] w-[61px] cursor-pointer border border-solid bg-white text-center text-[12px] font-medium leading-[20px]",
|
|
29053
|
-
index === 0 ? "rounded-l-[var(--cmp-select-sm-radius)] -mr-px" : "rounded-r-[var(--cmp-select-sm-radius)]",
|
|
29054
|
-
selected ? "z-[1] border-[var(--cmp-timepicker-border-focus)] text-[var(--cmp-timepicker-border-focus)]" : "border-[var(--cmp-timepicker-border-default)] text-[var(--cmp-field-hint-color)]"
|
|
29055
|
-
),
|
|
29056
|
-
children: item4 === "AM" ? "\uC624\uC804" : "\uC624\uD6C4"
|
|
29057
|
-
},
|
|
29058
|
-
item4
|
|
29059
|
-
);
|
|
29060
|
-
}) })
|
|
29061
|
-
]
|
|
29062
|
-
}
|
|
29063
|
-
),
|
|
29064
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29065
|
-
"div",
|
|
29104
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "whitespace-nowrap text-center text-[12px] font-bold leading-[20px] text-[var(--cmp-timepicker-selector-range-heading-color)]", children: title }),
|
|
29105
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29106
|
+
TimeListbox,
|
|
29066
29107
|
{
|
|
29067
|
-
|
|
29068
|
-
|
|
29069
|
-
|
|
29070
|
-
|
|
29071
|
-
|
|
29072
|
-
|
|
29073
|
-
|
|
29074
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
29075
|
-
"span",
|
|
29076
|
-
{
|
|
29077
|
-
className: "text-center",
|
|
29078
|
-
style: {
|
|
29079
|
-
width: "var(--cmp-timepicker-selector-colon-size)",
|
|
29080
|
-
color: "var(--cmp-field-hint-color)",
|
|
29081
|
-
fontSize: "var(--cmp-timepicker-selector-colon-size)",
|
|
29082
|
-
lineHeight: "var(--cmp-timepicker-selector-colon-size)"
|
|
29083
|
-
},
|
|
29084
|
-
children: ":"
|
|
29085
|
-
}
|
|
29086
|
-
),
|
|
29087
|
-
renderColumn(side, "minute")
|
|
29088
|
-
]
|
|
29108
|
+
value: side === "start" ? range[0] : range[1],
|
|
29109
|
+
showMeridiem,
|
|
29110
|
+
minuteStep,
|
|
29111
|
+
labelPrefix: side === "start" ? "\uC2DC\uC791" : "\uC885\uB8CC",
|
|
29112
|
+
elevated: false,
|
|
29113
|
+
isDisabled: strict && side === "end" ? (minutes) => minutes < toMinutes(range[0]) : void 0,
|
|
29114
|
+
onChange: (time) => change(side, time)
|
|
29089
29115
|
}
|
|
29090
29116
|
)
|
|
29091
29117
|
]
|
|
@@ -29144,11 +29170,9 @@ function STimeRangePicker({
|
|
|
29144
29170
|
style
|
|
29145
29171
|
}) {
|
|
29146
29172
|
const portalContainer = usePortalContainer();
|
|
29147
|
-
const triggerRef = react.useRef(null);
|
|
29148
29173
|
const [open, setOpen] = react.useState(false);
|
|
29149
29174
|
const triggerId = useFieldControlId();
|
|
29150
29175
|
const triggerTextId = `${triggerId}-text`;
|
|
29151
|
-
const [popoverAlignOffset, setPopoverAlignOffset] = react.useState(0);
|
|
29152
29176
|
const [focused, setFocused] = react.useState(false);
|
|
29153
29177
|
const [ruleError, setRuleError] = react.useState("");
|
|
29154
29178
|
const dims = TIMEPICKER_SIZE_CONFIG[size] ?? TIMEPICKER_SIZE_CONFIG.sm;
|
|
@@ -29172,10 +29196,6 @@ function STimeRangePicker({
|
|
|
29172
29196
|
const resolvedStatus = status ?? (rules && rules.length > 0 ? ruleError !== "" ? "error" : void 0 : void 0);
|
|
29173
29197
|
const commitOpen = (next) => {
|
|
29174
29198
|
if (disabled) return;
|
|
29175
|
-
if (next) {
|
|
29176
|
-
const triggerWidth = triggerRef.current?.getBoundingClientRect().width ?? 0;
|
|
29177
|
-
setPopoverAlignOffset(Math.max(0, (triggerWidth - TIME_RANGE_SELECTOR_WIDTH) / 2));
|
|
29178
|
-
}
|
|
29179
29199
|
setOpen(next);
|
|
29180
29200
|
onOpenChange?.(next);
|
|
29181
29201
|
if (!next && rules && rules.length > 0) setRuleError(runRules(value, rules));
|
|
@@ -29214,7 +29234,6 @@ function STimeRangePicker({
|
|
|
29214
29234
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover13__namespace.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
29215
29235
|
"div",
|
|
29216
29236
|
{
|
|
29217
|
-
ref: triggerRef,
|
|
29218
29237
|
id: triggerId,
|
|
29219
29238
|
role: "button",
|
|
29220
29239
|
"aria-disabled": disabled,
|
|
@@ -29278,9 +29297,8 @@ function STimeRangePicker({
|
|
|
29278
29297
|
RPopover13__namespace.Content,
|
|
29279
29298
|
{
|
|
29280
29299
|
side: "bottom",
|
|
29281
|
-
align: "
|
|
29300
|
+
align: "center",
|
|
29282
29301
|
sideOffset: 4,
|
|
29283
|
-
alignOffset: popoverAlignOffset,
|
|
29284
29302
|
className: cn(
|
|
29285
29303
|
"z-50 max-w-[var(--radix-popover-content-available-width)]",
|
|
29286
29304
|
FLOATING_SLIDE_ANIM
|