create-slots
Version:
Bring slots to React components
1 lines • 11.5 kB
Source Map (JSON)
{"version":3,"sources":["../../src/list/index.tsx","../../src/utils.ts","../../src/DevChildren.tsx","../../src/list/ScanContext.tsx","../../src/list/SlotsManager.ts","../../src/list/utils.ts"],"sourcesContent":["import * as React from 'react'\n\nimport {\n createSlotsContext,\n getComponentName,\n hoistStatics,\n useIsomorphicEffect,\n} from '../utils'\nimport { DevChildren } from '../DevChildren'\nimport { ScanContext, ScanProvider } from './ScanContext'\nimport { createSlotsManager } from './SlotsManager'\nimport { SlotElement } from './utils'\n\nexport * from './utils'\n\ntype Slots = ReturnType<typeof createSlotsManager>\ntype Callback = (slots: SlotElement[]) => JSX.Element | null\n\nconst SlotsContext = createSlotsContext<Slots | undefined>(undefined)\n\nconst Template = ({ children }: { children: () => ReturnType<Callback> }) => {\n return children()\n}\n\nconst createIdGenerator = (prefix: string) => {\n let id = 0\n return () => `${prefix}_${id++}`\n}\n\nconst genSlotId = createIdGenerator('s')\n\nexport const HostSlots = ({\n children,\n callback,\n}: {\n children: React.ReactNode\n callback: Callback\n}) => {\n const forceUpdate = React.useReducer(() => [], [])[1]\n const Slots = React.useMemo(\n () => createSlotsManager(forceUpdate),\n [forceUpdate]\n )\n\n return (\n <>\n <SlotsContext.Provider value={Slots}>\n <ScanProvider>\n {process.env.NODE_ENV === 'production' ? (\n children\n ) : (\n <DevChildren name=\"HostSlots\" forceUpdate={forceUpdate}>\n {children}\n </DevChildren>\n )}\n </ScanProvider>\n </SlotsContext.Provider>\n <Template>{() => callback(Slots.get())}</Template>\n </>\n )\n}\n\nexport const createHost = (children: React.ReactNode, callback: Callback) => {\n return <HostSlots children={children} callback={callback} />\n}\n\nexport const createSlot = <T extends React.ElementType>(Fallback?: T) => {\n const genId = createIdGenerator(genSlotId())\n\n const Slot = React.forwardRef(\n ({ $slot_key$: key, ...props }: any, ref: any) => {\n const Slots = React.useContext(SlotsContext)\n // istanbul ignore next\n if (!Slots) return null\n /* eslint-disable react-hooks/rules-of-hooks */\n const Scan = React.useContext(ScanContext)\n\n const element = <SlotWithKey key={key} ref={ref} {...props} />\n Slots.register(key, element)\n React.useEffect(() => {\n Slots.has(key) && Slots.update(key, element)\n })\n useIsomorphicEffect(() => {\n Slots.clear()\n Scan.rescan()\n return () => Slots.unmount(key)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [Slots])\n /* eslint-enable react-hooks/rules-of-hooks */\n\n return null\n }\n ) as unknown as T\n\n // provide stable key in StrictMode\n const ForwardRef = (props: any, ref: any) => {\n const Slots = React.useContext(SlotsContext)\n if (!Slots) return Fallback ? <Fallback ref={ref} {...props} /> : null\n\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const [key] = React.useState(genId)\n return <Slot ref={ref} $slot_key$={key} {...props} />\n }\n ForwardRef.displayName = Fallback\n ? `Slot(${getComponentName(Fallback)})`\n : 'Slot'\n const SlotWithKey = React.forwardRef(ForwardRef) as unknown as T\n\n return Fallback ? hoistStatics(SlotWithKey, Fallback) : SlotWithKey\n}\n","import * as React from 'react'\n\nexport const useIsomorphicEffect =\n // istanbul ignore next\n typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect\n\nexport const createSlotsContext = <T>(defaultValue: T) => {\n const context = React.createContext(defaultValue)\n context.displayName = 'SlotsContext'\n return context\n}\n\nexport const getComponentName = (Component: React.ElementType) => {\n if (typeof Component === 'string') return Component\n // istanbul ignore next\n return Component.displayName || Component.name || 'Component'\n}\n\nconst REACT_STATICS = ['$$typeof', 'render', 'displayName', 'defaultProps']\n\nexport const hoistStatics = <T extends React.ElementType>(\n target: T,\n source: T\n) => {\n if (typeof source === 'string') return target\n\n const statics = Object.getOwnPropertyNames(source).reduce((obj, key) => {\n if (!REACT_STATICS.includes(key)) {\n obj[key] = (source as any)[key]\n }\n return obj\n }, {} as Record<string, any>)\n\n return Object.assign(target, statics)\n}\n","import * as React from 'react'\n\nimport { useIsomorphicEffect } from './utils'\n\nconst Wrapper = 'slots-wrapper' as 'span'\nconst wrapperRegexp = new RegExp(`^<${Wrapper}>.*</${Wrapper}>$`)\n\ntype DevChildrenProps = {\n name: string\n forceUpdate: () => void\n children: React.ReactNode\n}\n\nexport const DevChildren = ({\n name,\n forceUpdate,\n children,\n}: DevChildrenProps) => {\n const ref = React.useRef<HTMLSpanElement>(null)\n const warnedRef = React.useRef(false)\n // const forceUpdate = React.useReducer(() => [], [])[1]\n\n useIsomorphicEffect(() => {\n if (!warnedRef.current && ref.current?.innerHTML) {\n const content = ref.current.innerHTML\n if (content && !wrapperRegexp.test(content)) {\n console.warn(\n `Unwrapped children found in \"${name}\", either wrap them in slots or remove`\n )\n }\n }\n warnedRef.current = true\n forceUpdate()\n }, [name])\n\n return warnedRef.current ? (\n <>{children}</>\n ) : (\n <Wrapper ref={ref}>{children}</Wrapper>\n )\n}\n","import * as React from 'react'\n\n// istanbul ignore next\nexport const ScanContext = React.createContext({ rescan: () => {} })\n\nexport const ScanProvider = ({ children }: { children: React.ReactNode }) => {\n const [state, rescan] = React.useReducer(() => [], [])\n\n const value = React.useMemo(\n () => ({ rescan }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [state]\n )\n return <ScanContext.Provider value={value}>{children}</ScanContext.Provider>\n}\n","import * as React from 'react'\n\nimport { SlotElement } from './utils'\n\ntype Key = React.Key\n\nexport const createSlotsManager = (onChange: (key: Key) => void) => {\n const itemMap = new Map<Key, React.ReactElement>()\n return {\n register(key: Key, element: React.ReactElement) {\n itemMap.set(key, element)\n },\n update(key: Key, element: React.ReactElement) {\n itemMap.set(key, element)\n onChange?.(key)\n },\n unmount(key: Key) {\n itemMap.delete(key)\n onChange?.(key)\n },\n clear() {\n itemMap.clear()\n },\n has(key: Key) {\n return itemMap.has(key)\n },\n get() {\n return Array.from(itemMap.values()) as SlotElement[]\n },\n }\n}\n","import * as React from 'react'\n\nexport type SlotElement<T extends React.ElementType = any> = React.ReactElement<\n React.ComponentPropsWithRef<T>,\n T\n> & {\n ref: React.ComponentPropsWithRef<T>['ref']\n}\n\nexport const getSlots = <T extends React.ElementType>(\n slots: React.ReactElement[],\n slot: T\n) => {\n return slots.filter((x) => x.type === slot) as SlotElement<T>[]\n}\n\nexport const getSlot = <T extends React.ElementType>(\n slots: React.ReactElement[],\n slot: T\n) => {\n return slots.find((x) => x.type === slot) as SlotElement<T> | undefined\n}\n\nexport const getLastSlot = <T extends React.ElementType>(\n slots: React.ReactElement[],\n slot: T\n) => {\n for (let i = slots.length - 1; i >= 0; i--) {\n if (slots[i].type === slot) {\n return slots[i] as SlotElement<T>\n }\n }\n return undefined\n}\n\nexport const isSlot = <T extends React.ElementType>(\n slotElement: SlotElement,\n slot: T\n): slotElement is SlotElement<T> => {\n return slotElement.type === slot\n}\n\nexport const getSlotProps = <\n T extends SlotElement | undefined,\n Props = T extends undefined\n ? undefined\n : React.ComponentPropsWithRef<T extends SlotElement<infer P> ? P : never>\n>(\n slotElement: T\n): Props => {\n if (!slotElement) return undefined as any\n\n const { key, ref, props } = slotElement\n return { ...props, key, ref }\n}\n"],"mappings":"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","names":[]}