@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, createEventGroupLogger, 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, getVirtualKeyboardOverlayHeight, 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, createEventGroupLogger, 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, getVirtualKeyboardOverlayHeight, 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, prefixFirstAndIndentRemainingLines, 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";
@@ -13207,6 +13207,12 @@ const useExecuteAction = (
13207
13207
  // whose command sent — is stored as the callout display target, so the
13208
13208
  // message appears on whoever asked rather than on the whole control.
13209
13209
  const element = elementRef.current;
13210
+ if (!element) {
13211
+ // The control left the page while its run was out (its own answer can
13212
+ // unmount it, see the outcome events below): there is no box to hang
13213
+ // the refusal on. onActionError is still told, by the run's side effect.
13214
+ return;
13215
+ }
13210
13216
  let target = requester;
13211
13217
  // A requester that is no longer on the page is not a place to show
13212
13218
  // anything: the clear cross leaves with the value it optimistically
@@ -13310,12 +13316,20 @@ const useExecuteAction = (
13310
13316
  // Either three callbacks, one per outcome, or a single one for "however
13311
13317
  // this ends" — it receives `{ aborted, reason }`, `{ error }` or
13312
13318
  // `{ data }`, so a caller that only needs to know the action settled
13313
- // (and whether it worked) does not have to register three.
13319
+ // (and whether it worked) does not have to register three. Each also
13320
+ // receives the outcome event (navi_action_abort/error/end) as its
13321
+ // second argument.
13314
13322
  const addSideEffect = (sideEffect) => {
13315
13323
  if (typeof sideEffect === "function") {
13316
- addAbortCallback((reason) => sideEffect({ aborted: true, reason }));
13317
- addErrorCallback((error) => sideEffect({ error }));
13318
- addCompleteCallback((data) => sideEffect({ data }));
13324
+ addAbortCallback((reason, outcomeEvent) =>
13325
+ sideEffect({ aborted: true, reason }, outcomeEvent),
13326
+ );
13327
+ addErrorCallback((error, outcomeEvent) =>
13328
+ sideEffect({ error }, outcomeEvent),
13329
+ );
13330
+ addCompleteCallback((data, outcomeEvent) =>
13331
+ sideEffect({ data }, outcomeEvent),
13332
+ );
13319
13333
  return;
13320
13334
  }
13321
13335
  const { abort, error, complete } = sideEffect;
@@ -13323,56 +13337,6 @@ const useExecuteAction = (
13323
13337
  addErrorCallback(error);
13324
13338
  addCompleteCallback(complete);
13325
13339
  };
13326
- addSideEffect({
13327
- abort: (reason) => {
13328
- const element = elementRef.current;
13329
- if (
13330
- // at this stage the action side effect might have removed the <element> from the DOM
13331
- // (in theory no because action side effect are batched to happen after)
13332
- // but other side effects might do this
13333
- element
13334
- ) {
13335
- dispatchInternalCustomEvent(element, "navi_action_abort", {
13336
- ...sharedActionEventDetail,
13337
- reason,
13338
- });
13339
- }
13340
- },
13341
- error: (error) => {
13342
- if (errorEffect === "show_validation_message") {
13343
- addErrorMessage(error, { requester });
13344
- } else if (errorEffect === "throw") {
13345
- setError(error);
13346
- }
13347
-
13348
- const element = elementRef.current;
13349
- if (
13350
- // at this stage the action side effect might have removed the <element> from the DOM
13351
- // (in theory no because action side effect are batched to happen after)
13352
- // but other side effects might do this
13353
- element
13354
- ) {
13355
- dispatchInternalCustomEvent(element, "navi_action_error", {
13356
- ...sharedActionEventDetail,
13357
- error,
13358
- });
13359
- }
13360
- },
13361
- complete: (data) => {
13362
- const element = elementRef.current;
13363
- if (
13364
- // at this stage the action side effect might have removed the <element> from the DOM
13365
- // (in theory no because action side effect are batched to happen after)
13366
- // but other side effects might do this
13367
- element
13368
- ) {
13369
- dispatchInternalCustomEvent(element, "navi_action_end", {
13370
- ...sharedActionEventDetail,
13371
- data,
13372
- });
13373
- }
13374
- },
13375
- });
13376
13340
 
13377
13341
  const actionStartEventDetail = {
13378
13342
  ...sharedActionEventDetail,
@@ -13384,13 +13348,50 @@ const useExecuteAction = (
13384
13348
  actionStartEventDetail,
13385
13349
  );
13386
13350
 
13351
+ // The outcome is told twice: to the element, when there still is one,
13352
+ // and to the side effects registered at navi_action_start, always. A
13353
+ // run can outlive its control — the run's own answer, written to a
13354
+ // store the parent reads to decide what it draws, unmounts the control
13355
+ // one microtask before the run settles — and the element is then no
13356
+ // place to dispatch to: Preact drops a detached element's listeners.
13357
+ // The side effects are the run's, not the element's, so the event
13358
+ // reaches them either way.
13359
+ const tellOutcome = (outcomeEventName, outcomeEventDetail, trigger) => {
13360
+ const outcomeEvent = createInternalCustomEvent(outcomeEventName, {
13361
+ ...sharedActionEventDetail,
13362
+ ...outcomeEventDetail,
13363
+ });
13364
+ const element = elementRef.current;
13365
+ if (element) {
13366
+ element.dispatchEvent(outcomeEvent);
13367
+ }
13368
+ trigger(outcomeEvent);
13369
+ };
13370
+
13387
13371
  const runAction = () => {
13388
13372
  return action[method]({
13389
13373
  event: actionEvent,
13390
13374
  reason: `"${event.type}" event on ${getElementSignature(event.target)}`,
13391
- onAbort: triggerAbort,
13392
- onError: triggerError,
13393
- onComplete: triggerComplete,
13375
+ onAbort: (reason) => {
13376
+ tellOutcome("navi_action_abort", { reason }, (outcomeEvent) => {
13377
+ triggerAbort(reason, outcomeEvent);
13378
+ });
13379
+ },
13380
+ onError: (error) => {
13381
+ if (errorEffect === "show_validation_message") {
13382
+ addErrorMessage(error, { requester });
13383
+ } else if (errorEffect === "throw") {
13384
+ setError(error);
13385
+ }
13386
+ tellOutcome("navi_action_error", { error }, (outcomeEvent) => {
13387
+ triggerError(error, outcomeEvent);
13388
+ });
13389
+ },
13390
+ onComplete: (data) => {
13391
+ tellOutcome("navi_action_end", { data }, (outcomeEvent) => {
13392
+ triggerComplete(data, outcomeEvent);
13393
+ });
13394
+ },
13394
13395
  });
13395
13396
  };
13396
13397
 
@@ -32476,8 +32477,26 @@ const resolveExplicitTarget = (element) => {
32476
32477
  }
32477
32478
  return undefined;
32478
32479
  };
32480
+ // The control around the source that a command can be about: one holding a
32481
+ // value of its own. A popup is a control root too (it answers interactions,
32482
+ // carries a callout), but it has no host of its own — asked for one, it
32483
+ // answers with the first control INSIDE it, which for a field sending
32484
+ // --navi-update from a picker's popup is that very field: the command then
32485
+ // sets the field to what it already holds, which fires the command again,
32486
+ // without end. Such a surface is walked past, up to the picker or the form.
32479
32487
  const resolveFirstParentControl = (el) => {
32480
- return getParentControl(el);
32488
+ let parentControl = getParentControl(el);
32489
+ while (parentControl && !ownsControlHost(parentControl)) {
32490
+ parentControl = getParentControl(parentControl);
32491
+ }
32492
+ return parentControl;
32493
+ };
32494
+ const ownsControlHost = (controlRoot) => {
32495
+ const controlHost = findControlHost(controlRoot);
32496
+ return (
32497
+ Boolean(controlHost) &&
32498
+ controlHost.closest("[navi-control]") === controlRoot
32499
+ );
32481
32500
  };
32482
32501
  const resolveFirstChildControl = (el) => {
32483
32502
  let startEl;
@@ -35278,6 +35297,21 @@ const warnPopupHasNoElementToOpen = (popupKind) => {
35278
35297
  */
35279
35298
  const OUTSIDE_REGION_ATTRIBUTE = "data-navi-popup-outside";
35280
35299
 
35300
+ /**
35301
+ * `data-navi-popup-inside` is the other direction: a box of the PAGE the
35302
+ * caller says belongs to a popup, so a press on it is not outside that popup
35303
+ * even though it lies beyond the border box — a card whose press is what
35304
+ * fills the one panel of a board. Its value names the popup by `id`, the way
35305
+ * `commandfor` does (several ids, space separated), because the exemption is
35306
+ * one popup's: a menu open beside the board still closes on the same press.
35307
+ *
35308
+ * Unlike the outside marker it answers for its whole subtree: the outside
35309
+ * marker carves free space out of a surface whose controls stay surface, this
35310
+ * one adds a whole thing — the card, its count, its buttons — to the popup's
35311
+ * own ground, and nothing in it is a dismissal.
35312
+ */
35313
+ const INSIDE_ATTRIBUTE = "data-navi-popup-inside";
35314
+
35281
35315
  /**
35282
35316
  * What a press landing on a region the caller declared as not-its-surface
35283
35317
  * does: exactly what the same press on the backdrop would do.
@@ -35424,6 +35458,14 @@ const armOutsidePressClose = (
35424
35458
  ) {
35425
35459
  return;
35426
35460
  }
35461
+ if (
35462
+ popupEl.id &&
35463
+ pointerDownEvent.target.closest?.(
35464
+ `[${INSIDE_ATTRIBUTE}~="${CSS.escape(popupEl.id)}"]`,
35465
+ )
35466
+ ) {
35467
+ return;
35468
+ }
35427
35469
  // Real DOM containment wins over the rectangle below — an element
35428
35470
  // genuinely inside the popup (`overflow: visible`, a negative margin, an
35429
35471
  // absolutely-positioned child) can be painted outside its border box, and
@@ -40869,10 +40911,24 @@ const useInteractiveProps = (props, {
40869
40911
  // aborted run, whose promise is awaited to completion (see
40870
40912
  // performRun in actions.js) — which is exactly what "the server is
40871
40913
  // done with it" means, and therefore when the queued request may go.
40872
- e.detail.addSideEffect(outcome => {
40914
+ e.detail.addSideEffect((outcome, outcomeEvent) => {
40873
40915
  uiStateController.actionInFlight = false;
40874
40916
  uiStateController.runningActionSignal.value = null;
40875
40917
  uiStateController.parallelGuard?.release(uiStateController);
40918
+ // The outcome callbacks are told from here, the run's side effect,
40919
+ // and not from the onnavi_action_* handlers below: the run can
40920
+ // outlive this control (its own answer, written to a store the
40921
+ // parent reads, unmounts it one microtask before it settles), and
40922
+ // whoever wears the run — a parent's `loading` following these,
40923
+ // see 12_picker_card_demo.html — must hear it end all the same.
40924
+ // The handlers below keep what needs the element.
40925
+ if (outcome.aborted) {
40926
+ onActionAborted?.(outcomeEvent);
40927
+ } else if ("error" in outcome) {
40928
+ onActionError?.(outcome.error, outcomeEvent);
40929
+ } else {
40930
+ onActionEnd?.(outcome.data, outcomeEvent);
40931
+ }
40876
40932
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
40877
40933
  if (!queuedEvent) {
40878
40934
  syncOptimisticAttribute(false);
@@ -40912,7 +40968,6 @@ const useInteractiveProps = (props, {
40912
40968
  if (resetOnAbort && !superseded) {
40913
40969
  dispatchRequestResetUIState(e.currentTarget, e);
40914
40970
  }
40915
- onActionAborted?.(e);
40916
40971
  },
40917
40972
  onnavi_action_error: e => {
40918
40973
  const {
@@ -40922,7 +40977,6 @@ const useInteractiveProps = (props, {
40922
40977
  if (resetOnError) {
40923
40978
  dispatchRequestResetUIState(e.currentTarget, e);
40924
40979
  }
40925
- onActionError?.(error, e);
40926
40980
  uiStateController.onActionError(e);
40927
40981
  },
40928
40982
  onnavi_action_end: e => {
@@ -40930,7 +40984,6 @@ const useInteractiveProps = (props, {
40930
40984
  data
40931
40985
  } = e.detail;
40932
40986
  debugAction(e, `action end with data: ${JSON.stringify(data)}`);
40933
- onActionEnd?.(data, e);
40934
40987
  controlRootProps.onnavi_action_end?.(e);
40935
40988
  uiStateController.onActionEnd(e);
40936
40989
 
@@ -48844,33 +48897,6 @@ const createToggleEvent = open => {
48844
48897
  return toggleEvent;
48845
48898
  };
48846
48899
 
48847
- const NextResolverContext = createContext(null);
48848
- const useNextResolver = () => useContext(NextResolverContext);
48849
-
48850
- /**
48851
- * Creates a renderComponent function that passes props through a chain of resolvers.
48852
- * Each resolver is a Preact component rendered in sequence (hooks are allowed).
48853
- * To pass through to the next resolver, call useNextResolver() and render the
48854
- * returned Next component with the desired props.
48855
- * To terminate the chain early (e.g. render a specialized component), render
48856
- * directly without calling Next.
48857
- *
48858
- * The last entry in the array is the final/target component — it receives null
48859
- * from useNextResolver() indicating it is terminal.
48860
- *
48861
- * Usage:
48862
- * const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
48863
- * // Then inside a component render:
48864
- * renderButton(props)
48865
- *
48866
- * Each position of the chain has a runner of its own, defined once: it renders
48867
- * its resolver under a NextResolverContext holding the runner after it. A
48868
- * resolver that re-renders on its own and renders <Next> therefore resumes at
48869
- * its own position without any bookkeeping — the Next it was given is the one
48870
- * made for it. A chain rendered by the hundred (a list's rows) pays two
48871
- * components per position, the runner and the resolver, so nothing here is a
48872
- * component that merely forwards.
48873
- */
48874
48900
  const createComponentResolver = (resolvers, {
48875
48901
  pure
48876
48902
  } = {}) => {
@@ -48879,13 +48905,10 @@ const createComponentResolver = (resolvers, {
48879
48905
  for (let index = 0; index < resolvers.length; index++) {
48880
48906
  const Resolver = resolvers[index];
48881
48907
  const isLast = index === lastIndex;
48882
- const Runner = props => jsx(NextResolverContext.Provider, {
48883
- value: isLast ? null : runners[index + 1],
48884
- children: jsx(Resolver, {
48885
- ...props
48886
- })
48887
- });
48888
- Runner.displayName = `${Resolver.displayName || Resolver.name}Runner`;
48908
+ function Runner(props) {
48909
+ return runResolverBody(this, Resolver, props, isLast ? null : runners[index + 1]);
48910
+ }
48911
+ Runner.displayName = Resolver.displayName || Resolver.name;
48889
48912
  runners.push(Runner);
48890
48913
  }
48891
48914
  const FirstRunner = runners[0];
@@ -48916,6 +48939,54 @@ const createComponentResolver = (resolvers, {
48916
48939
  }
48917
48940
  return PureRenderComponent;
48918
48941
  };
48942
+ const useNextResolver = () => {
48943
+ if (currentNext === OUTSIDE_RESOLVER_BODY) {
48944
+ 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.");
48945
+ }
48946
+ return currentNext;
48947
+ };
48948
+ const renderResolver = (Resolver, props) => {
48949
+ if (currentNext === OUTSIDE_RESOLVER_BODY) {
48950
+ throw new Error(`renderResolver(${Resolver.displayName || Resolver.name}) called outside a resolver body: only a resolver can render another one.`);
48951
+ }
48952
+ let SubRunner = subRunnerByResolver.get(Resolver);
48953
+ if (!SubRunner) {
48954
+ SubRunner = function (props) {
48955
+ const {
48956
+ [NEXT_PROP]: next,
48957
+ ...ownProps
48958
+ } = props;
48959
+ return runResolverBody(this, Resolver, ownProps, next);
48960
+ };
48961
+ SubRunner.displayName = Resolver.displayName || Resolver.name;
48962
+ subRunnerByResolver.set(Resolver, SubRunner);
48963
+ }
48964
+ return createElement(SubRunner, {
48965
+ ...props,
48966
+ [NEXT_PROP]: currentNext
48967
+ });
48968
+ };
48969
+
48970
+ // What useNextResolver() answers: the runner after the one whose body is
48971
+ // running, null for the last — and, outside any body, a value that is neither.
48972
+ const OUTSIDE_RESOLVER_BODY = Symbol("outside_resolver_body");
48973
+ let currentNext = OUTSIDE_RESOLVER_BODY;
48974
+ const runResolverBody = (instance, Resolver, props, next) => {
48975
+ const nextBefore = currentNext;
48976
+ currentNext = next;
48977
+ try {
48978
+ return Resolver.call(instance, props);
48979
+ } finally {
48980
+ currentNext = nextBefore;
48981
+ }
48982
+ };
48983
+
48984
+ // One runner per resolver rendered through renderResolver, made on first use:
48985
+ // the same component type on every render, so a resolver picked again is
48986
+ // updated in place and another one picked is a remount, like any child.
48987
+ const subRunnerByResolver = new WeakMap();
48988
+ // Rides on the props of a sub-runner, stripped before the body sees them.
48989
+ const NEXT_PROP = "navi-resolver-next";
48919
48990
  function pureShouldComponentUpdate(nextProps) {
48920
48991
  return shallowDiffers(this.props, nextProps);
48921
48992
  }
@@ -49741,9 +49812,7 @@ const ButtonConfirm = ({
49741
49812
  const ButtonRouteResolver = props => {
49742
49813
  const Next = useNextResolver();
49743
49814
  if (props.route) {
49744
- return jsx(ButtonWithRoute, {
49745
- ...props
49746
- });
49815
+ return renderResolver(ButtonWithRoute, props);
49747
49816
  }
49748
49817
  return jsx(Next, {
49749
49818
  ...props
@@ -52347,9 +52416,7 @@ const installInputCss = () => {
52347
52416
  const InputModeResolver = props => {
52348
52417
  const Next = useNextResolver();
52349
52418
  if (props.inputMode === "numeric" || props.inputMode === "decimal") {
52350
- return jsx(InputModeNumericOrDecimal, {
52351
- ...props
52352
- });
52419
+ return renderResolver(InputModeNumericOrDecimal, props);
52353
52420
  }
52354
52421
  return jsx(Next, {
52355
52422
  ...props
@@ -52577,39 +52644,25 @@ const InputTypeResolver = props => {
52577
52644
  // very same "icon while empty, clear button once filled" affordance without
52578
52645
  // pretending to be a search box.
52579
52646
  if (props.clearable && props.type !== "search") {
52580
- return jsx(InputClearable, {
52581
- ...props
52582
- });
52647
+ return renderResolver(InputClearable, props);
52583
52648
  }
52584
52649
  if (props.type === "search") {
52585
- return jsx(InputSearch, {
52586
- ...props
52587
- });
52650
+ return renderResolver(InputSearch, props);
52588
52651
  }
52589
52652
  if (props.type === "email") {
52590
- return jsx(InputEmail, {
52591
- ...props
52592
- });
52653
+ return renderResolver(InputEmail, props);
52593
52654
  }
52594
52655
  if (props.type === "tel") {
52595
- return jsx(InputTel, {
52596
- ...props
52597
- });
52656
+ return renderResolver(InputTel, props);
52598
52657
  }
52599
52658
  if (props.type === "number") {
52600
- return jsx(InputNumber, {
52601
- ...props
52602
- });
52659
+ return renderResolver(InputNumber, props);
52603
52660
  }
52604
52661
  if (props.type === "color") {
52605
- return jsx(InputColor, {
52606
- ...props
52607
- });
52662
+ return renderResolver(InputColor, props);
52608
52663
  }
52609
52664
  if (props.type === "datetime-local") {
52610
- return jsx(InputDatetimeLocal, {
52611
- ...props
52612
- });
52665
+ return renderResolver(InputDatetimeLocal, props);
52613
52666
  }
52614
52667
  return jsx(Next, {
52615
52668
  ...props
@@ -52722,9 +52775,7 @@ const InputDatetimeLocal = props => {
52722
52775
  const InputWithListResolver = props => {
52723
52776
  const Next = useNextResolver();
52724
52777
  if (props["navi-list"]) {
52725
- return jsx(InputWithList, {
52726
- ...props
52727
- });
52778
+ return renderResolver(InputWithList, props);
52728
52779
  }
52729
52780
  return jsx(Next, {
52730
52781
  ...props
@@ -52831,9 +52882,7 @@ const ChevronDownSvg$1 = () => {
52831
52882
  const InputWithSuggestionsResolver = props => {
52832
52883
  const Next = useNextResolver();
52833
52884
  if (props["navi-suggestions"]) {
52834
- return jsx(InputTextualWithSuggestions, {
52835
- ...props
52836
- });
52885
+ return renderResolver(InputTextualWithSuggestions, props);
52837
52886
  }
52838
52887
  return jsx(Next, {
52839
52888
  ...props
@@ -60414,34 +60463,44 @@ const css$E = /* css */`
60414
60463
  * `showModal()`'d (native focus trap, `Escape`-to-cancel, hardware
60415
60464
  * back-button dismissal, rest-of-document made inert), without one it is
60416
60465
  * shown through the Popover API and the page behind stays live.
60417
- * @param {boolean} [props.dockedOnSmallTouchScreen] - Turns the dialog into a
60418
- * bottom sheet (docked flush to the bottom edge, full width) on a small touch
60419
- * screen, and leaves it alone otherwise. For a dialog meant to be interacted
60420
- * with rather than merely read: on a phone the keyboard owns the bottom of
60421
- * the screen and a centered box ends up both cramped and out of thumb reach,
60422
- * while under a mouse the centered box is already the right shape. Both
60423
- * halves of the name matter (`smallTouchScreenSignal`): touch alone would
60424
- * dock a big touch screen — a tablet, a kiosk panel — a whole screen away
60425
- * from where the finger just tapped, and size alone would dock a narrow
60426
- * desktop window, which is still a mouse. It supplies defaults for
60427
- * `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`, so
60428
- * any of them can still be pinned explicitly — including `expandX={false}`,
60466
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] - Turns
60467
+ * the dialog into a sheet (docked flush to one edge, full width) on a small
60468
+ * touch screen, and leaves it alone otherwise: under a mouse the centered box
60469
+ * is already the right shape, while on a phone a centered box ends up cramped
60470
+ * between the keyboard and the top of the screen. The value says which edge
60471
+ * the sheet rests on. `true` (or `"top"`) rests it on the top edge, out of
60472
+ * the virtual keyboard's way: a field tapped in the sheet raises the keyboard
60473
+ * below it, and the question and the field stay where the finger found them.
60474
+ * `"bottom"` rests it on the bottom edge, where the thumbs are — for a sheet
60475
+ * that is read and tapped (a list, a couple of buttons, a confirmation) and
60476
+ * holds nothing that raises a keyboard; one that does is reflowed into the
60477
+ * strip left above the keyboard at every keystroke. The edge is said here
60478
+ * rather than through `positionArea`, which places both shapes:
60479
+ * `positionArea="bottom"` would also pin the centered box to the bottom of a
60480
+ * desktop window. Both halves of the name matter (`smallTouchScreenSignal`):
60481
+ * touch alone would dock a big touch screen — a tablet, a kiosk panel — a
60482
+ * whole screen away from where the finger just tapped, and size alone would
60483
+ * dock a narrow desktop window, which is still a mouse. It supplies defaults
60484
+ * for `positionArea`, `marginWithContainer`, `expandX` and `scrollCapture`,
60485
+ * so any of them can still be pinned explicitly — including `expandX={false}`,
60429
60486
  * which opts the docked dialog out of the full-width stretch and leaves it a
60430
- * floating box at the bottom. It also withdraws `maxWidth` while docked: a
60487
+ * floating box against its edge. It also withdraws `maxWidth` while docked: a
60431
60488
  * sheet is container-wide by definition, and a `maxWidth` is an answer about
60432
60489
  * the *centered* shape, so the two can be stated together (`maxWidth="16rem"
60433
60490
  * dockedOnSmallTouchScreen`) and each applies where it means something.
60434
60491
  * `minWidth` needs no such rule — its floor is below the full width — and
60435
60492
  * `maxHeight`/`minHeight` keep applying, a sheet being content-tall.
60436
- * Ignored entirely when `expandY`
60437
- * (or `expand`) is set: a dialog already filling the height is on the bottom
60438
- * edge docking would bring it to, so docking could only take away the shape
60439
- * the caller asked for. Re-resolves live as the pointer
60440
- * type or the window size changes. A sheet resting on the bottom edge is also
60441
- * pushed back down to close it, held by its header (a `Box` with the `header`
60442
- * prop) and by anything else carrying `data-swipe-grip`. The rest of the sheet
60443
- * is left to what it holds, so a board something is dragged across keeps its
60444
- * own gestures. See `swipe_to_close.js`.
60493
+ * Ignored entirely when `expandY` (or `expand`) is set: a dialog already
60494
+ * filling the height touches both edges, so docking could only take away the
60495
+ * shape the caller asked for. Re-resolves live as the pointer type or the
60496
+ * window size changes. A sheet is also pushed back through the edge it rests
60497
+ * on to close it (down for a bottom sheet, up for a top one), held by its
60498
+ * header (a `Box` with the `header` prop) and by anything else carrying
60499
+ * `data-swipe-grip`. The rest of the sheet is left to what it holds, so a
60500
+ * board something is dragged across keeps its own gestures. See
60501
+ * `swipe_to_close.js`. In dev, a `"bottom"` sheet found holding a field that
60502
+ * raises the keyboard is warned about at open, on any screen — the mistake
60503
+ * itself only shows on a phone.
60445
60504
  * @param {string} [props.positionArea="center"] - Where to dock the dialog
60446
60505
  * within its container (the viewport for `layer="top"`, the positioned
60447
60506
  * ancestor for `layer="local"`) — Dialog is never anchored to a real
@@ -60809,28 +60868,28 @@ const DialogLocal = props => {
60809
60868
  * own to render: a modal's is the native `::backdrop`, and `backdrop={false}`
60810
60869
  * has none at all.
60811
60870
  */
60812
- // What a dialog turns into on a small touch screen. "bottom" is not a taste:
60813
- // it puts the dialog in the zone a phone is actually operated from — where the
60814
- // thumbs rest and where the virtual keyboard comes up — instead of the middle
60815
- // of the screen, which is the farthest point from both.
60871
+ // What a dialog turns into on a small touch screen: a sheet flush against one
60872
+ // edge of the screen. Which edge is the caller's word (the prop's value, see
60873
+ // its JSDoc), the top one unless told "bottom": the cheap mistake must be the
60874
+ // default one — a sheet docked top by mistake is a little farther from the
60875
+ // thumb, a sheet docked bottom by mistake fights the virtual keyboard, which
60876
+ // rises from that very edge, on every keystroke.
60816
60877
  // Only defaults: an explicitly passed prop still wins, so the docked shape can
60817
60878
  // be adjusted one axis at a time instead of being all-or-nothing.
60818
60879
  const DOCKED = {
60819
- positionArea: "bottom",
60820
60880
  marginWithContainer: 0,
60821
60881
  expandX: true,
60822
- // A sheet resting on the bottom edge is dragged with a thumb, and a drag that
60823
- // runs past its own edge must not land on the page behind it: the same
60824
- // reasoning as "bottom" above, applied to the gesture instead of the shape.
60882
+ // A sheet is dragged with a thumb, and a drag that runs past its own edge
60883
+ // must not land on the page behind it.
60825
60884
  scrollCapture: true
60826
60885
  };
60827
60886
 
60828
- // Where a bottom sheet is held to push it back down: the strip a Box declares
60829
- // with `header`, plus anything the application marked as one more. Everything
60830
- // else in the sheet is content the finger came to operate — a board a piece is
60831
- // dragged across, a list, a map — and a press there belongs to it. A sheet with
60832
- // no header and nothing marked is not pushed down at all; it is closed by its
60833
- // own controls, by the backdrop and by Escape.
60887
+ // Where a sheet is held to push it back through its edge: the strip a Box
60888
+ // declares with `header`, plus anything the application marked as one more.
60889
+ // Everything else in the sheet is content the finger came to operate — a board
60890
+ // a piece is dragged across, a list, a map — and a press there belongs to it. A
60891
+ // sheet with no header and nothing marked is not pushed back at all; it is
60892
+ // closed by its own controls, by the backdrop and by Escape.
60834
60893
  const DOCKED_SWIPE_GRIP = "[data-header],[data-swipe-grip]";
60835
60894
  const useDialogProps = props => {
60836
60895
  const backdropProps = {};
@@ -60936,12 +60995,15 @@ const useDialogProps = props => {
60936
60995
  // Only a small touch screen changes anything: on a mouse — and on a touch
60937
60996
  // screen too big to reach the bottom edge of — a dialog already wants to be
60938
60997
  // the centered box it is by default, so there is nothing to resolve.
60939
- // expandY cancels the docking outright: docking exists to bring the dialog
60940
- // down to the edge the thumb is on, and a dialog already filling the height
60941
- // is on that edge — all docking could still do is take away the shape the
60942
- // caller asked for (and arm a swipe-down on something that never rose).
60998
+ // expandY cancels the docking outright: a dialog already filling the height
60999
+ // touches both edges — all docking could still do is take away the shape
61000
+ // the caller asked for (and arm a swipe on something that never rose).
60943
61001
  const isDocked = dockedOnSmallTouchScreen && smallTouchScreenSignal.value && !expandY;
60944
- const positionArea = positionAreaProp ?? (isDocked ? DOCKED.positionArea : "center");
61002
+ const dockedEdge = dockedOnSmallTouchScreen === "bottom" ? "bottom" : "top";
61003
+ const positionArea = positionAreaProp ?? (isDocked ? dockedEdge : "center");
61004
+ // Read off the shape a phone would give, not the one this screen gives: the
61005
+ // author of a bottom sheet tests on a desktop, where the mistake is silent.
61006
+ const sheetRestsOnBottom = Boolean(dockedOnSmallTouchScreen) && !expandY && restsOnBottom(positionAreaProp ?? dockedEdge);
60945
61007
  const marginWithContainer = marginWithContainerProp ?? (isDocked ? DOCKED.marginWithContainer :
60946
61008
  // A share of whatever holds the dialog: the app's own screen for a
60947
61009
  // top-layer one — where appw is exactly "3% of the container", the
@@ -61003,12 +61065,12 @@ const useDialogProps = props => {
61003
61065
  y: "center",
61004
61066
  x: "center"
61005
61067
  };
61006
- // Pushing the sheet back down closes it — a bottom sheet is reached with a
61007
- // thumb, and the thumb is already on the edge it would push. Only a sheet
61008
- // actually resting on the bottom edge: anywhere else the gesture would send
61009
- // the dialog somewhere it never came from.
61010
- const swipeToCloseDown = isDocked && parsedPositionArea.y === "bottom";
61011
- const onSwipePointerDown = swipeToCloseDown ? createSwipeToClose("bottom", {
61068
+ // Pushing the sheet back through the edge it rests on closes it — the way
61069
+ // it came in, and the way the thumb on that edge already pushes. Only a sheet
61070
+ // actually resting on an edge: anywhere else the gesture would send the
61071
+ // dialog somewhere it never came from.
61072
+ const swipeToCloseSide = isDocked && (parsedPositionArea.y === "top" || parsedPositionArea.y === "bottom") ? parsedPositionArea.y : null;
61073
+ const onSwipePointerDown = swipeToCloseSide ? createSwipeToClose(swipeToCloseSide, {
61012
61074
  grip: DOCKED_SWIPE_GRIP
61013
61075
  }) : null;
61014
61076
  // A corner sitting exactly on the container's own corner must not be
@@ -61136,6 +61198,9 @@ const useDialogProps = props => {
61136
61198
  }
61137
61199
  {
61138
61200
  warnAboutUnreachableOutsideRegions(dialogEl);
61201
+ if (sheetRestsOnBottom) {
61202
+ warnBottomSheetHoldingKeyboardControl(dialogEl);
61203
+ }
61139
61204
  }
61140
61205
 
61141
61206
  // Set by useOpenControllerByProps for the very first open triggered by
@@ -61684,12 +61749,12 @@ const useDialogProps = props => {
61684
61749
  "data-flush-right": flushEdges.right ? "" : undefined,
61685
61750
  "data-flush-bottom": flushEdges.bottom ? "" : undefined,
61686
61751
  "data-flush-left": flushEdges.left ? "" : undefined,
61687
- "data-swipe-to-close": swipeToCloseDown ? "" : undefined,
61752
+ "data-swipe-to-close": swipeToCloseSide ?? undefined,
61688
61753
  // The axis the sheet travels on when it is pushed back, said to the shared
61689
61754
  // gesture layer: it is what a box travelling inside the sheet reads to know
61690
61755
  // this axis is already walked (see @jsenv/dom's drag_to_travel).
61691
- "data-drag-travel": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
61692
- "data-travel-by-drag": swipeToCloseDown ? SWIPE_AXIS_BY_SIDE.bottom : undefined,
61756
+ "data-drag-travel": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
61757
+ "data-travel-by-drag": swipeToCloseSide ? SWIPE_AXIS_BY_SIDE[swipeToCloseSide] : undefined,
61693
61758
  "onPointerDown": e => {
61694
61759
  rest.onPointerDown?.(e);
61695
61760
  onSwipePointerDown?.(e);
@@ -61761,6 +61826,33 @@ const useDialogProps = props => {
61761
61826
  }
61762
61827
  return [!isTopLayer && backdrop ? backdropProps : null, contentProps];
61763
61828
  };
61829
+ const restsOnBottom = positionArea => {
61830
+ const parsed = parsePositionArea(positionArea);
61831
+ return Boolean(parsed) && parsed.y === "bottom";
61832
+ };
61833
+
61834
+ // What raises a virtual keyboard when tapped: a text-like input, a textarea,
61835
+ // anything editable. Not a `select` — it opens a menu, which takes no screen
61836
+ // from the sheet — and not a read-only field.
61837
+ const KEYBOARD_RAISING_CONTROL_SELECTOR = `
61838
+ input:not([type]):not([readonly]),
61839
+ input[type="text"]:not([readonly]),
61840
+ input[type="search"]:not([readonly]),
61841
+ input[type="email"]:not([readonly]),
61842
+ input[type="url"]:not([readonly]),
61843
+ input[type="tel"]:not([readonly]),
61844
+ input[type="password"]:not([readonly]),
61845
+ input[type="number"]:not([readonly]),
61846
+ textarea:not([readonly]),
61847
+ [contenteditable]:not([contenteditable="false"])
61848
+ `;
61849
+ const warnBottomSheetHoldingKeyboardControl = dialogEl => {
61850
+ const controlEl = dialogEl.querySelector(KEYBOARD_RAISING_CONTROL_SELECTOR);
61851
+ if (!controlEl) {
61852
+ return;
61853
+ }
61854
+ console.warn(`[navi] a Dialog docked to the bottom edge (dockedOnSmallTouchScreen="bottom") holds a field that raises the virtual keyboard. On a phone the keyboard rises from the edge the sheet rests on and reflows the sheet into the strip left above it, at every keystroke. A sheet one types into rests on the top edge: dockedOnSmallTouchScreen without a value. "bottom" is for a sheet one reads and taps.`, controlEl);
61855
+ };
61764
61856
  const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
61765
61857
 
61766
61858
  // Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
@@ -63492,8 +63584,8 @@ const css$C = /* css */`@layer navi {
63492
63584
  * @param {string} [props.positionAreaFixed] - **Popover-only**, same guard.
63493
63585
  * @param {string} [props.positionAreaWhenAnchorIsInvalid] - **Popover-only**,
63494
63586
  * same guard.
63495
- * @param {boolean} [props.dockedOnSmallTouchScreen] - **Dialog-only** (a
63496
- * popover is never a bottom sheet), destructured out for the same reason
63587
+ * @param {boolean|"top"|"bottom"} [props.dockedOnSmallTouchScreen] -
63588
+ * **Dialog-only** (a popover is never a sheet), destructured out for the same reason
63497
63589
  * turned around: it must not land on the popover element as a stray DOM
63498
63590
  * attribute when the screen-size resolution picks `mode="popover"`. Where
63499
63591
  * that resolution lands is what decides whether it applies at all, so a
@@ -63521,7 +63613,7 @@ const css$C = /* css */`@layer navi {
63521
63613
  * honoured in either mode — a wall-less `Dialog` is shown through the
63522
63614
  * Popover API rather than `showModal()` — so which mode the screen-size
63523
63615
  * resolution picks says nothing about whether the page behind stays live.
63524
- * It is also what makes a sheet docked to the bottom of a phone's screen
63616
+ * It is also what makes a sheet docked to an edge of a phone's screen
63525
63617
  * (`dockedOnSmallTouchScreen`) non-modal.
63526
63618
  * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
63527
63619
  * as-is to whichever component renders (both understand it identically):
@@ -63717,9 +63809,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
63717
63809
  const PickerCustomResolver = props => {
63718
63810
  import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
63719
63811
  if (props.children === undefined) {
63720
- return jsx(PickerNative, {
63721
- ...props
63722
- });
63812
+ return renderResolver(PickerNative, props);
63723
63813
  }
63724
63814
  if (props.mode === "callout") {
63725
63815
  // A tooltip is an icon one presses, unless told otherwise. Own-property
@@ -63781,14 +63871,12 @@ const PickerCustomResolver = props => {
63781
63871
  // how a field says its value is a JS one, kept beside the DOM (see
63782
63872
  // controller_registry.js) — the same thing type="array"/"object" already
63783
63873
  // say for their shapes.
63784
- return jsx(PickerCustom, {
63874
+ return renderResolver(PickerCustom, {
63785
63875
  ...props,
63786
63876
  type: "navi_js"
63787
63877
  });
63788
63878
  }
63789
- return jsx(PickerCustom, {
63790
- ...props
63791
- });
63879
+ return renderResolver(PickerCustom, props);
63792
63880
  };
63793
63881
  const PickerNative = props => {
63794
63882
  const Next = useNextResolver();
@@ -64414,9 +64502,9 @@ const PickerCustom = props => {
64414
64502
  });
64415
64503
  }
64416
64504
  }
64417
- return jsx(PickerContentInsidePopup, {
64505
+ return renderResolver(PickerContentInsidePopup, {
64418
64506
  ...pickerProps,
64419
- mode: mode
64507
+ mode
64420
64508
  });
64421
64509
  };
64422
64510
 
@@ -65683,14 +65771,10 @@ const createItemTracker = (onChange) => {
65683
65771
  const ListItemHeaderOrFooterResolver = props => {
65684
65772
  const Next = useNextResolver();
65685
65773
  if (props.header) {
65686
- return jsx(ListItemHeader, {
65687
- ...props
65688
- });
65774
+ return renderResolver(ListItemHeader, props);
65689
65775
  }
65690
65776
  if (props.footer) {
65691
- return jsx(ListItemFooter, {
65692
- ...props
65693
- });
65777
+ return renderResolver(ListItemFooter, props);
65694
65778
  }
65695
65779
  return jsx(Next, {
65696
65780
  ...props
@@ -65922,9 +66006,7 @@ const ListSelectableContext = createContext(false);
65922
66006
  const ListSelectableResolver = props => {
65923
66007
  const Next = useNextResolver();
65924
66008
  if (props.selectable) {
65925
- return jsx(ListSelectable, {
65926
- ...props
65927
- });
66009
+ return renderResolver(ListSelectable, props);
65928
66010
  }
65929
66011
  return jsx(ListSelectableContext.Provider, {
65930
66012
  value: false,
@@ -66224,7 +66306,7 @@ const ListItemSelectableResolver = props => {
66224
66306
  const isHeaderOrFooter = Boolean(props.header || props.footer);
66225
66307
  const selectable = props.selectable ?? (isHeaderOrFooter || props.role === "presentation" ? false : listSelectable);
66226
66308
  if (selectable) {
66227
- return jsx(ListItemSelectable, {
66309
+ return renderResolver(ListItemSelectable, {
66228
66310
  ...props,
66229
66311
  selectable: true
66230
66312
  });
@@ -71532,14 +71614,10 @@ const generateTimeSlots = (min, max, stepSeconds) => {
71532
71614
  const PickerPresetResolver = props => {
71533
71615
  const Next = useNextResolver();
71534
71616
  if (props.type === "navi_time") {
71535
- return jsx(PickerNaviTime, {
71536
- ...props
71537
- });
71617
+ return renderResolver(PickerNaviTime, props);
71538
71618
  }
71539
71619
  if (props.type === "navi_minute") {
71540
- return jsx(PickerNaviMinute, {
71541
- ...props
71542
- });
71620
+ return renderResolver(PickerNaviMinute, props);
71543
71621
  }
71544
71622
  return jsx(Next, {
71545
71623
  ...props
@@ -72337,59 +72415,37 @@ const PencilSvg = () => {
72337
72415
  const PickerTypeResolver = props => {
72338
72416
  const Next = useNextResolver();
72339
72417
  if (props.type === "color") {
72340
- return jsx(PickerColor, {
72341
- ...props
72342
- });
72418
+ return renderResolver(PickerColor, props);
72343
72419
  }
72344
72420
  if (props.type === "datetime") {
72345
- return jsx(PickerDatetime, {
72346
- ...props
72347
- });
72421
+ return renderResolver(PickerDatetime, props);
72348
72422
  }
72349
72423
  if (props.type === "date") {
72350
- return jsx(PickerDate, {
72351
- ...props
72352
- });
72424
+ return renderResolver(PickerDate, props);
72353
72425
  }
72354
72426
  if (props.type === "month") {
72355
- return jsx(PickerMonth, {
72356
- ...props
72357
- });
72427
+ return renderResolver(PickerMonth, props);
72358
72428
  }
72359
72429
  if (props.type === "week") {
72360
- return jsx(PickerWeek, {
72361
- ...props
72362
- });
72430
+ return renderResolver(PickerWeek, props);
72363
72431
  }
72364
72432
  if (props.type === "time") {
72365
- return jsx(PickerTime, {
72366
- ...props
72367
- });
72433
+ return renderResolver(PickerTime, props);
72368
72434
  }
72369
72435
  if (props.type === "duration") {
72370
- return jsx(PickerDuration, {
72371
- ...props
72372
- });
72436
+ return renderResolver(PickerDuration, props);
72373
72437
  }
72374
72438
  if (props.type === "file") {
72375
- return jsx(PickerFile, {
72376
- ...props
72377
- });
72439
+ return renderResolver(PickerFile, props);
72378
72440
  }
72379
72441
  if (props.type === "text") {
72380
- return jsx(PickerText, {
72381
- ...props
72382
- });
72442
+ return renderResolver(PickerText, props);
72383
72443
  }
72384
72444
  if (props.type === "array") {
72385
- return jsx(PickerArray, {
72386
- ...props
72387
- });
72445
+ return renderResolver(PickerArray, props);
72388
72446
  }
72389
72447
  if (props.type === "object") {
72390
- return jsx(PickerObject, {
72391
- ...props
72392
- });
72448
+ return renderResolver(PickerObject, props);
72393
72449
  }
72394
72450
  return jsx(Next, {
72395
72451
  ...props
@@ -76812,7 +76868,7 @@ const css$n = /* css */`.navi_split_button {
76812
76868
  * dialogExpand?: boolean,
76813
76869
  * dialogExpandX?: boolean,
76814
76870
  * dialogExpandY?: boolean,
76815
- * dockedOnSmallTouchScreen?: boolean,
76871
+ * dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
76816
76872
  * marginWithContainer?: number | string,
76817
76873
  * backdrop?: boolean,
76818
76874
  * backdropVariant?: "auto" | "discrete" | "invisible",
@@ -86097,7 +86153,9 @@ const css = /* css */`.navi_side_panel {
86097
86153
  * closes the panel on an outside click instead, and also enables trapping
86098
86154
  * Tab navigation inside the panel (`focusCapture`) — closing on outside
86099
86155
  * interaction only makes sense paired with not letting focus silently
86100
- * leave the panel first.
86156
+ * leave the panel first. A box of the page whose press must not close the
86157
+ * panel (a card that fills it) names the panel:
86158
+ * `data-navi-popup-inside={id}` — see docs/popup_backdrop.md.
86101
86159
  * @param {boolean} [props.swipeToClose=true] - Pushing the panel back
86102
86160
  * towards the edge it is docked to closes it: the panel follows the
86103
86161
  * pointer and finishes leaving (or comes back to rest) when it is
@@ -86232,6 +86290,7 @@ SidePanel.Foot = SidePanelFoot;
86232
86290
 
86233
86291
  const createSlot = (SlotRenderer = Box) => {
86234
86292
  const slotPropsSignal = signal();
86293
+ let filler = null;
86235
86294
  const Slot = () => {
86236
86295
  const props = slotPropsSignal.value;
86237
86296
  return jsx(SlotRenderer, {
@@ -86240,9 +86299,18 @@ const createSlot = (SlotRenderer = Box) => {
86240
86299
  });
86241
86300
  };
86242
86301
  const SlotFill = props => {
86302
+ const fillerRef = useRef();
86303
+ filler = fillerRef;
86243
86304
  slotPropsSignal.value = props;
86244
86305
  useLayoutEffect(() => {
86245
86306
  return () => {
86307
+ // Within one diff preact mounts the newcomer before unmounting the
86308
+ // leaver, so the leaver may no longer be the filler by the time it
86309
+ // is cleaned up.
86310
+ if (filler !== fillerRef) {
86311
+ return;
86312
+ }
86313
+ filler = null;
86246
86314
  slotPropsSignal.value = null;
86247
86315
  };
86248
86316
  }, []);