@endfield-ui/react 0.3.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +2 -2
- package/README.md +103 -8
- package/dist/index.d.ts +69 -15
- package/dist/index.js +1102 -237
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/README.md +11 -2
- package/src/components/alert/Alert.tsx +63 -1
- package/src/components/avatar/Avatar.tsx +5 -2
- package/src/components/bracket-title/BracketTitle.tsx +32 -3
- package/src/components/button/Button.tsx +37 -5
- package/src/components/calendar/Calendar.tsx +49 -5
- package/src/components/checkbox/Checkbox.tsx +1 -0
- package/src/components/checkbox/choice-style.ts +9 -4
- package/src/components/chip/capsule-style.ts +18 -3
- package/src/components/combobox/Combobox.tsx +10 -0
- package/src/components/completion-banner/CompletionBanner.tsx +30 -2
- package/src/components/dash-indicator/DashIndicator.tsx +5 -2
- package/src/components/data-row/DataRow.tsx +2 -1
- package/src/components/date-picker/DateField.tsx +5 -1
- package/src/components/dropdown-menu/MenuCheck.tsx +4 -2
- package/src/components/dropdown-menu/menu-style.ts +13 -3
- package/src/components/empty-state/EmptyState.tsx +5 -0
- package/src/components/field/Field.tsx +39 -7
- package/src/components/file-upload/FileUpload.tsx +78 -19
- package/src/components/icon-button/IconButton.tsx +18 -5
- package/src/components/input/control-box.ts +23 -2
- package/src/components/input/value-chip.ts +3 -2
- package/src/components/loader/Loader.tsx +12 -41
- package/src/components/nav-menu/NavMenu.tsx +3 -2
- package/src/components/otp-input/OtpInput.tsx +8 -2
- package/src/components/page-header/PageHeader.tsx +39 -3
- package/src/components/pagination/Pagination.tsx +2 -0
- package/src/components/progress/Progress.tsx +10 -3
- package/src/components/progress/ProgressRing.tsx +19 -4
- package/src/components/radio/Radio.tsx +2 -0
- package/src/components/rolling-number/RollingNumber.tsx +125 -0
- package/src/components/rolling-number/index.ts +1 -0
- package/src/components/schedule/Schedule.tsx +5 -3
- package/src/components/segmented-control/SegmentedControl.tsx +25 -4
- package/src/components/skeleton/Skeleton.tsx +4 -1
- package/src/components/slider/Slider.tsx +21 -4
- package/src/components/sparkline/Sparkline.tsx +29 -5
- package/src/components/spinner/Spinner.tsx +6 -2
- package/src/components/stepper/Stepper.tsx +4 -0
- package/src/components/steps/Steps.tsx +18 -6
- package/src/components/switch/Switch.tsx +26 -2
- package/src/components/table/Table.tsx +33 -9
- package/src/components/tabs/Tabs.tsx +55 -9
- package/src/components/tag-input/TagInput.tsx +82 -43
- package/src/components/textarea/Textarea.tsx +2 -1
- package/src/components/timeline/Timeline.tsx +4 -3
- package/src/components/toc/Toc.tsx +89 -7
- package/src/components/toolbar/Toolbar.tsx +24 -0
- package/src/hooks/README.md +8 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useAddedKeys.ts +26 -0
- package/src/hooks/useFlip.ts +211 -0
- package/src/hooks/useIndicator.ts +135 -0
- package/src/hooks/useLingering.ts +122 -0
- package/src/hooks/usePresence.ts +100 -0
- package/src/hooks/useReducedMotion.ts +26 -0
- package/src/hooks/useTheme.ts +44 -4
- package/src/index.ts +1 -0
- package/src/lib/README.md +2 -1
- package/src/lib/cn.ts +1 -0
- package/src/lib/inline-style.ts +12 -0
- package/src/lib/motion.ts +75 -0
- package/src/styles/theme.css +38 -2
package/dist/index.js
CHANGED
|
@@ -78,7 +78,8 @@ const twMerge = extendTailwindMerge({ extend: { theme: {
|
|
|
78
78
|
"fade-in",
|
|
79
79
|
"indeterminate",
|
|
80
80
|
"bracket-in",
|
|
81
|
-
"shift-in"
|
|
81
|
+
"shift-in",
|
|
82
|
+
"wipe-in"
|
|
82
83
|
]
|
|
83
84
|
} } });
|
|
84
85
|
function cn(...inputs) {
|
|
@@ -170,6 +171,74 @@ function AccordionItem({ value, title, extra, disabled, className, children, ...
|
|
|
170
171
|
});
|
|
171
172
|
}
|
|
172
173
|
//#endregion
|
|
174
|
+
//#region src/hooks/usePresence.ts
|
|
175
|
+
/** 循环的动画(加载指示那一类)等不到头,不等 */
|
|
176
|
+
function ends(animation) {
|
|
177
|
+
return animation.effect?.getComputedTiming().iterations !== Infinity;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* 等这个元素上**正在跑的**、有尽头的过渡和动画走完,再调 `done`;一个都没有
|
|
181
|
+
* (没有写退场的样式、测试环境里没有动效)就当场调。返回的函数取消这一次等待。
|
|
182
|
+
*/
|
|
183
|
+
function afterMotion(node, done) {
|
|
184
|
+
let live = true;
|
|
185
|
+
const finish = () => {
|
|
186
|
+
if (live) done();
|
|
187
|
+
};
|
|
188
|
+
const running = typeof node?.getAnimations === "function" ? node.getAnimations().filter(ends) : [];
|
|
189
|
+
if (running.length === 0) finish();
|
|
190
|
+
else Promise.allSettled(running.map((animation) => animation.finished)).then(finish);
|
|
191
|
+
return () => {
|
|
192
|
+
live = false;
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* 退场后再卸载:`open` 变成假之后元素先留着,等它身上的动效走完才卸载。
|
|
197
|
+
*
|
|
198
|
+
* 等的是这个元素上**正在跑的**过渡和动画,不另抄一份时长;一个都没有
|
|
199
|
+
* (没有写退场的样式、测试环境里没有动效)就当场卸载。退场途中又打开:不卸载,
|
|
200
|
+
* 过渡自己折回去。
|
|
201
|
+
*/
|
|
202
|
+
function usePresence(open, { onLeave, onExited } = {}) {
|
|
203
|
+
const ref = useRef(null);
|
|
204
|
+
const [mounted, setMounted] = useState(open);
|
|
205
|
+
const [entered, setEntered] = useState(false);
|
|
206
|
+
if (open && !mounted) {
|
|
207
|
+
setMounted(true);
|
|
208
|
+
setEntered(true);
|
|
209
|
+
}
|
|
210
|
+
const leaving = !open && mounted;
|
|
211
|
+
const handlers = useRef({
|
|
212
|
+
onLeave,
|
|
213
|
+
onExited
|
|
214
|
+
});
|
|
215
|
+
useLayoutEffect(() => {
|
|
216
|
+
handlers.current = {
|
|
217
|
+
onLeave,
|
|
218
|
+
onExited
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
useLayoutEffect(() => {
|
|
222
|
+
if (!leaving) return;
|
|
223
|
+
const node = ref.current;
|
|
224
|
+
const undo = node ? handlers.current.onLeave?.(node) : void 0;
|
|
225
|
+
const cancel = afterMotion(node, () => {
|
|
226
|
+
setMounted(false);
|
|
227
|
+
handlers.current.onExited?.();
|
|
228
|
+
});
|
|
229
|
+
return () => {
|
|
230
|
+
cancel();
|
|
231
|
+
undo?.();
|
|
232
|
+
};
|
|
233
|
+
}, [leaving]);
|
|
234
|
+
return {
|
|
235
|
+
ref,
|
|
236
|
+
mounted,
|
|
237
|
+
leaving,
|
|
238
|
+
entered
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
173
242
|
//#region src/icons/Close.tsx
|
|
174
243
|
const Close = createIcon("Close", /* @__PURE__ */ jsx("path", { d: "M5 5l14 14M19 5L5 19" }));
|
|
175
244
|
//#endregion
|
|
@@ -193,6 +262,15 @@ const StatusSuccess = createIcon("StatusSuccess", /* @__PURE__ */ jsxs(Fragment$
|
|
|
193
262
|
/** 三角:警告 */
|
|
194
263
|
const StatusWarning = createIcon("StatusWarning", /* @__PURE__ */ jsx("path", { d: "M12 4l9 16H3zM12 10v5M12 16.5v2" }));
|
|
195
264
|
//#endregion
|
|
265
|
+
//#region src/lib/merge-refs.ts
|
|
266
|
+
/** 把几个 ref 合成一个:组件自己要用节点,同时还得把它交给使用方。 */
|
|
267
|
+
function mergeRefs(...refs) {
|
|
268
|
+
return (node) => {
|
|
269
|
+
for (const ref of refs) if (typeof ref === "function") ref(node);
|
|
270
|
+
else if (ref) ref.current = node;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
196
274
|
//#region src/components/alert/Alert.tsx
|
|
197
275
|
const toneIcon$1 = {
|
|
198
276
|
info: StatusInfo,
|
|
@@ -212,15 +290,43 @@ const toneText$1 = {
|
|
|
212
290
|
warning: "text-warning",
|
|
213
291
|
danger: "text-danger"
|
|
214
292
|
};
|
|
293
|
+
function collapseOut(node) {
|
|
294
|
+
const { style } = node;
|
|
295
|
+
const before = style.cssText;
|
|
296
|
+
let gap = 0;
|
|
297
|
+
if (node.parentElement) {
|
|
298
|
+
const layout = getComputedStyle(node.parentElement);
|
|
299
|
+
if (layout.display.includes("grid") || layout.display.includes("flex") && layout.flexDirection.startsWith("column")) gap = Number.parseFloat(layout.rowGap) || 0;
|
|
300
|
+
}
|
|
301
|
+
style.height = `${node.getBoundingClientRect().height}px`;
|
|
302
|
+
node.getBoundingClientRect();
|
|
303
|
+
style.height = "0px";
|
|
304
|
+
style.minHeight = "0px";
|
|
305
|
+
style.paddingBlock = "0px";
|
|
306
|
+
style.borderBlockWidth = "0px";
|
|
307
|
+
style.marginTop = "0px";
|
|
308
|
+
style.marginBottom = `${-gap}px`;
|
|
309
|
+
style.opacity = "0";
|
|
310
|
+
return () => {
|
|
311
|
+
style.cssText = before;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
215
314
|
/** 页面内的一条常驻消息。需要用户处理的重要信息用它,不用会消失的轻提示。 */
|
|
216
|
-
function Alert({ tone = "info", title, action, onClose, closeLabel = "关闭", icon, className, children, ...props }) {
|
|
315
|
+
function Alert({ tone = "info", title, action, onClose, open = true, onExited, closeLabel = "关闭", icon, className, ref, children, ...props }) {
|
|
316
|
+
const { ref: rootRef, mounted, leaving } = usePresence(open, {
|
|
317
|
+
onLeave: collapseOut,
|
|
318
|
+
onExited
|
|
319
|
+
});
|
|
320
|
+
if (!mounted) return null;
|
|
217
321
|
const Icon = toneIcon$1[tone];
|
|
218
322
|
const graphic = icon === void 0 ? /* @__PURE__ */ jsx(Icon, { size: 20 }) : icon;
|
|
219
323
|
return /* @__PURE__ */ jsxs("div", {
|
|
220
324
|
role: tone === "danger" || tone === "warning" ? "alert" : "status",
|
|
221
325
|
...props,
|
|
326
|
+
ref: mergeRefs(rootRef, ref),
|
|
222
327
|
"data-tone": tone,
|
|
223
|
-
|
|
328
|
+
inert: leaving,
|
|
329
|
+
className: cn("flex items-start gap-3 border border-l-4 border-line bg-surface-raised py-3 pr-3 pl-3 text-sm text-ink", toneBar[tone], leaving && ["pointer-events-none overflow-hidden", "transition-[height,padding,margin,border-width,opacity] duration-(--duration-fast) ease-standard"], className),
|
|
224
330
|
children: [
|
|
225
331
|
graphic !== null && /* @__PURE__ */ jsx("span", {
|
|
226
332
|
"aria-hidden": "true",
|
|
@@ -278,7 +384,7 @@ function Avatar({ name, src, alt = name ?? "", size = "md", selected = false, cl
|
|
|
278
384
|
},
|
|
279
385
|
"data-size": size,
|
|
280
386
|
"data-selected": selected ? "" : void 0,
|
|
281
|
-
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-clip rounded-full bg-surface-muted font-medium text-ink-secondary select-none", sizeClass$13[size], selected && "shadow-[0_0_0_2px_var(--ef-surface),0_0_0_5px_var(--ef-action),0_0_0_6px_var(--ef-ink)]", className),
|
|
387
|
+
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-clip rounded-full bg-surface-muted font-medium text-ink-secondary select-none", sizeClass$13[size], selected && "shadow-[0_0_0_2px_var(--ef-surface),0_0_0_5px_var(--ef-action),0_0_0_6px_var(--ef-ink)] forced-colors:border-[3px] forced-colors:border-[Highlight]", className),
|
|
282
388
|
children: [src && /* @__PURE__ */ jsx(Avatar$1.Image, {
|
|
283
389
|
src,
|
|
284
390
|
alt: "",
|
|
@@ -319,7 +425,7 @@ const ChevronRight = createIcon("ChevronRight", /* @__PURE__ */ jsx("path", { d:
|
|
|
319
425
|
//#endregion
|
|
320
426
|
//#region src/components/icon-button/IconButton.tsx
|
|
321
427
|
const sizeClass$12 = {
|
|
322
|
-
sm: "size-8 after:absolute after:-inset-1 after:content-[''] [&_svg]:size-4",
|
|
428
|
+
sm: "size-8 after:absolute after:-inset-1 after:content-[''] forced-colors:after:-inset-[5px] [&_svg]:size-4",
|
|
323
429
|
md: "size-10 [&_svg]:size-5",
|
|
324
430
|
lg: "size-12 [&_svg]:size-6"
|
|
325
431
|
};
|
|
@@ -335,6 +441,10 @@ const pressedClass = {
|
|
|
335
441
|
accent: "bg-action text-on-action active:bg-action-pressed",
|
|
336
442
|
inverse: "bg-surface-inverse text-accent-ink-inverse"
|
|
337
443
|
};
|
|
444
|
+
const outlineForced = "forced-colors:border";
|
|
445
|
+
const enabledForced = "forced-colors:border-[ButtonText]";
|
|
446
|
+
const pressedForced = "forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]";
|
|
447
|
+
const disabledForced = "forced-colors:border-[GrayText]";
|
|
338
448
|
const disabledClass = {
|
|
339
449
|
plain: "cursor-not-allowed bg-surface-sunken text-ink-disabled",
|
|
340
450
|
floating: "cursor-not-allowed rounded-full bg-neutral-50 text-neutral-400 shadow-sm",
|
|
@@ -349,7 +459,7 @@ function IconButton({ variant = "plain", size = "md", pressed, disabled = false,
|
|
|
349
459
|
"aria-pressed": pressed,
|
|
350
460
|
"data-variant": variant,
|
|
351
461
|
"data-size": size,
|
|
352
|
-
className: cn("relative inline-flex shrink-0 items-center justify-center", "transition-colors duration-(--duration-fast) ease-standard", focusRing, sizeClass$12[size], disabled ? disabledClass[variant] : pressed ? pressedClass[variant] : enabledClass$1[variant], className),
|
|
462
|
+
className: cn("relative inline-flex shrink-0 items-center justify-center", "transition-colors duration-(--duration-fast) ease-standard", focusRing, sizeClass$12[size], outlineForced, disabled ? [disabledClass[variant], disabledForced] : pressed ? [pressedClass[variant], pressedForced] : [enabledClass$1[variant], enabledForced], className),
|
|
353
463
|
children
|
|
354
464
|
});
|
|
355
465
|
}
|
|
@@ -608,27 +718,69 @@ function Badge({ count, max = 99, showZero = false, dot = false, label, classNam
|
|
|
608
718
|
});
|
|
609
719
|
}
|
|
610
720
|
//#endregion
|
|
721
|
+
//#region src/hooks/useInView.ts
|
|
722
|
+
/**
|
|
723
|
+
* 元素第一次进入视口后返回 true,之后不再变回 false——入场动画只播一次。
|
|
724
|
+
*/
|
|
725
|
+
function useInView({ disabled = false, rootMargin = "0px", threshold = 0 } = {}) {
|
|
726
|
+
const ref = useRef(null);
|
|
727
|
+
const [inView, setInView] = useState(false);
|
|
728
|
+
useEffect(() => {
|
|
729
|
+
const node = ref.current;
|
|
730
|
+
if (disabled || inView || !node) return;
|
|
731
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
732
|
+
setInView(true);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const observer = new IntersectionObserver((entries) => {
|
|
736
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
737
|
+
setInView(true);
|
|
738
|
+
observer.disconnect();
|
|
739
|
+
}
|
|
740
|
+
}, {
|
|
741
|
+
rootMargin,
|
|
742
|
+
threshold
|
|
743
|
+
});
|
|
744
|
+
observer.observe(node);
|
|
745
|
+
return () => observer.disconnect();
|
|
746
|
+
}, [
|
|
747
|
+
disabled,
|
|
748
|
+
inView,
|
|
749
|
+
rootMargin,
|
|
750
|
+
threshold
|
|
751
|
+
]);
|
|
752
|
+
return [ref, inView];
|
|
753
|
+
}
|
|
754
|
+
//#endregion
|
|
611
755
|
//#region src/components/bracket-title/BracketTitle.tsx
|
|
612
756
|
const bracket = "font-medium text-ink-tertiary";
|
|
613
757
|
/**
|
|
614
758
|
* 方括号标题 `[ 名称 ]`:人物、地点、物品的名称——"一个被归档的条目"。
|
|
615
759
|
* 普通的分节标题用 `SectionTitle`。字号用 `className` 改,默认是名称用的 `text-4xl`。
|
|
616
760
|
*/
|
|
617
|
-
function BracketTitle({ level = 3, className, children, ...props }) {
|
|
761
|
+
function BracketTitle({ level = 3, animate = true, className, children, ref, ...props }) {
|
|
618
762
|
const Heading = `h${level}`;
|
|
763
|
+
const [inViewRef, inView] = useInView({ disabled: !animate });
|
|
764
|
+
const pending = animate && !inView;
|
|
765
|
+
const playing = animate && inView;
|
|
766
|
+
const bracketEnter = cn(pending && "opacity-0", playing && "animate-fade-in");
|
|
619
767
|
return /* @__PURE__ */ jsxs(Heading, {
|
|
620
768
|
...props,
|
|
769
|
+
ref: mergeRefs(inViewRef, ref),
|
|
621
770
|
className: cn("text-4xl font-bold wrap-anywhere text-ink", className),
|
|
622
771
|
children: [
|
|
623
772
|
/* @__PURE__ */ jsx("span", {
|
|
624
773
|
"aria-hidden": "true",
|
|
625
|
-
className: cn(bracket, "mr-[0.4em]"),
|
|
774
|
+
className: cn(bracket, "mr-[0.4em]", bracketEnter),
|
|
626
775
|
children: "["
|
|
627
776
|
}),
|
|
628
|
-
|
|
777
|
+
/* @__PURE__ */ jsx("span", {
|
|
778
|
+
className: cn(pending && "opacity-0", playing && "animate-fade-in [animation-delay:100ms]") || void 0,
|
|
779
|
+
children
|
|
780
|
+
}),
|
|
629
781
|
/* @__PURE__ */ jsx("span", {
|
|
630
782
|
"aria-hidden": "true",
|
|
631
|
-
className: cn(bracket, "ml-[0.4em]"),
|
|
783
|
+
className: cn(bracket, "ml-[0.4em]", bracketEnter),
|
|
632
784
|
children: "]"
|
|
633
785
|
})
|
|
634
786
|
]
|
|
@@ -747,7 +899,7 @@ const sizeClass$10 = {
|
|
|
747
899
|
sm: "size-3",
|
|
748
900
|
md: "size-4"
|
|
749
901
|
};
|
|
750
|
-
const square = "animate-spin bg-current motion-reduce:rotate-45 motion-reduce:animate-none";
|
|
902
|
+
const square = ["animate-spin bg-current motion-reduce:rotate-45 motion-reduce:animate-none", "forced-colors:bg-[CanvasText]"].join(" ");
|
|
751
903
|
/**
|
|
752
904
|
* 行内加载指示:一个匀速旋转的小方块,颜色继承文字。
|
|
753
905
|
* 圆环形的用 `<ProgressRing size={16} />`;整块内容加载用 `Skeleton`。
|
|
@@ -837,13 +989,17 @@ const enabledClass = {
|
|
|
837
989
|
back: "hatch hatch-mid bg-control text-white shadow-xs hover:bg-neutral-600 active:bg-control-pressed",
|
|
838
990
|
text: "text-ink active:text-ink-secondary"
|
|
839
991
|
};
|
|
992
|
+
const forcedOutline = "forced-colors:border";
|
|
993
|
+
const forcedEnabled = "forced-colors:border-[ButtonText]";
|
|
994
|
+
const forcedDisabled = "forced-colors:border-[GrayText] forced-colors:text-[GrayText]";
|
|
840
995
|
function Button(props) {
|
|
841
996
|
const { variant = "control", size = "md", iconStart, iconEnd, loading = false, loadingText, block = false, disabled = false, className, children, ...rest } = props;
|
|
842
997
|
const inert = disabled || loading;
|
|
843
998
|
const isText = variant === "text";
|
|
844
999
|
const marker = markerColor[variant];
|
|
845
1000
|
const iconSize = inlineIconSize[size];
|
|
846
|
-
const
|
|
1001
|
+
const forcedMark = disabled ? "forced-colors:bg-[GrayText]" : "forced-colors:bg-[ButtonText]";
|
|
1002
|
+
const classes = cn("group relative inline-flex shrink-0 items-center justify-center font-medium leading-none whitespace-nowrap select-none", "transition-colors duration-(--duration-fast) ease-standard", focusRing, sizeClass$9[size], marker && markerPadding[size], variant === "action" && actionPadding[size], variant === "back" && backPadding[size], isText && "h-auto border-b border-current py-1", disabled ? isText ? "cursor-not-allowed text-ink-disabled" : "cursor-not-allowed bg-disabled text-on-disabled" : enabledClass[variant], !isText && forcedOutline, disabled ? forcedDisabled : !isText && forcedEnabled, loading && !disabled && "forced-colors:text-[ButtonText]", loading && "pointer-events-none", block && "flex w-full", className);
|
|
847
1003
|
let start = iconStart;
|
|
848
1004
|
let end = iconEnd;
|
|
849
1005
|
if (variant === "back" && start === void 0) start = loading ? /* @__PURE__ */ jsx("span", {
|
|
@@ -854,7 +1010,7 @@ function Button(props) {
|
|
|
854
1010
|
},
|
|
855
1011
|
children: /* @__PURE__ */ jsx(Spinner, {
|
|
856
1012
|
label: null,
|
|
857
|
-
className: "size-2"
|
|
1013
|
+
className: cn("size-2", forcedMark)
|
|
858
1014
|
})
|
|
859
1015
|
}) : /* @__PURE__ */ jsx(ChevronLeft, { size: iconSize });
|
|
860
1016
|
if (isText && end === void 0) end = loading ? /* @__PURE__ */ jsx("span", {
|
|
@@ -865,7 +1021,7 @@ function Button(props) {
|
|
|
865
1021
|
},
|
|
866
1022
|
children: /* @__PURE__ */ jsx(Spinner, {
|
|
867
1023
|
label: null,
|
|
868
|
-
className: "size-2"
|
|
1024
|
+
className: cn("size-2", forcedMark)
|
|
869
1025
|
})
|
|
870
1026
|
}) : /* @__PURE__ */ jsx(ArrowCorner, {
|
|
871
1027
|
size: iconSize,
|
|
@@ -888,11 +1044,11 @@ function Button(props) {
|
|
|
888
1044
|
className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2", markerSpinner[size]),
|
|
889
1045
|
children: /* @__PURE__ */ jsx(Spinner, {
|
|
890
1046
|
label: null,
|
|
891
|
-
className: cn("block size-full", marker)
|
|
1047
|
+
className: cn("block size-full", marker, forcedMark)
|
|
892
1048
|
})
|
|
893
1049
|
}) : /* @__PURE__ */ jsx("span", {
|
|
894
1050
|
"aria-hidden": "true",
|
|
895
|
-
className: cn("marker-bar pointer-events-none absolute top-1/2 -translate-y-1/2", "transition-[clip-path,translate] duration-(--duration-base) ease-standard", markerBox[size], disabled ? "bg-on-disabled" : [
|
|
1051
|
+
className: cn("marker-bar pointer-events-none absolute top-1/2 -translate-y-1/2", "transition-[clip-path,translate] duration-(--duration-base) ease-standard", markerBox[size], forcedMark, disabled ? "bg-on-disabled" : [
|
|
896
1052
|
marker,
|
|
897
1053
|
"group-hover:marker-arrow group-active:marker-arrow",
|
|
898
1054
|
markerShift[size]
|
|
@@ -903,12 +1059,12 @@ function Button(props) {
|
|
|
903
1059
|
end,
|
|
904
1060
|
variant === "action" && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
905
1061
|
"aria-hidden": "true",
|
|
906
|
-
className: cn("pointer-events-none absolute top-[17.5%] h-[65%]", "transition-colors duration-(--duration-fast) ease-standard", actionDivider[size], disabled ? "bg-on-disabled/20" : "bg-on-action/10 group-hover:bg-on-action/15 group-active:bg-on-action/20")
|
|
1062
|
+
className: cn("pointer-events-none absolute top-[17.5%] h-[65%]", "transition-colors duration-(--duration-fast) ease-standard", actionDivider[size], disabled ? "bg-on-disabled/20" : ["bg-on-action/10 group-hover:bg-on-action/15 group-active:bg-on-action/20", "forced-colors:group-hover:bg-[ButtonText] forced-colors:group-active:bg-[ButtonText]"], forcedMark)
|
|
907
1063
|
}), loading && /* @__PURE__ */ jsx("span", {
|
|
908
1064
|
className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2", actionSpinner[size]),
|
|
909
1065
|
children: /* @__PURE__ */ jsx(Spinner, {
|
|
910
1066
|
label: null,
|
|
911
|
-
className: "block size-full"
|
|
1067
|
+
className: cn("block size-full", forcedMark)
|
|
912
1068
|
})
|
|
913
1069
|
})] })
|
|
914
1070
|
] });
|
|
@@ -1135,6 +1291,30 @@ function Calendar({ range = false, value: valueProp, defaultValue = null, onValu
|
|
|
1135
1291
|
pendingFocus.current = false;
|
|
1136
1292
|
gridRef.current?.querySelector(`[data-date="${focused}"]`)?.focus();
|
|
1137
1293
|
}, [focused, month]);
|
|
1294
|
+
const turned = useRef({
|
|
1295
|
+
month,
|
|
1296
|
+
until: 0,
|
|
1297
|
+
length: 0
|
|
1298
|
+
});
|
|
1299
|
+
useLayoutEffect(() => {
|
|
1300
|
+
const last = turned.current;
|
|
1301
|
+
if (last.month === month) return;
|
|
1302
|
+
const forward = month > last.month;
|
|
1303
|
+
last.month = month;
|
|
1304
|
+
const grid = gridRef.current;
|
|
1305
|
+
if (!grid) return;
|
|
1306
|
+
const now = performance.now();
|
|
1307
|
+
delete grid.dataset.turn;
|
|
1308
|
+
if (now < last.until) {
|
|
1309
|
+
last.until = now + last.length;
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
grid.offsetWidth;
|
|
1313
|
+
grid.dataset.turn = forward ? "next" : "previous";
|
|
1314
|
+
const running = typeof grid.getAnimations === "function" ? grid.getAnimations()[0] : void 0;
|
|
1315
|
+
last.length = Number(running?.effect?.getComputedTiming().endTime ?? 0);
|
|
1316
|
+
last.until = now + last.length;
|
|
1317
|
+
}, [month]);
|
|
1138
1318
|
const weeks = useMemo(() => monthGrid(month, weekStartsOn), [month, weekStartsOn]);
|
|
1139
1319
|
const formats = useMemo(() => ({
|
|
1140
1320
|
title: new Intl.DateTimeFormat(locale, {
|
|
@@ -1232,6 +1412,7 @@ function Calendar({ range = false, value: valueProp, defaultValue = null, onValu
|
|
|
1232
1412
|
children: formats.weekdayShort.format(toDateObject(date))
|
|
1233
1413
|
}, date)) }) }), /* @__PURE__ */ jsx("tbody", {
|
|
1234
1414
|
ref: gridRef,
|
|
1415
|
+
className: "data-turn:animate-shift-in data-[turn=next]:[--shift-x:var(--motion-shift-lg)] data-[turn=previous]:[--shift-x:calc(var(--motion-shift-lg)*-1)]",
|
|
1235
1416
|
onPointerLeave: () => setHovered(null),
|
|
1236
1417
|
onKeyDown: (event) => {
|
|
1237
1418
|
if (event.key === "Escape" && anchor !== null) {
|
|
@@ -1278,7 +1459,7 @@ function Calendar({ range = false, value: valueProp, defaultValue = null, onValu
|
|
|
1278
1459
|
onClick: () => select(date),
|
|
1279
1460
|
onFocus: () => setFocused(date),
|
|
1280
1461
|
onPointerEnter: range ? () => setHovered(date) : void 0,
|
|
1281
|
-
className: cn("relative flex size-10 items-center justify-center font-tech text-sm tabular-nums", "transition-colors duration-(--duration-fast) ease-standard", focusRingInset, endpoint ? "bg-surface-inverse font-bold text-ink-inverse" : [between ? "bg-ink/10" : previewing && "bg-ink/5", disabled ? "cursor-not-allowed text-ink-disabled line-through" : [outside ? "text-ink-tertiary" : "text-ink", between ? "hover:bg-ink/15" : "hover:bg-ink/5"]], isToday && "after:absolute after:bottom-1.5 after:left-1/2 after:h-0.5 after:w-3 after:-translate-x-1/2 after:content-['']", isToday && (endpoint ? "after:bg-accent-ink-inverse" : "after:bg-accent-ink")),
|
|
1462
|
+
className: cn("relative flex size-10 items-center justify-center font-tech text-sm tabular-nums", "transition-colors duration-(--duration-fast) ease-standard", focusRingInset, endpoint ? "bg-surface-inverse font-bold text-ink-inverse focus-visible:outline-accent-ink-inverse forced-colors:forced-color-adjust-none forced-colors:bg-[Highlight] forced-colors:text-[HighlightText] forced-colors:focus-visible:-outline-offset-4 forced-colors:focus-visible:outline-[HighlightText]" : [between ? "bg-ink/10 forced-colors:border-y-2 forced-colors:border-[Highlight]" : previewing && "bg-ink/5", disabled ? "cursor-not-allowed text-ink-disabled line-through" : [outside ? "text-ink-tertiary" : "text-ink", between ? "hover:bg-ink/15" : "hover:bg-ink/5"]], isToday && "after:absolute after:bottom-1.5 after:left-1/2 after:h-0.5 after:w-3 after:-translate-x-1/2 after:content-['']", isToday && (endpoint ? "after:bg-accent-ink-inverse forced-colors:after:bg-[HighlightText]" : "after:bg-accent-ink forced-colors:after:bg-[CanvasText]")),
|
|
1282
1463
|
children: dateParts(date).day
|
|
1283
1464
|
})
|
|
1284
1465
|
}, date);
|
|
@@ -1309,7 +1490,7 @@ function DashIndicator({ count, index, className, ...props }) {
|
|
|
1309
1490
|
className: cn("inline-flex items-center gap-1", className),
|
|
1310
1491
|
children: Array.from({ length: total }, (_, position) => /* @__PURE__ */ jsx("span", {
|
|
1311
1492
|
"data-current": position === current ? "" : void 0,
|
|
1312
|
-
className: cn("h-0.5 transition-[width,background-color] duration-(--duration-base) ease-standard", position === current ? "w-8 bg-accent-ink" : "w-4 bg-line-strong")
|
|
1493
|
+
className: cn("h-0.5 transition-[width,background-color] duration-(--duration-base) ease-standard", position === current ? "w-8 bg-accent-ink forced-colors:bg-[Highlight]" : "w-4 bg-line-strong forced-colors:bg-[CanvasText]")
|
|
1313
1494
|
}, position))
|
|
1314
1495
|
});
|
|
1315
1496
|
}
|
|
@@ -1510,27 +1691,18 @@ function CarouselSlide({ title: _title, description: _description, className, ch
|
|
|
1510
1691
|
/** 对勾:已选、已完成 */
|
|
1511
1692
|
const Check = createIcon("Check", /* @__PURE__ */ jsx("path", { d: "M4 12l6 6L20 6" }));
|
|
1512
1693
|
//#endregion
|
|
1513
|
-
//#region src/lib/merge-refs.ts
|
|
1514
|
-
/** 把几个 ref 合成一个:组件自己要用节点,同时还得把它交给使用方。 */
|
|
1515
|
-
function mergeRefs(...refs) {
|
|
1516
|
-
return (node) => {
|
|
1517
|
-
for (const ref of refs) if (typeof ref === "function") ref(node);
|
|
1518
|
-
else if (ref) ref.current = node;
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
//#endregion
|
|
1522
1694
|
//#region src/components/checkbox/choice-style.ts
|
|
1523
|
-
const choiceLabel = "group inline-flex min-h-10 items-start gap-2 py-2 text-base text-ink has-disabled:cursor-not-allowed has-disabled:text-ink-disabled";
|
|
1695
|
+
const choiceLabel = "group inline-flex min-h-10 items-start gap-2 py-2 text-base text-ink has-disabled:cursor-not-allowed has-disabled:text-ink-disabled forced-colors:has-disabled:text-[GrayText]";
|
|
1524
1696
|
/** 控件的定位盒:高度等于一行文字,标签换行时控件仍然对齐第一行 */
|
|
1525
1697
|
const choiceControl = "flex h-[1lh] shrink-0 items-center";
|
|
1526
|
-
const choiceBox = "peer size-5 shrink-0 appearance-none border-2 choice-diamond:size-3.5 choice-diamond:rotate-45 choice-diamond:checked:disabled:bg-ink-disabled border-line-strong bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard group-hover:border-ink-secondary checked:border-surface-inverse checked:bg-surface-inverse aria-invalid:border-danger disabled:cursor-not-allowed disabled:border-ink-disabled disabled:bg-surface-sunken";
|
|
1698
|
+
const choiceBox = ["peer size-5 shrink-0 appearance-none border-2 choice-diamond:size-3.5 choice-diamond:rotate-45 choice-diamond:checked:disabled:bg-ink-disabled border-line-strong bg-surface-sunken transition-colors duration-(--duration-fast) ease-standard group-hover:border-ink-secondary checked:border-surface-inverse checked:bg-surface-inverse aria-invalid:border-danger disabled:cursor-not-allowed disabled:border-ink-disabled disabled:bg-surface-sunken", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight] forced-colors:disabled:border-[GrayText] forced-colors:disabled:bg-[Canvas] forced-colors:choice-diamond:checked:disabled:bg-[GrayText]"].join(" ");
|
|
1527
1699
|
/** 方格 / 圆的定位盒:菱形比它小一圈,所以居中 */
|
|
1528
1700
|
const choiceFrame = "relative inline-flex size-5 items-center justify-center";
|
|
1529
1701
|
/**
|
|
1530
1702
|
* 压在方格 / 圆正中的记号,默认透明,由 peer 的状态点亮。
|
|
1531
1703
|
* 淡入淡出,和方格换底色的那一下同时走——底色有过渡而记号硬切,看起来是两件事
|
|
1532
1704
|
*/
|
|
1533
|
-
const choiceMark = "pointer-events-none absolute inset-0 m-auto text-accent-ink-inverse opacity-0 transition-opacity duration-(--duration-fast) ease-standard peer-disabled:text-ink-disabled";
|
|
1705
|
+
const choiceMark = "pointer-events-none absolute inset-0 m-auto text-accent-ink-inverse opacity-0 transition-opacity duration-(--duration-fast) ease-standard peer-disabled:text-ink-disabled forced-colors:text-[HighlightText] forced-colors:peer-disabled:text-[GrayText]";
|
|
1534
1706
|
//#endregion
|
|
1535
1707
|
//#region src/components/checkbox/Checkbox.tsx
|
|
1536
1708
|
/**
|
|
@@ -1559,7 +1731,7 @@ function Checkbox({ indeterminate = false, invalid = false, onCheckedChange, onC
|
|
|
1559
1731
|
type: "checkbox",
|
|
1560
1732
|
"aria-invalid": invalid || void 0,
|
|
1561
1733
|
onChange: handleChange,
|
|
1562
|
-
className: cn(choiceBox, "indeterminate:border-surface-inverse indeterminate:bg-surface-inverse", focusRing)
|
|
1734
|
+
className: cn(choiceBox, "indeterminate:border-surface-inverse indeterminate:bg-surface-inverse", "forced-colors:indeterminate:border-[Highlight] forced-colors:indeterminate:bg-[Highlight]", focusRing)
|
|
1563
1735
|
}),
|
|
1564
1736
|
/* @__PURE__ */ jsx(Check, {
|
|
1565
1737
|
size: 14,
|
|
@@ -1586,11 +1758,20 @@ const capsuleSize = {
|
|
|
1586
1758
|
sm: "h-7 px-3 text-xs",
|
|
1587
1759
|
md: "h-8 px-4 text-sm"
|
|
1588
1760
|
};
|
|
1761
|
+
const forcedSelected = "forced-colors:forced-color-adjust-none forced-colors:focus-visible:outline-[Highlight]";
|
|
1589
1762
|
const capsuleState = {
|
|
1590
|
-
selected:
|
|
1763
|
+
selected: [
|
|
1764
|
+
"border-transparent bg-surface-inverse text-ink-inverse",
|
|
1765
|
+
"forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
|
|
1766
|
+
forcedSelected
|
|
1767
|
+
].join(" "),
|
|
1591
1768
|
rest: "border-line-strong text-ink hover:bg-ink/5",
|
|
1592
1769
|
disabled: "cursor-not-allowed border-line text-ink-disabled",
|
|
1593
|
-
selectedDisabled:
|
|
1770
|
+
selectedDisabled: [
|
|
1771
|
+
"cursor-not-allowed border-transparent bg-disabled text-on-disabled",
|
|
1772
|
+
"forced-colors:border-[GrayText] forced-colors:bg-[GrayText] forced-colors:text-[Canvas]",
|
|
1773
|
+
forcedSelected
|
|
1774
|
+
].join(" ")
|
|
1594
1775
|
};
|
|
1595
1776
|
//#endregion
|
|
1596
1777
|
//#region src/components/chip/FilterChip.tsx
|
|
@@ -1712,7 +1893,7 @@ function MenuCheck({ checked, disabled = false }) {
|
|
|
1712
1893
|
"data-checked": checked ? "" : void 0,
|
|
1713
1894
|
className: "flex size-4 shrink-0 items-center justify-center",
|
|
1714
1895
|
children: /* @__PURE__ */ jsx("span", {
|
|
1715
|
-
className: cn("flex size-4 items-center justify-center border-2 transition-colors duration-(--duration-fast) ease-standard choice-diamond:size-3 choice-diamond:rotate-45", disabled ? ["border-ink-disabled bg-surface-sunken text-ink-disabled", checked && "choice-diamond:bg-ink-disabled"] : checked ? "border-surface-inverse bg-surface-inverse text-accent-ink-inverse" : "border-line-strong bg-surface-sunken"),
|
|
1896
|
+
className: cn("flex size-4 items-center justify-center border-2 transition-colors duration-(--duration-fast) ease-standard choice-diamond:size-3 choice-diamond:rotate-45", disabled ? ["border-ink-disabled bg-surface-sunken text-ink-disabled", checked && "choice-diamond:bg-ink-disabled forced-colors:choice-diamond:bg-[GrayText]"] : checked ? "border-surface-inverse bg-surface-inverse text-accent-ink-inverse forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]" : "border-line-strong bg-surface-sunken"),
|
|
1716
1897
|
children: checked && /* @__PURE__ */ jsx(Check, {
|
|
1717
1898
|
size: 10,
|
|
1718
1899
|
strokeWidth: 4,
|
|
@@ -1738,6 +1919,54 @@ const enterFromSide = [
|
|
|
1738
1919
|
"data-starting-style:data-[side=inline-end]:-translate-x-(--motion-shift)",
|
|
1739
1920
|
"data-starting-style:data-[side=inline-start]:translate-x-(--motion-shift)"
|
|
1740
1921
|
].join(" ");
|
|
1922
|
+
/**
|
|
1923
|
+
* 按高度收放,不量高度:外面这一层是只有一行的网格,行高在 `0fr` 和 `1fr` 之间过渡;
|
|
1924
|
+
* 里面再套一层 `collapseInner`,裁掉还放不下的那一截。表格的明细行、字段下面的错误文字
|
|
1925
|
+
* 都这么收放——内容后来变高了也跟着走。
|
|
1926
|
+
*
|
|
1927
|
+
* 展开 300ms、收起 200ms。收起由 `data-leaving` 触发,配 `usePresence`:收完再卸载。
|
|
1928
|
+
*/
|
|
1929
|
+
const collapse = [
|
|
1930
|
+
"grid grid-rows-[1fr]",
|
|
1931
|
+
"transition-[grid-template-rows] duration-(--duration-base) ease-standard",
|
|
1932
|
+
"data-leaving:grid-rows-[0fr] data-leaving:duration-(--duration-fast)"
|
|
1933
|
+
].join(" ");
|
|
1934
|
+
/** 只裁纵向:横向照旧,里面的东西该多宽还是多宽 */
|
|
1935
|
+
const collapseInner = "min-h-0 min-w-0 overflow-y-clip";
|
|
1936
|
+
/**
|
|
1937
|
+
* 选中指示:画在容器的 `::after` 上的一块东西,压在容器的底色之上、各项之下。
|
|
1938
|
+
* 位置和尺寸来自 `useIndicator` 写在容器上的变量;换了一项时滑过去(200ms,
|
|
1939
|
+
* 和各项换字色同一拍),没有当前项时原地淡出。
|
|
1940
|
+
*
|
|
1941
|
+
* 容器上没有 `data-indicator`(量不到)时不画——各项照旧自己画选中态,
|
|
1942
|
+
* 所以给各项的选中底加一个 `in-data-indicator:` 的变体把它让出来。
|
|
1943
|
+
* 这里只管定位和过渡:颜色、圆角由用的地方给,几何用 `indicatorBox` 或者自己写。
|
|
1944
|
+
*/
|
|
1945
|
+
const indicator = [
|
|
1946
|
+
"relative isolate",
|
|
1947
|
+
"after:pointer-events-none after:absolute after:top-0 after:left-0 after:-z-1",
|
|
1948
|
+
"after:hidden data-indicator:after:block",
|
|
1949
|
+
"after:opacity-0 data-[indicator=on]:after:opacity-100",
|
|
1950
|
+
"after:transition-[translate,width,height,opacity] after:duration-(--duration-fast) after:ease-standard",
|
|
1951
|
+
"data-[indicator=off]:after:transition-[opacity]",
|
|
1952
|
+
"data-indicator-instant:after:transition-none"
|
|
1953
|
+
].join(" ");
|
|
1954
|
+
/** 指示盖住当前项的整个盒子:分段选择的墨块、胶囊页签的底 */
|
|
1955
|
+
const indicatorBox = ["after:h-(--indicator-h) after:w-(--indicator-w)", "after:translate-x-(--indicator-x) after:translate-y-(--indicator-y)"].join(" ");
|
|
1956
|
+
/**
|
|
1957
|
+
* 补位(`useFlip`):一排里有一项走了,其余的从旧位置滑到新位置。
|
|
1958
|
+
*
|
|
1959
|
+
* `flipBox` 给那一排——走的那一项以它为参照钉在原处,并且压在别的项下面。
|
|
1960
|
+
* 少了一行它就矮了:高度由钩子量着收(200ms),这期间还在滑的项不露到它外面去;
|
|
1961
|
+
* 各行靠上排,撑着的那一段空在底下。
|
|
1962
|
+
* `flipLeaving` 给会走的每一项:带上 `data-leaving` 之后脱离排版(位置由钩子给)、
|
|
1963
|
+
* 原地淡出、点不到。
|
|
1964
|
+
*
|
|
1965
|
+
* 过渡由用的地方自己写:各项的 `translate`(滑回原位)、走的那一项的 `opacity`,
|
|
1966
|
+
* 都是 200ms——这几项多半本来就带着换色的过渡,得写进同一份清单里。
|
|
1967
|
+
*/
|
|
1968
|
+
const flipBox = ["relative isolate content-start overflow-y-clip", "transition-[min-height] duration-(--duration-fast) ease-standard"].join(" ");
|
|
1969
|
+
const flipLeaving = ["data-leaving:pointer-events-none data-leaving:absolute data-leaving:-z-1", "data-leaving:opacity-0"].join(" ");
|
|
1741
1970
|
//#endregion
|
|
1742
1971
|
//#region src/components/dropdown-menu/menu-style.ts
|
|
1743
1972
|
/** 选项要知道自己在哪种面板里,才知道"当前项"怎么画 */
|
|
@@ -1768,10 +1997,19 @@ const menuItem = [
|
|
|
1768
1997
|
].join(" ");
|
|
1769
1998
|
/** 不是当前项的选项才有悬停底:当前项的底不能被悬停盖掉 */
|
|
1770
1999
|
const menuItemHover = "data-highlighted:bg-ink/5";
|
|
1771
|
-
/**
|
|
2000
|
+
/**
|
|
2001
|
+
* 当前项(下拉选择的已选项、单选菜单项的选中项)。
|
|
2002
|
+
*
|
|
2003
|
+
* `strong` 的当前项是一整行黄底,而面板是深色的局部主题,焦点色也是黄的:
|
|
2004
|
+
* 画在里面的焦点环换成压得住黄底的颜色,并且再往里收 2px——那个颜色是墨色,
|
|
2005
|
+
* 贴着边的话和深色的面板分不开,看上去只是黄底小了一圈。
|
|
2006
|
+
*
|
|
2007
|
+
* 高对比模式下底色都被系统换掉:两种面板的当前项都是左缘一条系统"选中"色的竖条
|
|
2008
|
+
* (`strong` 平时没有这一条,只在这时候画)
|
|
2009
|
+
*/
|
|
1772
2010
|
const menuItemCurrent = {
|
|
1773
|
-
plain: "bg-surface-muted font-medium before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-ink before:content-['']",
|
|
1774
|
-
strong: "bg-action font-medium text-on-action"
|
|
2011
|
+
plain: "bg-surface-muted font-medium before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-ink before:content-[''] forced-colors:before:bg-[Highlight]",
|
|
2012
|
+
strong: "bg-action font-medium text-on-action focus-visible:-outline-offset-4 focus-visible:outline-on-action forced-colors:before:absolute forced-colors:before:inset-y-0 forced-colors:before:left-0 forced-colors:before:w-[3px] forced-colors:before:bg-[Highlight] forced-colors:before:content-['']"
|
|
1775
2013
|
};
|
|
1776
2014
|
const menuGroupLabel = "px-3 pt-2 pb-1 text-xs text-ink-tertiary";
|
|
1777
2015
|
const menuSeparator = "my-1 h-px bg-line";
|
|
@@ -1807,6 +2045,9 @@ function Field({ label, help, error, required = false, disabled = false, group =
|
|
|
1807
2045
|
const hasError = error !== void 0 && error !== null && error !== false;
|
|
1808
2046
|
const hasHelp = help !== void 0 && help !== null && help !== false;
|
|
1809
2047
|
const describedBy = [hasError && errorId, hasHelp && helpId].filter(Boolean).join(" ") || void 0;
|
|
2048
|
+
const { ref: errorRef, mounted: errorMounted, leaving: errorLeaving, entered: errorEntered } = usePresence(hasError);
|
|
2049
|
+
const [lastError, setLastError] = useState(error);
|
|
2050
|
+
if (hasError && !Object.is(lastError, error)) setLastError(error);
|
|
1810
2051
|
const context = useMemo(() => ({
|
|
1811
2052
|
controlId: group ? void 0 : id,
|
|
1812
2053
|
labelId,
|
|
@@ -1830,16 +2071,25 @@ function Field({ label, help, error, required = false, disabled = false, group =
|
|
|
1830
2071
|
"data-required": "",
|
|
1831
2072
|
className: cn("ml-1.5 inline-block size-1.5 rotate-45 align-middle", disabled ? "bg-ink-disabled" : "bg-accent-ink")
|
|
1832
2073
|
})] });
|
|
1833
|
-
const messages = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2074
|
+
const messages = /* @__PURE__ */ jsxs(Fragment$1, { children: [errorMounted && /* @__PURE__ */ jsx("div", {
|
|
2075
|
+
ref: errorRef,
|
|
2076
|
+
"data-leaving": errorLeaving ? "" : void 0,
|
|
2077
|
+
"aria-hidden": errorLeaving || void 0,
|
|
2078
|
+
className: cn(collapse, "transition-[grid-template-rows,opacity] duration-(--duration-fast) data-leaving:opacity-0", errorEntered && ["starting:grid-rows-[0fr]", "starting:opacity-0"]),
|
|
2079
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
2080
|
+
className: "min-h-0 min-w-0 overflow-y-clip",
|
|
2081
|
+
children: /* @__PURE__ */ jsxs("p", {
|
|
2082
|
+
id: errorId,
|
|
2083
|
+
className: "flex items-start gap-1.5 pt-1.5 text-sm text-danger",
|
|
2084
|
+
children: [/* @__PURE__ */ jsx(StatusDanger, {
|
|
2085
|
+
size: 16,
|
|
2086
|
+
className: "mt-0.5 shrink-0"
|
|
2087
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2088
|
+
className: "min-w-0 wrap-anywhere",
|
|
2089
|
+
children: hasError ? error : lastError
|
|
2090
|
+
})]
|
|
2091
|
+
})
|
|
2092
|
+
})
|
|
1843
2093
|
}), hasHelp && /* @__PURE__ */ jsx("p", {
|
|
1844
2094
|
id: helpId,
|
|
1845
2095
|
className: cn("mt-1.5 text-sm", disabled ? "text-ink-disabled" : "text-ink-secondary"),
|
|
@@ -1908,10 +2158,16 @@ const fill$1 = {
|
|
|
1908
2158
|
outline: "bg-surface"
|
|
1909
2159
|
};
|
|
1910
2160
|
function controlBox({ variant = "sunken", invalid, disabled, readOnly }) {
|
|
1911
|
-
return cn("flex w-full min-w-0 text-ink", frame[variant], "transition-colors duration-(--duration-fast) ease-standard", focusRingWithin, readOnly ? "border-transparent bg-transparent" : fill$1[variant], disabled ? "cursor-not-allowed border-line text-ink-disabled" : invalid ? "border-danger" : !readOnly && "border-line-strong hover:not-focus-within:border-ink-secondary focus-within:border-ink");
|
|
2161
|
+
return cn("flex w-full min-w-0 text-ink", frame[variant], "transition-colors duration-(--duration-fast) ease-standard", focusRingWithin, readOnly ? "border-transparent bg-transparent" : fill$1[variant], readOnly && !disabled && !invalid && "forced-colors:border-[Canvas]", disabled ? "cursor-not-allowed border-line text-ink-disabled" : invalid ? "border-danger" : !readOnly && "border-line-strong hover:not-focus-within:border-ink-secondary focus-within:border-ink");
|
|
1912
2162
|
}
|
|
1913
|
-
/**
|
|
1914
|
-
|
|
2163
|
+
/**
|
|
2164
|
+
* 框里真正的 `<input>` / `<textarea>`:没有自己的底和边,焦点环画在外框上。
|
|
2165
|
+
*
|
|
2166
|
+
* 高对比模式下禁用的字色要自己写明:平时的 `ink-disabled` 写在外框上,系统换掉之后
|
|
2167
|
+
* 只有它自己认得的禁用控件才是灰字色——`<textarea>`、写了 `type` 的 `<input>` 认得,
|
|
2168
|
+
* 没写 `type` 的 `<input>`(输入框、组合框都是)不认,字和没禁用的一个颜色
|
|
2169
|
+
*/
|
|
2170
|
+
const controlElement = "min-w-0 flex-1 bg-transparent outline-none placeholder:text-ink-tertiary disabled:cursor-not-allowed disabled:placeholder:text-ink-disabled forced-colors:disabled:text-[GrayText]";
|
|
1915
2171
|
//#endregion
|
|
1916
2172
|
//#region src/components/input/value-chip.ts
|
|
1917
2173
|
/** 小块高 20 / 24 / 32px,跟着外框的三档 */
|
|
@@ -1920,9 +2176,9 @@ const valueChipSize = {
|
|
|
1920
2176
|
md: "h-6 text-sm",
|
|
1921
2177
|
lg: "h-8 text-base"
|
|
1922
2178
|
};
|
|
1923
|
-
const valueChip = "flex max-w-full min-w-0 cursor-default items-center bg-surface-muted pl-2 text-ink";
|
|
2179
|
+
const valueChip = "flex max-w-full min-w-0 cursor-default items-center bg-surface-muted pl-2 text-ink forced-colors:border forced-colors:border-[ButtonText]";
|
|
1924
2180
|
/** 小块右端的删除叉:一个和小块等高的方钮 */
|
|
1925
|
-
const valueChipRemove = "flex aspect-square h-full shrink-0 items-center justify-center text-ink-secondary hover:bg-line hover:text-ink";
|
|
2181
|
+
const valueChipRemove = "flex aspect-square h-full shrink-0 items-center justify-center text-ink-secondary hover:bg-line hover:text-ink forced-colors:in-data-disabled:text-[GrayText]";
|
|
1926
2182
|
/** 和小块排在一起的那个输入框:和小块一样高,这一行才不会比小块高出一截 */
|
|
1927
2183
|
const valueInputHeight = {
|
|
1928
2184
|
sm: "h-5",
|
|
@@ -1948,6 +2204,7 @@ const currentHighlighted = {
|
|
|
1948
2204
|
plain: "data-highlighted:bg-line",
|
|
1949
2205
|
strong: "data-highlighted:bg-action-pressed"
|
|
1950
2206
|
};
|
|
2207
|
+
const highlightedForced = "forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-[Highlight]";
|
|
1951
2208
|
const iconButton = "flex shrink-0 items-center justify-center self-stretch text-ink-secondary hover:text-ink disabled:cursor-not-allowed disabled:text-ink-disabled disabled:hover:text-ink-disabled";
|
|
1952
2209
|
const byValue = (a, b) => a?.value === b?.value;
|
|
1953
2210
|
/**
|
|
@@ -1979,7 +2236,7 @@ function Combobox(props) {
|
|
|
1979
2236
|
const renderOption = (option) => /* @__PURE__ */ jsx(Combobox$1.Item, {
|
|
1980
2237
|
value: option,
|
|
1981
2238
|
disabled: option.disabled,
|
|
1982
|
-
className: (state) => cn(menuItem, multiple ? [menuItemHover, state.selected && "font-medium"] : state.selected ? [menuItemCurrent[panelVariant], currentHighlighted[panelVariant]] : menuItemHover),
|
|
2239
|
+
className: (state) => cn(menuItem, multiple ? [menuItemHover, state.selected && "font-medium"] : state.selected ? [menuItemCurrent[panelVariant], currentHighlighted[panelVariant]] : menuItemHover, highlightedForced),
|
|
1983
2240
|
render: multiple ? ({ children, ...itemProps }, state) => /* @__PURE__ */ jsxs("div", {
|
|
1984
2241
|
...itemProps,
|
|
1985
2242
|
children: [/* @__PURE__ */ jsx(MenuCheck, {
|
|
@@ -2020,7 +2277,7 @@ function Combobox(props) {
|
|
|
2020
2277
|
className: "flex min-w-0 flex-1 flex-wrap items-center gap-1 py-1 pl-2",
|
|
2021
2278
|
children: /* @__PURE__ */ jsx(Combobox$1.Value, { children: (selected) => /* @__PURE__ */ jsxs(Fragment$1, { children: [selected.map((option) => /* @__PURE__ */ jsxs(Combobox$1.Chip, {
|
|
2022
2279
|
"aria-label": option.label,
|
|
2023
|
-
className: cn(valueChip, "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", "data-disabled:text-ink-disabled", valueChipSize[size]),
|
|
2280
|
+
className: cn(valueChip, "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", "data-disabled:text-ink-disabled", "forced-colors:data-disabled:border-[GrayText] forced-colors:data-disabled:text-[GrayText]", valueChipSize[size]),
|
|
2024
2281
|
children: [/* @__PURE__ */ jsx("span", {
|
|
2025
2282
|
className: "min-w-0 truncate",
|
|
2026
2283
|
children: option.label
|
|
@@ -2158,13 +2415,17 @@ function GhostText({ variant = "hatch", className, ...props }) {
|
|
|
2158
2415
|
* 底是反转块:亮色主题下是深底黄字,暗色主题下是浅底深字。
|
|
2159
2416
|
* 整块是一个 `data-theme="inverse"` 的局部主题,`action` 里的按钮按这块底色取值。
|
|
2160
2417
|
*/
|
|
2161
|
-
function CompletionBanner({ title, word = "COMPLETED", description, action, level = 3, className, children, ...props }) {
|
|
2418
|
+
function CompletionBanner({ title, word = "COMPLETED", description, action, level = 3, animate = true, className, children, ref, ...props }) {
|
|
2162
2419
|
const Heading = `h${level}`;
|
|
2420
|
+
const [inViewRef, inView] = useInView({ disabled: !animate });
|
|
2421
|
+
const pending = animate && !inView;
|
|
2422
|
+
const playing = animate && inView;
|
|
2163
2423
|
return /* @__PURE__ */ jsxs("div", {
|
|
2164
2424
|
role: "status",
|
|
2165
2425
|
"data-theme": "inverse",
|
|
2166
2426
|
...props,
|
|
2167
|
-
|
|
2427
|
+
ref: mergeRefs(inViewRef, ref),
|
|
2428
|
+
className: cn("@container relative isolate overflow-clip bg-surface text-ink", pending && "[clip-path:inset(0_100%_0_0)]", playing && "animate-wipe-in", className),
|
|
2168
2429
|
children: [word !== null && /* @__PURE__ */ jsx(GhostText, {
|
|
2169
2430
|
variant: "outline",
|
|
2170
2431
|
className: "absolute -right-2 -bottom-3 -z-10 hidden text-5xl [--ghost-ink:color-mix(in_srgb,var(--ef-ink)_28%,transparent)] [mask-image:linear-gradient(to_left,black_45%,transparent)] @md:block",
|
|
@@ -2172,7 +2433,7 @@ function CompletionBanner({ title, word = "COMPLETED", description, action, leve
|
|
|
2172
2433
|
}), /* @__PURE__ */ jsxs("div", {
|
|
2173
2434
|
className: "flex flex-wrap items-center justify-between gap-x-6 gap-y-3 px-4 py-4 @md:px-6 @md:py-5",
|
|
2174
2435
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
2175
|
-
className: "min-w-0 border-l-4 border-accent-ink pl-3",
|
|
2436
|
+
className: cn("min-w-0 border-l-4 border-accent-ink pl-3", playing && "animate-shift-in [animation-delay:200ms] [--shift-x:calc(var(--motion-shift-lg)*-1)]"),
|
|
2176
2437
|
children: [
|
|
2177
2438
|
/* @__PURE__ */ jsx(Heading, {
|
|
2178
2439
|
className: "text-lg font-bold wrap-anywhere text-accent-ink @md:text-xl",
|
|
@@ -2185,7 +2446,7 @@ function CompletionBanner({ title, word = "COMPLETED", description, action, leve
|
|
|
2185
2446
|
children
|
|
2186
2447
|
]
|
|
2187
2448
|
}), action && /* @__PURE__ */ jsx("div", {
|
|
2188
|
-
className: "shrink-0",
|
|
2449
|
+
className: cn("shrink-0", playing && "animate-fade-in [animation-delay:200ms]"),
|
|
2189
2450
|
children: action
|
|
2190
2451
|
})]
|
|
2191
2452
|
})]
|
|
@@ -2365,6 +2626,8 @@ function sparklinePath(data, range = {}) {
|
|
|
2365
2626
|
area: `${line}L${SIZE} ${SIZE}L0 ${SIZE}Z`
|
|
2366
2627
|
};
|
|
2367
2628
|
}
|
|
2629
|
+
const areaForced = "forced-colors:fill-[CanvasText]";
|
|
2630
|
+
const lineForced = "forced-colors:stroke-[CanvasText]";
|
|
2368
2631
|
const areaClass = {
|
|
2369
2632
|
info: "fill-data/75",
|
|
2370
2633
|
danger: "fill-alert/75",
|
|
@@ -2381,11 +2644,14 @@ const lineClass = {
|
|
|
2381
2644
|
* 行内的小型图表:没有坐标轴、网格和图例,只有一块面积或一条折线。
|
|
2382
2645
|
* 撑满给它的盒子(默认高 32px、通宽)。它旁边应该有数字——图只是帮着看趋势。
|
|
2383
2646
|
*/
|
|
2384
|
-
function Sparkline({ data, variant = "area", tone = "info", min, max, label, className, ...props }) {
|
|
2647
|
+
function Sparkline({ data, variant = "area", tone = "info", min, max, label, animate = true, className, ref, ...props }) {
|
|
2385
2648
|
const path = sparklinePath(data, {
|
|
2386
2649
|
min,
|
|
2387
2650
|
max
|
|
2388
2651
|
});
|
|
2652
|
+
const [inViewRef, inView] = useInView({ disabled: !animate });
|
|
2653
|
+
const pending = animate && !inView;
|
|
2654
|
+
const playing = animate && inView;
|
|
2389
2655
|
return /* @__PURE__ */ jsx("svg", {
|
|
2390
2656
|
viewBox: `0 0 ${SIZE} ${SIZE}`,
|
|
2391
2657
|
preserveAspectRatio: "none",
|
|
@@ -2396,17 +2662,18 @@ function Sparkline({ data, variant = "area", tone = "info", min, max, label, cla
|
|
|
2396
2662
|
"data-variant": variant,
|
|
2397
2663
|
"data-tone": tone,
|
|
2398
2664
|
...props,
|
|
2399
|
-
|
|
2665
|
+
ref: mergeRefs(inViewRef, ref),
|
|
2666
|
+
className: cn("block h-8 w-full overflow-visible", pending && "[clip-path:inset(0_100%_0_0)]", playing && "animate-wipe-in [animation-duration:var(--duration-slower)] [--wipe-bleed:4px]", className),
|
|
2400
2667
|
children: path && (variant === "area" ? /* @__PURE__ */ jsx("path", {
|
|
2401
2668
|
d: path.area,
|
|
2402
|
-
className: areaClass[tone]
|
|
2669
|
+
className: cn(areaClass[tone], areaForced)
|
|
2403
2670
|
}) : /* @__PURE__ */ jsx("path", {
|
|
2404
2671
|
d: path.line,
|
|
2405
2672
|
fill: "none",
|
|
2406
2673
|
strokeWidth: 1.5,
|
|
2407
2674
|
strokeLinejoin: "miter",
|
|
2408
2675
|
vectorEffect: "non-scaling-stroke",
|
|
2409
|
-
className: lineClass[tone]
|
|
2676
|
+
className: cn(lineClass[tone], lineForced)
|
|
2410
2677
|
}))
|
|
2411
2678
|
});
|
|
2412
2679
|
}
|
|
@@ -2480,7 +2747,7 @@ const toneClass$1 = {
|
|
|
2480
2747
|
danger: "text-danger"
|
|
2481
2748
|
};
|
|
2482
2749
|
const favoriteDot = "block size-4 rounded-full";
|
|
2483
|
-
const favoriteOn = "bg-action";
|
|
2750
|
+
const favoriteOn = "bg-action forced-colors:bg-[Highlight]";
|
|
2484
2751
|
const favoriteOff = "border-2 border-ink-tertiary";
|
|
2485
2752
|
/** 数据行带里的一行。放在 `DataRowList` 里。 */
|
|
2486
2753
|
function DataRow({ name, categoryColor, favorite, onFavoriteChange, favoriteLabel = "收藏", series, value, tone = "neutral", reference, className, style, ...props }) {
|
|
@@ -2605,7 +2872,7 @@ function DateField({ display, placeholder, fields, variant, size, invalid, disab
|
|
|
2605
2872
|
}),
|
|
2606
2873
|
/* @__PURE__ */ jsx(CalendarIcon, {
|
|
2607
2874
|
size: iconSize,
|
|
2608
|
-
className: cn("shrink-0", disabled ? "text-ink-disabled" : "text-ink-secondary")
|
|
2875
|
+
className: cn("shrink-0", disabled ? "text-ink-disabled forced-colors:text-[GrayText]" : "text-ink-secondary forced-colors:text-[ButtonText]")
|
|
2609
2876
|
})
|
|
2610
2877
|
]
|
|
2611
2878
|
}), fields]
|
|
@@ -3229,12 +3496,12 @@ function Viewfinder$1() {
|
|
|
3229
3496
|
});
|
|
3230
3497
|
}
|
|
3231
3498
|
/** 没有内容时的占位。说清楚为什么为空、接下来能做什么;不要用大幅插画填满。 */
|
|
3232
|
-
function EmptyState({ title, description, action, icon, bordered = true, level = 3, className, children, ...props }) {
|
|
3499
|
+
function EmptyState({ title, description, action, icon, bordered = true, level = 3, animate = true, className, children, ...props }) {
|
|
3233
3500
|
const Heading = `h${level}`;
|
|
3234
3501
|
const graphic = icon === void 0 ? /* @__PURE__ */ jsx(Viewfinder$1, {}) : icon;
|
|
3235
3502
|
return /* @__PURE__ */ jsxs("div", {
|
|
3236
3503
|
...props,
|
|
3237
|
-
className: cn("flex flex-col items-center px-6 py-10 text-center text-ink", bordered && "border border-dashed border-line-strong", className),
|
|
3504
|
+
className: cn("flex flex-col items-center px-6 py-10 text-center text-ink", bordered && "border border-dashed border-line-strong", animate && "animate-fade-in", className),
|
|
3238
3505
|
children: [
|
|
3239
3506
|
graphic !== null && /* @__PURE__ */ jsx("div", {
|
|
3240
3507
|
"aria-hidden": "true",
|
|
@@ -3314,7 +3581,8 @@ const trackSize = {
|
|
|
3314
3581
|
sm: "h-1",
|
|
3315
3582
|
md: "h-2"
|
|
3316
3583
|
};
|
|
3317
|
-
const fillClass = "bg-action";
|
|
3584
|
+
const fillClass = "bg-action forced-colors:bg-[Highlight]";
|
|
3585
|
+
const trackClass$1 = "bg-line forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[CanvasText]";
|
|
3318
3586
|
/**
|
|
3319
3587
|
* 进度:灰轨 + 黄填充。表示"完成了多少";表示"数量有多少"用图表,那是蓝色的事。
|
|
3320
3588
|
*
|
|
@@ -3333,7 +3601,7 @@ function Progress({ value, max, segments, size = "md", showValue = false, format
|
|
|
3333
3601
|
className: "flex h-full gap-1",
|
|
3334
3602
|
children: Array.from({ length: segments }, (_, index) => /* @__PURE__ */ jsx("span", {
|
|
3335
3603
|
"data-done": index < done ? "" : void 0,
|
|
3336
|
-
className: cn("flex-1", index < done ? fillClass :
|
|
3604
|
+
className: cn("flex-1", index < done ? fillClass : trackClass$1)
|
|
3337
3605
|
}, index))
|
|
3338
3606
|
});
|
|
3339
3607
|
} else if (indeterminate) bar = /* @__PURE__ */ jsx("span", { className: cn("absolute inset-y-0 left-0 w-2/5 animate-indeterminate", "motion-reduce:translate-x-3/4", fillClass) });
|
|
@@ -3358,7 +3626,7 @@ function Progress({ value, max, segments, size = "md", showValue = false, format
|
|
|
3358
3626
|
"aria-valuenow": indeterminate ? void 0 : current,
|
|
3359
3627
|
"aria-valuetext": segmented && !indeterminate ? `${current} / ${total}` : void 0,
|
|
3360
3628
|
...props,
|
|
3361
|
-
className: cn("relative min-w-0 flex-1", trackSize[size], !segmented &&
|
|
3629
|
+
className: cn("relative min-w-0 flex-1", trackSize[size], !segmented && trackClass$1),
|
|
3362
3630
|
children: bar
|
|
3363
3631
|
}), text !== null && /* @__PURE__ */ jsx("span", {
|
|
3364
3632
|
className: "min-w-[4ch] shrink-0 text-right font-tech text-sm leading-none font-bold tabular-nums",
|
|
@@ -3477,6 +3745,121 @@ function FileItem({ name, size, status, progress, error, onRemove, onRetry, remo
|
|
|
3477
3745
|
});
|
|
3478
3746
|
}
|
|
3479
3747
|
//#endregion
|
|
3748
|
+
//#region src/hooks/useAddedKeys.ts
|
|
3749
|
+
const NONE = /* @__PURE__ */ new Set();
|
|
3750
|
+
const same$1 = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
|
|
3751
|
+
/**
|
|
3752
|
+
* 一串 key 里哪几个是后来加进来的。一开始就在的不算;删掉之后又加回来的算。
|
|
3753
|
+
*
|
|
3754
|
+
* 给"新项淡入"用:入场的类只加在这几个上。一开始就有的不该在载入时播一遍,
|
|
3755
|
+
* 已经在的也不会因为别的项变了而重播——它一直带着那个类,动画只在挂上时播一次。
|
|
3756
|
+
*/
|
|
3757
|
+
function useAddedKeys(keys) {
|
|
3758
|
+
const [state, setState] = useState({
|
|
3759
|
+
keys,
|
|
3760
|
+
added: NONE
|
|
3761
|
+
});
|
|
3762
|
+
if (!same$1(state.keys, keys)) {
|
|
3763
|
+
const before = new Set(state.keys);
|
|
3764
|
+
setState({
|
|
3765
|
+
keys,
|
|
3766
|
+
added: new Set(keys.filter((key) => !before.has(key) || state.added.has(key)))
|
|
3767
|
+
});
|
|
3768
|
+
}
|
|
3769
|
+
return state.added;
|
|
3770
|
+
}
|
|
3771
|
+
//#endregion
|
|
3772
|
+
//#region src/hooks/useLingering.ts
|
|
3773
|
+
const same = (a, b) => a.length === b.length && a.every((key, index) => key === b[index]);
|
|
3774
|
+
/**
|
|
3775
|
+
* 新的一份:现在的那几项按现在的顺序,走着的插回它原来挨着的那一项后面。
|
|
3776
|
+
* `keep` 是这一次新走的要不要留。
|
|
3777
|
+
*/
|
|
3778
|
+
function merge(before, keys, keep) {
|
|
3779
|
+
const now = new Set(keys);
|
|
3780
|
+
const next = keys.map((key) => ({
|
|
3781
|
+
key,
|
|
3782
|
+
leaving: false
|
|
3783
|
+
}));
|
|
3784
|
+
let anchor = null;
|
|
3785
|
+
for (const entry of before) {
|
|
3786
|
+
if (now.has(entry.key)) {
|
|
3787
|
+
anchor = entry.key;
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
if (!entry.leaving && !keep) continue;
|
|
3791
|
+
const at = anchor === null ? -1 : next.findIndex((item) => item.key === anchor);
|
|
3792
|
+
next.splice(at + 1, 0, {
|
|
3793
|
+
key: entry.key,
|
|
3794
|
+
leaving: true
|
|
3795
|
+
});
|
|
3796
|
+
anchor = entry.key;
|
|
3797
|
+
}
|
|
3798
|
+
return next;
|
|
3799
|
+
}
|
|
3800
|
+
/**
|
|
3801
|
+
* 列表里被拿掉的项先留着:`keys` 里少了一项,它不当场消失,而是带着 `leaving`
|
|
3802
|
+
* 留在原来的位置上,等它身上的退场走完才真的拿掉(等法和 `usePresence` 是同一套:
|
|
3803
|
+
* 看元素上正在跑的动效,一个都没有就当场拿掉——测试环境里东西删了就是删了)。
|
|
3804
|
+
*
|
|
3805
|
+
* 用法:`entries` 按顺序渲染成 `ref` 那个容器的直接子元素,走着的那几项带上
|
|
3806
|
+
* `data-leaving`(退场的样式挂在它上面),并且对键盘和读屏隐藏。钩子按这个标记找元素。
|
|
3807
|
+
*
|
|
3808
|
+
* - 这一次变化里同时有加进来的(整批换掉、"只收一个"换了一个):新走的不留,照旧直接换。
|
|
3809
|
+
* - 走到一半又加回来:它不再是走着的,留在页面上的还是同一个元素。
|
|
3810
|
+
* - `enabled` 为假时不留新走的(项太多的时候)。
|
|
3811
|
+
*/
|
|
3812
|
+
function useLingering(keys, enabled = true) {
|
|
3813
|
+
const ref = useRef(null);
|
|
3814
|
+
const [state, setState] = useState(() => ({
|
|
3815
|
+
keys,
|
|
3816
|
+
entries: merge([], keys, false)
|
|
3817
|
+
}));
|
|
3818
|
+
let { entries } = state;
|
|
3819
|
+
if (!same(state.keys, keys)) {
|
|
3820
|
+
const known = new Set(state.keys);
|
|
3821
|
+
const grew = keys.some((key) => !known.has(key));
|
|
3822
|
+
entries = merge(state.entries, keys, enabled && !grew);
|
|
3823
|
+
setState({
|
|
3824
|
+
keys,
|
|
3825
|
+
entries
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
const drop = useCallback((key) => {
|
|
3829
|
+
setState((current) => ({
|
|
3830
|
+
keys: current.keys,
|
|
3831
|
+
entries: current.entries.filter((entry) => !(entry.leaving && entry.key === key))
|
|
3832
|
+
}));
|
|
3833
|
+
}, []);
|
|
3834
|
+
const waits = useRef(/* @__PURE__ */ new Map());
|
|
3835
|
+
useLayoutEffect(() => {
|
|
3836
|
+
const pending = waits.current;
|
|
3837
|
+
const leaving = entries.filter((entry) => entry.leaving).map((entry) => entry.key);
|
|
3838
|
+
for (const [key, cancel] of pending) {
|
|
3839
|
+
if (leaving.includes(key)) continue;
|
|
3840
|
+
cancel();
|
|
3841
|
+
pending.delete(key);
|
|
3842
|
+
}
|
|
3843
|
+
if (leaving.length === 0) return;
|
|
3844
|
+
const nodes = [...ref.current?.children ?? []].filter((node) => node.hasAttribute("data-leaving"));
|
|
3845
|
+
leaving.forEach((key, index) => {
|
|
3846
|
+
if (pending.has(key)) return;
|
|
3847
|
+
pending.set(key, afterMotion(nodes[index] ?? null, () => drop(key)));
|
|
3848
|
+
});
|
|
3849
|
+
}, [entries, drop]);
|
|
3850
|
+
useEffect(() => {
|
|
3851
|
+
const pending = waits.current;
|
|
3852
|
+
return () => {
|
|
3853
|
+
for (const cancel of pending.values()) cancel();
|
|
3854
|
+
pending.clear();
|
|
3855
|
+
};
|
|
3856
|
+
}, []);
|
|
3857
|
+
return {
|
|
3858
|
+
ref,
|
|
3859
|
+
entries
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3862
|
+
//#endregion
|
|
3480
3863
|
//#region src/icons/Upload.tsx
|
|
3481
3864
|
/** 上传:一个托盘,上面一支朝上的箭头 */
|
|
3482
3865
|
const Upload = createIcon("Upload", /* @__PURE__ */ jsx("path", { d: "M12 16V4M7 9l5-5 5 5M4 15v5h16v-5" }));
|
|
@@ -3494,6 +3877,8 @@ const zoneSize = {
|
|
|
3494
3877
|
sm: "min-h-10 flex-wrap gap-x-3 px-3 py-2 text-sm",
|
|
3495
3878
|
md: "gap-3 px-4 py-4"
|
|
3496
3879
|
};
|
|
3880
|
+
/** 列表里认一个文件靠的那串字 */
|
|
3881
|
+
const fileKey = (file) => `${file.name}-${file.size}-${file.lastModified}`;
|
|
3497
3882
|
/** 拖进来的东西里的文件;文件夹不收 */
|
|
3498
3883
|
function droppedFiles(transfer) {
|
|
3499
3884
|
const items = [...transfer.items ?? []];
|
|
@@ -3505,6 +3890,25 @@ function droppedFiles(transfer) {
|
|
|
3505
3890
|
});
|
|
3506
3891
|
}
|
|
3507
3892
|
/**
|
|
3893
|
+
* 列表里的一行。淡入、收起都写在这一层:里面那一行可能是使用方自己画的(renderFile)。
|
|
3894
|
+
* 移除的那一行先留着——高度收到 0 并淡出,连它占的 4px 行距一起收,收完才卸载;
|
|
3895
|
+
* 下面的行和控件后面的内容是被带着上来的。
|
|
3896
|
+
*/
|
|
3897
|
+
function FileRow({ leaving, entering, children }) {
|
|
3898
|
+
const [last, setLast] = useState(children);
|
|
3899
|
+
if (!leaving && last !== children) setLast(children);
|
|
3900
|
+
return /* @__PURE__ */ jsx("li", {
|
|
3901
|
+
"data-leaving": leaving ? "" : void 0,
|
|
3902
|
+
inert: leaving,
|
|
3903
|
+
"aria-hidden": leaving || void 0,
|
|
3904
|
+
className: cn(collapse, "transition-[grid-template-rows,margin,opacity] duration-(--duration-fast)", "data-leaving:-mb-1 data-leaving:opacity-0", entering && "animate-fade-in [animation-duration:var(--duration-fast)]"),
|
|
3905
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3906
|
+
className: "min-h-0 min-w-0 in-data-leaving:overflow-y-clip",
|
|
3907
|
+
children: leaving ? last : children
|
|
3908
|
+
})
|
|
3909
|
+
});
|
|
3910
|
+
}
|
|
3911
|
+
/**
|
|
3508
3912
|
* 文件上传:选文件,或者把文件拖进来。它只管"选"和"列"——发请求、算进度是使用方的事。
|
|
3509
3913
|
*
|
|
3510
3914
|
* 里面是一个真的 `<input type="file">`,铺满整个投放区:点哪儿都是打开系统的文件框。
|
|
@@ -3529,6 +3933,9 @@ function FileUpload({ value, defaultValue, onValueChange, multiple = false, acce
|
|
|
3529
3933
|
defaultValue: defaultValue ?? [],
|
|
3530
3934
|
onChange: onValueChange
|
|
3531
3935
|
});
|
|
3936
|
+
const keys = files.map(fileKey);
|
|
3937
|
+
const added = useAddedKeys(keys);
|
|
3938
|
+
const lingering = useLingering(keys);
|
|
3532
3939
|
const [rejections, setRejections] = useState([]);
|
|
3533
3940
|
const [dragging, setDragging] = useState(false);
|
|
3534
3941
|
const depth = useRef(0);
|
|
@@ -3547,7 +3954,7 @@ function FileUpload({ value, defaultValue, onValueChange, multiple = false, acce
|
|
|
3547
3954
|
const index = pendingFocus.current;
|
|
3548
3955
|
if (index === null) return;
|
|
3549
3956
|
pendingFocus.current = null;
|
|
3550
|
-
const buttons = listRef.current?.querySelectorAll("[data-file-remove]") ?? [];
|
|
3957
|
+
const buttons = listRef.current?.querySelectorAll("li:not([data-leaving]) [data-file-remove]") ?? [];
|
|
3551
3958
|
const target = buttons[Math.min(index, buttons.length - 1)];
|
|
3552
3959
|
if (target) target.focus();
|
|
3553
3960
|
else inputRef.current?.focus();
|
|
@@ -3683,22 +4090,29 @@ function FileUpload({ value, defaultValue, onValueChange, multiple = false, acce
|
|
|
3683
4090
|
})]
|
|
3684
4091
|
})
|
|
3685
4092
|
}),
|
|
3686
|
-
|
|
3687
|
-
ref: listRef,
|
|
4093
|
+
lingering.entries.length > 0 && /* @__PURE__ */ jsx("ul", {
|
|
4094
|
+
ref: mergeRefs(listRef, lingering.ref),
|
|
3688
4095
|
"aria-label": labels.list,
|
|
3689
|
-
|
|
3690
|
-
|
|
4096
|
+
"data-emptying": files.length === 0 ? "" : void 0,
|
|
4097
|
+
className: "flex flex-col gap-1 transition-[margin] duration-(--duration-fast) ease-standard data-emptying:-mt-2",
|
|
4098
|
+
children: lingering.entries.map(({ key, leaving }) => {
|
|
4099
|
+
const index = keys.indexOf(key);
|
|
4100
|
+
const file = files[index];
|
|
3691
4101
|
const actions = {
|
|
3692
4102
|
remove: () => remove(index),
|
|
3693
4103
|
index
|
|
3694
4104
|
};
|
|
3695
|
-
return /* @__PURE__ */ jsx(
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
4105
|
+
return /* @__PURE__ */ jsx(FileRow, {
|
|
4106
|
+
leaving,
|
|
4107
|
+
entering: added.has(key),
|
|
4108
|
+
children: file !== void 0 && (renderFile ? renderFile(file, actions) : /* @__PURE__ */ jsx(FileItem, {
|
|
4109
|
+
name: file.name,
|
|
4110
|
+
size: file.size,
|
|
4111
|
+
disabled,
|
|
4112
|
+
removeLabel: removeLabel(file.name),
|
|
4113
|
+
onRemove: actions.remove
|
|
4114
|
+
}))
|
|
4115
|
+
}, key);
|
|
3702
4116
|
})
|
|
3703
4117
|
}),
|
|
3704
4118
|
/* @__PURE__ */ jsx("span", {
|
|
@@ -4574,23 +4988,8 @@ function ListRow({ start, end, description, selected, completed = false, complet
|
|
|
4574
4988
|
* 加载页:近黑底、大号等宽百分比、一行标语、黄色进度条。只用于整页的首次加载。
|
|
4575
4989
|
* 不伪造进度;内容就绪后立刻把 `open` 置为 `false`,不要为了播完动画而等待。
|
|
4576
4990
|
*/
|
|
4577
|
-
function Loader({ value, tagline, open = true, fullscreen = true, label = "加载中", onExited, onKeyDown,
|
|
4578
|
-
const rootRef =
|
|
4579
|
-
const [mounted, setMounted] = useState(open);
|
|
4580
|
-
if (open && !mounted) setMounted(true);
|
|
4581
|
-
const onExitedRef = useRef(onExited);
|
|
4582
|
-
useEffect(() => {
|
|
4583
|
-
onExitedRef.current = onExited;
|
|
4584
|
-
});
|
|
4585
|
-
const leaving = !open && mounted;
|
|
4586
|
-
useEffect(() => {
|
|
4587
|
-
if (!leaving) return;
|
|
4588
|
-
const timer = window.setTimeout(() => {
|
|
4589
|
-
setMounted(false);
|
|
4590
|
-
onExitedRef.current?.();
|
|
4591
|
-
}, 700);
|
|
4592
|
-
return () => window.clearTimeout(timer);
|
|
4593
|
-
}, [leaving]);
|
|
4991
|
+
function Loader({ value, tagline, open = true, fullscreen = true, label = "加载中", onExited, onKeyDown, className, ref, ...props }) {
|
|
4992
|
+
const { ref: rootRef, mounted } = usePresence(open, { onExited });
|
|
4594
4993
|
useEffect(() => {
|
|
4595
4994
|
if (!fullscreen || !open) return;
|
|
4596
4995
|
const page = document.documentElement;
|
|
@@ -4602,15 +5001,12 @@ function Loader({ value, tagline, open = true, fullscreen = true, label = "加
|
|
|
4602
5001
|
page.style.overflow = previousOverflow;
|
|
4603
5002
|
if (previousFocus instanceof HTMLElement && previousFocus.isConnected) previousFocus.focus({ preventScroll: true });
|
|
4604
5003
|
};
|
|
4605
|
-
}, [
|
|
5004
|
+
}, [
|
|
5005
|
+
fullscreen,
|
|
5006
|
+
open,
|
|
5007
|
+
rootRef
|
|
5008
|
+
]);
|
|
4606
5009
|
if (!mounted) return null;
|
|
4607
|
-
const handleTransitionEnd = (event) => {
|
|
4608
|
-
onTransitionEnd?.(event);
|
|
4609
|
-
if (leaving && event.target === event.currentTarget) {
|
|
4610
|
-
setMounted(false);
|
|
4611
|
-
onExitedRef.current?.();
|
|
4612
|
-
}
|
|
4613
|
-
};
|
|
4614
5010
|
const handleKeyDown = (event) => {
|
|
4615
5011
|
onKeyDown?.(event);
|
|
4616
5012
|
if (fullscreen && open && event.key === "Tab") event.preventDefault();
|
|
@@ -4618,12 +5014,11 @@ function Loader({ value, tagline, open = true, fullscreen = true, label = "加
|
|
|
4618
5014
|
const indeterminate = value === void 0;
|
|
4619
5015
|
const current = indeterminate ? 0 : Math.min(Math.max(value, 0), 100);
|
|
4620
5016
|
return /* @__PURE__ */ jsx("div", {
|
|
4621
|
-
ref: rootRef,
|
|
4622
5017
|
tabIndex: -1,
|
|
4623
5018
|
...props,
|
|
5019
|
+
ref: mergeRefs(rootRef, ref),
|
|
4624
5020
|
"data-state": open ? "open" : "closing",
|
|
4625
5021
|
onKeyDown: handleKeyDown,
|
|
4626
|
-
onTransitionEnd: handleTransitionEnd,
|
|
4627
5022
|
className: cn("@container flex flex-col justify-end bg-neutral-950 text-neutral-0 outline-none", fullscreen ? "fixed inset-0 z-(--z-loader)" : "absolute inset-0", "transition-[translate] duration-(--duration-slower) ease-emphasis", open ? "translate-y-0" : "pointer-events-none -translate-y-full", className),
|
|
4628
5023
|
children: /* @__PURE__ */ jsxs("div", {
|
|
4629
5024
|
className: "flex flex-col gap-4 p-6 @md:p-10",
|
|
@@ -4649,9 +5044,9 @@ function Loader({ value, tagline, open = true, fullscreen = true, label = "加
|
|
|
4649
5044
|
"aria-valuemin": 0,
|
|
4650
5045
|
"aria-valuemax": 100,
|
|
4651
5046
|
"aria-valuenow": indeterminate ? void 0 : current,
|
|
4652
|
-
className: "relative h-1.5 overflow-hidden rounded-sm bg-neutral-800",
|
|
4653
|
-
children: indeterminate ? /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-0 w-2/5 animate-indeterminate bg-action motion-reduce:translate-x-3/4" }) : /* @__PURE__ */ jsx("span", {
|
|
4654
|
-
className: "absolute inset-0 origin-left bg-action transition-transform duration-(--duration-base) ease-standard",
|
|
5047
|
+
className: "relative h-1.5 overflow-hidden rounded-sm bg-neutral-800 forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[CanvasText]",
|
|
5048
|
+
children: indeterminate ? /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 left-0 w-2/5 animate-indeterminate bg-action motion-reduce:translate-x-3/4 forced-colors:bg-[Highlight]" }) : /* @__PURE__ */ jsx("span", {
|
|
5049
|
+
className: "absolute inset-0 origin-left bg-action transition-transform duration-(--duration-base) ease-standard forced-colors:bg-[Highlight]",
|
|
4655
5050
|
style: { transform: `scaleX(${current / 100})` }
|
|
4656
5051
|
})
|
|
4657
5052
|
})
|
|
@@ -4963,7 +5358,7 @@ function NavMenuItem({ icon, current = false, disabled = false, closeOnClick = t
|
|
|
4963
5358
|
const classes = cn("relative flex h-14 w-full items-center gap-3 px-4 text-left text-base font-medium", "transition-colors duration-(--duration-fast) ease-standard", disabled ? "cursor-not-allowed bg-surface-sunken text-ink-disabled" : current ? [
|
|
4964
5359
|
"bg-action font-bold text-on-action",
|
|
4965
5360
|
"before:absolute before:inset-y-0 before:left-0 before:w-1 before:bg-on-action before:content-['']",
|
|
4966
|
-
"focus-visible:outline-2 focus-visible:-outline-offset-
|
|
5361
|
+
"focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-on-action"
|
|
4967
5362
|
] : ["bg-surface-sunken text-ink hover:bg-surface-muted active:bg-line", focusRingInset]);
|
|
4968
5363
|
const mark = disabled ? "text-ink-disabled" : current ? "text-on-action" : "text-ink-secondary";
|
|
4969
5364
|
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
@@ -5125,7 +5520,7 @@ function OtpInput({ length = 6, value, defaultValue, onValueChange, onComplete,
|
|
|
5125
5520
|
const labelId = useId();
|
|
5126
5521
|
const ownLabel = ariaLabelledBy ?? (ariaLabel ? labelId : void 0);
|
|
5127
5522
|
const groupLabel = ownLabel ?? fieldContext?.labelId;
|
|
5128
|
-
const slotClass = cn("min-w-0 shrink text-center font-tech font-medium tabular-nums text-transparent caret-ink", "transition-colors duration-(--duration-fast) ease-standard", disabled ? "data-filled:text-ink-disabled" : "data-filled:text-ink", focusRing, slotSize[size], slotFrame[variant], readOnly ? "border-transparent bg-transparent" : slotFill[variant], disabled ? "cursor-not-allowed border-line" : invalid ? "border-danger" : !readOnly && "border-line-strong hover:not-focus:border-ink-secondary focus:border-ink");
|
|
5523
|
+
const slotClass = cn("min-w-0 shrink text-center font-tech font-medium tabular-nums text-transparent caret-ink", "transition-colors duration-(--duration-fast) ease-standard", disabled ? "data-filled:text-ink-disabled" : "data-filled:text-ink", focusRing, slotSize[size], slotFrame[variant], readOnly ? "border-transparent bg-transparent" : slotFill[variant], readOnly && !disabled && !invalid && "forced-colors:border-[Canvas]", disabled ? "cursor-not-allowed border-line" : invalid ? "border-danger" : !readOnly && "border-line-strong hover:not-focus:border-ink-secondary focus:border-ink");
|
|
5129
5524
|
const group = groupSize !== void 0 && groupSize > 0 ? groupSize : 0;
|
|
5130
5525
|
return /* @__PURE__ */ jsxs(OTPField.Root, {
|
|
5131
5526
|
...props,
|
|
@@ -5157,7 +5552,7 @@ function OtpInput({ length = 6, value, defaultValue, onValueChange, onComplete,
|
|
|
5157
5552
|
}), Array.from({ length }, (_, index) => /* @__PURE__ */ jsxs(Fragment, { children: [group > 0 && index > 0 && index % group === 0 && /* @__PURE__ */ jsx("span", {
|
|
5158
5553
|
"aria-hidden": "true",
|
|
5159
5554
|
"data-separator": "",
|
|
5160
|
-
className: "h-0.5 w-2 shrink-0 bg-line-strong"
|
|
5555
|
+
className: "h-0.5 w-2 shrink-0 bg-line-strong forced-colors:bg-[CanvasText]"
|
|
5161
5556
|
}), /* @__PURE__ */ jsx(OTPField.Input, {
|
|
5162
5557
|
...index === 0 ? {
|
|
5163
5558
|
"aria-labelledby": ownLabel,
|
|
@@ -5175,12 +5570,20 @@ function OtpInput({ length = 6, value, defaultValue, onValueChange, onComplete,
|
|
|
5175
5570
|
const ArrowLeft = createIcon("ArrowLeft", /* @__PURE__ */ jsx("path", { d: "M20 12H5M11 5l-7 7 7 7" }));
|
|
5176
5571
|
//#endregion
|
|
5177
5572
|
//#region src/components/page-header/PageHeader.tsx
|
|
5573
|
+
const enter = "animate-shift-in [--shift-x:calc(var(--motion-shift-lg)*-1)]";
|
|
5574
|
+
const enterStep = [
|
|
5575
|
+
enter,
|
|
5576
|
+
`${enter} [animation-delay:100ms]`,
|
|
5577
|
+
`${enter} [animation-delay:200ms]`
|
|
5578
|
+
];
|
|
5178
5579
|
/**
|
|
5179
5580
|
* 页头:这是哪一页、怎么回去、在这一页能做什么。一页一个。
|
|
5180
5581
|
* 按自身的宽度响应:行动区放不下时折到标题下面。
|
|
5181
5582
|
*/
|
|
5182
|
-
function PageHeader({ title, level = 1, meta, description, breadcrumb, back, actions, divider = false, sticky = false, className, children, ...props }) {
|
|
5583
|
+
function PageHeader({ title, level = 1, meta, description, breadcrumb, back, actions, divider = false, sticky = false, animate = true, className, children, ...props }) {
|
|
5183
5584
|
const Heading = `h${level}`;
|
|
5585
|
+
const reveal = animate && !sticky;
|
|
5586
|
+
const first = meta ? 1 : 0;
|
|
5184
5587
|
return /* @__PURE__ */ jsxs("header", {
|
|
5185
5588
|
...props,
|
|
5186
5589
|
"data-sticky": sticky ? "" : void 0,
|
|
@@ -5190,7 +5593,7 @@ function PageHeader({ title, level = 1, meta, description, breadcrumb, back, act
|
|
|
5190
5593
|
/* @__PURE__ */ jsxs("div", {
|
|
5191
5594
|
className: "flex flex-col gap-1",
|
|
5192
5595
|
children: [meta && /* @__PURE__ */ jsx("p", {
|
|
5193
|
-
className: "font-tech text-xs text-ink-secondary",
|
|
5596
|
+
className: cn("font-tech text-xs text-ink-secondary", reveal && enterStep[0]),
|
|
5194
5597
|
children: meta
|
|
5195
5598
|
}), /* @__PURE__ */ jsxs("div", {
|
|
5196
5599
|
className: "flex flex-wrap items-start justify-between gap-x-6 gap-y-4",
|
|
@@ -5202,10 +5605,10 @@ function PageHeader({ title, level = 1, meta, description, breadcrumb, back, act
|
|
|
5202
5605
|
}), /* @__PURE__ */ jsxs("div", {
|
|
5203
5606
|
className: "flex min-w-0 flex-col gap-1",
|
|
5204
5607
|
children: [/* @__PURE__ */ jsx(Heading, {
|
|
5205
|
-
className: "min-w-0 text-2xl leading-10 font-bold wrap-anywhere @md:text-3xl @md:leading-12",
|
|
5608
|
+
className: cn("min-w-0 text-2xl leading-10 font-bold wrap-anywhere @md:text-3xl @md:leading-12", reveal && enterStep[first]),
|
|
5206
5609
|
children: title
|
|
5207
5610
|
}), description && /* @__PURE__ */ jsx("p", {
|
|
5208
|
-
className: "max-w-prose text-sm text-ink-secondary",
|
|
5611
|
+
className: cn("max-w-prose text-sm text-ink-secondary", reveal && enterStep[first + 1]),
|
|
5209
5612
|
children: description
|
|
5210
5613
|
})]
|
|
5211
5614
|
})]
|
|
@@ -5299,7 +5702,7 @@ function Pagination({ page, defaultPage = 1, onPageChange, pageCount, jump = fal
|
|
|
5299
5702
|
"aria-label": "分页",
|
|
5300
5703
|
...props,
|
|
5301
5704
|
"data-size": size,
|
|
5302
|
-
className: cn("@container hatch flex items-center justify-between gap-2 bg-surface-muted px-1 text-ink", barSize[size], className),
|
|
5705
|
+
className: cn("@container hatch flex items-center justify-between gap-2 bg-surface-muted px-1 text-ink", "forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[ButtonText]", barSize[size], className),
|
|
5303
5706
|
children: [
|
|
5304
5707
|
/* @__PURE__ */ jsx(IconButton, {
|
|
5305
5708
|
variant: "accent",
|
|
@@ -5458,6 +5861,8 @@ function PopoverClose({ children }) {
|
|
|
5458
5861
|
}
|
|
5459
5862
|
//#endregion
|
|
5460
5863
|
//#region src/components/progress/ProgressRing.tsx
|
|
5864
|
+
const trackClass = "stroke-line forced-colors:stroke-[CanvasText] forced-colors:stroke-1";
|
|
5865
|
+
const arcClass = "stroke-action forced-colors:stroke-[Highlight]";
|
|
5461
5866
|
/**
|
|
5462
5867
|
* 进度环:细圆环轨道 + 从 12 点方向顺时针的黄弧。用在紧凑的位置,或者围住一个头像、图标。
|
|
5463
5868
|
* 取色与 `Progress` 相同;需要名称时传 `aria-label`。
|
|
@@ -5479,16 +5884,16 @@ function ProgressRing({ value, max = 100, size = 48, thickness, showValue = fals
|
|
|
5479
5884
|
let arc = null;
|
|
5480
5885
|
if (indeterminate) arc = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("circle", {
|
|
5481
5886
|
...circle,
|
|
5482
|
-
className: "origin-center animate-spin
|
|
5887
|
+
className: cn("origin-center animate-spin motion-reduce:hidden", arcClass),
|
|
5483
5888
|
strokeDasharray: `${circumference / 4} ${circumference}`
|
|
5484
5889
|
}), /* @__PURE__ */ jsx("circle", {
|
|
5485
5890
|
...circle,
|
|
5486
|
-
className: "hidden
|
|
5891
|
+
className: cn("hidden motion-reduce:block", arcClass),
|
|
5487
5892
|
strokeDasharray: `${stroke} ${stroke * 2}`
|
|
5488
5893
|
})] });
|
|
5489
5894
|
else if (ratio > 0) arc = /* @__PURE__ */ jsx("circle", {
|
|
5490
5895
|
...circle,
|
|
5491
|
-
className: "
|
|
5896
|
+
className: cn("transition-[stroke-dashoffset] duration-(--duration-base) ease-standard", arcClass),
|
|
5492
5897
|
strokeDasharray: circumference,
|
|
5493
5898
|
strokeDashoffset: circumference * (1 - ratio)
|
|
5494
5899
|
});
|
|
@@ -5522,7 +5927,7 @@ function ProgressRing({ value, max = 100, size = 48, thickness, showValue = fals
|
|
|
5522
5927
|
className: "absolute inset-0 -rotate-90",
|
|
5523
5928
|
children: [/* @__PURE__ */ jsx("circle", {
|
|
5524
5929
|
...circle,
|
|
5525
|
-
className:
|
|
5930
|
+
className: trackClass
|
|
5526
5931
|
}), arc]
|
|
5527
5932
|
}), center]
|
|
5528
5933
|
});
|
|
@@ -5610,7 +6015,7 @@ function Radio({ value, disabled, onChange, className, children, ...props }) {
|
|
|
5610
6015
|
className: cn(choiceBox, "rounded-full choice-diamond:rounded-none", focusRing)
|
|
5611
6016
|
}), /* @__PURE__ */ jsx("span", {
|
|
5612
6017
|
"aria-hidden": "true",
|
|
5613
|
-
className: cn(choiceMark, "size-2 rounded-full bg-accent-ink-inverse peer-checked:opacity-100 peer-disabled:bg-ink-disabled choice-diamond:hidden")
|
|
6018
|
+
className: cn(choiceMark, "size-2 rounded-full bg-accent-ink-inverse peer-checked:opacity-100 peer-disabled:bg-ink-disabled choice-diamond:hidden", "forced-colors:bg-[HighlightText] forced-colors:peer-disabled:bg-[GrayText]")
|
|
5614
6019
|
})]
|
|
5615
6020
|
})
|
|
5616
6021
|
}), children !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
@@ -5671,6 +6076,105 @@ function RegistrationStrip({ orientation = "horizontal", rule = false, className
|
|
|
5671
6076
|
});
|
|
5672
6077
|
}
|
|
5673
6078
|
//#endregion
|
|
6079
|
+
//#region src/hooks/useReducedMotion.ts
|
|
6080
|
+
const QUERY = "(prefers-reduced-motion: reduce)";
|
|
6081
|
+
function subscribe$1(notify) {
|
|
6082
|
+
if (typeof window.matchMedia !== "function") return () => {};
|
|
6083
|
+
const media = window.matchMedia(QUERY);
|
|
6084
|
+
media.addEventListener("change", notify);
|
|
6085
|
+
return () => media.removeEventListener("change", notify);
|
|
6086
|
+
}
|
|
6087
|
+
function read() {
|
|
6088
|
+
return typeof window.matchMedia === "function" && window.matchMedia(QUERY).matches;
|
|
6089
|
+
}
|
|
6090
|
+
/**
|
|
6091
|
+
* 系统开了"减少动态效果"时返回 true,设置变了会跟着变。
|
|
6092
|
+
*
|
|
6093
|
+
* CSS 的动画和过渡不用它:`theme.css` 末尾的全局规则已经统一降级了。
|
|
6094
|
+
* 只有脚本驱动的动效(一帧一帧自己算的)才要看它——那条规则管不到脚本。
|
|
6095
|
+
*/
|
|
6096
|
+
function useReducedMotion() {
|
|
6097
|
+
return useSyncExternalStore(subscribe$1, read, () => false);
|
|
6098
|
+
}
|
|
6099
|
+
//#endregion
|
|
6100
|
+
//#region src/components/rolling-number/RollingNumber.tsx
|
|
6101
|
+
const FALLBACK_DURATION = 600;
|
|
6102
|
+
function readDuration(node) {
|
|
6103
|
+
if (!node) return FALLBACK_DURATION;
|
|
6104
|
+
const raw = getComputedStyle(node).getPropertyValue("--duration-slower");
|
|
6105
|
+
const amount = Number.parseFloat(raw);
|
|
6106
|
+
if (!Number.isFinite(amount) || amount <= 0) return FALLBACK_DURATION;
|
|
6107
|
+
return raw.trim().endsWith("ms") ? amount : amount * 1e3;
|
|
6108
|
+
}
|
|
6109
|
+
/** 减速:先快后慢,停得住。和 `ease-exit` 是一类,不带过冲 */
|
|
6110
|
+
const decelerate = (progress) => 1 - (1 - progress) ** 3;
|
|
6111
|
+
function fractionDigits(value) {
|
|
6112
|
+
const fraction = new Intl.NumberFormat("en-US", {
|
|
6113
|
+
maximumFractionDigits: 20,
|
|
6114
|
+
useGrouping: false
|
|
6115
|
+
}).format(value).split(".")[1];
|
|
6116
|
+
return fraction ? fraction.length : 0;
|
|
6117
|
+
}
|
|
6118
|
+
const never = () => () => {};
|
|
6119
|
+
/**
|
|
6120
|
+
* 滚动数字:进入视口时从 0 滚到终值,只滚一次。放进 `Stat` 的 `value` 里,
|
|
6121
|
+
* 或者任何"一个需要被一眼看到的数字"的地方。等宽数字由所在处给(`tabular-nums`)。
|
|
6122
|
+
*
|
|
6123
|
+
* - 读屏只读终值:终值一直在,滚的时候只是看不见;滚动的那一份对读屏隐藏。
|
|
6124
|
+
* - 宽度由终值占着,滚的时候旁边的东西不动。
|
|
6125
|
+
* - 服务端渲染出来的、打印出来的、"减少动态效果"下的,都直接是终值。
|
|
6126
|
+
* - 值后来变了直接换,不再滚。
|
|
6127
|
+
*/
|
|
6128
|
+
function RollingNumber({ value, format, animate = true, className, ref, ...props }) {
|
|
6129
|
+
const digits = fractionDigits(value);
|
|
6130
|
+
const write = format ?? ((amount) => amount.toLocaleString("en-US", {
|
|
6131
|
+
minimumFractionDigits: digits,
|
|
6132
|
+
maximumFractionDigits: digits
|
|
6133
|
+
}));
|
|
6134
|
+
const mounted = useSyncExternalStore(never, () => true, () => false);
|
|
6135
|
+
const reduced = useReducedMotion();
|
|
6136
|
+
const [done, setDone] = useState(false);
|
|
6137
|
+
const [shown, setShown] = useState(0);
|
|
6138
|
+
const rolling = mounted && animate && !reduced && !done;
|
|
6139
|
+
const [inViewRef, inView] = useInView({ disabled: !rolling });
|
|
6140
|
+
useEffect(() => {
|
|
6141
|
+
if (!rolling || !inView) return;
|
|
6142
|
+
const duration = readDuration(inViewRef.current);
|
|
6143
|
+
let started;
|
|
6144
|
+
let frame = requestAnimationFrame(function tick(now) {
|
|
6145
|
+
started ??= now;
|
|
6146
|
+
const progress = Math.min((now - started) / duration, 1);
|
|
6147
|
+
if (progress >= 1) {
|
|
6148
|
+
setDone(true);
|
|
6149
|
+
return;
|
|
6150
|
+
}
|
|
6151
|
+
setShown(Number((value * decelerate(progress)).toFixed(digits)));
|
|
6152
|
+
frame = requestAnimationFrame(tick);
|
|
6153
|
+
});
|
|
6154
|
+
return () => cancelAnimationFrame(frame);
|
|
6155
|
+
}, [
|
|
6156
|
+
rolling,
|
|
6157
|
+
inView,
|
|
6158
|
+
inViewRef,
|
|
6159
|
+
value,
|
|
6160
|
+
digits
|
|
6161
|
+
]);
|
|
6162
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
6163
|
+
...props,
|
|
6164
|
+
ref: mergeRefs(inViewRef, ref),
|
|
6165
|
+
"data-rolling": rolling ? "" : void 0,
|
|
6166
|
+
className: cn(rolling && "relative inline-block", className),
|
|
6167
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6168
|
+
className: rolling ? "opacity-0 print:opacity-100" : void 0,
|
|
6169
|
+
children: write(value)
|
|
6170
|
+
}), rolling && /* @__PURE__ */ jsx("span", {
|
|
6171
|
+
"aria-hidden": "true",
|
|
6172
|
+
className: "absolute inset-0 print:hidden",
|
|
6173
|
+
children: write(shown)
|
|
6174
|
+
})]
|
|
6175
|
+
});
|
|
6176
|
+
}
|
|
6177
|
+
//#endregion
|
|
5674
6178
|
//#region src/hooks/useOverflowing.ts
|
|
5675
6179
|
/**
|
|
5676
6180
|
* 容器里的内容是不是比容器宽、比容器高。
|
|
@@ -5806,11 +6310,11 @@ function Schedule({ label, start, end, anchors: anchorsProp, today, dayWidth = 3
|
|
|
5806
6310
|
}),
|
|
5807
6311
|
todayColumn !== null && /* @__PURE__ */ jsx("div", {
|
|
5808
6312
|
"aria-hidden": "true",
|
|
5809
|
-
className: cn("pointer-events-none
|
|
6313
|
+
className: cn("pointer-events-none absolute inset-y-0 right-0 z-2 select-none print:hidden", AFTER_RAIL, dayColumns),
|
|
5810
6314
|
children: /* @__PURE__ */ jsx("span", {
|
|
5811
6315
|
"data-today": "",
|
|
5812
6316
|
style: { gridColumn: todayColumn },
|
|
5813
|
-
className: "row-start-1 border-l-2 border-accent-ink"
|
|
6317
|
+
className: "row-start-1 border-l-2 border-accent-ink forced-colors:border-[Highlight]"
|
|
5814
6318
|
})
|
|
5815
6319
|
})
|
|
5816
6320
|
]
|
|
@@ -5872,7 +6376,7 @@ function ScheduleItem({ start, end, title, type, variant = "banner", media, href
|
|
|
5872
6376
|
gridColumn: `${from} / ${to}`,
|
|
5873
6377
|
...style
|
|
5874
6378
|
},
|
|
5875
|
-
className: cn("@container relative isolate flex min-w-0 overflow-clip text-sm", "has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-focus", banner ? "items-start justify-between gap-2 bg-surface-muted p-2" : "items-center gap-2 bg-control pr-2 text-on-control", className),
|
|
6379
|
+
className: cn("@container relative isolate flex min-w-0 overflow-clip text-sm", "has-focus-visible:outline-2 has-focus-visible:-outline-offset-2 has-focus-visible:outline-focus", "forced-colors:border forced-colors:border-[CanvasText]", banner ? "items-start justify-between gap-2 bg-surface-muted p-2" : "items-center gap-2 bg-control pr-2 text-on-control", className),
|
|
5876
6380
|
children: [banner ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5877
6381
|
media && /* @__PURE__ */ jsx("div", {
|
|
5878
6382
|
"aria-hidden": "true",
|
|
@@ -5995,40 +6499,6 @@ function ScrollHint({ label = "SCROLL", variant = "line", target, threshold = 8,
|
|
|
5995
6499
|
});
|
|
5996
6500
|
}
|
|
5997
6501
|
//#endregion
|
|
5998
|
-
//#region src/hooks/useInView.ts
|
|
5999
|
-
/**
|
|
6000
|
-
* 元素第一次进入视口后返回 true,之后不再变回 false——入场动画只播一次。
|
|
6001
|
-
*/
|
|
6002
|
-
function useInView({ disabled = false, rootMargin = "0px", threshold = 0 } = {}) {
|
|
6003
|
-
const ref = useRef(null);
|
|
6004
|
-
const [inView, setInView] = useState(false);
|
|
6005
|
-
useEffect(() => {
|
|
6006
|
-
const node = ref.current;
|
|
6007
|
-
if (disabled || inView || !node) return;
|
|
6008
|
-
if (typeof IntersectionObserver === "undefined") {
|
|
6009
|
-
setInView(true);
|
|
6010
|
-
return;
|
|
6011
|
-
}
|
|
6012
|
-
const observer = new IntersectionObserver((entries) => {
|
|
6013
|
-
if (entries.some((entry) => entry.isIntersecting)) {
|
|
6014
|
-
setInView(true);
|
|
6015
|
-
observer.disconnect();
|
|
6016
|
-
}
|
|
6017
|
-
}, {
|
|
6018
|
-
rootMargin,
|
|
6019
|
-
threshold
|
|
6020
|
-
});
|
|
6021
|
-
observer.observe(node);
|
|
6022
|
-
return () => observer.disconnect();
|
|
6023
|
-
}, [
|
|
6024
|
-
disabled,
|
|
6025
|
-
inView,
|
|
6026
|
-
rootMargin,
|
|
6027
|
-
threshold
|
|
6028
|
-
]);
|
|
6029
|
-
return [ref, inView];
|
|
6030
|
-
}
|
|
6031
|
-
//#endregion
|
|
6032
6502
|
//#region src/components/section-title/SectionTitle.tsx
|
|
6033
6503
|
function SectionTitle({ variant = "standard", children, latin, subtitle, meta, level = 2, animate = true, latinHidden = true, tone = "accent", illustration, className, ref: forwardedRef, ...props }) {
|
|
6034
6504
|
const Heading = `h${level}`;
|
|
@@ -6131,6 +6601,105 @@ function SectionTitle({ variant = "standard", children, latin, subtitle, meta, l
|
|
|
6131
6601
|
});
|
|
6132
6602
|
}
|
|
6133
6603
|
//#endregion
|
|
6604
|
+
//#region src/hooks/useIndicator.ts
|
|
6605
|
+
const VARS = [
|
|
6606
|
+
"--indicator-x",
|
|
6607
|
+
"--indicator-y",
|
|
6608
|
+
"--indicator-w",
|
|
6609
|
+
"--indicator-h"
|
|
6610
|
+
];
|
|
6611
|
+
function clear(node) {
|
|
6612
|
+
if (node.dataset.indicator === void 0) return;
|
|
6613
|
+
delete node.dataset.indicator;
|
|
6614
|
+
for (const name of VARS) node.style.removeProperty(name);
|
|
6615
|
+
}
|
|
6616
|
+
/** 量一次、写一次。`last` 记着上一次指的是哪一项:换了才滑 */
|
|
6617
|
+
function place(node, selector, last) {
|
|
6618
|
+
const box = node.getBoundingClientRect();
|
|
6619
|
+
if (box.width === 0 || box.height === 0) {
|
|
6620
|
+
clear(node);
|
|
6621
|
+
last.current = null;
|
|
6622
|
+
return;
|
|
6623
|
+
}
|
|
6624
|
+
const item = node.querySelector(selector);
|
|
6625
|
+
const rect = item?.getBoundingClientRect();
|
|
6626
|
+
if (!item || !rect || rect.width === 0 || rect.height === 0) {
|
|
6627
|
+
node.dataset.indicator = "off";
|
|
6628
|
+
last.current = null;
|
|
6629
|
+
return;
|
|
6630
|
+
}
|
|
6631
|
+
const laid = Number.parseFloat(getComputedStyle(node).width);
|
|
6632
|
+
const scale = laid > 0 ? box.width / laid : 1;
|
|
6633
|
+
const values = [
|
|
6634
|
+
(rect.left - box.left) / scale - node.clientLeft + node.scrollLeft,
|
|
6635
|
+
(rect.top - box.top) / scale - node.clientTop + node.scrollTop,
|
|
6636
|
+
rect.width / scale,
|
|
6637
|
+
rect.height / scale
|
|
6638
|
+
].map((value) => `${Math.round(value * 100) / 100}px`);
|
|
6639
|
+
const slide = node.dataset.indicator === "on" && last.current !== null && last.current !== item;
|
|
6640
|
+
last.current = item;
|
|
6641
|
+
if (VARS.some((name, at) => node.style.getPropertyValue(name) !== values[at])) {
|
|
6642
|
+
if (!slide) node.dataset.indicatorInstant = "";
|
|
6643
|
+
VARS.forEach((name, at) => node.style.setProperty(name, values[at]));
|
|
6644
|
+
if (!slide) {
|
|
6645
|
+
node.getBoundingClientRect();
|
|
6646
|
+
delete node.dataset.indicatorInstant;
|
|
6647
|
+
}
|
|
6648
|
+
}
|
|
6649
|
+
node.dataset.indicator = "on";
|
|
6650
|
+
}
|
|
6651
|
+
/**
|
|
6652
|
+
* 选中指示的位置:量出容器里"当前项"相对容器的位置和尺寸,写成容器上的 CSS 变量
|
|
6653
|
+
* (`--indicator-x` / `-y` / `-w` / `-h`),并在容器上标 `data-indicator`——`on` 是有当前项,
|
|
6654
|
+
* `off` 是没有。指示本身由用的地方画在容器的 `::after` 上(`lib/motion.ts` 的 `indicator`),
|
|
6655
|
+
* 换了一项时由 CSS 的过渡滑过去。
|
|
6656
|
+
*
|
|
6657
|
+
* 只有"换了一项"才滑;第一次到位、同一项挪了地方或变了大小(容器宽了、字体到了、
|
|
6658
|
+
* 前面多了一项)都直接到位。量不到(服务端、还没排版、藏着)时什么都不标,
|
|
6659
|
+
* 各项照旧自己画选中态——不会有哪一帧没有选中态。
|
|
6660
|
+
*
|
|
6661
|
+
* `current` 是当前项在容器里的选择器;传 `null` 是不用。
|
|
6662
|
+
*/
|
|
6663
|
+
function useIndicator(current) {
|
|
6664
|
+
const ref = useRef(null);
|
|
6665
|
+
const last = useRef(null);
|
|
6666
|
+
const observer = useRef(null);
|
|
6667
|
+
const watched = useRef([]);
|
|
6668
|
+
useLayoutEffect(() => {
|
|
6669
|
+
const node = ref.current;
|
|
6670
|
+
if (!node) return;
|
|
6671
|
+
if (current === null) {
|
|
6672
|
+
clear(node);
|
|
6673
|
+
last.current = null;
|
|
6674
|
+
return;
|
|
6675
|
+
}
|
|
6676
|
+
place(node, current, last);
|
|
6677
|
+
const watching = observer.current;
|
|
6678
|
+
if (!watching) return;
|
|
6679
|
+
const next = [node, ...node.children];
|
|
6680
|
+
const before = watched.current;
|
|
6681
|
+
if (next.length === before.length && next.every((element, at) => element === before[at])) return;
|
|
6682
|
+
watching.disconnect();
|
|
6683
|
+
for (const element of next) watching.observe(element);
|
|
6684
|
+
watched.current = next;
|
|
6685
|
+
});
|
|
6686
|
+
useLayoutEffect(() => {
|
|
6687
|
+
const node = ref.current;
|
|
6688
|
+
if (!node || current === null || typeof ResizeObserver === "undefined") return;
|
|
6689
|
+
const watching = new ResizeObserver(() => place(node, current, last));
|
|
6690
|
+
const targets = [node, ...node.children];
|
|
6691
|
+
for (const element of targets) watching.observe(element);
|
|
6692
|
+
observer.current = watching;
|
|
6693
|
+
watched.current = targets;
|
|
6694
|
+
return () => {
|
|
6695
|
+
watching.disconnect();
|
|
6696
|
+
observer.current = null;
|
|
6697
|
+
watched.current = [];
|
|
6698
|
+
};
|
|
6699
|
+
}, [current]);
|
|
6700
|
+
return ref;
|
|
6701
|
+
}
|
|
6702
|
+
//#endregion
|
|
6134
6703
|
//#region src/components/segmented-control/SegmentedControl.tsx
|
|
6135
6704
|
const SegmentedContext = createContext(null);
|
|
6136
6705
|
const track = {
|
|
@@ -6144,8 +6713,9 @@ const track = {
|
|
|
6144
6713
|
*
|
|
6145
6714
|
* 切换的是下面显示哪一块内容时用 `Tabs`,不是它。
|
|
6146
6715
|
*/
|
|
6147
|
-
function SegmentedControl({ value, defaultValue, onValueChange, name, size = "md", variant = "sunken", disabled: disabledProp, required: requiredProp, invalid: invalidProp, className, ...props }) {
|
|
6716
|
+
function SegmentedControl({ value, defaultValue, onValueChange, name, size = "md", variant = "sunken", disabled: disabledProp, required: requiredProp, invalid: invalidProp, className, ref, ...props }) {
|
|
6148
6717
|
const field = useFieldContext();
|
|
6718
|
+
const trackRef = useIndicator("[data-selected]");
|
|
6149
6719
|
const autoName = useId();
|
|
6150
6720
|
const [current, setCurrent] = useControllableState({
|
|
6151
6721
|
value,
|
|
@@ -6185,9 +6755,10 @@ function SegmentedControl({ value, defaultValue, onValueChange, name, size = "md
|
|
|
6185
6755
|
"aria-required": required || void 0,
|
|
6186
6756
|
"aria-disabled": disabled || void 0,
|
|
6187
6757
|
...props,
|
|
6758
|
+
ref: mergeRefs(trackRef, ref),
|
|
6188
6759
|
"data-variant": variant,
|
|
6189
6760
|
"data-size": size,
|
|
6190
|
-
className: cn("inline-grid max-w-full auto-cols-fr grid-flow-col p-0.5 text-ink", "transition-colors duration-(--duration-fast) ease-standard", track[variant], controlSize$1[size], disabled ? "border-line" : invalid ? "border-danger" : "border-line-strong focus-within:border-ink", className)
|
|
6761
|
+
className: cn("inline-grid max-w-full auto-cols-fr grid-flow-col p-0.5 text-ink", "transition-colors duration-(--duration-fast) ease-standard", indicator, indicatorBox, "after:bg-surface-inverse has-[[data-selected][data-disabled]]:after:bg-disabled", "forced-colors:after:bg-[Highlight] forced-colors:has-[[data-selected][data-disabled]]:after:bg-[GrayText]", track[variant], controlSize$1[size], disabled ? "border-line" : invalid ? "border-danger" : "border-line-strong focus-within:border-ink", className)
|
|
6191
6762
|
})
|
|
6192
6763
|
});
|
|
6193
6764
|
}
|
|
@@ -6206,7 +6777,11 @@ function Segment({ value, icon, disabled: disabledProp, "aria-label": ariaLabel,
|
|
|
6206
6777
|
...props,
|
|
6207
6778
|
"data-selected": checked ? "" : void 0,
|
|
6208
6779
|
"data-disabled": disabled ? "" : void 0,
|
|
6209
|
-
className: cn("relative flex min-w-0 items-center justify-center font-medium whitespace-nowrap select-none", "transition-colors duration-(--duration-fast) ease-standard", "has-focus-visible:z-1 has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-focus", segmentPadding[context.size], disabled ? "cursor-not-allowed" : "cursor-pointer", checked ?
|
|
6780
|
+
className: cn("relative flex min-w-0 items-center justify-center font-medium whitespace-nowrap select-none", "transition-colors duration-(--duration-fast) ease-standard", "has-focus-visible:z-1 has-focus-visible:outline-2 has-focus-visible:outline-offset-2 has-focus-visible:outline-focus", segmentPadding[context.size], disabled ? "cursor-not-allowed" : "cursor-pointer", checked ? [
|
|
6781
|
+
disabled ? "bg-disabled text-on-disabled forced-colors:bg-[GrayText] forced-colors:text-[Canvas]" : "bg-surface-inverse text-ink-inverse forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
|
|
6782
|
+
"in-data-indicator:bg-transparent forced-colors:in-data-indicator:bg-transparent",
|
|
6783
|
+
"forced-colors:forced-color-adjust-none forced-colors:has-focus-visible:outline-[Highlight]"
|
|
6784
|
+
] : disabled ? "text-ink-disabled forced-colors:text-[GrayText]" : "text-ink-secondary hover:bg-ink/5 hover:text-ink", className),
|
|
6210
6785
|
children: [
|
|
6211
6786
|
/* @__PURE__ */ jsx("input", {
|
|
6212
6787
|
type: "radio",
|
|
@@ -6705,7 +7280,7 @@ function SideRailSubItem({ current = false, end, disabled = false, href, target,
|
|
|
6705
7280
|
}
|
|
6706
7281
|
//#endregion
|
|
6707
7282
|
//#region src/components/skeleton/Skeleton.tsx
|
|
6708
|
-
const fill = "block bg-surface-muted";
|
|
7283
|
+
const fill = "block bg-surface-muted forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[GrayText]";
|
|
6709
7284
|
/**
|
|
6710
7285
|
* 骨架:只画大块,不画细节。
|
|
6711
7286
|
* 自身对辅助技术隐藏;"正在加载"由外层区域的 `aria-busy="true"` 表达。
|
|
@@ -6744,8 +7319,11 @@ const thumb = [
|
|
|
6744
7319
|
"after:absolute after:inset-y-1 after:left-1/2 after:w-0.5 after:-translate-x-1/2 after:bg-accent-ink-inverse after:opacity-0 after:transition-opacity after:duration-(--duration-fast) after:ease-standard after:content-[''] data-dragging:after:opacity-100",
|
|
6745
7320
|
"choice-diamond:size-3.5 choice-diamond:rotate-45 choice-diamond:after:inset-1 choice-diamond:after:w-auto choice-diamond:after:translate-x-0",
|
|
6746
7321
|
"data-disabled:bg-ink-disabled",
|
|
7322
|
+
"forced-colors:bg-[ButtonText] forced-colors:data-disabled:bg-[GrayText]",
|
|
6747
7323
|
focusRingWithin
|
|
6748
7324
|
].join(" ");
|
|
7325
|
+
const trackForced = "forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[ButtonText] forced-colors:data-disabled:outline-[GrayText]";
|
|
7326
|
+
const indicatorForced = "forced-colors:bg-[Highlight] forced-colors:data-disabled:bg-[GrayText]";
|
|
6749
7327
|
/**
|
|
6750
7328
|
* 滑块:在一个范围里拖着选一个数。要的是确切的数时用 `Stepper`。
|
|
6751
7329
|
* 每个滑块都要有名称:放进 `Field`,或者自己传 `aria-label`。
|
|
@@ -6795,8 +7373,8 @@ function Slider(props) {
|
|
|
6795
7373
|
children: [/* @__PURE__ */ jsx(Slider$1.Control, {
|
|
6796
7374
|
className: cn("flex touch-none items-center px-1.5 select-none data-disabled:cursor-not-allowed", controlSize[size]),
|
|
6797
7375
|
children: /* @__PURE__ */ jsxs(Slider$1.Track, {
|
|
6798
|
-
className: "h-1 w-full bg-line",
|
|
6799
|
-
children: [/* @__PURE__ */ jsx(Slider$1.Indicator, { className: "bg-ink transition-[inset-inline-start,width] duration-(--duration-fast) ease-standard data-disabled:bg-ink-disabled data-dragging:transition-none" }), (range ? [0, 1] : [0]).map((index) => /* @__PURE__ */ jsx(Slider$1.Thumb, {
|
|
7376
|
+
className: cn("h-1 w-full bg-line", trackForced),
|
|
7377
|
+
children: [/* @__PURE__ */ jsx(Slider$1.Indicator, { className: cn("bg-ink transition-[inset-inline-start,width] duration-(--duration-fast) ease-standard data-disabled:bg-ink-disabled data-dragging:transition-none", indicatorForced) }), (range ? [0, 1] : [0]).map((index) => /* @__PURE__ */ jsx(Slider$1.Thumb, {
|
|
6800
7378
|
index,
|
|
6801
7379
|
...range ? { "aria-label": thumbLabels[index] } : {
|
|
6802
7380
|
"aria-label": ariaLabel,
|
|
@@ -6811,13 +7389,13 @@ function Slider(props) {
|
|
|
6811
7389
|
className: "relative h-6 text-xs",
|
|
6812
7390
|
children: marks.map((mark) => /* @__PURE__ */ jsx("span", {
|
|
6813
7391
|
style: { left: markLeft((mark.value - min) / (max - min)) },
|
|
6814
|
-
className: cn("absolute top-0 flex flex-col gap-0.5 whitespace-nowrap text-ink-secondary before:h-1.5 before:w-px before:bg-line-strong before:content-['']", mark.value <= min ? "items-start" : mark.value >= max ? "-translate-x-full items-end" : "-translate-x-1/2 items-center"),
|
|
7392
|
+
className: cn("absolute top-0 flex flex-col gap-0.5 whitespace-nowrap text-ink-secondary before:h-1.5 before:w-px before:bg-line-strong before:content-[''] forced-colors:before:bg-[ButtonText]", mark.value <= min ? "items-start" : mark.value >= max ? "-translate-x-full items-end" : "-translate-x-1/2 items-center"),
|
|
6815
7393
|
children: mark.label
|
|
6816
7394
|
}, mark.value))
|
|
6817
7395
|
})]
|
|
6818
7396
|
}), showValue && /* @__PURE__ */ jsx(Slider$1.Value, {
|
|
6819
7397
|
style: { minWidth: valueWidth },
|
|
6820
|
-
className: cn("flex shrink-0 items-center justify-end font-tech text-sm font-bold tabular-nums data-disabled:text-ink-disabled", controlSize[size]),
|
|
7398
|
+
className: cn("flex shrink-0 items-center justify-end font-tech text-sm font-bold tabular-nums data-disabled:text-ink-disabled forced-colors:data-disabled:text-[GrayText]", controlSize[size]),
|
|
6821
7399
|
children: (formatted) => formatted.join(" – ")
|
|
6822
7400
|
})]
|
|
6823
7401
|
});
|
|
@@ -7007,7 +7585,7 @@ function Stepper({ value: valueProp, defaultValue, onValueChange, min, max, step
|
|
|
7007
7585
|
onBlur?.(event);
|
|
7008
7586
|
commitDraft();
|
|
7009
7587
|
};
|
|
7010
|
-
const buttonClass = (inactive) => cn("flex shrink-0 items-center justify-center select-none", "transition-colors duration-(--duration-fast) ease-standard", buttonSize[size], inactive ? "cursor-not-allowed bg-disabled text-on-disabled" : "bg-control text-on-control hover:bg-control-hover active:bg-control-pressed");
|
|
7588
|
+
const buttonClass = (inactive) => cn("flex shrink-0 items-center justify-center select-none", "transition-colors duration-(--duration-fast) ease-standard", "forced-colors:border forced-colors:border-[ButtonText] forced-colors:disabled:border-[GrayText]", buttonSize[size], inactive ? "cursor-not-allowed bg-disabled text-on-disabled" : "bg-control text-on-control hover:bg-control-hover active:bg-control-pressed");
|
|
7011
7589
|
return /* @__PURE__ */ jsxs("div", {
|
|
7012
7590
|
"data-size": size,
|
|
7013
7591
|
"data-invalid": invalid ? "" : void 0,
|
|
@@ -7045,7 +7623,7 @@ function Stepper({ value: valueProp, defaultValue, onValueChange, min, max, step
|
|
|
7045
7623
|
onChange: handleChange,
|
|
7046
7624
|
onKeyDown: handleKeyDown,
|
|
7047
7625
|
onBlur: handleBlur,
|
|
7048
|
-
className: cn("min-w-0 flex-1 border-y-2 border-transparent bg-surface-sunken text-center font-tech font-bold tabular-nums outline-none", "disabled:cursor-not-allowed", fieldSize[size], disabled ? "text-ink-disabled" : "text-ink", invalid && !disabled && "border-b-danger")
|
|
7626
|
+
className: cn("min-w-0 flex-1 border-y-2 border-transparent bg-surface-sunken text-center font-tech font-bold tabular-nums outline-none", "disabled:cursor-not-allowed", "forced-colors:border-y forced-colors:border-[ButtonText] forced-colors:disabled:border-[GrayText]", fieldSize[size], disabled ? "text-ink-disabled" : "text-ink", invalid && !disabled && "border-b-danger")
|
|
7049
7627
|
}),
|
|
7050
7628
|
/* @__PURE__ */ jsx("button", {
|
|
7051
7629
|
type: "button",
|
|
@@ -7084,8 +7662,8 @@ const railLayout = {
|
|
|
7084
7662
|
horizontal: "flex-col @md:flex-row"
|
|
7085
7663
|
};
|
|
7086
7664
|
const lineLayout = {
|
|
7087
|
-
vertical: "my-2 min-h-4 w-0.5",
|
|
7088
|
-
horizontal: "my-2 min-h-4 w-0.5 @md:mx-2 @md:my-0 @md:h-0.5 @md:min-h-0 @md:w-auto"
|
|
7665
|
+
vertical: "my-2 min-h-4 w-0.5 forced-colors:border-r",
|
|
7666
|
+
horizontal: "my-2 min-h-4 w-0.5 @md:mx-2 @md:my-0 @md:h-0.5 @md:min-h-0 @md:w-auto forced-colors:@max-md:border-r forced-colors:@md:border-b"
|
|
7089
7667
|
};
|
|
7090
7668
|
const bodyLayout = {
|
|
7091
7669
|
vertical: "pb-4 group-last/step:pb-0",
|
|
@@ -7128,8 +7706,8 @@ const nodeSize = {
|
|
|
7128
7706
|
md: "size-6 text-xs [&_svg]:size-3.5"
|
|
7129
7707
|
};
|
|
7130
7708
|
const nodeClass$1 = {
|
|
7131
|
-
done: "bg-surface-inverse text-accent-ink-inverse",
|
|
7132
|
-
current: "border border-ink bg-action font-bold text-on-action",
|
|
7709
|
+
done: "bg-surface-inverse text-accent-ink-inverse forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]",
|
|
7710
|
+
current: "border border-ink bg-action font-bold text-on-action forced-colors:forced-color-adjust-none forced-colors:border-[Highlight] forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]",
|
|
7133
7711
|
upcoming: "border border-line-strong text-ink-secondary"
|
|
7134
7712
|
};
|
|
7135
7713
|
const titleSize = {
|
|
@@ -7165,7 +7743,7 @@ function Step({ title, description, invalid = false, href, target, rel, render,
|
|
|
7165
7743
|
children: [/* @__PURE__ */ jsx("span", {
|
|
7166
7744
|
className: cn("flex shrink-0 items-center justify-center font-tech leading-none tabular-nums", "transition-colors duration-(--duration-fast) ease-standard", nodeSize[size], invalid ? "border border-danger text-danger" : nodeClass$1[status]),
|
|
7167
7745
|
children: invalid ? /* @__PURE__ */ jsx(StatusDanger, {}) : status === "done" ? /* @__PURE__ */ jsx(Check, {}) : String(index + 1).padStart(2, "0")
|
|
7168
|
-
}), /* @__PURE__ */ jsx("span", { className: cn("relative flex-1 overflow-clip bg-line-strong group-last/step:hidden", "before:absolute before:top-0 before:-right-4 before:-bottom-4 before:left-0 before:origin-top-left before:bg-ink before:transition-[scale] before:duration-(--duration-base) before:ease-exit before:content-['']", lineLayout[orientation], status === "done" ? "before:scale-100" : "before:scale-0") })]
|
|
7746
|
+
}), /* @__PURE__ */ jsx("span", { className: cn("relative flex-1 overflow-clip bg-line-strong group-last/step:hidden", "forced-colors:border-[CanvasText] forced-colors:before:bg-[CanvasText]", "before:absolute before:top-0 before:-right-4 before:-bottom-4 before:left-0 before:origin-top-left before:bg-ink before:transition-[scale] before:duration-(--duration-base) before:ease-exit before:content-['']", lineLayout[orientation], status === "done" ? "before:scale-100" : "before:scale-0") })]
|
|
7169
7747
|
}), /* @__PURE__ */ jsxs("div", {
|
|
7170
7748
|
className: cn("min-w-0 flex-1", bodyLayout[orientation]),
|
|
7171
7749
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -7195,6 +7773,8 @@ function Step({ title, description, invalid = false, href, target, rel, render,
|
|
|
7195
7773
|
//#region src/components/switch/Switch.tsx
|
|
7196
7774
|
const trackTransition = "transition-colors duration-(--duration-fast) ease-standard";
|
|
7197
7775
|
const dualLabel = "pointer-events-none relative min-w-9 px-3 text-center transition-colors duration-(--duration-fast) ease-standard peer-disabled:text-ink-disabled";
|
|
7776
|
+
const dualOffForced = "forced-colors:forced-color-adjust-none forced-colors:text-[HighlightText] forced-colors:peer-checked:text-[CanvasText] forced-colors:peer-disabled:text-[Canvas] forced-colors:peer-checked:peer-disabled:text-[GrayText]";
|
|
7777
|
+
const dualOnForced = "forced-colors:forced-color-adjust-none forced-colors:text-[CanvasText] forced-colors:peer-checked:text-[HighlightText] forced-colors:peer-disabled:text-[GrayText] forced-colors:peer-checked:peer-disabled:text-[Canvas]";
|
|
7198
7778
|
/**
|
|
7199
7779
|
* 开关:胶囊轨道 + 圆形旋钮,立即生效的设置用它(需要"保存"才生效的用复选框)。
|
|
7200
7780
|
* 原生 `<input type="checkbox" role="switch">`,受控用 `checked`、非受控用 `defaultChecked`。
|
|
@@ -7216,22 +7796,22 @@ function Switch({ onCheckedChange, onChange, offLabel, onLabel, className, child
|
|
|
7216
7796
|
type: "checkbox",
|
|
7217
7797
|
role: "switch",
|
|
7218
7798
|
onChange: handleChange,
|
|
7219
|
-
className: cn("peer absolute inset-0 size-full appearance-none rounded-full bg-surface-inverse/80 group-hover:bg-surface-inverse disabled:cursor-not-allowed disabled:bg-surface-muted", trackTransition, focusRing)
|
|
7799
|
+
className: cn("peer absolute inset-0 size-full appearance-none rounded-full bg-surface-inverse/80 group-hover:bg-surface-inverse disabled:cursor-not-allowed disabled:bg-surface-muted", "forced-colors:border forced-colors:border-[ButtonText] forced-colors:disabled:border-[GrayText]", trackTransition, focusRing)
|
|
7220
7800
|
}),
|
|
7221
7801
|
/* @__PURE__ */ jsx("span", {
|
|
7222
7802
|
"aria-hidden": "true",
|
|
7223
|
-
className: cn("pointer-events-none absolute inset-y-1 left-1 w-[calc(50%-0.25rem)] rounded-full bg-surface-muted", "transition-[translate] duration-(--duration-fast) ease-standard", "peer-checked:translate-x-full peer-disabled:bg-surface-raised")
|
|
7803
|
+
className: cn("pointer-events-none absolute inset-y-1 left-1 w-[calc(50%-0.25rem)] rounded-full bg-surface-muted", "transition-[translate] duration-(--duration-fast) ease-standard", "peer-checked:translate-x-full peer-disabled:bg-surface-raised", "forced-colors:bg-[Highlight] forced-colors:peer-disabled:bg-[GrayText]")
|
|
7224
7804
|
}),
|
|
7225
7805
|
/* @__PURE__ */ jsx("span", {
|
|
7226
7806
|
"aria-hidden": "true",
|
|
7227
7807
|
"data-value": "off",
|
|
7228
|
-
className: cn(dualLabel, "text-ink peer-checked:text-ink-inverse/70"),
|
|
7808
|
+
className: cn(dualLabel, "text-ink peer-checked:text-ink-inverse/70", dualOffForced),
|
|
7229
7809
|
children: offLabel
|
|
7230
7810
|
}),
|
|
7231
7811
|
/* @__PURE__ */ jsx("span", {
|
|
7232
7812
|
"aria-hidden": "true",
|
|
7233
7813
|
"data-value": "on",
|
|
7234
|
-
className: cn(dualLabel, "text-ink-inverse/70 peer-checked:text-ink"),
|
|
7814
|
+
className: cn(dualLabel, "text-ink-inverse/70 peer-checked:text-ink", dualOnForced),
|
|
7235
7815
|
children: onLabel
|
|
7236
7816
|
})
|
|
7237
7817
|
]
|
|
@@ -7242,10 +7822,10 @@ function Switch({ onCheckedChange, onChange, offLabel, onLabel, className, child
|
|
|
7242
7822
|
type: "checkbox",
|
|
7243
7823
|
role: "switch",
|
|
7244
7824
|
onChange: handleChange,
|
|
7245
|
-
className: cn("peer h-7 w-13 appearance-none rounded-full bg-line-strong group-hover:bg-ink-tertiary checked:bg-surface-inverse disabled:cursor-not-allowed disabled:bg-surface-muted", trackTransition, focusRing)
|
|
7825
|
+
className: cn("peer h-7 w-13 appearance-none rounded-full bg-line-strong group-hover:bg-ink-tertiary checked:bg-surface-inverse disabled:cursor-not-allowed disabled:bg-surface-muted", "forced-colors:border-2 forced-colors:border-[ButtonText] forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:checked:disabled:bg-[GrayText]", trackTransition, focusRing)
|
|
7246
7826
|
}), /* @__PURE__ */ jsx("span", {
|
|
7247
7827
|
"aria-hidden": "true",
|
|
7248
|
-
className: cn("pointer-events-none absolute top-1 left-1 size-5 rounded-full bg-surface-raised", "transition-[translate,background-color] duration-(--duration-fast) ease-standard", "peer-checked:translate-x-6 peer-checked:bg-accent-ink-inverse peer-disabled:bg-ink-disabled")
|
|
7828
|
+
className: cn("pointer-events-none absolute top-1 left-1 size-5 rounded-full bg-surface-raised", "transition-[translate,background-color] duration-(--duration-fast) ease-standard", "peer-checked:translate-x-6 peer-checked:bg-accent-ink-inverse peer-disabled:bg-ink-disabled", "forced-colors:bg-[ButtonText] forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-checked:peer-disabled:bg-[Canvas]")
|
|
7249
7829
|
})]
|
|
7250
7830
|
});
|
|
7251
7831
|
return /* @__PURE__ */ jsxs("label", {
|
|
@@ -7336,7 +7916,7 @@ function TableHead(props) {
|
|
|
7336
7916
|
})
|
|
7337
7917
|
});
|
|
7338
7918
|
}
|
|
7339
|
-
const ruledLines = ["[&>tr:nth-child(5n_of_:not([data-detail])):not([data-
|
|
7919
|
+
const ruledLines = ["[&>tr:nth-child(5n_of_:not([data-detail])):not(:has(+tr[data-detail]))>*]:border-line-strong", "[&>tr:nth-child(5n_of_:not([data-detail]))+tr[data-detail]>*]:border-line-strong"].join(" ");
|
|
7340
7920
|
function TableBody({ className, ...props }) {
|
|
7341
7921
|
const { ruled } = useContext(TableContext);
|
|
7342
7922
|
return /* @__PURE__ */ jsx(SectionContext, {
|
|
@@ -7360,6 +7940,7 @@ function TableRow({ selected, detail, expanded: expandedProp, defaultExpanded =
|
|
|
7360
7940
|
});
|
|
7361
7941
|
const expandable = detail !== void 0 && detail !== null && detail !== false;
|
|
7362
7942
|
const open = expandable && expanded;
|
|
7943
|
+
const detailPresence = usePresence(open);
|
|
7363
7944
|
const context = useMemo(() => expandable ? {
|
|
7364
7945
|
expanded: open,
|
|
7365
7946
|
toggle: () => setExpanded(!open),
|
|
@@ -7382,15 +7963,16 @@ function TableRow({ selected, detail, expanded: expandedProp, defaultExpanded =
|
|
|
7382
7963
|
"aria-selected": selected,
|
|
7383
7964
|
"data-selected": selected ? "" : void 0,
|
|
7384
7965
|
"data-expanded": open ? "" : void 0,
|
|
7385
|
-
className: cn("group/row transition-colors duration-(--duration-fast) ease-standard", selected ? rowSelected : rowIdle, "[
|
|
7966
|
+
className: cn("group/row transition-colors duration-(--duration-fast) ease-standard", selected ? rowSelected : rowIdle, "[&:has(+tr[data-detail])>*]:border-b-transparent", className),
|
|
7386
7967
|
children: /* @__PURE__ */ jsx(RowContext, {
|
|
7387
7968
|
value: context,
|
|
7388
7969
|
children
|
|
7389
7970
|
})
|
|
7390
|
-
}),
|
|
7971
|
+
}), detailPresence.mounted && /* @__PURE__ */ jsx(DetailRow, {
|
|
7391
7972
|
id: detailId,
|
|
7392
7973
|
rowRef,
|
|
7393
7974
|
estimate: countCells(children),
|
|
7975
|
+
presence: detailPresence,
|
|
7394
7976
|
children: detail
|
|
7395
7977
|
})] });
|
|
7396
7978
|
}
|
|
@@ -7403,7 +7985,7 @@ function countCells(children) {
|
|
|
7403
7985
|
}
|
|
7404
7986
|
return Math.max(total, 1);
|
|
7405
7987
|
}
|
|
7406
|
-
function DetailRow({ id, rowRef, estimate, children }) {
|
|
7988
|
+
function DetailRow({ id, rowRef, estimate, presence, children }) {
|
|
7407
7989
|
const [measured, setMeasured] = useState(null);
|
|
7408
7990
|
useLayoutEffect(() => {
|
|
7409
7991
|
const row = rowRef.current;
|
|
@@ -7415,12 +7997,21 @@ function DetailRow({ id, rowRef, estimate, children }) {
|
|
|
7415
7997
|
return /* @__PURE__ */ jsx("tr", {
|
|
7416
7998
|
id,
|
|
7417
7999
|
"data-detail": "",
|
|
8000
|
+
inert: presence.leaving,
|
|
7418
8001
|
children: /* @__PURE__ */ jsx("td", {
|
|
7419
8002
|
colSpan: measured ?? estimate,
|
|
7420
8003
|
className: "border-b border-line bg-surface-sunken p-0 [tr:last-child>&]:border-b-0",
|
|
7421
8004
|
children: /* @__PURE__ */ jsx("div", {
|
|
7422
|
-
|
|
7423
|
-
|
|
8005
|
+
ref: presence.ref,
|
|
8006
|
+
"data-leaving": presence.leaving ? "" : void 0,
|
|
8007
|
+
className: cn("sticky left-0 w-[var(--table-viewport,100%)]", collapse, presence.entered && "starting:grid-rows-[0fr]"),
|
|
8008
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
8009
|
+
className: collapseInner,
|
|
8010
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
8011
|
+
className: "px-4 py-3 text-sm whitespace-normal",
|
|
8012
|
+
children
|
|
8013
|
+
})
|
|
8014
|
+
})
|
|
7424
8015
|
})
|
|
7425
8016
|
})
|
|
7426
8017
|
});
|
|
@@ -7521,18 +8112,23 @@ function Tabs({ value, defaultValue, onValueChange, variant = "block", size = "m
|
|
|
7521
8112
|
defaultValue: defaultValue ?? "",
|
|
7522
8113
|
onChange: onValueChange
|
|
7523
8114
|
});
|
|
8115
|
+
const [initial] = useState(current);
|
|
8116
|
+
const [switched, setSwitched] = useState(false);
|
|
8117
|
+
if (!switched && current !== initial) setSwitched(true);
|
|
7524
8118
|
const context = useMemo(() => ({
|
|
7525
8119
|
value: current,
|
|
7526
8120
|
select,
|
|
7527
8121
|
baseId,
|
|
7528
8122
|
variant,
|
|
7529
|
-
size
|
|
8123
|
+
size,
|
|
8124
|
+
switched
|
|
7530
8125
|
}), [
|
|
7531
8126
|
current,
|
|
7532
8127
|
select,
|
|
7533
8128
|
baseId,
|
|
7534
8129
|
variant,
|
|
7535
|
-
size
|
|
8130
|
+
size,
|
|
8131
|
+
switched
|
|
7536
8132
|
]);
|
|
7537
8133
|
return /* @__PURE__ */ jsx(TabsContext, {
|
|
7538
8134
|
value: context,
|
|
@@ -7542,8 +8138,9 @@ function Tabs({ value, defaultValue, onValueChange, variant = "block", size = "m
|
|
|
7542
8138
|
})
|
|
7543
8139
|
});
|
|
7544
8140
|
}
|
|
7545
|
-
function TabList({ className, onKeyDown, ...props }) {
|
|
8141
|
+
function TabList({ className, onKeyDown, ref, ...props }) {
|
|
7546
8142
|
const { variant } = useTabsContext("TabList");
|
|
8143
|
+
const listRef = useIndicator(variant === "capsule" ? "[role=\"tab\"][aria-selected=\"true\"]" : null);
|
|
7547
8144
|
const handleKeyDown = (event) => {
|
|
7548
8145
|
onKeyDown?.(event);
|
|
7549
8146
|
if (event.defaultPrevented) return;
|
|
@@ -7573,10 +8170,17 @@ function TabList({ className, onKeyDown, ...props }) {
|
|
|
7573
8170
|
};
|
|
7574
8171
|
return /* @__PURE__ */ jsx("div", {
|
|
7575
8172
|
...props,
|
|
8173
|
+
ref: mergeRefs(listRef, ref),
|
|
7576
8174
|
role: "tablist",
|
|
7577
8175
|
"aria-orientation": "horizontal",
|
|
7578
8176
|
onKeyDown: handleKeyDown,
|
|
7579
|
-
className: cn("flex overflow-x-auto [scrollbar-width:thin]", variant === "capsule" &&
|
|
8177
|
+
className: cn("flex overflow-x-auto [scrollbar-width:thin]", variant === "capsule" && [
|
|
8178
|
+
"gap-2 p-1",
|
|
8179
|
+
indicator,
|
|
8180
|
+
indicatorBox,
|
|
8181
|
+
"after:z-0 after:rounded-full after:bg-surface-inverse",
|
|
8182
|
+
"forced-colors:after:bg-[Highlight]"
|
|
8183
|
+
], variant === "wedge" && "border-b-2 border-ink", className)
|
|
7580
8184
|
});
|
|
7581
8185
|
}
|
|
7582
8186
|
const blockSize = {
|
|
@@ -7616,12 +8220,15 @@ function Tab({ value, disabled = false, className, children, onClick, ...props }
|
|
|
7616
8220
|
};
|
|
7617
8221
|
if (variant === "capsule") return /* @__PURE__ */ jsx("button", {
|
|
7618
8222
|
...shared,
|
|
7619
|
-
className: cn(capsuleBase, focusRing, capsuleSize[size], selected ? capsuleState.selected : disabled ? capsuleState.disabled : capsuleState.rest, className),
|
|
7620
|
-
children
|
|
8223
|
+
className: cn(capsuleBase, focusRing, capsuleSize[size], selected ? [capsuleState.selected, "in-data-indicator:bg-transparent forced-colors:in-data-indicator:bg-transparent"] : disabled ? capsuleState.disabled : capsuleState.rest, className),
|
|
8224
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
8225
|
+
className: "relative z-1 inline-flex items-center gap-[inherit]",
|
|
8226
|
+
children
|
|
8227
|
+
})
|
|
7621
8228
|
});
|
|
7622
8229
|
if (variant === "wedge") return /* @__PURE__ */ jsx("button", {
|
|
7623
8230
|
...shared,
|
|
7624
|
-
className: cn("relative isolate inline-flex shrink-0 items-center justify-center leading-none font-medium whitespace-nowrap", "transition-colors duration-(--duration-fast) ease-standard", "before:absolute before:inset-0 before:-z-10 before:wedge-r before:transition-colors before:duration-(--duration-fast) before:ease-standard before:content-['']", focusRingInset, wedgeSize[size], selected ? "text-on-action before:bg-action" : disabled ? "cursor-not-allowed text-ink-disabled" : "text-ink hover:before:bg-ink/5", className),
|
|
8231
|
+
className: cn("relative isolate inline-flex shrink-0 items-center justify-center leading-none font-medium whitespace-nowrap", "transition-colors duration-(--duration-fast) ease-standard", "before:absolute before:inset-0 before:-z-10 before:wedge-r before:transition-colors before:duration-(--duration-fast) before:ease-standard before:content-['']", focusRingInset, wedgeSize[size], selected ? "text-on-action before:bg-action focus-visible:-outline-offset-4 focus-visible:outline-on-action forced-colors:forced-color-adjust-none forced-colors:text-[HighlightText] forced-colors:before:bg-[Highlight] forced-colors:focus-visible:-outline-offset-4 forced-colors:focus-visible:outline-[HighlightText]" : disabled ? "cursor-not-allowed text-ink-disabled" : "text-ink hover:before:bg-ink/5", className),
|
|
7625
8232
|
children
|
|
7626
8233
|
});
|
|
7627
8234
|
return /* @__PURE__ */ jsxs("button", {
|
|
@@ -7637,7 +8244,10 @@ function Tab({ value, disabled = false, className, children, onClick, ...props }
|
|
|
7637
8244
|
})]
|
|
7638
8245
|
});
|
|
7639
8246
|
}
|
|
7640
|
-
/**
|
|
8247
|
+
/**
|
|
8248
|
+
* 未选中的面板保留一个隐藏的空容器,让页签的 `aria-controls` 始终有所指。
|
|
8249
|
+
* 切过来的面板淡入;一开始就选中的那一块不动。
|
|
8250
|
+
*/
|
|
7641
8251
|
function TabPanel({ value, className, children, ...props }) {
|
|
7642
8252
|
const context = useTabsContext("TabPanel");
|
|
7643
8253
|
const selected = context.value === value;
|
|
@@ -7648,7 +8258,7 @@ function TabPanel({ value, className, children, ...props }) {
|
|
|
7648
8258
|
"aria-labelledby": toId(context.baseId, "tab", value),
|
|
7649
8259
|
hidden: !selected,
|
|
7650
8260
|
tabIndex: 0,
|
|
7651
|
-
className: cn(focusRing, className),
|
|
8261
|
+
className: cn(focusRing, selected && context.switched && "animate-fade-in [animation-duration:var(--duration-fast)]", className),
|
|
7652
8262
|
children: selected ? children : null
|
|
7653
8263
|
});
|
|
7654
8264
|
}
|
|
@@ -7671,6 +8281,170 @@ function TagPair({ name, value, emphasis = false, size = "md", className, ...pro
|
|
|
7671
8281
|
});
|
|
7672
8282
|
}
|
|
7673
8283
|
//#endregion
|
|
8284
|
+
//#region src/lib/inline-style.ts
|
|
8285
|
+
/**
|
|
8286
|
+
* 往元素上临时写过内联样式、又都撤掉之后调:原来没有 `style` 属性的,
|
|
8287
|
+
* 不留一个空的在上面。
|
|
8288
|
+
*
|
|
8289
|
+
* 先问一句"有没有这个属性"不是多余的:浏览器是懒着把内联样式同步成属性的,
|
|
8290
|
+
* 不问就去删,删的那一下落空,回头它又冒出来一个空的 `style=""`。
|
|
8291
|
+
*/
|
|
8292
|
+
function dropEmptyStyle(node) {
|
|
8293
|
+
if (node.style.length === 0 && node.hasAttribute("style")) node.removeAttribute("style");
|
|
8294
|
+
}
|
|
8295
|
+
const px = (value) => `${Math.round(value * 100) / 100}px`;
|
|
8296
|
+
/** 这个元素眼下挂着的位移:补位滑到一半时不是 0 */
|
|
8297
|
+
function flight(node) {
|
|
8298
|
+
const value = getComputedStyle(node).translate;
|
|
8299
|
+
if (!value || value === "none") return [0, 0];
|
|
8300
|
+
const [x = 0, y = 0] = value.split(" ").map((part) => Number.parseFloat(part) || 0);
|
|
8301
|
+
return [x, y];
|
|
8302
|
+
}
|
|
8303
|
+
/** 把写上去的内联样式撤干净:原来没有 style 属性的,不留一个空的在上面 */
|
|
8304
|
+
function unset(node, names) {
|
|
8305
|
+
for (const name of names) node.style.removeProperty(name);
|
|
8306
|
+
dropEmptyStyle(node);
|
|
8307
|
+
}
|
|
8308
|
+
const PIN = [
|
|
8309
|
+
"left",
|
|
8310
|
+
"top",
|
|
8311
|
+
"width",
|
|
8312
|
+
"height"
|
|
8313
|
+
];
|
|
8314
|
+
/** 往走的那一项上写过的全部:钉它的那四样,钉不住时的藏 */
|
|
8315
|
+
const HELD = [...PIN, "display"];
|
|
8316
|
+
/**
|
|
8317
|
+
* 量一遍、记下来。这一次有一项刚走,就补位:走的钉在原处,
|
|
8318
|
+
* 其余挪了地方的先摆回旧位置(不过渡),再撤掉——撤的那一下滑回原位;
|
|
8319
|
+
* 容器因此变矮了的话,高度也从原来的收到新的。
|
|
8320
|
+
*/
|
|
8321
|
+
function arrange(node, memory) {
|
|
8322
|
+
const { seen, pinned } = memory;
|
|
8323
|
+
const items = [...node.children];
|
|
8324
|
+
const leaving = (item) => item.hasAttribute("data-leaving");
|
|
8325
|
+
for (const item of items) {
|
|
8326
|
+
if (leaving(item) || !pinned.has(item)) continue;
|
|
8327
|
+
pinned.delete(item);
|
|
8328
|
+
unset(item, HELD);
|
|
8329
|
+
}
|
|
8330
|
+
const box = node.getBoundingClientRect();
|
|
8331
|
+
if (box.width === 0 || box.height === 0 || items.filter((item) => !leaving(item)).length > 50) {
|
|
8332
|
+
seen.clear();
|
|
8333
|
+
memory.height = 0;
|
|
8334
|
+
for (const item of items) {
|
|
8335
|
+
if (!leaving(item) || pinned.has(item)) continue;
|
|
8336
|
+
pinned.add(item);
|
|
8337
|
+
item.style.setProperty("display", "none");
|
|
8338
|
+
}
|
|
8339
|
+
return;
|
|
8340
|
+
}
|
|
8341
|
+
const laid = Number.parseFloat(getComputedStyle(node).width);
|
|
8342
|
+
const scale = laid > 0 ? box.width / laid : 1;
|
|
8343
|
+
const gone = [];
|
|
8344
|
+
const moved = [];
|
|
8345
|
+
const next = [];
|
|
8346
|
+
for (const item of items) {
|
|
8347
|
+
const before = seen.get(item);
|
|
8348
|
+
if (leaving(item)) {
|
|
8349
|
+
if (!pinned.has(item)) gone.push([item, before]);
|
|
8350
|
+
continue;
|
|
8351
|
+
}
|
|
8352
|
+
const rect = item.getBoundingClientRect();
|
|
8353
|
+
const [x, y] = flight(item);
|
|
8354
|
+
const place = {
|
|
8355
|
+
left: (rect.left - box.left) / scale - node.clientLeft + node.scrollLeft - x,
|
|
8356
|
+
top: (rect.top - box.top) / scale - node.clientTop + node.scrollTop - y,
|
|
8357
|
+
width: rect.width / scale,
|
|
8358
|
+
height: rect.height / scale
|
|
8359
|
+
};
|
|
8360
|
+
next.push([item, place]);
|
|
8361
|
+
if (!before) continue;
|
|
8362
|
+
const dx = before.left + x - place.left;
|
|
8363
|
+
const dy = before.top + y - place.top;
|
|
8364
|
+
if (Math.abs(dx) >= .5 || Math.abs(dy) >= .5) moved.push([
|
|
8365
|
+
item,
|
|
8366
|
+
dx,
|
|
8367
|
+
dy
|
|
8368
|
+
]);
|
|
8369
|
+
}
|
|
8370
|
+
seen.clear();
|
|
8371
|
+
for (const [item, place] of next) seen.set(item, place);
|
|
8372
|
+
const tall = box.height / scale;
|
|
8373
|
+
const shrinking = memory.settle !== null;
|
|
8374
|
+
const from = shrinking ? tall : memory.height;
|
|
8375
|
+
if (!shrinking) memory.height = tall;
|
|
8376
|
+
if (gone.length === 0) return;
|
|
8377
|
+
for (const [item, place] of gone) {
|
|
8378
|
+
pinned.add(item);
|
|
8379
|
+
if (place) PIN.forEach((name) => item.style.setProperty(name, px(place[name])));
|
|
8380
|
+
else item.style.setProperty("display", "none");
|
|
8381
|
+
}
|
|
8382
|
+
memory.settle?.();
|
|
8383
|
+
memory.settle = null;
|
|
8384
|
+
node.style.setProperty("transition", "none");
|
|
8385
|
+
node.style.removeProperty("min-height");
|
|
8386
|
+
const to = node.getBoundingClientRect().height / scale;
|
|
8387
|
+
memory.height = to;
|
|
8388
|
+
const shrink = from - to >= .5;
|
|
8389
|
+
if (shrink) node.style.setProperty("min-height", px(from));
|
|
8390
|
+
for (const [item, dx, dy] of moved) {
|
|
8391
|
+
item.style.setProperty("transition", "none");
|
|
8392
|
+
item.style.setProperty("translate", `${px(dx)} ${px(dy)}`);
|
|
8393
|
+
}
|
|
8394
|
+
node.getBoundingClientRect();
|
|
8395
|
+
for (const [item] of moved) unset(item, ["transition", "translate"]);
|
|
8396
|
+
node.style.removeProperty("transition");
|
|
8397
|
+
if (shrink) {
|
|
8398
|
+
node.style.setProperty("min-height", px(to));
|
|
8399
|
+
memory.settle = afterMotion(node, () => {
|
|
8400
|
+
memory.settle = null;
|
|
8401
|
+
unset(node, ["min-height"]);
|
|
8402
|
+
});
|
|
8403
|
+
} else dropEmptyStyle(node);
|
|
8404
|
+
}
|
|
8405
|
+
/**
|
|
8406
|
+
* 补位:容器里有一项走了,其余的直接子元素从旧位置滑到新位置,不是瞬移过去。
|
|
8407
|
+
* 返回的 `ref` 给容器。
|
|
8408
|
+
*
|
|
8409
|
+
* - 走的那一项要带 `data-leaving`、并且脱离排版(`lib/motion.ts` 的 `flipLeaving`):
|
|
8410
|
+
* 钩子把它钉在原来的位置上,它在那里把退场走完;容器要是定位的参照(`flipBox`)。
|
|
8411
|
+
* - 其余各项要给 `translate` 加上过渡:钩子只负责把它们摆回旧位置再放手,
|
|
8412
|
+
* 滑回来靠的是 CSS——所以系统开了"减少动态效果"时是直接到位的。
|
|
8413
|
+
* - 容器因此变矮了(少了一行),它的高度也从原来的收到新的(量着收,过渡同样在
|
|
8414
|
+
* `flipBox` 里);收完撤掉,不留在容器上。
|
|
8415
|
+
* - 各项只动 `translate`。正滑到一半又少一项,从眼前的位置接着走。
|
|
8416
|
+
* - 只有"少了一项"才补位;加一项、容器尺寸变了,各项直接到位。
|
|
8417
|
+
* - 超过 `FLIP_LIMIT` 项、量不到位置时不做:走的那一项也不留,照旧直接消失。
|
|
8418
|
+
*/
|
|
8419
|
+
function useFlip() {
|
|
8420
|
+
const ref = useRef(null);
|
|
8421
|
+
const memory = useRef({
|
|
8422
|
+
seen: /* @__PURE__ */ new Map(),
|
|
8423
|
+
height: 0,
|
|
8424
|
+
pinned: /* @__PURE__ */ new WeakSet(),
|
|
8425
|
+
settle: null
|
|
8426
|
+
});
|
|
8427
|
+
useLayoutEffect(() => {
|
|
8428
|
+
const node = ref.current;
|
|
8429
|
+
if (node) arrange(node, memory.current);
|
|
8430
|
+
});
|
|
8431
|
+
useLayoutEffect(() => {
|
|
8432
|
+
const node = ref.current;
|
|
8433
|
+
if (!node || typeof ResizeObserver === "undefined") return;
|
|
8434
|
+
const observer = new ResizeObserver(() => arrange(node, memory.current));
|
|
8435
|
+
observer.observe(node);
|
|
8436
|
+
return () => observer.disconnect();
|
|
8437
|
+
}, []);
|
|
8438
|
+
useEffect(() => {
|
|
8439
|
+
const held = memory.current;
|
|
8440
|
+
return () => {
|
|
8441
|
+
held.settle?.();
|
|
8442
|
+
held.settle = null;
|
|
8443
|
+
};
|
|
8444
|
+
}, []);
|
|
8445
|
+
return ref;
|
|
8446
|
+
}
|
|
8447
|
+
//#endregion
|
|
7674
8448
|
//#region src/components/tag-input/TagInput.tsx
|
|
7675
8449
|
const defaultLabels = {
|
|
7676
8450
|
summary: (tags) => tags.length === 0 ? "还没有标签" : `已有 ${tags.length} 个标签:${tags.join("、")}`,
|
|
@@ -7718,6 +8492,9 @@ function TagInput({ value, defaultValue, onValueChange, name, max, validate, sep
|
|
|
7718
8492
|
const [flash, setFlash] = useState(null);
|
|
7719
8493
|
const [message, setMessage] = useState("");
|
|
7720
8494
|
const full = max !== void 0 && tags.length >= max;
|
|
8495
|
+
const added = useAddedKeys(tags);
|
|
8496
|
+
const rowRef = useFlip();
|
|
8497
|
+
const lingering = useLingering(tags, tags.length < 50);
|
|
7721
8498
|
useEffect(() => {
|
|
7722
8499
|
if (flash === null) return;
|
|
7723
8500
|
const timer = setTimeout(() => setFlash(null), FLASH_MS);
|
|
@@ -7875,24 +8652,31 @@ function TagInput({ value, defaultValue, onValueChange, name, max, validate, sep
|
|
|
7875
8652
|
}), "items-center", multiValueBox[size], className),
|
|
7876
8653
|
children: [
|
|
7877
8654
|
/* @__PURE__ */ jsxs("div", {
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
8655
|
+
ref: mergeRefs(rowRef, lingering.ref),
|
|
8656
|
+
className: cn("flex min-w-0 flex-1 flex-wrap items-center gap-1 py-1 pl-2", flipBox),
|
|
8657
|
+
children: [lingering.entries.map(({ key: tag, leaving }) => {
|
|
8658
|
+
const index = tags.indexOf(tag);
|
|
8659
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
8660
|
+
"data-tag": leaving ? void 0 : "",
|
|
8661
|
+
"data-leaving": leaving ? "" : void 0,
|
|
8662
|
+
"data-flash": flash === tag && !leaving ? "" : void 0,
|
|
8663
|
+
inert: leaving,
|
|
8664
|
+
"aria-hidden": leaving || void 0,
|
|
8665
|
+
className: cn(valueChip, valueChipSize[size], "transition-[color,background-color,translate,opacity] duration-(--duration-fast) ease-standard", "data-flash:bg-surface-inverse data-flash:text-ink-inverse", "forced-colors:data-flash:outline-2 forced-colors:data-flash:outline-[Highlight]", flipLeaving, added.has(tag) && "animate-fade-in [animation-duration:var(--duration-fast)]", disabled && "text-ink-disabled forced-colors:border-[GrayText] forced-colors:text-[GrayText]", !editable && "pr-2"),
|
|
8666
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
8667
|
+
className: "min-w-0 truncate",
|
|
8668
|
+
children: tag
|
|
8669
|
+
}), editable && /* @__PURE__ */ jsx("button", {
|
|
8670
|
+
type: "button",
|
|
8671
|
+
tabIndex: -1,
|
|
8672
|
+
"aria-label": removeLabel(tag),
|
|
8673
|
+
onClick: (event) => remove(index, event.detail === 0 && tags.length > 1 ? index : "input"),
|
|
8674
|
+
onKeyDown: (event) => handleChipKeyDown(event, index),
|
|
8675
|
+
className: cn("flex aspect-square h-full shrink-0 items-center justify-center text-ink-secondary hover:bg-line hover:text-ink forced-colors:in-data-disabled:text-[GrayText]", "focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", flash === tag && "text-ink-inverse"),
|
|
8676
|
+
children: /* @__PURE__ */ jsx(Close, { size: 12 })
|
|
8677
|
+
})]
|
|
8678
|
+
}, tag);
|
|
8679
|
+
}), /* @__PURE__ */ jsx("input", {
|
|
7896
8680
|
...props,
|
|
7897
8681
|
ref: mergeRefs(inputRef, ref),
|
|
7898
8682
|
id,
|
|
@@ -7917,7 +8701,7 @@ function TagInput({ value, defaultValue, onValueChange, name, max, validate, sep
|
|
|
7917
8701
|
onBlur?.(event);
|
|
7918
8702
|
if (commitOnBlur && editable && text.trim() !== "") setText(commit([text]));
|
|
7919
8703
|
},
|
|
7920
|
-
className: cn(controlElement, "min-w-12 px-1", valueInputHeight[size])
|
|
8704
|
+
className: cn(controlElement, "min-w-12 px-1", valueInputHeight[size], "transition-[translate] duration-(--duration-fast) ease-standard")
|
|
7921
8705
|
})]
|
|
7922
8706
|
}),
|
|
7923
8707
|
(invalid || max !== void 0) && /* @__PURE__ */ jsxs("span", {
|
|
@@ -7928,7 +8712,7 @@ function TagInput({ value, defaultValue, onValueChange, name, max, validate, sep
|
|
|
7928
8712
|
}), max !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
7929
8713
|
"aria-hidden": "true",
|
|
7930
8714
|
"data-count": "",
|
|
7931
|
-
className: cn("font-tech text-xs tabular-nums", disabled ? "text-ink-disabled" : full ? "text-ink" : "text-ink-secondary"),
|
|
8715
|
+
className: cn("font-tech text-xs tabular-nums", disabled ? "text-ink-disabled forced-colors:text-[GrayText]" : full ? "text-ink" : "text-ink-secondary"),
|
|
7932
8716
|
children: `${tags.length} / ${max}`
|
|
7933
8717
|
})]
|
|
7934
8718
|
}),
|
|
@@ -8020,7 +8804,7 @@ function Textarea({ variant = "sunken", invalid: invalidProp, showCount = false,
|
|
|
8020
8804
|
className: cn(controlElement, "block min-h-[calc(3lh+1rem)] w-full resize-y px-3 py-2")
|
|
8021
8805
|
}), showCount && /* @__PURE__ */ jsx("span", {
|
|
8022
8806
|
"data-count": "",
|
|
8023
|
-
className: cn("pointer-events-none self-end px-3 pb-1.5 font-tech text-xs tabular-nums", disabled ? "text-ink-disabled" : "text-ink-secondary"),
|
|
8807
|
+
className: cn("pointer-events-none self-end px-3 pb-1.5 font-tech text-xs tabular-nums", disabled ? "text-ink-disabled forced-colors:text-[GrayText]" : "text-ink-secondary"),
|
|
8024
8808
|
children: maxLength !== void 0 ? `${length} / ${maxLength}` : length
|
|
8025
8809
|
})]
|
|
8026
8810
|
});
|
|
@@ -8089,8 +8873,8 @@ function Timeline({ className, ...props }) {
|
|
|
8089
8873
|
});
|
|
8090
8874
|
}
|
|
8091
8875
|
const nodeClass = {
|
|
8092
|
-
past: "bg-ink",
|
|
8093
|
-
current: "border border-ink bg-action",
|
|
8876
|
+
past: "bg-ink forced-colors:bg-[CanvasText]",
|
|
8877
|
+
current: "border border-ink bg-action forced-colors:bg-[Highlight]",
|
|
8094
8878
|
upcoming: "border-2 border-ink-tertiary"
|
|
8095
8879
|
};
|
|
8096
8880
|
const titleClass = {
|
|
@@ -8111,7 +8895,7 @@ function TimelineItem({ status = "past", date, title, className, children, ...pr
|
|
|
8111
8895
|
children: [/* @__PURE__ */ jsx("span", {
|
|
8112
8896
|
className: "flex h-[1lh] min-h-4 w-4 items-center justify-center",
|
|
8113
8897
|
children: /* @__PURE__ */ jsx("span", { className: cn("size-[11px] rotate-45", nodeClass[status]) })
|
|
8114
|
-
}), /* @__PURE__ */ jsx("span", { className: "w-0.5 flex-1 bg-line-strong group-last/item:hidden" })]
|
|
8898
|
+
}), /* @__PURE__ */ jsx("span", { className: "w-0.5 flex-1 bg-line-strong group-last/item:hidden forced-colors:bg-[CanvasText]" })]
|
|
8115
8899
|
}), /* @__PURE__ */ jsxs("div", {
|
|
8116
8900
|
className: "min-w-0 flex-1 pb-6 group-last/item:pb-0",
|
|
8117
8901
|
children: [
|
|
@@ -8257,13 +9041,42 @@ const TocContext = createContext({
|
|
|
8257
9041
|
/** 点了一项之后,多久没有滚动事件算这一次滚动停下了 */
|
|
8258
9042
|
const SETTLE_MS = 150;
|
|
8259
9043
|
/**
|
|
9044
|
+
* 让接下来的这一次滚动是平滑的:给这几个滚动容器写上内联的 `scroll-behavior`,
|
|
9045
|
+
* 滚动停下(`SETTLE_MS` 里没有再滚)就还原成原来的内联值。返回的函数提前还原。
|
|
9046
|
+
*
|
|
9047
|
+
* 滚动本身不归它管——跳过去的仍然是浏览器(锚点跳转)。
|
|
9048
|
+
*/
|
|
9049
|
+
function smoothOnce(scrollers) {
|
|
9050
|
+
const before = scrollers.map((scroller) => scroller.style.getPropertyValue("scroll-behavior"));
|
|
9051
|
+
for (const scroller of scrollers) scroller.style.setProperty("scroll-behavior", "smooth");
|
|
9052
|
+
const sources = scrollers.map((scroller) => scroller === document.documentElement ? window : scroller);
|
|
9053
|
+
let timer;
|
|
9054
|
+
const restore = () => {
|
|
9055
|
+
clearTimeout(timer);
|
|
9056
|
+
for (const source of sources) source.removeEventListener("scroll", wait);
|
|
9057
|
+
scrollers.forEach((scroller, index) => {
|
|
9058
|
+
scroller.style.setProperty("scroll-behavior", before[index]);
|
|
9059
|
+
dropEmptyStyle(scroller);
|
|
9060
|
+
});
|
|
9061
|
+
};
|
|
9062
|
+
function wait() {
|
|
9063
|
+
clearTimeout(timer);
|
|
9064
|
+
timer = setTimeout(restore, SETTLE_MS);
|
|
9065
|
+
}
|
|
9066
|
+
for (const source of sources) source.addEventListener("scroll", wait, { passive: true });
|
|
9067
|
+
wait();
|
|
9068
|
+
return restore;
|
|
9069
|
+
}
|
|
9070
|
+
/**
|
|
8260
9071
|
* 页内目录:这一页有哪几节、现在看到了哪一节。里面放 `TocItem`。
|
|
8261
9072
|
*
|
|
8262
|
-
* 位置由使用方给,通常是 `className="sticky top-6"
|
|
8263
|
-
*
|
|
9073
|
+
* 位置由使用方给,通常是 `className="sticky top-6"`。点一项仍然是点一个锚点链接——
|
|
9074
|
+
* 地址、后退、`scroll-mt` 都是浏览器原生的那一套;它只让这一次跳转滚得平滑
|
|
9075
|
+
* (系统开了"减少动态效果"时照旧直接跳)。
|
|
8264
9076
|
*/
|
|
8265
9077
|
function Toc({ title, offset = 0, target, onActiveChange, "aria-label": ariaLabel, className, ref, children, ...props }) {
|
|
8266
9078
|
const navRef = useRef(null);
|
|
9079
|
+
const listRef = useIndicator("[aria-current]");
|
|
8267
9080
|
const titleId = useId();
|
|
8268
9081
|
const count = Children.count(children);
|
|
8269
9082
|
const spied = useScrollPosition(target, useCallback(({ scrolled, room }) => {
|
|
@@ -8312,6 +9125,17 @@ function Toc({ title, offset = 0, target, onActiveChange, "aria-label": ariaLabe
|
|
|
8312
9125
|
};
|
|
8313
9126
|
}, [pinned, target]);
|
|
8314
9127
|
const active = pinned ?? spied;
|
|
9128
|
+
const restoreScroll = useRef(null);
|
|
9129
|
+
useEffect(() => () => restoreScroll.current?.(), []);
|
|
9130
|
+
const pick = useCallback((section) => {
|
|
9131
|
+
setPinned(section);
|
|
9132
|
+
restoreScroll.current?.();
|
|
9133
|
+
restoreScroll.current = null;
|
|
9134
|
+
if (!document.getElementById(section)) return;
|
|
9135
|
+
if (typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
9136
|
+
const container = target?.current;
|
|
9137
|
+
restoreScroll.current = smoothOnce(container ? [container, document.documentElement] : [document.documentElement]);
|
|
9138
|
+
}, [target]);
|
|
8315
9139
|
const report = useRef(onActiveChange);
|
|
8316
9140
|
report.current = onActiveChange;
|
|
8317
9141
|
const reported = useRef(null);
|
|
@@ -8333,8 +9157,8 @@ function Toc({ title, offset = 0, target, onActiveChange, "aria-label": ariaLabe
|
|
|
8333
9157
|
}, [active]);
|
|
8334
9158
|
const context = useMemo(() => ({
|
|
8335
9159
|
active,
|
|
8336
|
-
pick
|
|
8337
|
-
}), [active]);
|
|
9160
|
+
pick
|
|
9161
|
+
}), [active, pick]);
|
|
8338
9162
|
const hasTitle = title !== void 0 && title !== null && title !== false;
|
|
8339
9163
|
return /* @__PURE__ */ jsxs("nav", {
|
|
8340
9164
|
...props,
|
|
@@ -8347,7 +9171,8 @@ function Toc({ title, offset = 0, target, onActiveChange, "aria-label": ariaLabe
|
|
|
8347
9171
|
className: "mb-2 pl-4 font-tech text-xs text-ink-secondary",
|
|
8348
9172
|
children: title
|
|
8349
9173
|
}), /* @__PURE__ */ jsx("ul", {
|
|
8350
|
-
|
|
9174
|
+
ref: listRef,
|
|
9175
|
+
className: cn("before:absolute before:inset-y-0 before:left-0 before:-z-1 before:w-px before:bg-line before:content-['']", indicator, "after:top-1 after:h-[calc(var(--indicator-h)-0.5rem)] after:w-[3px] after:translate-y-(--indicator-y) after:bg-ink", "forced-colors:after:bg-[Highlight]"),
|
|
8351
9176
|
children: /* @__PURE__ */ jsx(TocContext, {
|
|
8352
9177
|
value: context,
|
|
8353
9178
|
children
|
|
@@ -8385,7 +9210,7 @@ function TocItem({ href, section = sectionOf(href), level = 1, render, onClick,
|
|
|
8385
9210
|
onClick?.(event);
|
|
8386
9211
|
if (!event.defaultPrevented && section !== void 0) pick(section);
|
|
8387
9212
|
},
|
|
8388
|
-
className: cn("relative block py-2 pr-2 leading-snug wrap-anywhere", "transition-colors duration-(--duration-fast) ease-standard", focusRingInset, levelPadding[level], current ? "text-ink before:absolute before:inset-y-1 before:left-0 before:w-[3px] before:bg-ink before:content-['']" : "text-ink-secondary hover:text-ink"),
|
|
9213
|
+
className: cn("relative block py-2 pr-2 leading-snug wrap-anywhere", "transition-colors duration-(--duration-fast) ease-standard", focusRingInset, levelPadding[level], current ? "text-ink before:absolute before:inset-y-1 before:left-0 before:w-[3px] before:bg-ink before:content-[''] in-data-indicator:before:hidden forced-colors:before:bg-[Highlight]" : "text-ink-secondary hover:text-ink"),
|
|
8389
9214
|
children
|
|
8390
9215
|
})
|
|
8391
9216
|
});
|
|
@@ -8400,6 +9225,10 @@ const barVariant = {
|
|
|
8400
9225
|
sunken: "bg-surface-sunken p-0.5",
|
|
8401
9226
|
outline: "border border-line-strong bg-surface p-px"
|
|
8402
9227
|
};
|
|
9228
|
+
const barForced = {
|
|
9229
|
+
sunken: "forced-colors:outline forced-colors:-outline-offset-1 forced-colors:outline-[ButtonText] forced-colors:data-disabled:outline-[GrayText]",
|
|
9230
|
+
outline: "forced-colors:border-[ButtonText] forced-colors:data-disabled:border-[GrayText]"
|
|
9231
|
+
};
|
|
8403
9232
|
/**
|
|
8404
9233
|
* 工具栏:一排作用于同一个对象的小工具,收在一条带子里。整条只占一个 Tab 停靠点,
|
|
8405
9234
|
* 方向键在各项之间走。里面放 `ToolbarButton`、`ToolbarToggle`、`ToolbarToggleGroup`、
|
|
@@ -8432,7 +9261,7 @@ function Toolbar({ size = "md", variant = "sunken", orientation = "horizontal",
|
|
|
8432
9261
|
onKeyDown: handleKeyDown,
|
|
8433
9262
|
"data-size": size,
|
|
8434
9263
|
"data-variant": variant,
|
|
8435
|
-
className: cn("inline-flex max-w-full gap-0.5 text-ink", orientation === "vertical" ? "flex-col" : "flex-wrap items-center", barVariant[variant], className)
|
|
9264
|
+
className: cn("inline-flex max-w-full gap-0.5 text-ink", orientation === "vertical" ? "flex-col" : "flex-wrap items-center", barVariant[variant], barForced[variant], className)
|
|
8436
9265
|
})
|
|
8437
9266
|
});
|
|
8438
9267
|
}
|
|
@@ -8457,6 +9286,10 @@ const itemBase = [
|
|
|
8457
9286
|
"data-popup-open:bg-surface-inverse data-popup-open:text-ink-inverse",
|
|
8458
9287
|
"data-disabled:cursor-not-allowed data-disabled:bg-transparent data-disabled:text-ink-disabled",
|
|
8459
9288
|
"data-disabled:data-pressed:bg-disabled data-disabled:data-pressed:text-on-disabled",
|
|
9289
|
+
"forced-colors:data-pressed:forced-color-adjust-none forced-colors:data-pressed:outline-[Highlight]",
|
|
9290
|
+
"forced-colors:data-pressed:bg-[Highlight] forced-colors:data-pressed:text-[HighlightText]",
|
|
9291
|
+
"forced-colors:data-disabled:data-pressed:bg-[GrayText] forced-colors:data-disabled:data-pressed:text-[Canvas]",
|
|
9292
|
+
"forced-colors:data-disabled:text-[GrayText]",
|
|
8460
9293
|
"focus-visible:z-1",
|
|
8461
9294
|
focusRing
|
|
8462
9295
|
].join(" ");
|
|
@@ -8464,7 +9297,9 @@ const itemIcon = [
|
|
|
8464
9297
|
"flex shrink-0",
|
|
8465
9298
|
"group-data-pressed/item:text-accent-ink-inverse",
|
|
8466
9299
|
"group-data-popup-open/item:text-accent-ink-inverse",
|
|
8467
|
-
"group-data-disabled/item:group-data-pressed/item:text-on-disabled"
|
|
9300
|
+
"group-data-disabled/item:group-data-pressed/item:text-on-disabled",
|
|
9301
|
+
"forced-colors:group-data-pressed/item:text-[HighlightText]",
|
|
9302
|
+
"forced-colors:group-data-disabled/item:group-data-pressed/item:text-[Canvas]"
|
|
8468
9303
|
].join(" ");
|
|
8469
9304
|
function useItemClass(iconOnly, className) {
|
|
8470
9305
|
const { size } = useContext(ToolbarContext);
|
|
@@ -8555,7 +9390,7 @@ function ToolbarGroup({ className, ...props }) {
|
|
|
8555
9390
|
function ToolbarSeparator({ className, ...props }) {
|
|
8556
9391
|
return /* @__PURE__ */ jsx(Toolbar$1.Separator, {
|
|
8557
9392
|
...props,
|
|
8558
|
-
className: cn("shrink-0 self-center bg-line-strong", "data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-4 data-[orientation=vertical]:w-px", "data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-4", className)
|
|
9393
|
+
className: cn("shrink-0 self-center bg-line-strong", "forced-colors:bg-[ButtonText] forced-colors:in-data-disabled:bg-[GrayText]", "data-[orientation=vertical]:mx-1 data-[orientation=vertical]:h-4 data-[orientation=vertical]:w-px", "data-[orientation=horizontal]:my-1 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-4", className)
|
|
8559
9394
|
});
|
|
8560
9395
|
}
|
|
8561
9396
|
//#endregion
|
|
@@ -8671,6 +9506,7 @@ function TopBar({ brand, tools, action, menu, className, children, ...props }) {
|
|
|
8671
9506
|
//#region src/hooks/useTheme.ts
|
|
8672
9507
|
const STORAGE_KEY = "ef-theme";
|
|
8673
9508
|
const DARK_QUERY = "(prefers-color-scheme: dark)";
|
|
9509
|
+
const REDUCE_QUERY = "(prefers-reduced-motion: reduce)";
|
|
8674
9510
|
const serverSnapshot = {
|
|
8675
9511
|
theme: "light",
|
|
8676
9512
|
resolvedTheme: "light"
|
|
@@ -8678,6 +9514,8 @@ const serverSnapshot = {
|
|
|
8678
9514
|
const listeners = /* @__PURE__ */ new Set();
|
|
8679
9515
|
let snapshot = null;
|
|
8680
9516
|
let stopWatchingSystem = null;
|
|
9517
|
+
/** 正在走的那一次切换(视图过渡):它走完才撤掉 <html> 上的标记 */
|
|
9518
|
+
let switching = null;
|
|
8681
9519
|
function isPreference(value) {
|
|
8682
9520
|
return value === "light" || value === "dark" || value === "system";
|
|
8683
9521
|
}
|
|
@@ -8703,13 +9541,39 @@ function getSnapshot() {
|
|
|
8703
9541
|
}
|
|
8704
9542
|
return snapshot;
|
|
8705
9543
|
}
|
|
9544
|
+
/**
|
|
9545
|
+
* 换主题。亮暗真的变了、浏览器有视图过渡、系统没开"减少动态效果"时,放在视图过渡里换:
|
|
9546
|
+
* 整页交叉淡化一次,而不是带换色过渡的慢慢过去、别的硬切,参差不齐。否则当场换。
|
|
9547
|
+
*
|
|
9548
|
+
* 走视图过渡的时候是晚一帧才换的——浏览器要先把旧的样子拍下来。
|
|
9549
|
+
* 期间 <html> 上标着 data-theme-switching:淡化的时长挂在它下面(theme.css),
|
|
9550
|
+
* 使用方自己的视图过渡不受影响。局部主题那几块新旧一个样,看不出变化。
|
|
9551
|
+
*/
|
|
8706
9552
|
function commit(theme) {
|
|
8707
|
-
|
|
9553
|
+
const next = {
|
|
8708
9554
|
theme,
|
|
8709
9555
|
resolvedTheme: resolve(theme)
|
|
8710
9556
|
};
|
|
8711
|
-
|
|
8712
|
-
|
|
9557
|
+
const element = document.documentElement;
|
|
9558
|
+
const apply = () => {
|
|
9559
|
+
snapshot = next;
|
|
9560
|
+
element.dataset.theme = next.resolvedTheme;
|
|
9561
|
+
for (const listener of listeners) listener();
|
|
9562
|
+
};
|
|
9563
|
+
if (element.dataset.theme === next.resolvedTheme || typeof document.startViewTransition !== "function" || window.matchMedia?.(REDUCE_QUERY).matches) {
|
|
9564
|
+
apply();
|
|
9565
|
+
return;
|
|
9566
|
+
}
|
|
9567
|
+
element.dataset.themeSwitching = "";
|
|
9568
|
+
const transition = document.startViewTransition(apply);
|
|
9569
|
+
switching = transition;
|
|
9570
|
+
const done = () => {
|
|
9571
|
+
if (switching !== transition) return;
|
|
9572
|
+
switching = null;
|
|
9573
|
+
delete element.dataset.themeSwitching;
|
|
9574
|
+
};
|
|
9575
|
+
transition.finished.then(done, done);
|
|
9576
|
+
transition.ready.catch(() => {});
|
|
8713
9577
|
}
|
|
8714
9578
|
function subscribe(listener) {
|
|
8715
9579
|
listeners.add(listener);
|
|
@@ -8740,7 +9604,8 @@ function setTheme(theme) {
|
|
|
8740
9604
|
*
|
|
8741
9605
|
* - `theme`:用户的选择,可以是 `"system"`;
|
|
8742
9606
|
* - `resolvedTheme`:实际生效的 `"light"` 或 `"dark"`;
|
|
8743
|
-
* - `setTheme`:切换并记到 `localStorage
|
|
9607
|
+
* - `setTheme`:切换并记到 `localStorage`。亮暗变了的时候整页交叉淡化一次
|
|
9608
|
+
* (浏览器支持视图过渡、系统没开"减少动态效果"时;否则直接切)。
|
|
8744
9609
|
*/
|
|
8745
9610
|
function useTheme() {
|
|
8746
9611
|
const { theme, resolvedTheme } = useSyncExternalStore(subscribe, getSnapshot, () => serverSnapshot);
|
|
@@ -8758,4 +9623,4 @@ function useTheme() {
|
|
|
8758
9623
|
/** 三条横线:打开菜单 */
|
|
8759
9624
|
const Menu = createIcon("Menu", /* @__PURE__ */ jsx("path", { d: "M3 6h18M3 12h18M3 18h18" }));
|
|
8760
9625
|
//#endregion
|
|
8761
|
-
export { Accordion, AccordionItem, Alert, ArrowCorner, ArrowLeft, ArrowRight, Avatar, AvatarSwitcher, AvatarSwitcherItem, BackToTop, Badge, BracketTitle, Breadcrumb, BreadcrumbItem, Button, ButtonGroup, Calendar, CalendarIcon, Carousel, CarouselSlide, Check, Checkbox, ChevronDown, ChevronLeft, ChevronRight, Close, Combobox, CompletionBanner, ContextMenu, CornerBrackets, Countdown, Crosshair, DashIndicator, DataRow, DataRowList, DatePicker, DateRangePicker, Dialog, DialogClose, Drawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuGroup, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, EmptyState, Field, FileItem, FileUpload, FilterChip, FlyoutBar, FlyoutBarItem, GhostText, Hatch, HazardStripe, HoverCard, IconButton, ImageViewer, ImageViewerItem, Input, ItemGrid, ItemSlot, Kbd, List, ListRow, Loader, Lock, Marquee, MediaCard, Menu, Minus, NavAction, NavMenu, NavMenuItem, Navigator, OtpInput, PageHeader, PageHeaderBack, Pagination, Panel, PanelBody, PanelHeader, PanelRow, PanelRows, PlayButton, PlayMark, Plus, Popover, PopoverClose, Progress, ProgressRing, Radio, RadioGroup, RecIndicator, RegistrationStrip, ResourceChip, Schedule, ScheduleItem, ScheduleTrack, ScrollArea, ScrollHint, SectionTitle, Segment, SegmentedControl, Select, SelectGroup, SelectItem, SelectSeparator, SideRail, SideRailGroup, SideRailItem, SideRailSub, SideRailSubItem, Skeleton, Slider, Sparkline, Spinner, Stat, StatusDanger, StatusInfo, StatusSuccess, StatusWarning, Step, Stepper, Steps, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableExpander, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagInput, TagPair, Term, Textarea, Texture, TickRing, Timeline, TimelineItem, ToastProvider, Toc, TocItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle, ToolbarToggleGroup, Tooltip, TooltipProvider, TopBar, TriangleRight, Upload, Viewfinder, cn, createIcon, useControllableState, useFieldContext, useFieldControl, useInView, usePortalScope, useTheme, useToast };
|
|
9626
|
+
export { Accordion, AccordionItem, Alert, ArrowCorner, ArrowLeft, ArrowRight, Avatar, AvatarSwitcher, AvatarSwitcherItem, BackToTop, Badge, BracketTitle, Breadcrumb, BreadcrumbItem, Button, ButtonGroup, Calendar, CalendarIcon, Carousel, CarouselSlide, Check, Checkbox, ChevronDown, ChevronLeft, ChevronRight, Close, Combobox, CompletionBanner, ContextMenu, CornerBrackets, Countdown, Crosshair, DashIndicator, DataRow, DataRowList, DatePicker, DateRangePicker, Dialog, DialogClose, Drawer, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuGroup, DropdownMenuItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, EmptyState, Field, FileItem, FileUpload, FilterChip, FlyoutBar, FlyoutBarItem, GhostText, Hatch, HazardStripe, HoverCard, IconButton, ImageViewer, ImageViewerItem, Input, ItemGrid, ItemSlot, Kbd, List, ListRow, Loader, Lock, Marquee, MediaCard, Menu, Minus, NavAction, NavMenu, NavMenuItem, Navigator, OtpInput, PageHeader, PageHeaderBack, Pagination, Panel, PanelBody, PanelHeader, PanelRow, PanelRows, PlayButton, PlayMark, Plus, Popover, PopoverClose, Progress, ProgressRing, Radio, RadioGroup, RecIndicator, RegistrationStrip, ResourceChip, RollingNumber, Schedule, ScheduleItem, ScheduleTrack, ScrollArea, ScrollHint, SectionTitle, Segment, SegmentedControl, Select, SelectGroup, SelectItem, SelectSeparator, SideRail, SideRailGroup, SideRailItem, SideRailSub, SideRailSubItem, Skeleton, Slider, Sparkline, Spinner, Stat, StatusDanger, StatusInfo, StatusSuccess, StatusWarning, Step, Stepper, Steps, Switch, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableExpander, TableHead, TableHeaderCell, TableRow, Tabs, Tag, TagInput, TagPair, Term, Textarea, Texture, TickRing, Timeline, TimelineItem, ToastProvider, Toc, TocItem, Toolbar, ToolbarButton, ToolbarGroup, ToolbarSeparator, ToolbarToggle, ToolbarToggleGroup, Tooltip, TooltipProvider, TopBar, TriangleRight, Upload, Viewfinder, cn, createIcon, useControllableState, useFieldContext, useFieldControl, useInView, usePortalScope, useReducedMotion, useTheme, useToast };
|