@jsenv/navi 0.29.323 → 0.29.325

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,10 @@
4
4
  */
5
5
  import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
6
6
  export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
7
- import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
7
+ import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
8
8
  export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
9
9
  import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
10
- import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, Fragment as Fragment$1 } from "preact";
10
+ import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
11
11
  import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
12
12
  import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
13
13
  export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
@@ -12616,6 +12616,12 @@ const useExecuteAction = (
12616
12616
  // whose command sent — is stored as the callout display target, so the
12617
12617
  // message appears on whoever asked rather than on the whole control.
12618
12618
  const element = elementRef.current;
12619
+ if (!element) {
12620
+ // The control left the page while its run was out (its own answer can
12621
+ // unmount it, see the outcome events below): there is no box to hang
12622
+ // the refusal on. onActionError is still told, by the run's side effect.
12623
+ return;
12624
+ }
12619
12625
  let target = requester;
12620
12626
  // A requester that is no longer on the page is not a place to show
12621
12627
  // anything: the clear cross leaves with the value it optimistically
@@ -12718,12 +12724,20 @@ const useExecuteAction = (
12718
12724
  // Either three callbacks, one per outcome, or a single one for "however
12719
12725
  // this ends" — it receives `{ aborted, reason }`, `{ error }` or
12720
12726
  // `{ data }`, so a caller that only needs to know the action settled
12721
- // (and whether it worked) does not have to register three.
12727
+ // (and whether it worked) does not have to register three. Each also
12728
+ // receives the outcome event (navi_action_abort/error/end) as its
12729
+ // second argument.
12722
12730
  const addSideEffect = (sideEffect) => {
12723
12731
  if (typeof sideEffect === "function") {
12724
- addAbortCallback((reason) => sideEffect({ aborted: true, reason }));
12725
- addErrorCallback((error) => sideEffect({ error }));
12726
- addCompleteCallback((data) => sideEffect({ data }));
12732
+ addAbortCallback((reason, outcomeEvent) =>
12733
+ sideEffect({ aborted: true, reason }, outcomeEvent),
12734
+ );
12735
+ addErrorCallback((error, outcomeEvent) =>
12736
+ sideEffect({ error }, outcomeEvent),
12737
+ );
12738
+ addCompleteCallback((data, outcomeEvent) =>
12739
+ sideEffect({ data }, outcomeEvent),
12740
+ );
12727
12741
  return;
12728
12742
  }
12729
12743
  const { abort, error, complete } = sideEffect;
@@ -12731,56 +12745,6 @@ const useExecuteAction = (
12731
12745
  addErrorCallback(error);
12732
12746
  addCompleteCallback(complete);
12733
12747
  };
12734
- addSideEffect({
12735
- abort: (reason) => {
12736
- const element = elementRef.current;
12737
- if (
12738
- // at this stage the action side effect might have removed the <element> from the DOM
12739
- // (in theory no because action side effect are batched to happen after)
12740
- // but other side effects might do this
12741
- element
12742
- ) {
12743
- dispatchInternalCustomEvent(element, "navi_action_abort", {
12744
- ...sharedActionEventDetail,
12745
- reason,
12746
- });
12747
- }
12748
- },
12749
- error: (error) => {
12750
- if (errorEffect === "show_validation_message") {
12751
- addErrorMessage(error, { requester });
12752
- } else if (errorEffect === "throw") {
12753
- setError(error);
12754
- }
12755
-
12756
- const element = elementRef.current;
12757
- if (
12758
- // at this stage the action side effect might have removed the <element> from the DOM
12759
- // (in theory no because action side effect are batched to happen after)
12760
- // but other side effects might do this
12761
- element
12762
- ) {
12763
- dispatchInternalCustomEvent(element, "navi_action_error", {
12764
- ...sharedActionEventDetail,
12765
- error,
12766
- });
12767
- }
12768
- },
12769
- complete: (data) => {
12770
- const element = elementRef.current;
12771
- if (
12772
- // at this stage the action side effect might have removed the <element> from the DOM
12773
- // (in theory no because action side effect are batched to happen after)
12774
- // but other side effects might do this
12775
- element
12776
- ) {
12777
- dispatchInternalCustomEvent(element, "navi_action_end", {
12778
- ...sharedActionEventDetail,
12779
- data,
12780
- });
12781
- }
12782
- },
12783
- });
12784
12748
 
12785
12749
  const actionStartEventDetail = {
12786
12750
  ...sharedActionEventDetail,
@@ -12792,13 +12756,50 @@ const useExecuteAction = (
12792
12756
  actionStartEventDetail,
12793
12757
  );
12794
12758
 
12759
+ // The outcome is told twice: to the element, when there still is one,
12760
+ // and to the side effects registered at navi_action_start, always. A
12761
+ // run can outlive its control — the run's own answer, written to a
12762
+ // store the parent reads to decide what it draws, unmounts the control
12763
+ // one microtask before the run settles — and the element is then no
12764
+ // place to dispatch to: Preact drops a detached element's listeners.
12765
+ // The side effects are the run's, not the element's, so the event
12766
+ // reaches them either way.
12767
+ const tellOutcome = (outcomeEventName, outcomeEventDetail, trigger) => {
12768
+ const outcomeEvent = createInternalCustomEvent(outcomeEventName, {
12769
+ ...sharedActionEventDetail,
12770
+ ...outcomeEventDetail,
12771
+ });
12772
+ const element = elementRef.current;
12773
+ if (element) {
12774
+ element.dispatchEvent(outcomeEvent);
12775
+ }
12776
+ trigger(outcomeEvent);
12777
+ };
12778
+
12795
12779
  const runAction = () => {
12796
12780
  return action[method]({
12797
12781
  event: actionEvent,
12798
12782
  reason: `"${event.type}" event on ${getElementSignature(event.target)}`,
12799
- onAbort: triggerAbort,
12800
- onError: triggerError,
12801
- onComplete: triggerComplete,
12783
+ onAbort: (reason) => {
12784
+ tellOutcome("navi_action_abort", { reason }, (outcomeEvent) => {
12785
+ triggerAbort(reason, outcomeEvent);
12786
+ });
12787
+ },
12788
+ onError: (error) => {
12789
+ if (errorEffect === "show_validation_message") {
12790
+ addErrorMessage(error, { requester });
12791
+ } else if (errorEffect === "throw") {
12792
+ setError(error);
12793
+ }
12794
+ tellOutcome("navi_action_error", { error }, (outcomeEvent) => {
12795
+ triggerError(error, outcomeEvent);
12796
+ });
12797
+ },
12798
+ onComplete: (data) => {
12799
+ tellOutcome("navi_action_end", { data }, (outcomeEvent) => {
12800
+ triggerComplete(data, outcomeEvent);
12801
+ });
12802
+ },
12802
12803
  });
12803
12804
  };
12804
12805
 
@@ -31648,8 +31649,26 @@ const resolveExplicitTarget = (element) => {
31648
31649
  }
31649
31650
  return undefined;
31650
31651
  };
31652
+ // The control around the source that a command can be about: one holding a
31653
+ // value of its own. A popup is a control root too (it answers interactions,
31654
+ // carries a callout), but it has no host of its own — asked for one, it
31655
+ // answers with the first control INSIDE it, which for a field sending
31656
+ // --navi-update from a picker's popup is that very field: the command then
31657
+ // sets the field to what it already holds, which fires the command again,
31658
+ // without end. Such a surface is walked past, up to the picker or the form.
31651
31659
  const resolveFirstParentControl = (el) => {
31652
- return getParentControl(el);
31660
+ let parentControl = getParentControl(el);
31661
+ while (parentControl && !ownsControlHost(parentControl)) {
31662
+ parentControl = getParentControl(parentControl);
31663
+ }
31664
+ return parentControl;
31665
+ };
31666
+ const ownsControlHost = (controlRoot) => {
31667
+ const controlHost = findControlHost(controlRoot);
31668
+ return (
31669
+ Boolean(controlHost) &&
31670
+ controlHost.closest("[navi-control]") === controlRoot
31671
+ );
31653
31672
  };
31654
31673
  const resolveFirstChildControl = (el) => {
31655
31674
  let startEl;
@@ -34400,6 +34419,21 @@ const resolveAutoAnimationKind = (anchor, parsedPositionArea) => {
34400
34419
  */
34401
34420
  const OUTSIDE_REGION_ATTRIBUTE = "data-navi-popup-outside";
34402
34421
 
34422
+ /**
34423
+ * `data-navi-popup-inside` is the other direction: a box of the PAGE the
34424
+ * caller says belongs to a popup, so a press on it is not outside that popup
34425
+ * even though it lies beyond the border box — a card whose press is what
34426
+ * fills the one panel of a board. Its value names the popup by `id`, the way
34427
+ * `commandfor` does (several ids, space separated), because the exemption is
34428
+ * one popup's: a menu open beside the board still closes on the same press.
34429
+ *
34430
+ * Unlike the outside marker it answers for its whole subtree: the outside
34431
+ * marker carves free space out of a surface whose controls stay surface, this
34432
+ * one adds a whole thing — the card, its count, its buttons — to the popup's
34433
+ * own ground, and nothing in it is a dismissal.
34434
+ */
34435
+ const INSIDE_ATTRIBUTE = "data-navi-popup-inside";
34436
+
34403
34437
  /**
34404
34438
  * What a press landing on a region the caller declared as not-its-surface
34405
34439
  * does: exactly what the same press on the backdrop would do.
@@ -34524,6 +34558,14 @@ const armOutsidePressClose = (
34524
34558
  ) {
34525
34559
  return;
34526
34560
  }
34561
+ if (
34562
+ popupEl.id &&
34563
+ pointerDownEvent.target.closest?.(
34564
+ `[${INSIDE_ATTRIBUTE}~="${CSS.escape(popupEl.id)}"]`,
34565
+ )
34566
+ ) {
34567
+ return;
34568
+ }
34527
34569
  // Real DOM containment wins over the rectangle below — an element
34528
34570
  // genuinely inside the popup (`overflow: visible`, a negative margin, an
34529
34571
  // absolutely-positioned child) can be painted outside its border box, and
@@ -39827,10 +39869,24 @@ const useInteractiveProps = (props, {
39827
39869
  // aborted run, whose promise is awaited to completion (see
39828
39870
  // performRun in actions.js) — which is exactly what "the server is
39829
39871
  // done with it" means, and therefore when the queued request may go.
39830
- e.detail.addSideEffect(outcome => {
39872
+ e.detail.addSideEffect((outcome, outcomeEvent) => {
39831
39873
  uiStateController.actionInFlight = false;
39832
39874
  uiStateController.runningActionSignal.value = null;
39833
39875
  uiStateController.parallelGuard?.release(uiStateController);
39876
+ // The outcome callbacks are told from here, the run's side effect,
39877
+ // and not from the onnavi_action_* handlers below: the run can
39878
+ // outlive this control (its own answer, written to a store the
39879
+ // parent reads, unmounts it one microtask before it settles), and
39880
+ // whoever wears the run — a parent's `loading` following these,
39881
+ // see 12_picker_card_demo.html — must hear it end all the same.
39882
+ // The handlers below keep what needs the element.
39883
+ if (outcome.aborted) {
39884
+ onActionAborted?.(outcomeEvent);
39885
+ } else if ("error" in outcome) {
39886
+ onActionError?.(outcome.error, outcomeEvent);
39887
+ } else {
39888
+ onActionEnd?.(outcome.data, outcomeEvent);
39889
+ }
39834
39890
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
39835
39891
  if (!queuedEvent) {
39836
39892
  syncOptimisticAttribute(false);
@@ -39870,7 +39926,6 @@ const useInteractiveProps = (props, {
39870
39926
  if (resetOnAbort && !superseded) {
39871
39927
  dispatchRequestResetUIState(e.currentTarget, e);
39872
39928
  }
39873
- onActionAborted?.(e);
39874
39929
  },
39875
39930
  onnavi_action_error: e => {
39876
39931
  const {
@@ -39879,7 +39934,6 @@ const useInteractiveProps = (props, {
39879
39934
  if (resetOnError) {
39880
39935
  dispatchRequestResetUIState(e.currentTarget, e);
39881
39936
  }
39882
- onActionError?.(error, e);
39883
39937
  uiStateController.onActionError(e);
39884
39938
  },
39885
39939
  onnavi_action_end: e => {
@@ -39887,7 +39941,6 @@ const useInteractiveProps = (props, {
39887
39941
  data
39888
39942
  } = e.detail;
39889
39943
  debugAction(e, `action end with data: ${JSON.stringify(data)}`);
39890
- onActionEnd?.(data, e);
39891
39944
  controlRootProps.onnavi_action_end?.(e);
39892
39945
  uiStateController.onActionEnd(e);
39893
39946
 
@@ -47792,33 +47845,6 @@ const createToggleEvent = open => {
47792
47845
  return toggleEvent;
47793
47846
  };
47794
47847
 
47795
- const NextResolverContext = createContext(null);
47796
- const useNextResolver = () => useContext(NextResolverContext);
47797
-
47798
- /**
47799
- * Creates a renderComponent function that passes props through a chain of resolvers.
47800
- * Each resolver is a Preact component rendered in sequence (hooks are allowed).
47801
- * To pass through to the next resolver, call useNextResolver() and render the
47802
- * returned Next component with the desired props.
47803
- * To terminate the chain early (e.g. render a specialized component), render
47804
- * directly without calling Next.
47805
- *
47806
- * The last entry in the array is the final/target component — it receives null
47807
- * from useNextResolver() indicating it is terminal.
47808
- *
47809
- * Usage:
47810
- * const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
47811
- * // Then inside a component render:
47812
- * renderButton(props)
47813
- *
47814
- * Each position of the chain has a runner of its own, defined once: it renders
47815
- * its resolver under a NextResolverContext holding the runner after it. A
47816
- * resolver that re-renders on its own and renders <Next> therefore resumes at
47817
- * its own position without any bookkeeping — the Next it was given is the one
47818
- * made for it. A chain rendered by the hundred (a list's rows) pays two
47819
- * components per position, the runner and the resolver, so nothing here is a
47820
- * component that merely forwards.
47821
- */
47822
47848
  const createComponentResolver = (resolvers, {
47823
47849
  pure
47824
47850
  } = {}) => {
@@ -47827,13 +47853,10 @@ const createComponentResolver = (resolvers, {
47827
47853
  for (let index = 0; index < resolvers.length; index++) {
47828
47854
  const Resolver = resolvers[index];
47829
47855
  const isLast = index === lastIndex;
47830
- const Runner = props => jsx(NextResolverContext.Provider, {
47831
- value: isLast ? null : runners[index + 1],
47832
- children: jsx(Resolver, {
47833
- ...props
47834
- })
47835
- });
47836
- Runner.displayName = `${Resolver.displayName || Resolver.name}Runner`;
47856
+ function Runner(props) {
47857
+ return runResolverBody(this, Resolver, props, isLast ? null : runners[index + 1]);
47858
+ }
47859
+ Runner.displayName = Resolver.displayName || Resolver.name;
47837
47860
  runners.push(Runner);
47838
47861
  }
47839
47862
  const FirstRunner = runners[0];
@@ -47864,6 +47887,54 @@ const createComponentResolver = (resolvers, {
47864
47887
  }
47865
47888
  return PureRenderComponent;
47866
47889
  };
47890
+ const useNextResolver = () => {
47891
+ if (currentNext === OUTSIDE_RESOLVER_BODY) {
47892
+ throw new Error("useNextResolver() called outside a resolver body: a resolver rendered by another resolver is rendered with renderResolver(Resolver, props), not as a JSX child.");
47893
+ }
47894
+ return currentNext;
47895
+ };
47896
+ const renderResolver = (Resolver, props) => {
47897
+ if (currentNext === OUTSIDE_RESOLVER_BODY) {
47898
+ throw new Error(`renderResolver(${Resolver.displayName || Resolver.name}) called outside a resolver body: only a resolver can render another one.`);
47899
+ }
47900
+ let SubRunner = subRunnerByResolver.get(Resolver);
47901
+ if (!SubRunner) {
47902
+ SubRunner = function (props) {
47903
+ const {
47904
+ [NEXT_PROP]: next,
47905
+ ...ownProps
47906
+ } = props;
47907
+ return runResolverBody(this, Resolver, ownProps, next);
47908
+ };
47909
+ SubRunner.displayName = Resolver.displayName || Resolver.name;
47910
+ subRunnerByResolver.set(Resolver, SubRunner);
47911
+ }
47912
+ return createElement(SubRunner, {
47913
+ ...props,
47914
+ [NEXT_PROP]: currentNext
47915
+ });
47916
+ };
47917
+
47918
+ // What useNextResolver() answers: the runner after the one whose body is
47919
+ // running, null for the last — and, outside any body, a value that is neither.
47920
+ const OUTSIDE_RESOLVER_BODY = Symbol("outside_resolver_body");
47921
+ let currentNext = OUTSIDE_RESOLVER_BODY;
47922
+ const runResolverBody = (instance, Resolver, props, next) => {
47923
+ const nextBefore = currentNext;
47924
+ currentNext = next;
47925
+ try {
47926
+ return Resolver.call(instance, props);
47927
+ } finally {
47928
+ currentNext = nextBefore;
47929
+ }
47930
+ };
47931
+
47932
+ // One runner per resolver rendered through renderResolver, made on first use:
47933
+ // the same component type on every render, so a resolver picked again is
47934
+ // updated in place and another one picked is a remount, like any child.
47935
+ const subRunnerByResolver = new WeakMap();
47936
+ // Rides on the props of a sub-runner, stripped before the body sees them.
47937
+ const NEXT_PROP = "navi-resolver-next";
47867
47938
  function pureShouldComponentUpdate(nextProps) {
47868
47939
  return shallowDiffers(this.props, nextProps);
47869
47940
  }
@@ -48689,9 +48760,7 @@ const ButtonConfirm = ({
48689
48760
  const ButtonRouteResolver = props => {
48690
48761
  const Next = useNextResolver();
48691
48762
  if (props.route) {
48692
- return jsx(ButtonWithRoute, {
48693
- ...props
48694
- });
48763
+ return renderResolver(ButtonWithRoute, props);
48695
48764
  }
48696
48765
  return jsx(Next, {
48697
48766
  ...props
@@ -51289,9 +51358,7 @@ const installInputCss = () => {
51289
51358
  const InputModeResolver = props => {
51290
51359
  const Next = useNextResolver();
51291
51360
  if (props.inputMode === "numeric" || props.inputMode === "decimal") {
51292
- return jsx(InputModeNumericOrDecimal, {
51293
- ...props
51294
- });
51361
+ return renderResolver(InputModeNumericOrDecimal, props);
51295
51362
  }
51296
51363
  return jsx(Next, {
51297
51364
  ...props
@@ -51519,39 +51586,25 @@ const InputTypeResolver = props => {
51519
51586
  // very same "icon while empty, clear button once filled" affordance without
51520
51587
  // pretending to be a search box.
51521
51588
  if (props.clearable && props.type !== "search") {
51522
- return jsx(InputClearable, {
51523
- ...props
51524
- });
51589
+ return renderResolver(InputClearable, props);
51525
51590
  }
51526
51591
  if (props.type === "search") {
51527
- return jsx(InputSearch, {
51528
- ...props
51529
- });
51592
+ return renderResolver(InputSearch, props);
51530
51593
  }
51531
51594
  if (props.type === "email") {
51532
- return jsx(InputEmail, {
51533
- ...props
51534
- });
51595
+ return renderResolver(InputEmail, props);
51535
51596
  }
51536
51597
  if (props.type === "tel") {
51537
- return jsx(InputTel, {
51538
- ...props
51539
- });
51598
+ return renderResolver(InputTel, props);
51540
51599
  }
51541
51600
  if (props.type === "number") {
51542
- return jsx(InputNumber, {
51543
- ...props
51544
- });
51601
+ return renderResolver(InputNumber, props);
51545
51602
  }
51546
51603
  if (props.type === "color") {
51547
- return jsx(InputColor, {
51548
- ...props
51549
- });
51604
+ return renderResolver(InputColor, props);
51550
51605
  }
51551
51606
  if (props.type === "datetime-local") {
51552
- return jsx(InputDatetimeLocal, {
51553
- ...props
51554
- });
51607
+ return renderResolver(InputDatetimeLocal, props);
51555
51608
  }
51556
51609
  return jsx(Next, {
51557
51610
  ...props
@@ -51664,9 +51717,7 @@ const InputDatetimeLocal = props => {
51664
51717
  const InputWithListResolver = props => {
51665
51718
  const Next = useNextResolver();
51666
51719
  if (props["navi-list"]) {
51667
- return jsx(InputWithList, {
51668
- ...props
51669
- });
51720
+ return renderResolver(InputWithList, props);
51670
51721
  }
51671
51722
  return jsx(Next, {
51672
51723
  ...props
@@ -51773,9 +51824,7 @@ const ChevronDownSvg$1 = () => {
51773
51824
  const InputWithSuggestionsResolver = props => {
51774
51825
  const Next = useNextResolver();
51775
51826
  if (props["navi-suggestions"]) {
51776
- return jsx(InputTextualWithSuggestions, {
51777
- ...props
51778
- });
51827
+ return renderResolver(InputTextualWithSuggestions, props);
51779
51828
  }
51780
51829
  return jsx(Next, {
51781
51830
  ...props
@@ -59279,34 +59328,44 @@ const css$E = /* css */`
59279
59328
  * `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
59280
59329
  * back-button dismissal, rest-of-document made inert), without one it is
59281
59330
  * shown through the Popover API and the page behind stays live.
59282
- * @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
59283
- * bottom sheet (docked flush to the bottom edge, full width) on a small touch
59284
- * screen, and leaves it alone otherwise. For a dialog meant to be interacted
59285
- * with rather than merely read: on a phone the keyboard owns the bottom of
59286
- * the screen and a centered box ends up both cramped and out of thumb reach,
59287
- * while under a mouse the centered box is already the right shape. Both
59288
- * halves of the name matter (`smallTouchScreenSignal`): touch alone would
59289
- * dock a big touch screen — a tablet, a kiosk panel — a whole screen away
59290
- * from where the finger just tapped, and size alone would dock a narrow
59291
- * desktop window, which is still a mouse. It supplies defaults for
59292
- * `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`, so
59293
- * any of them can still be pinned explicitly — including `expandX={false}`,
59331
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] - Turns
59332
+ * the dialog into a sheet (docked flush to one edge, full width) on a small
59333
+ * touch screen, and leaves it alone otherwise: under a mouse the centered box
59334
+ * is already the right shape, while on a phone a centered box ends up cramped
59335
+ * between the keyboard and the top of the screen. The value says which edge
59336
+ * the sheet rests on. `true` (or `"top"`) rests it on the top edge, out of
59337
+ * the virtual keyboard's way: a field tapped in the sheet raises the keyboard
59338
+ * below it, and the question and the field stay where the finger found them.
59339
+ * `"bottom"` rests it on the bottom edge, where the thumbs are — for a sheet
59340
+ * that is read and tapped (a list, a couple of buttons, a confirmation) and
59341
+ * holds nothing that raises a keyboard; one that does is reflowed into the
59342
+ * strip left above the keyboard at every keystroke. The edge is said here
59343
+ * rather than through `positionArea`, which places both shapes:
59344
+ * `positionArea="bottom"` would also pin the centered box to the bottom of a
59345
+ * desktop window. Both halves of the name matter (`smallTouchScreenSignal`):
59346
+ * touch alone would dock a big touch screen — a tablet, a kiosk panel — a
59347
+ * whole screen away from where the finger just tapped, and size alone would
59348
+ * dock a narrow desktop window, which is still a mouse. It supplies defaults
59349
+ * for `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`,
59350
+ * so any of them can still be pinned explicitly — including `expandX={false}`,
59294
59351
  * which opts the docked dialog out of the full-width stretch and leaves it a
59295
- * floating box at the bottom. It also withdraws `maxWidth` while docked: a
59352
+ * floating box against its edge. It also withdraws `maxWidth` while docked: a
59296
59353
  * sheet is container-wide by definition, and a `maxWidth` is an answer about
59297
59354
  * the *centered* shape, so the two can be stated together (`maxWidth="16rem"
59298
59355
  * dockedOnSmallTouchScreen`) and each applies where it means something.
59299
59356
  * `minWidth` needs no such rule — its floor is below the full width — and
59300
59357
  * `maxHeight`/`minHeight` keep applying, a sheet being content-tall.
59301
- * Ignored entirely when `expandY`
59302
- * (or `expand`) is set: a dialog already filling the height is on the bottom
59303
- * edge docking would bring it to, so docking could only take away the shape
59304
- * the caller asked for. Re-resolves live as the pointer
59305
- * type or the window size changes. A sheet resting on the bottom edge is also
59306
- * pushed back down to close it, held by its header (a `Box` with the `header`
59307
- * prop) and by anything else carrying `data-swipe-grip`. The rest of the sheet
59308
- * is left to what it holds, so a board something is dragged across keeps its
59309
- * own gestures. See `swipe_to_close.js`.
59358
+ * Ignored entirely when `expandY` (or `expand`) is set: a dialog already
59359
+ * filling the height touches both edges, so docking could only take away the
59360
+ * shape the caller asked for. Re-resolves live as the pointer type or the
59361
+ * window size changes. A sheet is also pushed back through the edge it rests
59362
+ * on to close it (down for a bottom sheet, up for a top one), held by its
59363
+ * header (a `Box` with the `header` prop) and by anything else carrying
59364
+ * `data-swipe-grip`. The rest of the sheet is left to what it holds, so a
59365
+ * board something is dragged across keeps its own gestures. See
59366
+ * `swipe_to_close.js`. In dev, a `"bottom"` sheet found holding a field that
59367
+ * raises the keyboard is warned about at open, on any screen — the mistake
59368
+ * itself only shows on a phone.
59310
59369
  * @param {string} [props.positionArea="center"] - Where to dock the dialog
59311
59370
  * within its container (the viewport for `layer="top"`, the positioned
59312
59371
  * ancestor for `layer="local"`) — Dialog is never anchored to a real
@@ -59672,28 +59731,28 @@ const DialogLocal = props => {
59672
59731
  * own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
59673
59732
  * has none at all.
59674
59733
  */
59675
- // What a dialog turns into on a small touch screen. "bottom" is not a taste:
59676
- // it puts the dialog in the zone a phone is actually operated from — where the
59677
- // thumbs rest and where the virtual keyboard comes up — instead of the middle
59678
- // of the screen, which is the farthest point from both.
59734
+ // What a dialog turns into on a small touch screen: a sheet flush against one
59735
+ // edge of the screen. Which edge is the caller's word (the prop's value, see
59736
+ // its JSDoc), the top one unless told "bottom": the cheap mistake must be the
59737
+ // default one — a sheet docked top by mistake is a little farther from the
59738
+ // thumb, a sheet docked bottom by mistake fights the virtual keyboard, which
59739
+ // rises from that very edge, on every keystroke.
59679
59740
  // Only defaults: an explicitly passed prop still wins, so the docked shape can
59680
59741
  // be adjusted one axis at a time instead of being all-or-nothing.
59681
59742
  const DOCKED = {
59682
- positionArea: "bottom",
59683
59743
  marginWithContainer: 0,
59684
59744
  expandX: true,
59685
- // A sheet resting on the bottom edge is dragged with a thumb, and a drag that
59686
- // runs past its own edge must not land on the page behind it: the same
59687
- // reasoning as "bottom" above, applied to the gesture instead of the shape.
59745
+ // A sheet is dragged with a thumb, and a drag that runs past its own edge
59746
+ // must not land on the page behind it.
59688
59747
  scrollCapture: true
59689
59748
  };
59690
59749
 
59691
- // Where a bottom sheet is held to push it back down: the strip a Box declares
59692
- // with `header`, plus anything the application marked as one more. Everything
59693
- // else in the sheet is content the finger came to operate — a board a piece is
59694
- // dragged across, a list, a map — and a press there belongs to it. A sheet with
59695
- // no header and nothing marked is not pushed down at all; it is closed by its
59696
- // own controls, by the backdrop and by Escape.
59750
+ // Where a sheet is held to push it back through its edge: the strip a Box
59751
+ // declares with `header`, plus anything the application marked as one more.
59752
+ // Everything else in the sheet is content the finger came to operate — a board
59753
+ // a piece is dragged across, a list, a map — and a press there belongs to it. A
59754
+ // sheet with no header and nothing marked is not pushed back at all; it is
59755
+ // closed by its own controls, by the backdrop and by Escape.
59697
59756
  const DOCKED_SWIPE_GRIP = "[data-header],[data-swipe-grip]";
59698
59757
  const useDialogProps = props => {
59699
59758
  const backdropProps = {};
@@ -59799,12 +59858,12 @@ const useDialogProps = props => {
59799
59858
  // Only a small touch screen changes anything: on a mouse — and on a touch
59800
59859
  // screen too big to reach the bottom edge of — a dialog already wants to be
59801
59860
  // the centered box it is by default, so there is nothing to resolve.
59802
- // expandY cancels the docking outright: docking exists to bring the dialog
59803
- // down to the edge the thumb is on, and a dialog already filling the height
59804
- // is on that edge — all docking could still do is take away the shape the
59805
- // caller asked for (and arm a swipe-down on something that never rose).
59861
+ // expandY cancels the docking outright: a dialog already filling the height
59862
+ // touches both edges — all docking could still do is take away the shape
59863
+ // the caller asked for (and arm a swipe on something that never rose).
59806
59864
  const isDocked = dockedOnSmallTouchScreen && smallTouchScreenSignal.value && !expandY;
59807
- const positionArea = positionAreaProp ?? (isDocked ? DOCKED.positionArea : "center");
59865
+ const dockedEdge = dockedOnSmallTouchScreen === "bottom" ? "bottom" : "top";
59866
+ const positionArea = positionAreaProp ?? (isDocked ? dockedEdge : "center");
59808
59867
  const marginWithContainer = marginWithContainerProp ?? (isDocked ? DOCKED.marginWithContainer :
59809
59868
  // A share of whatever holds the dialog: the app's own screen for a
59810
59869
  // top-layer one — where appw is exactly "3% of the container", the
@@ -59866,12 +59925,12 @@ const useDialogProps = props => {
59866
59925
  y: "center",
59867
59926
  x: "center"
59868
59927
  };
59869
- // Pushing the sheet back down closes it — a bottom sheet is reached with a
59870
- // thumb, and the thumb is already on the edge it would push. Only a sheet
59871
- // actually resting on the bottom edge: anywhere else the gesture would send
59872
- // the dialog somewhere it never came from.
59873
- const swipeToCloseDown = isDocked && parsedPositionArea.y === "bottom";
59874
- const onSwipePointerDown = swipeToCloseDown ? createSwipeToClose("bottom", {
59928
+ // Pushing the sheet back through the edge it rests on closes it — the way
59929
+ // it came in, and the way the thumb on that edge already pushes. Only a sheet
59930
+ // actually resting on an edge: anywhere else the gesture would send the
59931
+ // dialog somewhere it never came from.
59932
+ const swipeToCloseSide = isDocked && (parsedPositionArea.y === "top" || parsedPositionArea.y === "bottom") ? parsedPositionArea.y : null;
59933
+ const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose(swipeToCloseSide, {
59875
59934
  grip: DOCKED_SWIPE_GRIP
59876
59935
  }) : null;
59877
59936
  // A corner sitting exactly on the container's own corner must not be
@@ -60503,12 +60562,12 @@ const useDialogProps = props => {
60503
60562
  "data-flush-right": flushEdges.right ? "" : undefined,
60504
60563
  "data-flush-bottom": flushEdges.bottom ? "" : undefined,
60505
60564
  "data-flush-left": flushEdges.left ? "" : undefined,
60506
- "data-swipe-to-close": swipeToCloseDown ? "" : undefined,
60565
+ "data-swipe-to-close": swipeToCloseSide ?? undefined,
60507
60566
  // The axis the sheet travels on when it is pushed back, said to the shared
60508
60567
  // gesture layer: it is what a box travelling inside the sheet reads to know
60509
60568
  // this axis is already walked (see @jsenv/dom's drag_to_travel).
60510
- "data-drag-travel": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
60511
- "data-travel-by-drag": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
60569
+ "data-drag-travel": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
60570
+ "data-travel-by-drag": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
60512
60571
  "onPointerDown": e => {
60513
60572
  rest.onPointerDown?.(e);
60514
60573
  onSwipePointerDown?.(e);
@@ -62302,8 +62361,8 @@ const css$C = /* css */`@layer navi {
62302
62361
  * @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
62303
62362
  * @param {string} [props.positionAreaWhenAnchorIsInvalid] - **Popover-only**,
62304
62363
  * same guard.
62305
- * @param {boolean} [props.dockedOnSmallTouchScreen] - **Dialog-only** (a
62306
- * popover is never a bottom sheet), destructured out for the same reason
62364
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] -
62365
+ * **Dialog-only** (a popover is never a sheet), destructured out for the same reason
62307
62366
  * turned around: it must not land on the popover element as a stray DOM
62308
62367
  * attribute when the screen-size resolution picks `mode="popover"`. Where
62309
62368
  * that resolution lands is what decides whether it applies at all, so a
@@ -62331,7 +62390,7 @@ const css$C = /* css */`@layer navi {
62331
62390
  * honoured in either mode — a wall-less `Dialog` is shown through the
62332
62391
  * Popover API rather than `showModal()` — so which mode the screen-size
62333
62392
  * resolution picks says nothing about whether the page behind stays live.
62334
- * It is also what makes a sheet docked to the bottom of a phone's screen
62393
+ * It is also what makes a sheet docked to an edge of a phone's screen
62335
62394
  * (`dockedOnSmallTouchScreen`) non-modal.
62336
62395
  * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
62337
62396
  * as-is to whichever component renders (both understand it identically):
@@ -62527,9 +62586,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
62527
62586
  const PickerCustomResolver = props => {
62528
62587
  import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
62529
62588
  if (props.children === undefined) {
62530
- return jsx(PickerNative, {
62531
- ...props
62532
- });
62589
+ return renderResolver(PickerNative, props);
62533
62590
  }
62534
62591
  if (props.mode === "callout") {
62535
62592
  // A tooltip is an icon one presses, unless told otherwise. Own-property
@@ -62591,14 +62648,12 @@ const PickerCustomResolver = props => {
62591
62648
  // how a field says its value is a JS one, kept beside the DOM (see
62592
62649
  // controller_registry.js) — the same thing type="array"/"object" already
62593
62650
  // say for their shapes.
62594
- return jsx(PickerCustom, {
62651
+ return renderResolver(PickerCustom, {
62595
62652
  ...props,
62596
62653
  type: "navi_js"
62597
62654
  });
62598
62655
  }
62599
- return jsx(PickerCustom, {
62600
- ...props
62601
- });
62656
+ return renderResolver(PickerCustom, props);
62602
62657
  };
62603
62658
  const PickerNative = props => {
62604
62659
  const Next = useNextResolver();
@@ -63210,9 +63265,9 @@ const PickerCustom = props => {
63210
63265
  });
63211
63266
  }
63212
63267
  }
63213
- return jsx(PickerContentInsidePopup, {
63268
+ return renderResolver(PickerContentInsidePopup, {
63214
63269
  ...pickerProps,
63215
- mode: mode
63270
+ mode
63216
63271
  });
63217
63272
  };
63218
63273
 
@@ -64479,14 +64534,10 @@ const createItemTracker = (onChange) => {
64479
64534
  const ListItemHeaderOrFooterResolver = props => {
64480
64535
  const Next = useNextResolver();
64481
64536
  if (props.header) {
64482
- return jsx(ListItemHeader, {
64483
- ...props
64484
- });
64537
+ return renderResolver(ListItemHeader, props);
64485
64538
  }
64486
64539
  if (props.footer) {
64487
- return jsx(ListItemFooter, {
64488
- ...props
64489
- });
64540
+ return renderResolver(ListItemFooter, props);
64490
64541
  }
64491
64542
  return jsx(Next, {
64492
64543
  ...props
@@ -64718,9 +64769,7 @@ const ListSelectableContext = createContext(false);
64718
64769
  const ListSelectableResolver = props => {
64719
64770
  const Next = useNextResolver();
64720
64771
  if (props.selectable) {
64721
- return jsx(ListSelectable, {
64722
- ...props
64723
- });
64772
+ return renderResolver(ListSelectable, props);
64724
64773
  }
64725
64774
  return jsx(ListSelectableContext.Provider, {
64726
64775
  value: false,
@@ -65020,7 +65069,7 @@ const ListItemSelectableResolver = props => {
65020
65069
  const isHeaderOrFooter = Boolean(props.header || props.footer);
65021
65070
  const selectable = props.selectable ?? (isHeaderOrFooter || props.role === "presentation" ? false : listSelectable);
65022
65071
  if (selectable) {
65023
- return jsx(ListItemSelectable, {
65072
+ return renderResolver(ListItemSelectable, {
65024
65073
  ...props,
65025
65074
  selectable: true
65026
65075
  });
@@ -70263,14 +70312,10 @@ const generateTimeSlots = (min, max, stepSeconds) => {
70263
70312
  const PickerPresetResolver = props => {
70264
70313
  const Next = useNextResolver();
70265
70314
  if (props.type === "navi_time") {
70266
- return jsx(PickerNaviTime, {
70267
- ...props
70268
- });
70315
+ return renderResolver(PickerNaviTime, props);
70269
70316
  }
70270
70317
  if (props.type === "navi_minute") {
70271
- return jsx(PickerNaviMinute, {
70272
- ...props
70273
- });
70318
+ return renderResolver(PickerNaviMinute, props);
70274
70319
  }
70275
70320
  return jsx(Next, {
70276
70321
  ...props
@@ -71068,59 +71113,37 @@ const PencilSvg = () => {
71068
71113
  const PickerTypeResolver = props => {
71069
71114
  const Next = useNextResolver();
71070
71115
  if (props.type === "color") {
71071
- return jsx(PickerColor, {
71072
- ...props
71073
- });
71116
+ return renderResolver(PickerColor, props);
71074
71117
  }
71075
71118
  if (props.type === "datetime") {
71076
- return jsx(PickerDatetime, {
71077
- ...props
71078
- });
71119
+ return renderResolver(PickerDatetime, props);
71079
71120
  }
71080
71121
  if (props.type === "date") {
71081
- return jsx(PickerDate, {
71082
- ...props
71083
- });
71122
+ return renderResolver(PickerDate, props);
71084
71123
  }
71085
71124
  if (props.type === "month") {
71086
- return jsx(PickerMonth, {
71087
- ...props
71088
- });
71125
+ return renderResolver(PickerMonth, props);
71089
71126
  }
71090
71127
  if (props.type === "week") {
71091
- return jsx(PickerWeek, {
71092
- ...props
71093
- });
71128
+ return renderResolver(PickerWeek, props);
71094
71129
  }
71095
71130
  if (props.type === "time") {
71096
- return jsx(PickerTime, {
71097
- ...props
71098
- });
71131
+ return renderResolver(PickerTime, props);
71099
71132
  }
71100
71133
  if (props.type === "duration") {
71101
- return jsx(PickerDuration, {
71102
- ...props
71103
- });
71134
+ return renderResolver(PickerDuration, props);
71104
71135
  }
71105
71136
  if (props.type === "file") {
71106
- return jsx(PickerFile, {
71107
- ...props
71108
- });
71137
+ return renderResolver(PickerFile, props);
71109
71138
  }
71110
71139
  if (props.type === "text") {
71111
- return jsx(PickerText, {
71112
- ...props
71113
- });
71140
+ return renderResolver(PickerText, props);
71114
71141
  }
71115
71142
  if (props.type === "array") {
71116
- return jsx(PickerArray, {
71117
- ...props
71118
- });
71143
+ return renderResolver(PickerArray, props);
71119
71144
  }
71120
71145
  if (props.type === "object") {
71121
- return jsx(PickerObject, {
71122
- ...props
71123
- });
71146
+ return renderResolver(PickerObject, props);
71124
71147
  }
71125
71148
  return jsx(Next, {
71126
71149
  ...props
@@ -74972,7 +74995,7 @@ const css$n = /* css */`.navi_split_button {
74972
74995
  * dialogExpand?: boolean,
74973
74996
  * dialogExpandX?: boolean,
74974
74997
  * dialogExpandY?: boolean,
74975
- * dockedOnSmallTouchScreen?: boolean,
74998
+ * dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
74976
74999
  * marginWithContainer?: number | string,
74977
75000
  * backdrop?: boolean,
74978
75001
  * backdropVariant?: "auto" | "discrete" | "invisible",
@@ -84249,7 +84272,9 @@ const css = /* css */`.navi_side_panel {
84249
84272
  * closes the panel on an outside click instead, and also enables trapping
84250
84273
  * Tab navigation inside the panel (`focusCapture`) — closing on outside
84251
84274
  * interaction only makes sense paired with not letting focus silently
84252
- * leave the panel first.
84275
+ * leave the panel first. A box of the page whose press must not close the
84276
+ * panel (a card that fills it) names the panel:
84277
+ * `data-navi-popup-inside={id}` — see docs/popup_backdrop.md.
84253
84278
  * @param {boolean} [props.swipeToClose=true] - Pushing the panel back
84254
84279
  * towards the edge it is docked to closes it: the panel follows the
84255
84280
  * pointer and finishes leaving (or comes back to rest) when it is
@@ -84384,6 +84409,7 @@ SidePanel.Foot = SidePanelFoot;
84384
84409
 
84385
84410
  const createSlot = (SlotRenderer = Box) => {
84386
84411
  const slotPropsSignal = signal();
84412
+ let filler = null;
84387
84413
  const Slot = () => {
84388
84414
  const props = slotPropsSignal.value;
84389
84415
  return jsx(SlotRenderer, {
@@ -84392,9 +84418,18 @@ const createSlot = (SlotRenderer = Box) => {
84392
84418
  });
84393
84419
  };
84394
84420
  const SlotFill = props => {
84421
+ const fillerRef = useRef();
84422
+ filler = fillerRef;
84395
84423
  slotPropsSignal.value = props;
84396
84424
  useLayoutEffect(() => {
84397
84425
  return () => {
84426
+ // Within one diff preact mounts the newcomer before unmounting the
84427
+ // leaver, so the leaver may no longer be the filler by the time it
84428
+ // is cleaned up.
84429
+ if (filler !== fillerRef) {
84430
+ return;
84431
+ }
84432
+ filler = null;
84398
84433
  slotPropsSignal.value = null;
84399
84434
  };
84400
84435
  }, []);