3838 * events.
3939 *
4040 * Content is not built until the first expansion and stays built afterwards —
41- * same policy, same prop names as popups (see popup_content_mount.js):
42- * `mountWhenClosed` builds it right away, `unmountWhenClosed` throws it away
43- * once the collapse settles (so a closing animation still plays on real
44- * content).
41+ * same policy, same `mount` prop as popups (see popup_content_mount.js):
42+ * `"always"` builds it right away, `"while-opened"` throws it away once the
43+ * collapse settles (so a closing animation still plays on real content).
4544 *
4645 * The animation is a REVEAL, not a resize: the expandable's own footprint
4746 * grows/shrinks progressively (the content's grid track interpolates
@@ -73,6 +72,7 @@ import { ActionRenderer } from "../../action/action_renderer.jsx";
7372import { useAction } from "../../action/use_action.js" ;
7473import { useActionStatus } from "../../action/use_action_status.js" ;
7574import { Box } from "../../box/box.jsx" ;
75+ import { MOUNT_DEFAULT } from "../../layout/popup_content_mount.js" ;
7676import { whenTransitionSettles } from "../../layout/popup_shared.js" ;
7777import { onNaviCommand } from "../commands.js" ;
7878import { warnSignalCollision } from "../control_value.js" ;
@@ -187,7 +187,7 @@ const css = /* css */ `
187187 grid-template-columns: 1fr;
188188 grid-template-rows: 1fr;
189189 }
190- /* mountWhenClosed : the content is built and width-frozen while closed
190+ /* mount="always" : the content is built and width-frozen while closed
191191 (see the component), so it can size the height at all times — the
192192 expandable then keeps one stable height and only the width reveals. */
193193 &[data-closed-content-sized] > .navi_expandable_content_container {
@@ -260,8 +260,7 @@ const useExpandableContext = (partName) => {
260260 * layout?: "row" | "column",
261261 * autoFocus?: boolean,
262262 * maxContentHeight?: string | number,
263- * mountWhenClosed?: boolean,
264- * unmountWhenClosed?: boolean,
263+ * mount?: "always" | "from-first-open" | "while-opened",
265264 * arrowKeyShortcuts?: boolean,
266265 * openKeyShortcut?: string,
267266 * closeKeyShortcut?: string,
@@ -298,13 +297,15 @@ const useExpandableContext = (partName) => {
298297 * the UI part (it would otherwise be lost to the closed, inert content).
299298 * @param maxContentHeight - Caps the content height; taller content scrolls
300299 * inside the expandable instead of growing it.
301- * @param mountWhenClosed - Builds the content right away instead of on first
302- * expansion. In layout="column" it also gives the closed expandable its
303- * content's height (the content is kept laid out at its open width), so
304- * opening only reveals the width instead of changing the height too.
305- * @param unmountWhenClosed - Throws the content away once the collapse
306- * settles — after the closing animation, so it still plays on real content —
307- * and rebuilds it from scratch on every expansion.
300+ * @param mount - When the content is built and thrown away, same three values
301+ * as a popup's (see popup_content_mount.js). `"from-first-open"` (the
302+ * default) builds it on the first expansion and keeps it afterwards.
303+ * `"always"` builds it right away; in layout="column" it also gives the
304+ * closed expandable its content's height (the content is kept laid out at
305+ * its open width), so opening only reveals the width instead of changing the
306+ * height too. `"while-opened"` throws the content away once the collapse
307+ * settles — after the closing animation, so it still plays on real
308+ * content — and rebuilds it from scratch on every expansion.
308309 */
309310export const Expandable = ( props ) => {
310311 import . meta. css = css ;
@@ -320,8 +321,7 @@ export const Expandable = (props) => {
320321 layout,
321322 autoFocus,
322323 maxContentHeight,
323- mountWhenClosed,
324- unmountWhenClosed,
324+ mount = MOUNT_DEFAULT ,
325325 arrowKeyShortcuts = true ,
326326 openKeyShortcut = "ArrowRight" ,
327327 closeKeyShortcut = "ArrowLeft" ,
@@ -335,7 +335,9 @@ export const Expandable = (props) => {
335335 const contentContainerRef = useRef ( ) ;
336336 const contentId = useId ( ) ;
337337 const isColumn = layout === "column" ;
338- const closedContentSized = Boolean ( isColumn && mountWhenClosed ) ;
338+ const mountedAlways = mount === "always" ;
339+ const mountedWhileOpened = mount === "while-opened" ;
340+ const closedContentSized = isColumn && mountedAlways ;
339341
340342 if ( signal ) {
341343 warnSignalCollision ( props , "expandable" , "open" ) ;
@@ -353,14 +355,11 @@ export const Expandable = (props) => {
353355 const { loading : actionLoading } = useActionStatus ( effectiveAction ) ;
354356
355357 const [ contentMounted , setContentMounted ] = useState (
356- ( ) => Boolean ( mountWhenClosed ) || opened ,
358+ ( ) => mountedAlways || opened ,
357359 ) ;
358- // Same exclusion as popup_content_mount.js: content that must exist while
359- // closed cannot also be thrown away on close.
360- const effectiveUnmountWhenClosed = unmountWhenClosed && ! mountWhenClosed ;
361360
362361 // Fully open and no longer moving — what allows overflow to become visible
363- // (see the CSS) and what unmountWhenClosed waits for before emptying.
362+ // (see the CSS) and what mount="while-opened" waits for before emptying.
364363 const [ settled , setSettled ] = useState ( true ) ;
365364
366365 // Read before the close touches the DOM: flipping the content to inert can
@@ -435,7 +434,7 @@ export const Expandable = (props) => {
435434 focusedAtPointerDownRef . current = null ;
436435 setOpened ( nextOpen ) ;
437436 // Flipped here, before the closing/opening commit, so effects of that very
438- // commit already see the movement as started — unmountWhenClosed must not
437+ // commit already see the movement as started — mount="while-opened" must not
439438 // read a stale "settled" and empty the content under a closing animation.
440439 setSettled ( ! animation ) ;
441440 if ( signal ) {
@@ -573,17 +572,17 @@ export const Expandable = (props) => {
573572 } , [ opened ] ) ;
574573
575574 useLayoutEffect ( ( ) => {
576- if ( settled && ! opened && effectiveUnmountWhenClosed ) {
575+ if ( settled && ! opened && mountedWhileOpened ) {
577576 setContentMounted ( false ) ;
578577 }
579- } , [ settled , opened , effectiveUnmountWhenClosed ] ) ;
578+ } , [ settled , opened , mountedWhileOpened ] ) ;
580579 useLayoutEffect ( ( ) => {
581- if ( mountWhenClosed ) {
580+ if ( mountedAlways ) {
582581 setContentMounted ( true ) ;
583582 }
584- } , [ mountWhenClosed ] ) ;
583+ } , [ mountedAlways ] ) ;
585584
586- // closedContentSized (column + mountWhenClosed ): the closed content sizes
585+ // closedContentSized (column + mount="always" ): the closed content sizes
587586 // the height (see the CSS), which is only right if it lies at its OPEN
588587 // width — at its natural closed width (a 0-wide track) it would wrap
589588 // against nothing and stack word by word. So while closed, its width is
0 commit comments