@diskette/use-render 0.11.0 → 0.12.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.
@@ -1,8 +1,10 @@
1
- import type { ComponentProps, ComponentPropsWithRef, ElementType, JSX, ReactNode } from 'react';
1
+ import type { ComponentProps, ComponentPropsWithRef, ElementType, HTMLAttributes, JSX, ReactNode, Ref } from 'react';
2
2
  import type { DataAttributes } from '../types.ts';
3
- export type SlotRenderer<T extends ElementType> = (props: ComponentPropsWithRef<T>) => ReactNode;
4
- export type SlotProps<T extends ElementType> = ComponentPropsWithRef<T> & {
5
- render?: SlotRenderer<T> | JSX.Element;
3
+ export type SlotRenderer = (props: HTMLAttributes<any> & {
4
+ ref?: Ref<any> | undefined;
5
+ }) => ReactNode;
6
+ export type SlotProps<T extends ElementType> = ComponentPropsWithRef<T> & DataAttributes & {
7
+ render?: SlotRenderer | JSX.Element;
6
8
  };
7
9
  export interface RenderSlotOptions<T extends ElementType> {
8
10
  baseProps?: ComponentProps<T> & DataAttributes;
@@ -1,5 +1,5 @@
1
- import { cloneElement, createElement, isValidElement } from 'react';
2
- import { cx, isFunction, isString, mergeProps } from "../utils.js";
1
+ import { createElement, isValidElement } from 'react';
2
+ import { cloneRenderElement, cx, isFunction, isString, mergeProps } from "../utils.js";
3
3
  /**
4
4
  * Pure function for rendering slot elements with render prop support and prop merging.
5
5
  * RSC-compatible version of useRenderSlot without ref handling.
@@ -42,7 +42,7 @@ export function renderSlot(tag, options = {}) {
42
42
  const resolvedChildren = children ?? baseChildren;
43
43
  // For `<Component render={<a />} />`
44
44
  if (isValidElement(render)) {
45
- return cloneElement(render, resolvedProps, resolvedChildren);
45
+ return cloneRenderElement(render, resolvedProps, resolvedChildren);
46
46
  }
47
47
  // For `<Component render={(props) => <a {...props} />)} />`
48
48
  if (isFunction(render)) {
@@ -1,6 +1,6 @@
1
- import { cloneElement, createElement, isValidElement, useCallback, useMemo, } from 'react';
1
+ import { createElement, isValidElement, useCallback, useMemo, } from 'react';
2
2
  import { useComposedRef } from "./use-composed-ref.js";
3
- import { isFunction, isString, mergeProps, resolveClassName, resolveStyle, } from "./utils.js";
3
+ import { cloneRenderElement, isFunction, isString, mergeProps, resolveClassName, resolveStyle, } from "./utils.js";
4
4
  /**
5
5
  * Hook for rendering container elements with item-level render control.
6
6
  *
@@ -68,14 +68,14 @@ export function useRenderContainer(tag, containerState, options = {}) {
68
68
  }, [base, rest, mergedRef, resolvedClassName, resolvedStyle]);
69
69
  // Container component that handles render prop
70
70
  const Container = useCallback(({ children: containerChildren }) => {
71
+ // For `<Component render={<section />} />`
72
+ if (isValidElement(render)) {
73
+ return cloneRenderElement(render, resolvedProps, containerChildren);
74
+ }
71
75
  const propsWithChildren = {
72
76
  ...resolvedProps,
73
77
  children: containerChildren,
74
78
  };
75
- // For `<Component render={<section />} />`
76
- if (isValidElement(render)) {
77
- return cloneElement(render, propsWithChildren);
78
- }
79
79
  // For `<Component render={(props, state) => <section {...props} />} />`
80
80
  if (isFunction(render)) {
81
81
  return render(propsWithChildren, containerState);
@@ -1,6 +1,6 @@
1
- import { cloneElement, createElement, isValidElement } from 'react';
1
+ import { createElement, isValidElement } from 'react';
2
2
  import { useComposedRef } from "./use-composed-ref.js";
3
- import { isFunction, isString, mergeProps, resolveClassName, resolveStyle, } from "./utils.js";
3
+ import { cloneRenderElement, isFunction, isString, mergeProps, resolveClassName, resolveStyle, } from "./utils.js";
4
4
  /**
5
5
  * Hook for rendering elements with render prop support, prop merging, and state-driven className/style resolution.
6
6
  *
@@ -44,7 +44,7 @@ export function useRender(tag, state, options = {}) {
44
44
  const resolvedChildren = isFunction(children) ? children(state) : children;
45
45
  // For `<Component render={<a />} />`
46
46
  if (isValidElement(render)) {
47
- return cloneElement(render, resolvedProps, resolvedChildren);
47
+ return cloneRenderElement(render, resolvedProps, resolvedChildren);
48
48
  }
49
49
  // For `<Component render={(props) => <a {...props} />)} />`
50
50
  if (isFunction(render)) {
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CSSProperties } from 'react';
1
+ import type { CSSProperties, ReactElement, ReactNode } from 'react';
2
2
  import type { ClassName, Style } from './types.ts';
3
3
  export declare const isString: (value: unknown) => value is string;
4
4
  export declare const isFunction: (value: unknown) => value is Function;
@@ -27,3 +27,11 @@ export declare function resolveStyle<State>(state: State, defaultStyle?: Style<S
27
27
  * Returns a string with the truthy values of `args` separated by space.
28
28
  */
29
29
  export declare function cx(...args: Array<string | null | false | 0 | undefined>): string | undefined;
30
+ /**
31
+ * Clones a React element, merging its props with resolved props.
32
+ * - className values are merged via `cx`
33
+ * - style objects are spread-merged (render element styles as base, resolved styles on top)
34
+ * - event handlers are composed via `mergeProps`
35
+ * - children are set if provided
36
+ */
37
+ export declare function cloneRenderElement(render: ReactElement, resolvedProps: React.ComponentProps<'div'>, children?: ReactNode): ReactNode;
package/dist/utils.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { cloneElement } from 'react';
1
2
  export const isString = (value) => typeof value === 'string';
2
3
  export const isFunction = (value) => typeof value === 'function';
3
4
  export const isUndefined = (value) => typeof value === 'undefined';
@@ -73,3 +74,26 @@ export function resolveStyle(state, defaultStyle, propsStyle) {
73
74
  export function cx(...args) {
74
75
  return args.filter(Boolean).join(' ') || undefined;
75
76
  }
77
+ /**
78
+ * Clones a React element, merging its props with resolved props.
79
+ * - className values are merged via `cx`
80
+ * - style objects are spread-merged (render element styles as base, resolved styles on top)
81
+ * - event handlers are composed via `mergeProps`
82
+ * - children are set if provided
83
+ */
84
+ export function cloneRenderElement(render, resolvedProps, children) {
85
+ const { className: renderClassName, style: renderStyle, children: _renderChildren, ...renderRest } = render.props;
86
+ const { className: resolvedClassName, style: resolvedStyle, children: _resolvedChildren, ...resolvedRest } = resolvedProps;
87
+ const merged = mergeProps(renderRest, resolvedRest);
88
+ const mergedClassName = cx(renderClassName, resolvedClassName);
89
+ if (isString(mergedClassName)) {
90
+ merged.className = mergedClassName;
91
+ }
92
+ if (renderStyle || resolvedStyle) {
93
+ merged.style = { ...renderStyle, ...resolvedStyle };
94
+ }
95
+ if (children !== undefined) {
96
+ merged.children = children;
97
+ }
98
+ return cloneElement(render, merged);
99
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@diskette/use-render",
3
3
  "type": "module",
4
- "version": "0.11.0",
4
+ "version": "0.12.0",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
7
7
  "./fns": "./dist/fns/index.js"