diff --git a/packages/core/src/merge-props.ts b/packages/core/src/merge-props.ts index c0742fa5e0..3a53681da5 100644 --- a/packages/core/src/merge-props.ts +++ b/packages/core/src/merge-props.ts @@ -1,6 +1,6 @@ import { callAll, isString } from "@zag-js/utils" -interface Props { +export interface Props { [key: string | symbol]: any } @@ -38,45 +38,59 @@ type TupleTypes = T[number] type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never -export function mergeProps(...args: Array): UnionToIntersection> { - let result: Props = {} +type ClassMergeFn = (existing: any, incoming: any) => any +type StyleMergeFn = (existing: any, incoming: any) => any - for (let props of args) { - if (!props) continue +interface MergeOptions { + classMerge?: ClassMergeFn + styleMerge?: StyleMergeFn +} - // Handle string keys - for (let key in result) { - if (key.startsWith("on") && typeof result[key] === "function" && typeof props[key] === "function") { - result[key] = callAll(props[key], result[key]) - continue - } +export function createMergeProps(options: MergeOptions = {}) { + const { classMerge = clsx, styleMerge = css } = options - if (key === "className" || key === "class") { - result[key] = clsx(result[key], props[key]) - continue - } + return function mergeProps(...args: Array): UnionToIntersection> { + let result: Props = {} + + for (let props of args) { + if (!props) continue + + // Handle string keys + for (let key in result) { + if (key.startsWith("on") && typeof result[key] === "function" && typeof props[key] === "function") { + result[key] = callAll(props[key], result[key]) + continue + } + + if (key === "className" || key === "class") { + result[key] = classMerge(result[key], props[key]) + continue + } - if (key === "style") { - result[key] = css(result[key], props[key]) - continue + if (key === "style") { + result[key] = styleMerge(result[key], props[key]) + continue + } + + result[key] = props[key] !== undefined ? props[key] : result[key] } - result[key] = props[key] !== undefined ? props[key] : result[key] - } + // Add props from b that are not in a + for (let key in props) { + if (result[key] === undefined) { + result[key] = props[key] + } + } - // Add props from b that are not in a - for (let key in props) { - if (result[key] === undefined) { - result[key] = props[key] + // Handle symbol keys (for Svelte attachments) + const symbols = Object.getOwnPropertySymbols(props) + for (let symbol of symbols) { + result[symbol] = props[symbol] } } - // Handle symbol keys (for Svelte attachments) - const symbols = Object.getOwnPropertySymbols(props) - for (let symbol of symbols) { - result[symbol] = props[symbol] - } + return result as any } - - return result as any } + +export const mergeProps = createMergeProps() diff --git a/packages/frameworks/svelte/src/merge-props.ts b/packages/frameworks/svelte/src/merge-props.ts index 5769026698..0495e4fd31 100644 --- a/packages/frameworks/svelte/src/merge-props.ts +++ b/packages/frameworks/svelte/src/merge-props.ts @@ -1,4 +1,4 @@ -import { mergeProps as zagMergeProps } from "@zag-js/core" +import { createMergeProps } from "@zag-js/core" import { toStyleString } from "./normalize-props" const CSS_REGEX = /((?:--)?(?:\w+-?)+)\s*:\s*([^;]*)/g @@ -14,35 +14,69 @@ const serialize = (style: string): CSSObject => { return res } -export function mergeProps(...args: Record[]) { - // Collect all class values (as-is, without conversion) - // Svelte 5.15+ supports ClassValue types (string | array | object) and handles - // the conversion internally using clsx. We collect them into an array and let - // Svelte's native class handling resolve them at render time. - // @see https://github.com/sveltejs/svelte/pull/14714 +// Custom class merge function for Svelte +// Collect all class values (as-is, without conversion) +// Svelte 5.15+ supports ClassValue types (string | array | object) and handles +// the conversion internally using clsx. We collect them into an array and let +// Svelte's native class handling resolve them at render time. +// @see https://github.com/sveltejs/svelte/pull/14714 +const svelteClassMerge = (existing: any, incoming: any) => { const classNames: any[] = [] - for (const props of args) { - if (!props) continue - if ("class" in props && props.class != null) { - classNames.push(props.class) - } - } - - const merged = zagMergeProps(...args) + if (existing != null) classNames.push(existing) + if (incoming != null) classNames.push(incoming) - // Override class with our collected values // If only one value, return as-is; if multiple, return as array - if (classNames.length > 0) { - merged.class = classNames.length === 1 ? classNames[0] : classNames + return classNames.length === 1 ? classNames[0] : classNames +} + +// Custom style merge function for Svelte +const svelteStyleMerge = (existing: any, incoming: any) => { + // First handle the basic merging (string to object conversion if needed) + let merged = existing + if (existing && incoming) { + if (typeof existing === "string" && typeof incoming === "string") { + merged = `${existing};${incoming}` + } else { + if (typeof existing === "string") existing = serialize(existing) + if (typeof incoming === "string") incoming = serialize(incoming) + merged = Object.assign({}, existing ?? {}, incoming ?? {}) + } + } else { + merged = incoming ?? existing } - if ("style" in merged) { - if (typeof merged.style === "string") { - merged.style = serialize(merged.style) + // Convert final result to Svelte's expected format + if (merged) { + if (typeof merged === "string") { + merged = serialize(merged) } - merged.style = toStyleString(merged.style) + merged = toStyleString(merged) } return merged } + +type SvelteClassValue = + | string + | (string | Record | null | undefined | boolean | (string | Record)[])[] + | Record + | null + | undefined + +type SvelteProps = Record & { + class?: SvelteClassValue + style?: string | Record +} + +export function mergeProps( + ...args: T +): Record & { + class?: SvelteClassValue + style?: string +} { + return createMergeProps({ + classMerge: svelteClassMerge, + styleMerge: svelteStyleMerge, + })(...args) as any +}