Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@bento/slots

Package Overview
Dependencies
Maintainers
3
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@bento/slots - npm Package Compare versions

Comparing version
0.1.3
to
0.2.0
+118
src/contains.ts
import React from 'react';
/**
* Checks if children contain all required slot assignments, including namespaced paths.
*
* This utility iterates through React children to verify that all required
* slot names are present. It supports both simple slot names ('trigger') and
* namespaced paths ('submit.icon') for deeply nested slot validation.
*
* **Important:** Only components wrapped with `withSlots()` are validated. Raw HTML
* elements with `slot` props are ignored, as they are not part of the Bento slot system.
*
* @param slotNames - Array of required slot names to check for (e.g., ['trigger', 'submit.icon'])
* @param children - React children to search through
* @returns true if all required slots are found, false otherwise. Returns true
* for render prop functions since slots can't be validated until executed.
*
* @example
* ```tsx
* // Simple slot validation
* function MyComponent({ children }) {
* if (!contains(['trigger', 'content'], children)) {
* throw new BentoError({
* name: 'my-component',
* method: 'MyComponent',
* message: 'Missing required slots: trigger and content'
* });
* }
* return <div>{children}</div>;
* }
*
* // Namespaced slot validation
* function Form({ children }) {
* if (!contains(['submit.icon'], children)) {
* throw new Error('Submit button must have an icon');
* }
* return <form>{children}</form>;
* }
* ```
*
* @public
*/
export function contains(slotNames: string[], children: React.ReactNode): boolean {
if (!slotNames || slotNames.length === 0) return true;
if (!children) return false;
// Skip validation for render prop functions - we can't inspect slots
// until the function is executed with state/props
if (typeof children === 'function') return true;
const foundSlots = new Set<string>();
/**
* Recursively search through children to find slot assignments, building
* the path as we go to support namespaced lookups like 'submit.icon'.
*
* When a child has a slot prop, we add both the simple slot name and the full
* namespaced path to our set. For example, if we find:
* - `<Button slot="submit"><Icon slot="icon" /></Button>`
*
* We'll add: 'submit', 'submit', 'icon', and 'submit.icon' to foundSlots.
*
* Children without slot props (like wrapper divs) are searched through
* transparently without extending the path.
*
* Only components wrapped with `withSlots()` are considered valid slotted
* components. They can be identified by the `bento` property set to `true`.
*
* @param node - The React children to search through
* @param path - The accumulated path of slot names from parent to current level
*/
function search(node: React.ReactNode, path: string[] = []): void {
React.Children.forEach(node, function processChild(child) {
if (!React.isValidElement(child)) return;
// Check if this is a component wrapped with withSlots
// withSlots components have a `bento` property set to true
const childType = child.type as any;
const isSlottedComponent = childType?.bento === true;
// Check if this child has a slot prop
const slot = (child.props as any)?.slot;
if (typeof slot === 'string' && slot.length > 0) {
// Only process slot assignments on withSlots components
if (isSlottedComponent) {
// Build the full path including this slot
const fullPath = [...path, slot];
const pathString = fullPath.join('.');
// Add both the simple slot name and the full namespaced path
foundSlots.add(slot);
foundSlots.add(pathString);
// Continue searching children with the updated path
if (child.props?.children) {
search(child.props.children, fullPath);
}
} else {
// Not a withSlots component - search children without extending path
if (child.props?.children) {
search(child.props.children, path);
}
}
} else {
// No slot on this child, but continue searching its children
// without extending the path
if (child.props?.children) {
search(child.props.children, path);
}
}
});
}
search(children);
// Check if all required slots were found
return slotNames.every((name) => foundSlots.has(name));
}
+47
-6

@@ -7,2 +7,3 @@ 'use strict';

var error = require('@bento/error');
var forward = require('@bento/forward');
var useDataAttributes = require('@bento/use-data-attributes');

@@ -77,6 +78,7 @@

function withSlots(name, Component, modifiers = [replace, override]) {
function WrappedComponent(propsAndSlots) {
const ComponentWithRef = forward.withForwardRef(Component);
const SlottedForwardRef = forward.withForwardRef(function SlottedComponent2(propsAndSlots, forwardedRef) {
const { slot = "", slots = {}, ...restProps } = propsAndSlots;
let props = { ...restProps };
let Element = Component;
let Element = ComponentWithRef;
let ctx = { ...React.useContext(box.Box) };

@@ -130,10 +132,13 @@ ctx.env = { ...ctx.env };

});
const baseProps = { ...props };
const propsWithRef = forwardedRef != null ? { ...baseProps, ref: forwardedRef } : baseProps;
const context = useDeepCompare.useDeepCompareMemo(() => ctx, [ctx]);
const rendered = /* @__PURE__ */ React__default.default.createElement(box.Box.Provider, { value: context }, /* @__PURE__ */ React__default.default.createElement(Element, { ...props }));
const rendered = /* @__PURE__ */ React__default.default.createElement(box.Box.Provider, { value: context }, /* @__PURE__ */ React__default.default.createElement(Element, { ...propsWithRef }));
const slotted = ctx.slots.assigned[ctx.slots.namespace.join(".")];
if (typeof slotted !== "function") return rendered;
return slotted({ props, original: rendered.props.children });
}
const SlottedComponent = React.memo(WrappedComponent);
return slotted({ props: propsWithRef, original: rendered.props.children });
});
const SlottedComponent = React.memo(SlottedForwardRef);
SlottedComponent.displayName = `Slotted(${name})`;
SlottedComponent.bento = true;
if (process.env.NODE_ENV !== "production") {

@@ -160,3 +165,39 @@ SlottedComponent.whyDidYouRender = true;

}
function contains(slotNames, children) {
if (!slotNames || slotNames.length === 0) return true;
if (!children) return false;
if (typeof children === "function") return true;
const foundSlots = /* @__PURE__ */ new Set();
function search(node, path = []) {
React__default.default.Children.forEach(node, function processChild(child) {
if (!React__default.default.isValidElement(child)) return;
const childType = child.type;
const isSlottedComponent = childType?.bento === true;
const slot = child.props?.slot;
if (typeof slot === "string" && slot.length > 0) {
if (isSlottedComponent) {
const fullPath = [...path, slot];
const pathString = fullPath.join(".");
foundSlots.add(slot);
foundSlots.add(pathString);
if (child.props?.children) {
search(child.props.children, fullPath);
}
} else {
if (child.props?.children) {
search(child.props.children, path);
}
}
} else {
if (child.props?.children) {
search(child.props.children, path);
}
}
});
}
search(children);
return slotNames.every((name) => foundSlots.has(name));
}
exports.contains = contains;
exports.library = library;

@@ -163,0 +204,0 @@ exports.override = override;

