@@ -3,10 +3,59 @@ import {warning} from '../utils/warning'
33import { isSlot } from '../utils/is-slot'
44import type { SlotMarker } from '../utils/types'
55
6- // slot config allows 2 options:
7- // 1. Component to match, example: { leadingVisual: LeadingVisual }
6+ /**
7+ * useSlots - Extract slot components from children for SSR-compatible slot APIs.
8+ *
9+ * Given a list of children and a config mapping slot names to component types,
10+ * separates children into two groups: matched slots and the rest.
11+ *
12+ * Config: { leadingVisual: LeadingVisual, description: Description }
13+ *
14+ * Children: Matching: Output:
15+ * +----------------+ slots = {
16+ * | LeadingVisual | -> matches slot 0 --> leadingVisual: <LeadingVisual />
17+ * | "Project name" | -> no match --> description: <Description />
18+ * | Description | -> matches slot 1 --> }
19+ * | TrailingVisual | -> no match --> rest = ["Project name", <TrailingVisual />]
20+ * +----------------+
21+ *
22+ * Performance-sensitive: called per item in lists (e.g. 100-item ActionList
23+ * calls this 101 times per render). Key optimizations:
24+ *
25+ * 1. for-loop matching instead of findIndex (no closure allocation per child)
26+ * 2. Pre-computed isArrayMatcher[] (avoids Array.isArray in hot loop)
27+ * 3. Short-circuit: once all slots filled, skip matching entirely
28+ * - In production: single integer comparison, straight to rest
29+ * - In dev: scans for duplicates to warn, then to rest
30+ *
31+ * Flow per child:
32+ *
33+ * child ──> isValidElement? ──no──> rest[]
34+ * |
35+ * yes
36+ * |
37+ * v
38+ * all slots filled? ──yes──> rest[] (prod)
39+ * | |
40+ * no [dev: check for
41+ * | duplicate warning]
42+ * v
43+ * match against unfilled slots
44+ * |
45+ * found match? ──no──> rest[]
46+ * |
47+ * yes
48+ * |
49+ * v
50+ * slots[key] = child, slotsFound++
51+ *
52+ * Slot config supports two matcher styles:
53+ * 1. Component reference: { visual: LeadingVisual }
54+ * 2. Component + test fn: { block: [Description, props => props.variant === 'block'] }
55+ */
56+
57+ // Slot config: Component reference, or [Component, testFn] tuple
858type ComponentMatcher = React . ElementType < Props >
9- // 2. Component to match + a test function, example: { blockDescription: [Description, props => props.variant === 'block'] }
1059type ComponentAndPropsMatcher = [ ComponentMatcher , ( props : Props ) => boolean ]
1160
1261export type SlotConfig = Record < string , ComponentMatcher | ComponentAndPropsMatcher >
@@ -19,21 +68,17 @@ type SlotElements<Config extends SlotConfig> = {
1968 [ Property in keyof Config ] : SlotValue < Config , Property >
2069}
2170
22- type SlotValue < Config , Property extends keyof Config > = Config [ Property ] extends React . ElementType // config option 1
71+ type SlotValue < Config , Property extends keyof Config > = Config [ Property ] extends React . ElementType
2372 ? React . ReactElement < React . ComponentPropsWithoutRef < Config [ Property ] > , Config [ Property ] >
2473 : Config [ Property ] extends readonly [
25- infer ElementType extends React . ElementType , // config option 2, infer array[0] as component
74+ infer ElementType extends React . ElementType ,
2675 // eslint-disable-next-line @typescript-eslint/no-unused-vars
27- infer _testFn , // even though we don't use testFn, we need to infer it to support types for slots.*.props
76+ infer _testFn ,
2877 ]
2978 ? React . ReactElement < React . ComponentPropsWithoutRef < ElementType > , ElementType >
30- : never // useful for narrowing types, third option is not possible
79+ : never
3180
32- /**
33- * Extract components from `children` so we can render them in different places,
34- * allowing us to implement components with SSR-compatible slot APIs.
35- * Note: We can only extract direct children, not nested ones.
36- */
81+ /** Extract slot components from children. See file header for details. */
3782export function useSlots < Config extends SlotConfig > (
3883 children : React . ReactNode ,
3984 config : Config ,
0 commit comments