@@ -25,16 +25,13 @@ import { mergeProps, normalize } from '@dunky.dev/solid-state-machine'
2525import { DialogContext , useDialogContext } from './context'
2626import { useDialog } from './use-dialog'
2727
28- // A part's bindings merge INSIDE the JSX spread: the compiler wraps the
29- // expression in a reactive scope, so a machine transition re-translates it.
30- // `children` never rides that spread β a re-evaluated spread re-CREATES the
31- // children it carries, and a child whose lifecycle writes to the machine
32- // (Title's presence) would then loop machine -> spread -> remount -> machine.
33- // Every part strips it and renders `{props.children}` explicitly.
34-
35- // A consumer ref that crossed a component boundary is a setter function or an
36- // array of them (Solid 2.0 refs are functions; arrays compose); apply it
37- // alongside the part's own element capture.
28+ // Bindings merge inside the JSX spread so they stay reactive. `children` must
29+ // never ride that spread: a re-evaluated spread re-creates the children, and a
30+ // child that writes to the machine on mount (Title) would loop forever. Every
31+ // part omits it and renders `{props.children}` explicitly.
32+
33+ // A consumer ref that crossed a component boundary is a function or an array
34+ // of functions.
3835function applyConsumerRef < T > ( ref : Ref < T > | undefined , element : T ) : void {
3936 if ( typeof ref === 'function' ) ( ref as ( element : T ) => void ) ( element )
4037 else if ( Array . isArray ( ref ) ) for ( const entry of ref ) applyConsumerRef ( entry as Ref < T > , element )
@@ -56,11 +53,8 @@ export const Dialog: Component<DialogProps> & Parts = props => {
5653 const backdropRef : { current : HTMLDivElement | null } = { current : null }
5754
5855 // closeOnBack: while open, a guard entry in the session history turns the
59- // host's Back into a dismissal instead of a navigation. Every decision
60- // (gate, veto, controlled) lives in the core's backNavigate; this effect
61- // only wires the web mechanics. It tracks `api.open` alone β fresh callback
62- // identities never churn real session-history entries. It lives on the
63- // root β the guard concerns the dialog's openness, not any rendered part.
56+ // browser's Back into a dismissal. The decision (gate, veto, controlled)
57+ // lives in the core's backNavigate; this only wires the web mechanics.
6458 createEffect (
6559 ( ) => api . open ,
6660 open => {
@@ -111,8 +105,8 @@ export const Portal: Component<DialogPortalProps> = props => {
111105 const context = useDialogContext ( )
112106 if ( isServer ) return null
113107 return (
114- // `mounted`, not `open`: an animated dialog stays in the tree through
115- // `closing` so its exit visual can play before everything unmounts .
108+ // `mounted`, not `open`: an animated dialog stays mounted through
109+ // `closing` so its exit visual can play.
116110 < Show when = { context . api . mounted } >
117111 { /* keyed: the host portal's mount is fixed at creation, so a container
118112 swap re-creates the portal on the new target. */ }
@@ -140,8 +134,6 @@ export interface DialogBackdropProps extends ComponentProps<'div'> {}
140134export const Backdrop : Component < DialogBackdropProps > = props => {
141135 const { api, machine, backdropRef } = useDialogContext ( )
142136 const rest = omit ( props , 'ref' , 'children' )
143- // The shared slot must not outlive the element: the context box lives on
144- // the root, the element only until this part's owner disposes.
145137 onSettled ( ( ) => ( ) => ( backdropRef . current = null ) )
146138
147139 const bindings = ( ) : Record < string , unknown > => {
@@ -189,9 +181,8 @@ export const Viewport: Component<DialogViewportProps> = props => {
189181 } & Record < string , unknown >
190182 return {
191183 ...attrs ,
192- // Content presses bubble up here β only a press that started on the
193- // viewport itself is an outside interaction, and only the topmost dialog
194- // of a stack answers it.
184+ // Only a press that started on the viewport itself is an outside
185+ // interaction, and only the topmost dialog of a stack answers it.
195186 onClick : ( event : MouseEvent ) => {
196187 if ( event . target !== event . currentTarget ) return
197188 if ( ! isTopmostLayer ( machine . context . id ) ) return
@@ -210,8 +201,7 @@ export const Viewport: Component<DialogViewportProps> = props => {
210201
211202export interface DialogContentProps extends ComponentProps < 'div' > {
212203 /** The element to focus when the dialog opens β an element, or an accessor
213- * resolved at open time (the Solid idiom for a ref variable that fills
214- * during render). @default the dialog window */
204+ * resolved at open time. @default the dialog window */
215205 initialFocus ?: HTMLElement | ( ( ) => HTMLElement | null | undefined )
216206}
217207
@@ -223,14 +213,11 @@ export const Content: Component<DialogContentProps> = props => {
223213 const rest = omit ( props , 'ref' , 'initialFocus' , 'children' )
224214 let contentEl : HTMLDivElement | undefined
225215
226- // The machine's `open` state is the edge, not mount/unmount β an animated
227- // dialog stays mounted through `closing`, and the stack, containment, and
228- // focus must release the moment the exit starts, not when it finishes.
229- // One effect keeps the ordering right both ways: the stack joins before focus
230- // moves in, and on close it must release the layers beneath (un-inert them)
231- // before focus can move back out to one of them. Apply-phase reads go
232- // through untrack β `api.open` is the one edge; the options must not re-run
233- // the effect.
216+ // The `open` state is the edge, not mount/unmount: an animated dialog stays
217+ // mounted through `closing`, and the stack, containment, and focus must
218+ // release the moment the exit starts. One effect keeps the order right both
219+ // ways: the stack joins before focus moves in; on close it releases the
220+ // layers beneath before focus moves back out.
234221 createEffect (
235222 ( ) => api . open ,
236223 open => {
@@ -246,13 +233,12 @@ export const Content: Component<DialogContentProps> = props => {
246233 backdrop : ( ) => backdropRef . current ,
247234 } )
248235
249- // preventScroll everywhere: the scroll lock already froze the surface, so
250- // moving focus must not scroll it β otherwise opening jumps the (top-of-
251- // container) dialog into view and closing jumps back to the trigger.
236+ // preventScroll everywhere: moving focus must not scroll the locked
237+ // surface, or open/close jumps the view.
252238 const target =
253239 untrack ( ( ) => resolveInitialFocus ( props . initialFocus ) ) ?? getInitialFocus ( content )
254240 target . focus ( { preventScroll : true } )
255- // A target that can't take focus (disabled, hidden) falls back to the panel.
241+ // A target that can't take focus falls back to the panel.
256242 if ( document . activeElement !== target ) content . focus ( { preventScroll : true } )
257243
258244 return ( ) => {
@@ -262,10 +248,9 @@ export const Content: Component<DialogContentProps> = props => {
262248 } ,
263249 )
264250
265- // The exit window: Content live while not open only happens in `closing`.
266- // The layer has already released everything above, so hide the still-painting
267- // layer from interaction and report when its visual is done; the cleanup is
268- // the reopen interrupt (and final unmount) undoing both.
251+ // The exit window: mounted while not open only happens in `closing`. Hide
252+ // the still-painting layer and report when its visual is done; the cleanup
253+ // is the reopen interrupt (and final unmount) undoing both.
269254 createEffect (
270255 ( ) => api . open ,
271256 open => {
@@ -284,24 +269,19 @@ export const Content: Component<DialogContentProps> = props => {
284269 )
285270
286271 // The lock spans the whole mount β through `closing` too: releasing it
287- // mid-exit would bring the scrollbar back and reflow the page under the
288- // still-painting layer. A scoped dialog locks its portal container; a page
289- // dialog locks the body.
272+ // mid-exit would reflow the page under the still-painting layer.
290273 useScrollLock ( ( ) => machine . context . modal , container )
291274
292275 useFocusTrap ( ( ) => contentEl ?? null , {
293- // Only a modal dialog traps, and only while topmost β a nested dialog
294- // owns focus while open.
276+ // Only a modal dialog traps, and only while topmost.
295277 enabled : ( ) => machine . context . modal && isTopmostLayer ( machine . context . id ) ,
296- // The Close part is the cycle's last stop wherever it renders (core
297- // SPEC); found by its derived id.
278+ // Close is the cycle's last stop wherever it renders (core SPEC).
298279 last : ( ) => document . getElementById ( api . ids . close ) ,
299280 } )
300281
301- // A neutral element with the role, not <dialog>: the window is the initial
302- // focus target, so it carries tabindex β which HTML forbids on <dialog> β
303- // and the native element only pays off via showModal(), which this contract
304- // deliberately doesn't use.
282+ // A neutral element with the role, not <dialog>: the window carries
283+ // tabindex (forbidden on <dialog>), and this contract doesn't use
284+ // showModal() β see SPEC.md.
305285 return (
306286 < div
307287 { ...mergeProps < DialogContentProps > ( rest , normalize ( api . parts . content ) ) }
@@ -325,8 +305,7 @@ export const Title: Component<DialogTitleProps> = props => {
325305 const { api, machine } = useDialogContext ( )
326306 const rest = omit ( props , 'children' )
327307
328- // Presence reports from the settled phase: the machine starts on the root's
329- // settle, which owner order puts before this one.
308+ // onSettled: the machine starts on the root's settle, which runs first.
330309 onSettled ( ( ) => {
331310 machine . send ( { type : 'part.presence' , part : 'title' , present : true } )
332311 return ( ) => machine . send ( { type : 'part.presence' , part : 'title' , present : false } )
0 commit comments