+1
-1

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/override.ts","../src/replace.ts","../src/slots.tsx"],"names":["override","useDataAttributes","useContext","Box","mergedFnSlot","props","useDeepCompareMemo","React","memo","BentoError"],"mappings":"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","file":"index.cjs","sourcesContent":["import { type ComponentType, type CSSProperties } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\n\n/**\n * Checks if a given key is a CSS variable.\n *\n * @param key - The key to check.\n * @returns true if the key is a CSS variable, otherwise false.\n * @private\n */\nfunction isCSSVariable(key: string) {\n return key.startsWith('--');\n}\n\n/**\n * Interface representing the arguments required to override a component.\n *\n * @private\n */\ninterface OverrideArgs<Props> {\n /** The properties of the component. */\n props: Props;\n\n /** The component which should be rendered. */\n Component: ComponentType<Props>;\n\n /** The current Slot context. */\n context: BoxContext<Props>;\n\n /** The name of the component. */\n name: string;\n}\n\n/**\n * Represents the result of an override operation.\n *\n * @private\n */\ninterface OverrideResult {\n /** The properties of the override result. */\n props: {\n /** A string indicating the override data. */\n 'data-override'?: string;\n };\n}\n\nconst triggers: string[] = ['className', 'style'];\n\n/**\n * Overrides the properties of a given context based on certain conditions.\n *\n * @param args.context - The context object.\n * @param args.props - The properties object.\n * @returns The result containing the updated properties or undefined if no overrides are applied.\n * @public\n */\nexport function override<Props extends Record<string, any>>({\n context,\n props\n}: OverrideArgs<Props>): OverrideResult | undefined {\n const causes: string[] = [];\n const { namespace, assigned, override } = context.slots;\n const slot: Record<string, any> | undefined = assigned[namespace.join('.')];\n\n if (typeof props['data-override'] === 'string') {\n causes.push(...props['data-override'].split(' '));\n }\n\n if (override && !causes.includes('context')) causes.push('context');\n if ('className' in props && !causes.includes('className')) causes.push('className');\n\n //\n // For style we need to take a more sophisticated approach, users are allowed\n // to define CSS variables in the style prop, so we need to check if the keys\n // are prefixed with `--` or not.\n //\n if ('style' in props && !causes.includes('style')) {\n const style = props.style as CSSProperties;\n const keys = Object.keys(style);\n\n if (keys.some((key) => !isCSSVariable(key))) {\n causes.push('style');\n }\n }\n\n if (slot) {\n Object.keys(slot).forEach(function forEach(name) {\n if (triggers.includes(name) && !causes.includes(name)) causes.push(name);\n if (!causes.includes('slot')) causes.push('slot');\n });\n }\n\n if (!causes.length) return;\n return {\n props: useDataAttributes({ override: causes })\n };\n}\n","import { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\nimport { type ComponentType } from 'react';\n\n/**\n * Interface representing the arguments required for replacing a component.\n *\n * @interface ReplaceArgs\n * @property {Props} props - The properties of the component.\n * @property {ComponentType<Props>} Component - The component which should be rendered.\n * @property {SlotContext<Props>} context - The current Slot context.\n * @property {string} name - The name of the component.\n * @private\n */\ninterface ReplaceArgs<Props> {\n props: Props;\n Component: ComponentType<Props>;\n context: BoxContext<Props>;\n name: string;\n}\n\n/**\n * Interface for props override objects\n * @private\n */\ninterface PropsOverride {\n /** The properties of the props override. */\n props: Record<string, any>;\n}\n\n/**\n * Checks if the provided data is a props override instead of a component\n * override.\n *\n * @param data - The data to check.\n * @returns Returns true if the data is a props override, false otherwise.\n * @private\n */\nfunction isPropsOverride(data: any): data is PropsOverride {\n if (typeof data !== 'object' || Array.isArray(data)) return false;\n\n const keys = Object.keys(data);\n return keys.includes('props') && keys.length === 1;\n}\n\n/**\n * Replaces a component in the context with the specified name and props.\n *\n * @param args.props - The properties to be passed to the component.\n * @param args.name - The name of the component to be replaced.\n * @param args.context - The context containing the components.\n * @returns An object containing the updated props, the component to be replaced, and the updated context.\n * @public\n */\nexport function replace<Props extends Record<string, any>>({ props, name, context }: ReplaceArgs<Props>) {\n if (!context.env || !context.env.components || !(name in context.env.components)) return;\n\n const target = context.env.components[name];\n if (!target) return;\n\n const result = {\n context: {\n slots: {\n override: !!target\n }\n },\n props: {}\n } as any;\n\n const causes = (props['data-override'] || '').split(' ').filter(Boolean);\n if (!causes.includes('context')) causes.push('context');\n result.props = useDataAttributes({ override: causes });\n\n if (isPropsOverride(target)) result.props = { ...target.props };\n else result.Component = target;\n\n return result;\n}\n","/// <reference types=\"vite/client\" />\nimport React, { useContext, memo } from 'react';\nimport { useDeepCompareMemo } from 'use-deep-compare';\nimport { Box, type BoxContext } from '@bento/box';\nimport { BentoError } from '@bento/error';\nimport { override } from './override.ts';\nimport { replace } from './replace.ts';\n\n/**\n * Interface representing a collection of slots.\n */\nexport interface Slots {\n /**\n * A named part of a component that can be customized. This is implemented by the consuming component.\n * The exposed slot names of a component are available in the components documentation.\n */\n slot?: string;\n /**\n * An object that contains the customizations for the slots.\n * The main way you interact with the slot system as a consumer.\n */\n slots?: Record<string, object | Function>;\n}\n\n//\n// A list of libraries that are currently registered by the system. This is\n// needed to prevent duplicate component names from being registered as the\n// names are used to target the correct component with slot overrides.\n//\nexport const library = new Set<string>();\n\n/**\n * Higher-order component that wraps a given component with slot functionality.\n * This allows components to be dynamically replaced or overridden based on the provided context and slots.\n *\n * @param name - The unique name of the component. This is used to identify the component for slot overrides.\n * @param Component - The component that should be rendered.\n * @param modifiers - The modifier functions that should be applied to the component.\n * @returns The wrapped component.\n * @public\n *\n * @throws {BentoError} If the component name has already been registered (in development mode).\n * @throws {BentoError} If any of the supplied modifiers are not functions.\n *\n * @example\n * ```tsx\n * const MyComponent = () => <div>My Component</div>;\n * const SlottedMyComponent = withSlots('MyComponent', MyComponent);\n * ```\n */\nexport function withSlots<Props extends object>(\n name: string,\n Component: React.ComponentType<Props>,\n modifiers = [replace, override]\n) {\n function WrappedComponent(propsAndSlots: Props & Slots) {\n const { slot = '', slots = {}, ...restProps } = propsAndSlots;\n let props = { ...restProps } as Props;\n let Element = Component;\n\n //\n // We need to create a new context object to prevent introducing properties\n // to a context that is shared across components. Without cloning the context\n // properties like `namespace` will become corrupted as they would start to\n // include the slot name of siblings instead of just being a pure parent/child\n // relationship.\n let ctx = { ...useContext<BoxContext<Props>>(Box) };\n ctx.env = { ...ctx.env };\n ctx.slots = { ...ctx.slots };\n ctx.slots.namespace = [...ctx.slots.namespace, slot].filter(Boolean);\n\n const currentNamespace = ctx.slots.namespace.join('.');\n const inheritedSlots: Record<string, any> = {};\n const prefix = `${currentNamespace}.`;\n\n //\n // Only inherit slots from the parent that match the current namespace.\n // If the current namespace is empty (root level), inherit all slots\n // that don't contain a dot (i.e., slots at the root level only).\n //\n for (const key in ctx.slots.assigned) {\n if (currentNamespace === '' && !key.includes('.')) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n } else if (key === currentNamespace || key.startsWith(prefix)) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n }\n }\n\n ctx.slots.assigned = inheritedSlots;\n\n //\n // merge the new slots with the assigned slots,\n // parent component slots should take precedence over child ones.\n //\n for (const slotKey in slots) {\n // Build the fully qualified slot key by prefixing with current namespace\n const namespacedKey = ctx.slots.namespace.length > 0 ? `${currentNamespace}.${slotKey}` : slotKey;\n const assignedSlot = ctx.slots.assigned[namespacedKey];\n const newSlot = slots[slotKey];\n\n //\n // New slots are assigned if none exist, or merged with the new slots.\n // If the assigned slot exists and is an object, keep it as is.\n //\n if (!assignedSlot) {\n ctx.slots.assigned[namespacedKey] = newSlot;\n } else if (typeof assignedSlot === 'object') {\n ctx.slots.assigned[namespacedKey] = { ...newSlot, ...assignedSlot };\n } else if (typeof assignedSlot === 'function') {\n const existingPrevious = assignedSlot.__slotPrevious || [];\n const newPrevious = [newSlot, ...existingPrevious];\n const mergedFnSlot = function mergedFnSlot({ props, original, previous }: any) {\n return assignedSlot({ props, original, previous: previous || newPrevious });\n };\n\n mergedFnSlot.__slotPrevious = newPrevious;\n ctx.slots.assigned[namespacedKey] = mergedFnSlot;\n }\n }\n\n //\n // Modifiers allow you to manipulate the context, props, and component with\n // a single function. This makes it easier to turn on or off functionality\n // that you might not need for your components.\n //\n // For Bento based components we supply the following modifiers by default:\n // - replace: Allows you to replace the component with another component\n // based on the slots and provided context.\n // - override: Makes a note of potential design or functional overrides\n // that are applied to the component. Making it easier to provide support\n // to find common overrides in your application.\n //\n modifiers.forEach(function forEach(modifier) {\n const mods: {\n Component?: React.ComponentType<Props>;\n context?: Partial<BoxContext<Props>> | Record<string, any>;\n props?: object;\n } =\n modifier({\n Component: Element,\n context: ctx,\n props,\n name\n }) || {};\n\n if (typeof mods.context === 'object')\n ctx = {\n env: { ...ctx.env, ...mods.context?.env },\n slots: { ...ctx.slots, ...mods.context?.slots }\n };\n\n if (typeof mods.props === 'object') props = { ...props, ...mods.props };\n if (mods.Component) Element = mods.Component;\n });\n\n const context = useDeepCompareMemo(() => ctx, [ctx]);\n const rendered = (\n <Box.Provider value={context}>\n <Element {...props} />\n </Box.Provider>\n );\n\n const slotted = ctx.slots.assigned[ctx.slots.namespace.join('.')];\n\n if (typeof slotted !== 'function') return rendered;\n return slotted({ props, original: rendered.props.children });\n }\n\n const SlottedComponent = memo<Props & Slots>(WrappedComponent);\n SlottedComponent.displayName = `Slotted(${name})`;\n\n if (process.env.NODE_ENV !== 'production') {\n //\n // This enables re-render tracking for every Bento based component using the\n // `@welldone-software/why-did-you-render` package.\n //\n (SlottedComponent as any).whyDidYouRender = true;\n\n //\n // We want to throw the following error only in a development environment.\n //\n // While the requirement is to have unique components so each component can\n // be individually and correctly targeted using our provided context we\n // want to be mindful that we're breaking an application for the right\n // reasons. We should only throw in production if an application cannot\n // recover from this error.\n //\n // In the case of these overrides\n //\n if (library.has(name) && !import.meta.hot)\n throw new BentoError({\n message: 'The supplied component %s has already been registered.',\n args: [name],\n method: 'withSlots',\n name: 'slots'\n });\n\n library.add(name);\n }\n\n modifiers.forEach(function forEach(modifier) {\n if (typeof modifier !== 'function')\n throw new BentoError({\n message: 'The supplied component modifier is not a function.',\n method: 'withSlots',\n name: 'slots'\n });\n });\n\n return SlottedComponent;\n}\n"]}
{"version":3,"sources":["../src/override.ts","../src/replace.ts","../src/slots.tsx","../src/contains.ts"],"names":["override","useDataAttributes","withForwardRef","SlottedComponent","useContext","Box","mergedFnSlot","props","useDeepCompareMemo","React","memo","BentoError"],"mappings":"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","file":"index.cjs","sourcesContent":["import { type ComponentType, type CSSProperties } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\n\n/**\n * Checks if a given key is a CSS variable.\n *\n * @param key - The key to check.\n * @returns true if the key is a CSS variable, otherwise false.\n * @private\n */\nfunction isCSSVariable(key: string) {\n return key.startsWith('--');\n}\n\n/**\n * Interface representing the arguments required to override a component.\n *\n * @private\n */\ninterface OverrideArgs<Props> {\n /** The properties of the component. */\n props: Props;\n\n /** The component which should be rendered. */\n Component: ComponentType<Props>;\n\n /** The current Slot context. */\n context: BoxContext<Props>;\n\n /** The name of the component. */\n name: string;\n}\n\n/**\n * Represents the result of an override operation.\n *\n * @private\n */\ninterface OverrideResult {\n /** The properties of the override result. */\n props: {\n /** A string indicating the override data. */\n 'data-override'?: string;\n };\n}\n\nconst triggers: string[] = ['className', 'style'];\n\n/**\n * Overrides the properties of a given context based on certain conditions.\n *\n * @param args.context - The context object.\n * @param args.props - The properties object.\n * @returns The result containing the updated properties or undefined if no overrides are applied.\n * @public\n */\nexport function override<Props extends Record<string, any>>({\n context,\n props\n}: OverrideArgs<Props>): OverrideResult | undefined {\n const causes: string[] = [];\n const { namespace, assigned, override } = context.slots;\n const slot: Record<string, any> | undefined = assigned[namespace.join('.')];\n\n if (typeof props['data-override'] === 'string') {\n causes.push(...props['data-override'].split(' '));\n }\n\n if (override && !causes.includes('context')) causes.push('context');\n if ('className' in props && !causes.includes('className')) causes.push('className');\n\n //\n // For style we need to take a more sophisticated approach, users are allowed\n // to define CSS variables in the style prop, so we need to check if the keys\n // are prefixed with `--` or not.\n //\n if ('style' in props && !causes.includes('style')) {\n const style = props.style as CSSProperties;\n const keys = Object.keys(style);\n\n if (keys.some((key) => !isCSSVariable(key))) {\n causes.push('style');\n }\n }\n\n if (slot) {\n Object.keys(slot).forEach(function forEach(name) {\n if (triggers.includes(name) && !causes.includes(name)) causes.push(name);\n if (!causes.includes('slot')) causes.push('slot');\n });\n }\n\n if (!causes.length) return;\n return {\n props: useDataAttributes({ override: causes })\n };\n}\n","import { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\nimport { type ComponentType } from 'react';\n\n/**\n * Interface representing the arguments required for replacing a component.\n *\n * @interface ReplaceArgs\n * @property {Props} props - The properties of the component.\n * @property {ComponentType<Props>} Component - The component which should be rendered.\n * @property {SlotContext<Props>} context - The current Slot context.\n * @property {string} name - The name of the component.\n * @private\n */\ninterface ReplaceArgs<Props> {\n props: Props;\n Component: ComponentType<Props>;\n context: BoxContext<Props>;\n name: string;\n}\n\n/**\n * Interface for props override objects\n * @private\n */\ninterface PropsOverride {\n /** The properties of the props override. */\n props: Record<string, any>;\n}\n\n/**\n * Checks if the provided data is a props override instead of a component\n * override.\n *\n * @param data - The data to check.\n * @returns Returns true if the data is a props override, false otherwise.\n * @private\n */\nfunction isPropsOverride(data: any): data is PropsOverride {\n if (typeof data !== 'object' || Array.isArray(data)) return false;\n\n const keys = Object.keys(data);\n return keys.includes('props') && keys.length === 1;\n}\n\n/**\n * Replaces a component in the context with the specified name and props.\n *\n * @param args.props - The properties to be passed to the component.\n * @param args.name - The name of the component to be replaced.\n * @param args.context - The context containing the components.\n * @returns An object containing the updated props, the component to be replaced, and the updated context.\n * @public\n */\nexport function replace<Props extends Record<string, any>>({ props, name, context }: ReplaceArgs<Props>) {\n if (!context.env || !context.env.components || !(name in context.env.components)) return;\n\n const target = context.env.components[name];\n if (!target) return;\n\n const result = {\n context: {\n slots: {\n override: !!target\n }\n },\n props: {}\n } as any;\n\n const causes = (props['data-override'] || '').split(' ').filter(Boolean);\n if (!causes.includes('context')) causes.push('context');\n result.props = useDataAttributes({ override: causes });\n\n if (isPropsOverride(target)) result.props = { ...target.props };\n else result.Component = target;\n\n return result;\n}\n","/// <reference types=\"vite/client\" />\nimport React, { useContext, memo } from 'react';\nimport { useDeepCompareMemo } from 'use-deep-compare';\nimport { Box, type BoxContext } from '@bento/box';\nimport { BentoError } from '@bento/error';\nimport { withForwardRef } from '@bento/forward';\nimport { override } from './override.ts';\nimport { replace } from './replace.ts';\n\n/**\n * Interface representing a collection of slots.\n */\nexport interface Slots {\n /**\n * A named part of a component that can be customized. This is implemented by the consuming component.\n * The exposed slot names of a component are available in the components documentation.\n */\n slot?: string;\n /**\n * An object that contains the customizations for the slots.\n * The main way you interact with the slot system as a consumer.\n */\n slots?: Record<string, object | Function>;\n}\n\n//\n// A list of libraries that are currently registered by the system. This is\n// needed to prevent duplicate component names from being registered as the\n// names are used to target the correct component with slot overrides.\n//\nexport const library = new Set<string>();\n\n/**\n * Higher-order component that wraps a given component with slot functionality.\n * This allows components to be dynamically replaced or overridden based on the provided context and slots.\n *\n * @param name - The unique name of the component. This is used to identify the component for slot overrides.\n * @param Component - The component that should be rendered.\n * @param modifiers - The modifier functions that should be applied to the component.\n * @returns The wrapped component.\n * @public\n *\n * @throws {BentoError} If the component name has already been registered (in development mode).\n * @throws {BentoError} If any of the supplied modifiers are not functions.\n *\n * @example\n * ```tsx\n * const MyComponent = () => <div>My Component</div>;\n * const SlottedMyComponent = withSlots('MyComponent', MyComponent);\n * ```\n */\nexport function withSlots<Props extends object>(\n name: string,\n Component: React.ComponentType<Props>,\n modifiers = [replace, override]\n) {\n //\n // Use @bento/forward to handle ref forwarding for React 18/19 compatibility\n // and to ensure the component is wrapped with forwardRef if needed.\n //\n const ComponentWithRef = withForwardRef(Component);\n const SlottedForwardRef = withForwardRef(function SlottedComponent(propsAndSlots: Props & Slots, forwardedRef: any) {\n const { slot = '', slots = {}, ...restProps } = propsAndSlots;\n let props = { ...restProps } as Props;\n let Element: React.ComponentType<any> = ComponentWithRef;\n\n //\n // We need to create a new context object to prevent introducing properties\n // to a context that is shared across components. Without cloning the context\n // properties like `namespace` will become corrupted as they would start to\n // include the slot name of siblings instead of just being a pure parent/child\n // relationship.\n //\n let ctx = { ...useContext<BoxContext<Props>>(Box) };\n ctx.env = { ...ctx.env };\n ctx.slots = { ...ctx.slots };\n ctx.slots.namespace = [...ctx.slots.namespace, slot].filter(Boolean);\n\n const currentNamespace = ctx.slots.namespace.join('.');\n const inheritedSlots: Record<string, any> = {};\n const prefix = `${currentNamespace}.`;\n\n //\n // Only inherit slots from the parent that match the current namespace.\n // If the current namespace is empty (root level), inherit all slots\n // that don't contain a dot (i.e., slots at the root level only).\n //\n for (const key in ctx.slots.assigned) {\n if (currentNamespace === '' && !key.includes('.')) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n } else if (key === currentNamespace || key.startsWith(prefix)) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n }\n }\n\n ctx.slots.assigned = inheritedSlots;\n\n //\n // merge the new slots with the assigned slots,\n // parent component slots should take precedence over child ones.\n //\n for (const slotKey in slots) {\n // Build the fully qualified slot key by prefixing with current namespace\n const namespacedKey = ctx.slots.namespace.length > 0 ? `${currentNamespace}.${slotKey}` : slotKey;\n const assignedSlot = ctx.slots.assigned[namespacedKey];\n const newSlot = slots[slotKey];\n\n //\n // New slots are assigned if none exist, or merged with the new slots.\n // If the assigned slot exists and is an object, keep it as is.\n //\n if (!assignedSlot) {\n ctx.slots.assigned[namespacedKey] = newSlot;\n } else if (typeof assignedSlot === 'object') {\n ctx.slots.assigned[namespacedKey] = { ...newSlot, ...assignedSlot };\n } else if (typeof assignedSlot === 'function') {\n const existingPrevious = assignedSlot.__slotPrevious || [];\n const newPrevious = [newSlot, ...existingPrevious];\n const mergedFnSlot = function mergedFnSlot({ props, original, previous }: any) {\n return assignedSlot({ props, original, previous: previous || newPrevious });\n };\n\n mergedFnSlot.__slotPrevious = newPrevious;\n ctx.slots.assigned[namespacedKey] = mergedFnSlot;\n }\n }\n\n //\n // Modifiers allow you to manipulate the context, props, and component with\n // a single function. This makes it easier to turn on or off functionality\n // that you might not need for your components.\n //\n // For Bento based components we supply the following modifiers by default:\n // - replace: Allows you to replace the component with another component\n // based on the slots and provided context.\n // - override: Makes a note of potential design or functional overrides\n // that are applied to the component. Making it easier to provide support\n // to find common overrides in your application.\n //\n modifiers.forEach(function forEach(modifier) {\n const mods: {\n Component?: React.ComponentType<Props>;\n context?: Partial<BoxContext<Props>> | Record<string, any>;\n props?: object;\n } =\n modifier({\n Component: Element,\n context: ctx,\n props,\n name\n }) || {};\n\n if (typeof mods.context === 'object')\n ctx = {\n env: { ...ctx.env, ...mods.context?.env },\n slots: { ...ctx.slots, ...mods.context?.slots }\n };\n\n if (typeof mods.props === 'object') props = { ...props, ...mods.props };\n if (mods.Component) Element = mods.Component as React.ComponentType<any>;\n });\n\n const baseProps = { ...(props as object) } as Props;\n\n // Add ref to props. withForwardRef handles the React 18/19 differences:\n // - React 18: Component is wrapped with forwardRef, so ref is passed separately\n // - React 19: ref is just a regular prop\n const propsWithRef = forwardedRef != null ? ({ ...baseProps, ref: forwardedRef } as Props) : baseProps;\n\n const context = useDeepCompareMemo(() => ctx, [ctx]);\n const rendered = (\n <Box.Provider value={context}>\n <Element {...propsWithRef} />\n </Box.Provider>\n );\n\n const slotted = ctx.slots.assigned[ctx.slots.namespace.join('.')];\n\n if (typeof slotted !== 'function') return rendered;\n return slotted({ props: propsWithRef, original: rendered.props.children });\n });\n\n const SlottedComponent = memo(SlottedForwardRef);\n SlottedComponent.displayName = `Slotted(${name})`;\n\n //\n // Mark this as a Bento component wrapped with withSlots\n // This is used by the contains() utility to identify valid slotted components\n //\n (SlottedComponent as any).bento = true;\n\n if (process.env.NODE_ENV !== 'production') {\n //\n // This enables re-render tracking for every Bento based component using the\n // `@welldone-software/why-did-you-render` package.\n //\n (SlottedComponent as any).whyDidYouRender = true;\n\n //\n // We want to throw the following error only in a development environment.\n //\n // While the requirement is to have unique components so each component can\n // be individually and correctly targeted using our provided context we\n // want to be mindful that we're breaking an application for the right\n // reasons. We should only throw in production if an application cannot\n // recover from this error.\n //\n // In the case of these overrides\n //\n if (library.has(name) && !import.meta.hot)\n throw new BentoError({\n message: 'The supplied component %s has already been registered.',\n args: [name],\n method: 'withSlots',\n name: 'slots'\n });\n\n library.add(name);\n }\n\n modifiers.forEach(function forEach(modifier) {\n if (typeof modifier !== 'function')\n throw new BentoError({\n message: 'The supplied component modifier is not a function.',\n method: 'withSlots',\n name: 'slots'\n });\n });\n\n return SlottedComponent;\n}\n","import React from 'react';\n\n/**\n * Checks if children contain all required slot assignments, including namespaced paths.\n *\n * This utility iterates through React children to verify that all required\n * slot names are present. It supports both simple slot names ('trigger') and\n * namespaced paths ('submit.icon') for deeply nested slot validation.\n *\n * **Important:** Only components wrapped with `withSlots()` are validated. Raw HTML\n * elements with `slot` props are ignored, as they are not part of the Bento slot system.\n *\n * @param slotNames - Array of required slot names to check for (e.g., ['trigger', 'submit.icon'])\n * @param children - React children to search through\n * @returns true if all required slots are found, false otherwise. Returns true\n * for render prop functions since slots can't be validated until executed.\n *\n * @example\n * ```tsx\n * // Simple slot validation\n * function MyComponent({ children }) {\n * if (!contains(['trigger', 'content'], children)) {\n * throw new BentoError({\n * name: 'my-component',\n * method: 'MyComponent',\n * message: 'Missing required slots: trigger and content'\n * });\n * }\n * return <div>{children}</div>;\n * }\n *\n * // Namespaced slot validation\n * function Form({ children }) {\n * if (!contains(['submit.icon'], children)) {\n * throw new Error('Submit button must have an icon');\n * }\n * return <form>{children}</form>;\n * }\n * ```\n *\n * @public\n */\nexport function contains(slotNames: string[], children: React.ReactNode): boolean {\n if (!slotNames || slotNames.length === 0) return true;\n if (!children) return false;\n\n // Skip validation for render prop functions - we can't inspect slots\n // until the function is executed with state/props\n if (typeof children === 'function') return true;\n\n const foundSlots = new Set<string>();\n\n /**\n * Recursively search through children to find slot assignments, building\n * the path as we go to support namespaced lookups like 'submit.icon'.\n *\n * When a child has a slot prop, we add both the simple slot name and the full\n * namespaced path to our set. For example, if we find:\n * - `<Button slot=\"submit\"><Icon slot=\"icon\" /></Button>`\n *\n * We'll add: 'submit', 'submit', 'icon', and 'submit.icon' to foundSlots.\n *\n * Children without slot props (like wrapper divs) are searched through\n * transparently without extending the path.\n *\n * Only components wrapped with `withSlots()` are considered valid slotted\n * components. They can be identified by the `bento` property set to `true`.\n *\n * @param node - The React children to search through\n * @param path - The accumulated path of slot names from parent to current level\n */\n function search(node: React.ReactNode, path: string[] = []): void {\n React.Children.forEach(node, function processChild(child) {\n if (!React.isValidElement(child)) return;\n\n // Check if this is a component wrapped with withSlots\n // withSlots components have a `bento` property set to true\n const childType = child.type as any;\n const isSlottedComponent = childType?.bento === true;\n\n // Check if this child has a slot prop\n const slot = (child.props as any)?.slot;\n if (typeof slot === 'string' && slot.length > 0) {\n // Only process slot assignments on withSlots components\n if (isSlottedComponent) {\n // Build the full path including this slot\n const fullPath = [...path, slot];\n const pathString = fullPath.join('.');\n\n // Add both the simple slot name and the full namespaced path\n foundSlots.add(slot);\n foundSlots.add(pathString);\n\n // Continue searching children with the updated path\n if (child.props?.children) {\n search(child.props.children, fullPath);\n }\n } else {\n // Not a withSlots component - search children without extending path\n if (child.props?.children) {\n search(child.props.children, path);\n }\n }\n } else {\n // No slot on this child, but continue searching its children\n // without extending the path\n if (child.props?.children) {\n search(child.props.children, path);\n }\n }\n });\n }\n\n search(children);\n\n // Check if all required slots were found\n return slotNames.every((name) => foundSlots.has(name));\n}\n"]}

@@ -66,3 +66,3 @@ import React, { ComponentType } from 'react';

*/
declare function withSlots<Props extends object>(name: string, Component: React.ComponentType<Props>, modifiers?: (typeof replace)[]): React.NamedExoticComponent<Props & Slots>;
declare function withSlots<Props extends object>(name: string, Component: React.ComponentType<Props>, modifiers?: (typeof replace)[]): React.MemoExoticComponent<React.ComponentType<Props & Slots>>;

@@ -106,2 +106,44 @@ /**

export { type Slots, library, override, replace, withSlots };
/**
* Checks if children contain all required slot assignments, including namespaced paths.
*
* This utility iterates through React children to verify that all required
* slot names are present. It supports both simple slot names ('trigger') and
* namespaced paths ('submit.icon') for deeply nested slot validation.
*
* **Important:** Only components wrapped with `withSlots()` are validated. Raw HTML
* elements with `slot` props are ignored, as they are not part of the Bento slot system.
*
* @param slotNames - Array of required slot names to check for (e.g., ['trigger', 'submit.icon'])
* @param children - React children to search through
* @returns true if all required slots are found, false otherwise. Returns true
* for render prop functions since slots can't be validated until executed.
*
* @example
* ```tsx
* // Simple slot validation
* function MyComponent({ children }) {
* if (!contains(['trigger', 'content'], children)) {
* throw new BentoError({
* name: 'my-component',
* method: 'MyComponent',
* message: 'Missing required slots: trigger and content'
* });
* }
* return <div>{children}</div>;
* }
*
* // Namespaced slot validation
* function Form({ children }) {
* if (!contains(['submit.icon'], children)) {
* throw new Error('Submit button must have an icon');
* }
* return <form>{children}</form>;
* }
* ```
*
* @public
*/
declare function contains(slotNames: string[], children: React.ReactNode): boolean;
export { type Slots, contains, library, override, replace, withSlots };

@@ -66,3 +66,3 @@ import React, { ComponentType } from 'react';

*/
declare function withSlots<Props extends object>(name: string, Component: React.ComponentType<Props>, modifiers?: (typeof replace)[]): React.NamedExoticComponent<Props & Slots>;
declare function withSlots<Props extends object>(name: string, Component: React.ComponentType<Props>, modifiers?: (typeof replace)[]): React.MemoExoticComponent<React.ComponentType<Props & Slots>>;

@@ -106,2 +106,44 @@ /**

export { type Slots, library, override, replace, withSlots };
/**
* Checks if children contain all required slot assignments, including namespaced paths.
*
* This utility iterates through React children to verify that all required
* slot names are present. It supports both simple slot names ('trigger') and
* namespaced paths ('submit.icon') for deeply nested slot validation.
*
* **Important:** Only components wrapped with `withSlots()` are validated. Raw HTML
* elements with `slot` props are ignored, as they are not part of the Bento slot system.
*
* @param slotNames - Array of required slot names to check for (e.g., ['trigger', 'submit.icon'])
* @param children - React children to search through
* @returns true if all required slots are found, false otherwise. Returns true
* for render prop functions since slots can't be validated until executed.
*
* @example
* ```tsx
* // Simple slot validation
* function MyComponent({ children }) {
* if (!contains(['trigger', 'content'], children)) {
* throw new BentoError({
* name: 'my-component',
* method: 'MyComponent',
* message: 'Missing required slots: trigger and content'
* });
* }
* return <div>{children}</div>;
* }
*
* // Namespaced slot validation
* function Form({ children }) {
* if (!contains(['submit.icon'], children)) {
* throw new Error('Submit button must have an icon');
* }
* return <form>{children}</form>;
* }
* ```
*
* @public
*/
declare function contains(slotNames: string[], children: React.ReactNode): boolean;
export { type Slots, contains, library, override, replace, withSlots };

@@ -1,5 +0,6 @@

import React, { memo, useContext } from 'react';
import React, { useContext, memo } from 'react';
import { useDeepCompareMemo } from 'use-deep-compare';
import { Box } from '@bento/box';
import { BentoError } from '@bento/error';
import { withForwardRef } from '@bento/forward';
import { useDataAttributes } from '@bento/use-data-attributes';

@@ -70,6 +71,7 @@

function withSlots(name, Component, modifiers = [replace, override]) {
function WrappedComponent(propsAndSlots) {
const ComponentWithRef = withForwardRef(Component);
const SlottedForwardRef = withForwardRef(function SlottedComponent2(propsAndSlots, forwardedRef) {
const { slot = "", slots = {}, ...restProps } = propsAndSlots;
let props = { ...restProps };
let Element = Component;
let Element = ComponentWithRef;
let ctx = { ...useContext(Box) };

@@ -123,10 +125,13 @@ ctx.env = { ...ctx.env };

});
const baseProps = { ...props };
const propsWithRef = forwardedRef != null ? { ...baseProps, ref: forwardedRef } : baseProps;
const context = useDeepCompareMemo(() => ctx, [ctx]);
const rendered = /* @__PURE__ */ React.createElement(Box.Provider, { value: context }, /* @__PURE__ */ React.createElement(Element, { ...props }));
const rendered = /* @__PURE__ */ React.createElement(Box.Provider, { value: context }, /* @__PURE__ */ React.createElement(Element, { ...propsWithRef }));
const slotted = ctx.slots.assigned[ctx.slots.namespace.join(".")];
if (typeof slotted !== "function") return rendered;
return slotted({ props, original: rendered.props.children });
}
const SlottedComponent = memo(WrappedComponent);
return slotted({ props: propsWithRef, original: rendered.props.children });
});
const SlottedComponent = memo(SlottedForwardRef);
SlottedComponent.displayName = `Slotted(${name})`;
SlottedComponent.bento = true;
if (process.env.NODE_ENV !== "production") {

@@ -153,5 +158,40 @@ SlottedComponent.whyDidYouRender = true;

}
function contains(slotNames, children) {
if (!slotNames || slotNames.length === 0) return true;
if (!children) return false;
if (typeof children === "function") return true;
const foundSlots = /* @__PURE__ */ new Set();
function search(node, path = []) {
React.Children.forEach(node, function processChild(child) {
if (!React.isValidElement(child)) return;
const childType = child.type;
const isSlottedComponent = childType?.bento === true;
const slot = child.props?.slot;
if (typeof slot === "string" && slot.length > 0) {
if (isSlottedComponent) {
const fullPath = [...path, slot];
const pathString = fullPath.join(".");
foundSlots.add(slot);
foundSlots.add(pathString);
if (child.props?.children) {
search(child.props.children, fullPath);
}
} else {
if (child.props?.children) {
search(child.props.children, path);
}
}
} else {
if (child.props?.children) {
search(child.props.children, path);
}
}
});
}
search(children);
return slotNames.every((name) => foundSlots.has(name));
}
export { library, override, replace, withSlots };
export { contains, library, override, replace, withSlots };
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/override.ts","../src/replace.ts","../src/slots.tsx"],"names":["override","useDataAttributes","mergedFnSlot","props"],"mappings":"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","file":"index.js","sourcesContent":["import { type ComponentType, type CSSProperties } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\n\n/**\n * Checks if a given key is a CSS variable.\n *\n * @param key - The key to check.\n * @returns true if the key is a CSS variable, otherwise false.\n * @private\n */\nfunction isCSSVariable(key: string) {\n return key.startsWith('--');\n}\n\n/**\n * Interface representing the arguments required to override a component.\n *\n * @private\n */\ninterface OverrideArgs<Props> {\n /** The properties of the component. */\n props: Props;\n\n /** The component which should be rendered. */\n Component: ComponentType<Props>;\n\n /** The current Slot context. */\n context: BoxContext<Props>;\n\n /** The name of the component. */\n name: string;\n}\n\n/**\n * Represents the result of an override operation.\n *\n * @private\n */\ninterface OverrideResult {\n /** The properties of the override result. */\n props: {\n /** A string indicating the override data. */\n 'data-override'?: string;\n };\n}\n\nconst triggers: string[] = ['className', 'style'];\n\n/**\n * Overrides the properties of a given context based on certain conditions.\n *\n * @param args.context - The context object.\n * @param args.props - The properties object.\n * @returns The result containing the updated properties or undefined if no overrides are applied.\n * @public\n */\nexport function override<Props extends Record<string, any>>({\n context,\n props\n}: OverrideArgs<Props>): OverrideResult | undefined {\n const causes: string[] = [];\n const { namespace, assigned, override } = context.slots;\n const slot: Record<string, any> | undefined = assigned[namespace.join('.')];\n\n if (typeof props['data-override'] === 'string') {\n causes.push(...props['data-override'].split(' '));\n }\n\n if (override && !causes.includes('context')) causes.push('context');\n if ('className' in props && !causes.includes('className')) causes.push('className');\n\n //\n // For style we need to take a more sophisticated approach, users are allowed\n // to define CSS variables in the style prop, so we need to check if the keys\n // are prefixed with `--` or not.\n //\n if ('style' in props && !causes.includes('style')) {\n const style = props.style as CSSProperties;\n const keys = Object.keys(style);\n\n if (keys.some((key) => !isCSSVariable(key))) {\n causes.push('style');\n }\n }\n\n if (slot) {\n Object.keys(slot).forEach(function forEach(name) {\n if (triggers.includes(name) && !causes.includes(name)) causes.push(name);\n if (!causes.includes('slot')) causes.push('slot');\n });\n }\n\n if (!causes.length) return;\n return {\n props: useDataAttributes({ override: causes })\n };\n}\n","import { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\nimport { type ComponentType } from 'react';\n\n/**\n * Interface representing the arguments required for replacing a component.\n *\n * @interface ReplaceArgs\n * @property {Props} props - The properties of the component.\n * @property {ComponentType<Props>} Component - The component which should be rendered.\n * @property {SlotContext<Props>} context - The current Slot context.\n * @property {string} name - The name of the component.\n * @private\n */\ninterface ReplaceArgs<Props> {\n props: Props;\n Component: ComponentType<Props>;\n context: BoxContext<Props>;\n name: string;\n}\n\n/**\n * Interface for props override objects\n * @private\n */\ninterface PropsOverride {\n /** The properties of the props override. */\n props: Record<string, any>;\n}\n\n/**\n * Checks if the provided data is a props override instead of a component\n * override.\n *\n * @param data - The data to check.\n * @returns Returns true if the data is a props override, false otherwise.\n * @private\n */\nfunction isPropsOverride(data: any): data is PropsOverride {\n if (typeof data !== 'object' || Array.isArray(data)) return false;\n\n const keys = Object.keys(data);\n return keys.includes('props') && keys.length === 1;\n}\n\n/**\n * Replaces a component in the context with the specified name and props.\n *\n * @param args.props - The properties to be passed to the component.\n * @param args.name - The name of the component to be replaced.\n * @param args.context - The context containing the components.\n * @returns An object containing the updated props, the component to be replaced, and the updated context.\n * @public\n */\nexport function replace<Props extends Record<string, any>>({ props, name, context }: ReplaceArgs<Props>) {\n if (!context.env || !context.env.components || !(name in context.env.components)) return;\n\n const target = context.env.components[name];\n if (!target) return;\n\n const result = {\n context: {\n slots: {\n override: !!target\n }\n },\n props: {}\n } as any;\n\n const causes = (props['data-override'] || '').split(' ').filter(Boolean);\n if (!causes.includes('context')) causes.push('context');\n result.props = useDataAttributes({ override: causes });\n\n if (isPropsOverride(target)) result.props = { ...target.props };\n else result.Component = target;\n\n return result;\n}\n","/// <reference types=\"vite/client\" />\nimport React, { useContext, memo } from 'react';\nimport { useDeepCompareMemo } from 'use-deep-compare';\nimport { Box, type BoxContext } from '@bento/box';\nimport { BentoError } from '@bento/error';\nimport { override } from './override.ts';\nimport { replace } from './replace.ts';\n\n/**\n * Interface representing a collection of slots.\n */\nexport interface Slots {\n /**\n * A named part of a component that can be customized. This is implemented by the consuming component.\n * The exposed slot names of a component are available in the components documentation.\n */\n slot?: string;\n /**\n * An object that contains the customizations for the slots.\n * The main way you interact with the slot system as a consumer.\n */\n slots?: Record<string, object | Function>;\n}\n\n//\n// A list of libraries that are currently registered by the system. This is\n// needed to prevent duplicate component names from being registered as the\n// names are used to target the correct component with slot overrides.\n//\nexport const library = new Set<string>();\n\n/**\n * Higher-order component that wraps a given component with slot functionality.\n * This allows components to be dynamically replaced or overridden based on the provided context and slots.\n *\n * @param name - The unique name of the component. This is used to identify the component for slot overrides.\n * @param Component - The component that should be rendered.\n * @param modifiers - The modifier functions that should be applied to the component.\n * @returns The wrapped component.\n * @public\n *\n * @throws {BentoError} If the component name has already been registered (in development mode).\n * @throws {BentoError} If any of the supplied modifiers are not functions.\n *\n * @example\n * ```tsx\n * const MyComponent = () => <div>My Component</div>;\n * const SlottedMyComponent = withSlots('MyComponent', MyComponent);\n * ```\n */\nexport function withSlots<Props extends object>(\n name: string,\n Component: React.ComponentType<Props>,\n modifiers = [replace, override]\n) {\n function WrappedComponent(propsAndSlots: Props & Slots) {\n const { slot = '', slots = {}, ...restProps } = propsAndSlots;\n let props = { ...restProps } as Props;\n let Element = Component;\n\n //\n // We need to create a new context object to prevent introducing properties\n // to a context that is shared across components. Without cloning the context\n // properties like `namespace` will become corrupted as they would start to\n // include the slot name of siblings instead of just being a pure parent/child\n // relationship.\n let ctx = { ...useContext<BoxContext<Props>>(Box) };\n ctx.env = { ...ctx.env };\n ctx.slots = { ...ctx.slots };\n ctx.slots.namespace = [...ctx.slots.namespace, slot].filter(Boolean);\n\n const currentNamespace = ctx.slots.namespace.join('.');\n const inheritedSlots: Record<string, any> = {};\n const prefix = `${currentNamespace}.`;\n\n //\n // Only inherit slots from the parent that match the current namespace.\n // If the current namespace is empty (root level), inherit all slots\n // that don't contain a dot (i.e., slots at the root level only).\n //\n for (const key in ctx.slots.assigned) {\n if (currentNamespace === '' && !key.includes('.')) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n } else if (key === currentNamespace || key.startsWith(prefix)) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n }\n }\n\n ctx.slots.assigned = inheritedSlots;\n\n //\n // merge the new slots with the assigned slots,\n // parent component slots should take precedence over child ones.\n //\n for (const slotKey in slots) {\n // Build the fully qualified slot key by prefixing with current namespace\n const namespacedKey = ctx.slots.namespace.length > 0 ? `${currentNamespace}.${slotKey}` : slotKey;\n const assignedSlot = ctx.slots.assigned[namespacedKey];\n const newSlot = slots[slotKey];\n\n //\n // New slots are assigned if none exist, or merged with the new slots.\n // If the assigned slot exists and is an object, keep it as is.\n //\n if (!assignedSlot) {\n ctx.slots.assigned[namespacedKey] = newSlot;\n } else if (typeof assignedSlot === 'object') {\n ctx.slots.assigned[namespacedKey] = { ...newSlot, ...assignedSlot };\n } else if (typeof assignedSlot === 'function') {\n const existingPrevious = assignedSlot.__slotPrevious || [];\n const newPrevious = [newSlot, ...existingPrevious];\n const mergedFnSlot = function mergedFnSlot({ props, original, previous }: any) {\n return assignedSlot({ props, original, previous: previous || newPrevious });\n };\n\n mergedFnSlot.__slotPrevious = newPrevious;\n ctx.slots.assigned[namespacedKey] = mergedFnSlot;\n }\n }\n\n //\n // Modifiers allow you to manipulate the context, props, and component with\n // a single function. This makes it easier to turn on or off functionality\n // that you might not need for your components.\n //\n // For Bento based components we supply the following modifiers by default:\n // - replace: Allows you to replace the component with another component\n // based on the slots and provided context.\n // - override: Makes a note of potential design or functional overrides\n // that are applied to the component. Making it easier to provide support\n // to find common overrides in your application.\n //\n modifiers.forEach(function forEach(modifier) {\n const mods: {\n Component?: React.ComponentType<Props>;\n context?: Partial<BoxContext<Props>> | Record<string, any>;\n props?: object;\n } =\n modifier({\n Component: Element,\n context: ctx,\n props,\n name\n }) || {};\n\n if (typeof mods.context === 'object')\n ctx = {\n env: { ...ctx.env, ...mods.context?.env },\n slots: { ...ctx.slots, ...mods.context?.slots }\n };\n\n if (typeof mods.props === 'object') props = { ...props, ...mods.props };\n if (mods.Component) Element = mods.Component;\n });\n\n const context = useDeepCompareMemo(() => ctx, [ctx]);\n const rendered = (\n <Box.Provider value={context}>\n <Element {...props} />\n </Box.Provider>\n );\n\n const slotted = ctx.slots.assigned[ctx.slots.namespace.join('.')];\n\n if (typeof slotted !== 'function') return rendered;\n return slotted({ props, original: rendered.props.children });\n }\n\n const SlottedComponent = memo<Props & Slots>(WrappedComponent);\n SlottedComponent.displayName = `Slotted(${name})`;\n\n if (process.env.NODE_ENV !== 'production') {\n //\n // This enables re-render tracking for every Bento based component using the\n // `@welldone-software/why-did-you-render` package.\n //\n (SlottedComponent as any).whyDidYouRender = true;\n\n //\n // We want to throw the following error only in a development environment.\n //\n // While the requirement is to have unique components so each component can\n // be individually and correctly targeted using our provided context we\n // want to be mindful that we're breaking an application for the right\n // reasons. We should only throw in production if an application cannot\n // recover from this error.\n //\n // In the case of these overrides\n //\n if (library.has(name) && !import.meta.hot)\n throw new BentoError({\n message: 'The supplied component %s has already been registered.',\n args: [name],\n method: 'withSlots',\n name: 'slots'\n });\n\n library.add(name);\n }\n\n modifiers.forEach(function forEach(modifier) {\n if (typeof modifier !== 'function')\n throw new BentoError({\n message: 'The supplied component modifier is not a function.',\n method: 'withSlots',\n name: 'slots'\n });\n });\n\n return SlottedComponent;\n}\n"]}
{"version":3,"sources":["../src/override.ts","../src/replace.ts","../src/slots.tsx","../src/contains.ts"],"names":["override","useDataAttributes","SlottedComponent","mergedFnSlot","props","React"],"mappings":"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","file":"index.js","sourcesContent":["import { type ComponentType, type CSSProperties } from 'react';\nimport { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\n\n/**\n * Checks if a given key is a CSS variable.\n *\n * @param key - The key to check.\n * @returns true if the key is a CSS variable, otherwise false.\n * @private\n */\nfunction isCSSVariable(key: string) {\n return key.startsWith('--');\n}\n\n/**\n * Interface representing the arguments required to override a component.\n *\n * @private\n */\ninterface OverrideArgs<Props> {\n /** The properties of the component. */\n props: Props;\n\n /** The component which should be rendered. */\n Component: ComponentType<Props>;\n\n /** The current Slot context. */\n context: BoxContext<Props>;\n\n /** The name of the component. */\n name: string;\n}\n\n/**\n * Represents the result of an override operation.\n *\n * @private\n */\ninterface OverrideResult {\n /** The properties of the override result. */\n props: {\n /** A string indicating the override data. */\n 'data-override'?: string;\n };\n}\n\nconst triggers: string[] = ['className', 'style'];\n\n/**\n * Overrides the properties of a given context based on certain conditions.\n *\n * @param args.context - The context object.\n * @param args.props - The properties object.\n * @returns The result containing the updated properties or undefined if no overrides are applied.\n * @public\n */\nexport function override<Props extends Record<string, any>>({\n context,\n props\n}: OverrideArgs<Props>): OverrideResult | undefined {\n const causes: string[] = [];\n const { namespace, assigned, override } = context.slots;\n const slot: Record<string, any> | undefined = assigned[namespace.join('.')];\n\n if (typeof props['data-override'] === 'string') {\n causes.push(...props['data-override'].split(' '));\n }\n\n if (override && !causes.includes('context')) causes.push('context');\n if ('className' in props && !causes.includes('className')) causes.push('className');\n\n //\n // For style we need to take a more sophisticated approach, users are allowed\n // to define CSS variables in the style prop, so we need to check if the keys\n // are prefixed with `--` or not.\n //\n if ('style' in props && !causes.includes('style')) {\n const style = props.style as CSSProperties;\n const keys = Object.keys(style);\n\n if (keys.some((key) => !isCSSVariable(key))) {\n causes.push('style');\n }\n }\n\n if (slot) {\n Object.keys(slot).forEach(function forEach(name) {\n if (triggers.includes(name) && !causes.includes(name)) causes.push(name);\n if (!causes.includes('slot')) causes.push('slot');\n });\n }\n\n if (!causes.length) return;\n return {\n props: useDataAttributes({ override: causes })\n };\n}\n","import { useDataAttributes } from '@bento/use-data-attributes';\nimport { type BoxContext } from '@bento/box';\nimport { type ComponentType } from 'react';\n\n/**\n * Interface representing the arguments required for replacing a component.\n *\n * @interface ReplaceArgs\n * @property {Props} props - The properties of the component.\n * @property {ComponentType<Props>} Component - The component which should be rendered.\n * @property {SlotContext<Props>} context - The current Slot context.\n * @property {string} name - The name of the component.\n * @private\n */\ninterface ReplaceArgs<Props> {\n props: Props;\n Component: ComponentType<Props>;\n context: BoxContext<Props>;\n name: string;\n}\n\n/**\n * Interface for props override objects\n * @private\n */\ninterface PropsOverride {\n /** The properties of the props override. */\n props: Record<string, any>;\n}\n\n/**\n * Checks if the provided data is a props override instead of a component\n * override.\n *\n * @param data - The data to check.\n * @returns Returns true if the data is a props override, false otherwise.\n * @private\n */\nfunction isPropsOverride(data: any): data is PropsOverride {\n if (typeof data !== 'object' || Array.isArray(data)) return false;\n\n const keys = Object.keys(data);\n return keys.includes('props') && keys.length === 1;\n}\n\n/**\n * Replaces a component in the context with the specified name and props.\n *\n * @param args.props - The properties to be passed to the component.\n * @param args.name - The name of the component to be replaced.\n * @param args.context - The context containing the components.\n * @returns An object containing the updated props, the component to be replaced, and the updated context.\n * @public\n */\nexport function replace<Props extends Record<string, any>>({ props, name, context }: ReplaceArgs<Props>) {\n if (!context.env || !context.env.components || !(name in context.env.components)) return;\n\n const target = context.env.components[name];\n if (!target) return;\n\n const result = {\n context: {\n slots: {\n override: !!target\n }\n },\n props: {}\n } as any;\n\n const causes = (props['data-override'] || '').split(' ').filter(Boolean);\n if (!causes.includes('context')) causes.push('context');\n result.props = useDataAttributes({ override: causes });\n\n if (isPropsOverride(target)) result.props = { ...target.props };\n else result.Component = target;\n\n return result;\n}\n","/// <reference types=\"vite/client\" />\nimport React, { useContext, memo } from 'react';\nimport { useDeepCompareMemo } from 'use-deep-compare';\nimport { Box, type BoxContext } from '@bento/box';\nimport { BentoError } from '@bento/error';\nimport { withForwardRef } from '@bento/forward';\nimport { override } from './override.ts';\nimport { replace } from './replace.ts';\n\n/**\n * Interface representing a collection of slots.\n */\nexport interface Slots {\n /**\n * A named part of a component that can be customized. This is implemented by the consuming component.\n * The exposed slot names of a component are available in the components documentation.\n */\n slot?: string;\n /**\n * An object that contains the customizations for the slots.\n * The main way you interact with the slot system as a consumer.\n */\n slots?: Record<string, object | Function>;\n}\n\n//\n// A list of libraries that are currently registered by the system. This is\n// needed to prevent duplicate component names from being registered as the\n// names are used to target the correct component with slot overrides.\n//\nexport const library = new Set<string>();\n\n/**\n * Higher-order component that wraps a given component with slot functionality.\n * This allows components to be dynamically replaced or overridden based on the provided context and slots.\n *\n * @param name - The unique name of the component. This is used to identify the component for slot overrides.\n * @param Component - The component that should be rendered.\n * @param modifiers - The modifier functions that should be applied to the component.\n * @returns The wrapped component.\n * @public\n *\n * @throws {BentoError} If the component name has already been registered (in development mode).\n * @throws {BentoError} If any of the supplied modifiers are not functions.\n *\n * @example\n * ```tsx\n * const MyComponent = () => <div>My Component</div>;\n * const SlottedMyComponent = withSlots('MyComponent', MyComponent);\n * ```\n */\nexport function withSlots<Props extends object>(\n name: string,\n Component: React.ComponentType<Props>,\n modifiers = [replace, override]\n) {\n //\n // Use @bento/forward to handle ref forwarding for React 18/19 compatibility\n // and to ensure the component is wrapped with forwardRef if needed.\n //\n const ComponentWithRef = withForwardRef(Component);\n const SlottedForwardRef = withForwardRef(function SlottedComponent(propsAndSlots: Props & Slots, forwardedRef: any) {\n const { slot = '', slots = {}, ...restProps } = propsAndSlots;\n let props = { ...restProps } as Props;\n let Element: React.ComponentType<any> = ComponentWithRef;\n\n //\n // We need to create a new context object to prevent introducing properties\n // to a context that is shared across components. Without cloning the context\n // properties like `namespace` will become corrupted as they would start to\n // include the slot name of siblings instead of just being a pure parent/child\n // relationship.\n //\n let ctx = { ...useContext<BoxContext<Props>>(Box) };\n ctx.env = { ...ctx.env };\n ctx.slots = { ...ctx.slots };\n ctx.slots.namespace = [...ctx.slots.namespace, slot].filter(Boolean);\n\n const currentNamespace = ctx.slots.namespace.join('.');\n const inheritedSlots: Record<string, any> = {};\n const prefix = `${currentNamespace}.`;\n\n //\n // Only inherit slots from the parent that match the current namespace.\n // If the current namespace is empty (root level), inherit all slots\n // that don't contain a dot (i.e., slots at the root level only).\n //\n for (const key in ctx.slots.assigned) {\n if (currentNamespace === '' && !key.includes('.')) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n } else if (key === currentNamespace || key.startsWith(prefix)) {\n inheritedSlots[key] = ctx.slots.assigned[key];\n }\n }\n\n ctx.slots.assigned = inheritedSlots;\n\n //\n // merge the new slots with the assigned slots,\n // parent component slots should take precedence over child ones.\n //\n for (const slotKey in slots) {\n // Build the fully qualified slot key by prefixing with current namespace\n const namespacedKey = ctx.slots.namespace.length > 0 ? `${currentNamespace}.${slotKey}` : slotKey;\n const assignedSlot = ctx.slots.assigned[namespacedKey];\n const newSlot = slots[slotKey];\n\n //\n // New slots are assigned if none exist, or merged with the new slots.\n // If the assigned slot exists and is an object, keep it as is.\n //\n if (!assignedSlot) {\n ctx.slots.assigned[namespacedKey] = newSlot;\n } else if (typeof assignedSlot === 'object') {\n ctx.slots.assigned[namespacedKey] = { ...newSlot, ...assignedSlot };\n } else if (typeof assignedSlot === 'function') {\n const existingPrevious = assignedSlot.__slotPrevious || [];\n const newPrevious = [newSlot, ...existingPrevious];\n const mergedFnSlot = function mergedFnSlot({ props, original, previous }: any) {\n return assignedSlot({ props, original, previous: previous || newPrevious });\n };\n\n mergedFnSlot.__slotPrevious = newPrevious;\n ctx.slots.assigned[namespacedKey] = mergedFnSlot;\n }\n }\n\n //\n // Modifiers allow you to manipulate the context, props, and component with\n // a single function. This makes it easier to turn on or off functionality\n // that you might not need for your components.\n //\n // For Bento based components we supply the following modifiers by default:\n // - replace: Allows you to replace the component with another component\n // based on the slots and provided context.\n // - override: Makes a note of potential design or functional overrides\n // that are applied to the component. Making it easier to provide support\n // to find common overrides in your application.\n //\n modifiers.forEach(function forEach(modifier) {\n const mods: {\n Component?: React.ComponentType<Props>;\n context?: Partial<BoxContext<Props>> | Record<string, any>;\n props?: object;\n } =\n modifier({\n Component: Element,\n context: ctx,\n props,\n name\n }) || {};\n\n if (typeof mods.context === 'object')\n ctx = {\n env: { ...ctx.env, ...mods.context?.env },\n slots: { ...ctx.slots, ...mods.context?.slots }\n };\n\n if (typeof mods.props === 'object') props = { ...props, ...mods.props };\n if (mods.Component) Element = mods.Component as React.ComponentType<any>;\n });\n\n const baseProps = { ...(props as object) } as Props;\n\n // Add ref to props. withForwardRef handles the React 18/19 differences:\n // - React 18: Component is wrapped with forwardRef, so ref is passed separately\n // - React 19: ref is just a regular prop\n const propsWithRef = forwardedRef != null ? ({ ...baseProps, ref: forwardedRef } as Props) : baseProps;\n\n const context = useDeepCompareMemo(() => ctx, [ctx]);\n const rendered = (\n <Box.Provider value={context}>\n <Element {...propsWithRef} />\n </Box.Provider>\n );\n\n const slotted = ctx.slots.assigned[ctx.slots.namespace.join('.')];\n\n if (typeof slotted !== 'function') return rendered;\n return slotted({ props: propsWithRef, original: rendered.props.children });\n });\n\n const SlottedComponent = memo(SlottedForwardRef);\n SlottedComponent.displayName = `Slotted(${name})`;\n\n //\n // Mark this as a Bento component wrapped with withSlots\n // This is used by the contains() utility to identify valid slotted components\n //\n (SlottedComponent as any).bento = true;\n\n if (process.env.NODE_ENV !== 'production') {\n //\n // This enables re-render tracking for every Bento based component using the\n // `@welldone-software/why-did-you-render` package.\n //\n (SlottedComponent as any).whyDidYouRender = true;\n\n //\n // We want to throw the following error only in a development environment.\n //\n // While the requirement is to have unique components so each component can\n // be individually and correctly targeted using our provided context we\n // want to be mindful that we're breaking an application for the right\n // reasons. We should only throw in production if an application cannot\n // recover from this error.\n //\n // In the case of these overrides\n //\n if (library.has(name) && !import.meta.hot)\n throw new BentoError({\n message: 'The supplied component %s has already been registered.',\n args: [name],\n method: 'withSlots',\n name: 'slots'\n });\n\n library.add(name);\n }\n\n modifiers.forEach(function forEach(modifier) {\n if (typeof modifier !== 'function')\n throw new BentoError({\n message: 'The supplied component modifier is not a function.',\n method: 'withSlots',\n name: 'slots'\n });\n });\n\n return SlottedComponent;\n}\n","import React from 'react';\n\n/**\n * Checks if children contain all required slot assignments, including namespaced paths.\n *\n * This utility iterates through React children to verify that all required\n * slot names are present. It supports both simple slot names ('trigger') and\n * namespaced paths ('submit.icon') for deeply nested slot validation.\n *\n * **Important:** Only components wrapped with `withSlots()` are validated. Raw HTML\n * elements with `slot` props are ignored, as they are not part of the Bento slot system.\n *\n * @param slotNames - Array of required slot names to check for (e.g., ['trigger', 'submit.icon'])\n * @param children - React children to search through\n * @returns true if all required slots are found, false otherwise. Returns true\n * for render prop functions since slots can't be validated until executed.\n *\n * @example\n * ```tsx\n * // Simple slot validation\n * function MyComponent({ children }) {\n * if (!contains(['trigger', 'content'], children)) {\n * throw new BentoError({\n * name: 'my-component',\n * method: 'MyComponent',\n * message: 'Missing required slots: trigger and content'\n * });\n * }\n * return <div>{children}</div>;\n * }\n *\n * // Namespaced slot validation\n * function Form({ children }) {\n * if (!contains(['submit.icon'], children)) {\n * throw new Error('Submit button must have an icon');\n * }\n * return <form>{children}</form>;\n * }\n * ```\n *\n * @public\n */\nexport function contains(slotNames: string[], children: React.ReactNode): boolean {\n if (!slotNames || slotNames.length === 0) return true;\n if (!children) return false;\n\n // Skip validation for render prop functions - we can't inspect slots\n // until the function is executed with state/props\n if (typeof children === 'function') return true;\n\n const foundSlots = new Set<string>();\n\n /**\n * Recursively search through children to find slot assignments, building\n * the path as we go to support namespaced lookups like 'submit.icon'.\n *\n * When a child has a slot prop, we add both the simple slot name and the full\n * namespaced path to our set. For example, if we find:\n * - `<Button slot=\"submit\"><Icon slot=\"icon\" /></Button>`\n *\n * We'll add: 'submit', 'submit', 'icon', and 'submit.icon' to foundSlots.\n *\n * Children without slot props (like wrapper divs) are searched through\n * transparently without extending the path.\n *\n * Only components wrapped with `withSlots()` are considered valid slotted\n * components. They can be identified by the `bento` property set to `true`.\n *\n * @param node - The React children to search through\n * @param path - The accumulated path of slot names from parent to current level\n */\n function search(node: React.ReactNode, path: string[] = []): void {\n React.Children.forEach(node, function processChild(child) {\n if (!React.isValidElement(child)) return;\n\n // Check if this is a component wrapped with withSlots\n // withSlots components have a `bento` property set to true\n const childType = child.type as any;\n const isSlottedComponent = childType?.bento === true;\n\n // Check if this child has a slot prop\n const slot = (child.props as any)?.slot;\n if (typeof slot === 'string' && slot.length > 0) {\n // Only process slot assignments on withSlots components\n if (isSlottedComponent) {\n // Build the full path including this slot\n const fullPath = [...path, slot];\n const pathString = fullPath.join('.');\n\n // Add both the simple slot name and the full namespaced path\n foundSlots.add(slot);\n foundSlots.add(pathString);\n\n // Continue searching children with the updated path\n if (child.props?.children) {\n search(child.props.children, fullPath);\n }\n } else {\n // Not a withSlots component - search children without extending path\n if (child.props?.children) {\n search(child.props.children, path);\n }\n }\n } else {\n // No slot on this child, but continue searching its children\n // without extending the path\n if (child.props?.children) {\n search(child.props.children, path);\n }\n }\n });\n }\n\n search(children);\n\n // Check if all required slots were found\n return slotNames.every((name) => foundSlots.has(name));\n}\n"]}
{
"name": "@bento/slots",
"version": "0.1.3",
"version": "0.2.0",
"description": "The slots implementation of Bento",

@@ -43,4 +43,5 @@ "type": "module",

"dependencies": {
"@bento/box": "^0.1.1",
"@bento/box": "^0.2.0",
"@bento/error": "^0.1.1",
"@bento/forward": "^0.1.0",
"@bento/use-data-attributes": "^0.1.1",

@@ -47,0 +48,0 @@ "use-deep-compare": "^1.3.0"

+108
-1

@@ -43,2 +43,43 @@ # Slots

## Forwarding Refs
Components created with `withSlots` automatically support ref forwarding. The `withSlots` function:
1. Detects if your component accepts a ref parameter (by checking if it has 2 parameters)
2. Automatically wraps it with `React.forwardRef` in React 18 (if not already wrapped)
3. In React 19+, it relies on the native ref-as-prop behavior (no wrapping needed)
When using `useProps`, you can access the merged ref that combines:
- The ref passed directly to the component
- Any refs supplied through slots
- The forwarded ref from parent components
```javascript
import { useProps } from '@bento/use-props';
import { withSlots } from '@bento/slots';
import React from 'react';
export const Button = withSlots(
'BentoButton',
React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(function BentoButton(args, forwardedRef) {
const { props, apply, ref } = useProps(args, {}, forwardedRef);
return <button {...apply({}, ['ref'])} ref={ref}>{ props.children }</button>;
})
);
```
The `ForwardRefExample` demonstrates how refs coming from the top-level consumer and from slot overrides are merged into a single handle.
<Source language='tsx' code={ SourceForwardRef } />
### React 19 Compatibility
The ref forwarding implementation is designed to be React 19-compatible:
- **React 18**: Components with 2 parameters are automatically wrapped with `forwardRef`
- **React 19**: Components use the native ref-as-prop behavior, no wrapping needed
This means your components will work seamlessly when upgrading from React 18 to React 19 without any code changes.
## Modifiers

@@ -221,2 +262,68 @@

- **Mixed types**: Function slots take precedence over object slots
- **Previous array**: Ordered from closest child to furthest ancestor
- **Previous array**: Ordered from closest child to furthest ancestor
## Validation
Compositional components often depend on specific child slots to function
correctly. For example, an `Overlay` component needs content to display, or a
`Dialog` component requires both a title and body. Without validation, missing
required slots would cause runtime errors or silent failures that are difficult
to debug.
### contains
The `contains` utility validates that required slot assignments are present in
children. This is useful for compositional components that depend on specific
child slots to function correctly.
```javascript
import { contains } from '@bento/slots';
import { BentoError } from '@bento/error';
import { Container } from '@bento/container';
export const Dialog = withSlots('Dialog', function Dialog(args) {
const { children } = args;
// Validate required slots are present
if (!contains(['title', 'content'], children)) {
throw new BentoError({
name: 'dialog',
method: 'Dialog',
message: 'Dialog requires children with slot="title" and slot="content"'
});
}
return <Container>{children}</Container>;
});
```
The `contains` function recursively searches through React children to find
components with matching `slot` props. It only validates components wrapped with
`withSlots()` - raw HTML elements with slot props are ignored since they are not
part of the Bento slot system.
#### Namespaced Slot Validation
You can validate deeply nested slot structures using dot notation. When
searching for `'submit.icon'`, `contains` will look for a component with
`slot="submit"` that has a child component with `slot="icon"`:
```javascript
import { Button } from '@bento/button';
import { Icon } from '@bento/icon';
import { BentoError } from '@bento/error';
// Check for a nested slot path
if (!contains(['submit.icon'], children)) {
throw new BentoError({
name: 'form',
method: 'Form',
message: 'Submit button must have an icon'
});
}
// This will find:
// <Button slot="submit">
// <Icon slot="icon">→</Icon>
// </Button>
```
export * from './slots.tsx';
export * from './override.ts';
export * from './replace.ts';
export * from './contains.ts';

@@ -6,2 +6,3 @@ /// <reference types="vite/client" />

import { BentoError } from '@bento/error';
import { withForwardRef } from '@bento/forward';
import { override } from './override.ts';

@@ -57,6 +58,11 @@ import { replace } from './replace.ts';

) {
function WrappedComponent(propsAndSlots: Props & Slots) {
//
// Use @bento/forward to handle ref forwarding for React 18/19 compatibility
// and to ensure the component is wrapped with forwardRef if needed.
//
const ComponentWithRef = withForwardRef(Component);
const SlottedForwardRef = withForwardRef(function SlottedComponent(propsAndSlots: Props & Slots, forwardedRef: any) {
const { slot = '', slots = {}, ...restProps } = propsAndSlots;
let props = { ...restProps } as Props;
let Element = Component;
let Element: React.ComponentType<any> = ComponentWithRef;

@@ -69,2 +75,3 @@ //

// relationship.
//
let ctx = { ...useContext<BoxContext<Props>>(Box) };

@@ -156,9 +163,16 @@ ctx.env = { ...ctx.env };

if (typeof mods.props === 'object') props = { ...props, ...mods.props };
if (mods.Component) Element = mods.Component;
if (mods.Component) Element = mods.Component as React.ComponentType<any>;
});
const baseProps = { ...(props as object) } as Props;
// Add ref to props. withForwardRef handles the React 18/19 differences:
// - React 18: Component is wrapped with forwardRef, so ref is passed separately
// - React 19: ref is just a regular prop
const propsWithRef = forwardedRef != null ? ({ ...baseProps, ref: forwardedRef } as Props) : baseProps;
const context = useDeepCompareMemo(() => ctx, [ctx]);
const rendered = (
<Box.Provider value={context}>
<Element {...props} />
<Element {...propsWithRef} />
</Box.Provider>

@@ -170,8 +184,14 @@ );

if (typeof slotted !== 'function') return rendered;
return slotted({ props, original: rendered.props.children });
}
return slotted({ props: propsWithRef, original: rendered.props.children });
});
const SlottedComponent = memo<Props & Slots>(WrappedComponent);
const SlottedComponent = memo(SlottedForwardRef);
SlottedComponent.displayName = `Slotted(${name})`;
//
// Mark this as a Bento component wrapped with withSlots
// This is used by the contains() utility to identify valid slotted components
//
(SlottedComponent as any).bento = true;
if (process.env.NODE_ENV !== 'production') {

@@ -178,0 +198,0 @@ //

Sorry, the diff of this file is not supported yet