This commit is contained in:
2026-06-08 04:19:23 +08:00
parent 8351498071
commit f2d8ad0de2
152 changed files with 2362 additions and 2227 deletions

View File

@@ -0,0 +1,14 @@
import { cloneElement, isValidElement } from "react";
import { mergeProps } from "./mergeProps";
export const Slot = (props: any) => {
const { children, ...rest } = props;
if (!isValidElement(children)) {
throw new Error("Slot requires a single valid React element");
}
const mergedProps = mergeProps(children.props as Record<string, any>, rest);
return cloneElement(children, mergedProps);
};

View File

@@ -0,0 +1,107 @@
type AnyProps = Record<string, any>;
function defaultClassMergeFn(...classes: string[]): string {
return classes
.map((s) => s.trim())
.filter(Boolean)
.join(" ")
.trim();
}
// 工具类型
type UnionToIntersection<U> =
(U extends any ? (k: U) => void : never) extends
(k: infer I) => void ? I : never;
// 泛型入口
export function mergeProps<
T extends AnyProps[]
>(
...args: [...T]
): UnionToIntersection<T[number]>;
export function mergeProps<
T extends AnyProps[]
>(
options: { classMergeFn: (...cls: string[]) => string },
...args: [...T]
): UnionToIntersection<T[number]>;
export function mergeProps(
arg1: any,
...rest: any[]
): any {
let options = {
classMergeFn: defaultClassMergeFn,
};
let propsList: AnyProps[];
if (
arg1 &&
typeof arg1 === "object" &&
!Array.isArray(arg1) &&
"classMergeFn" in arg1 &&
typeof arg1.classMergeFn === "function"
) {
options = arg1;
propsList = rest;
} else {
propsList = [arg1, ...rest];
}
const result: AnyProps = {};
const eventHandlers = new Map<string, Function[]>();
const refs: any[] = [];
for (const props of propsList) {
if (!props) continue;
if (props.className) {
result.className = options.classMergeFn(
result.className ?? "",
props.className,
);
}
if (props.style) {
result.style = { ...(result.style ?? {}), ...props.style };
}
if (props.ref) {
refs.push(props.ref);
}
for (const key of Object.keys(props)) {
if (key.startsWith("on") && typeof props[key] === "function") {
if (!eventHandlers.has(key)) {
eventHandlers.set(key, []);
}
eventHandlers.get(key)!.push(props[key]);
} else if (key !== "ref" && key !== "className" && key !== "style") {
result[key] = props[key];
}
}
}
// refs
if (refs.length === 1) {
result.ref = refs[0];
} else if (refs.length > 1) {
result.ref = (node: any) => {
refs.forEach((ref) => {
if (typeof ref === "function") ref(node);
else if (ref) ref.current = node;
});
};
}
// events
eventHandlers.forEach((handlers, key) => {
result[key] = (...args: any[]) => {
handlers.forEach((fn) => fn(...args));
};
});
return result;
}

View File

@@ -0,0 +1,10 @@
import { useMemo } from "react";
export function useDefaultedProps<
P extends Record<string, any>,
D extends Partial<P>,
>(props: P, defaults: D): P & D {
return useMemo(() => {
return { ...defaults, ...props };
}, [props, defaults]);
}

View File

@@ -0,0 +1,8 @@
// useButtonSlots.ts
import { useState } from "react";
export function useSlotRegistry<S>() {
const [slots, setSlots] = useState<S>()
return { slots, setSlots }
}