@jsenv/navi 0.29.41 → 0.29.43

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.
@@ -51,7 +51,15 @@ const css$10 = /* css */`
51
51
  /* Kept stuck while something scrolls under it: a list header, the head
52
52
  and foot of a side panel, a table's sticky cells. Above raised
53
53
  controls — a control scrolling past must go under the header that
54
- pins the column it belongs to, never over it. */
54
+ pins the column it belongs to, never over it.
55
+
56
+ "While stuck" is the whole condition, and a sticky element cannot read
57
+ its own stuck state in CSS: List marks its parts with a navi-stuck
58
+ attribute and applies this band only there (see --list-*-z-index in
59
+ list.jsx). A
60
+ sticky part at rest is a block in the flow with nothing passing under
61
+ it; giving it this band anyway is what slices whatever a neighbouring
62
+ row lets out of its box. */
55
63
  --navi-z-index-sticky: 10;
56
64
 
57
65
  /* Pinned to the viewport, over the whole page: FixedBar. A decade of its
@@ -22983,6 +22991,9 @@ const useUIStateController = (
22983
22991
  // resolveCommandValue in commands.js.
22984
22992
  ownUIStateSignal,
22985
22993
  value: controlInfo.value,
22994
+ // The suggestion this control started on — what tells a field showing
22995
+ // its default from one carrying an answer (see isUIStateHeld).
22996
+ defaultValue: controlInfo.defaultValue,
22986
22997
 
22987
22998
  facadeChild: null,
22988
22999
  getManagedControls: () => {
@@ -23401,9 +23412,16 @@ const useUIStateController = (
23401
23412
  controller.id = props.id; // never supposed to change, not supported for now
23402
23413
  controller.name = props.name;
23403
23414
  controller.parentUIStateController = parentUIStateController;
23404
- const { value, hasStateProp, state, stateInitial, stateFromSignal } =
23405
- controlInfo;
23415
+ const {
23416
+ value,
23417
+ defaultValue,
23418
+ hasStateProp,
23419
+ state,
23420
+ stateInitial,
23421
+ stateFromSignal,
23422
+ } = controlInfo;
23406
23423
  controller.value = value;
23424
+ controller.defaultValue = defaultValue;
23407
23425
  if (hasStateProp) {
23408
23426
  controller.hasStateProp = true;
23409
23427
  const currentState = controller.state;
@@ -24334,8 +24352,10 @@ const useUIFacadeStateController = (props, realUIStateController) => {
24334
24352
  const childType = child.controlType;
24335
24353
  if (firstChildControllerRef.current) {
24336
24354
  console.warn(
24337
- `[useUIFacadeStateController] A second child ("${childType}"${child.name ? ` name="${child.name}"` : ""}) tried to register in the picker facade. ` +
24338
- `The facade only syncs with the first child wrap multiple controls in a single ControlGroup.`,
24355
+ `[navi] a second control ("${childType}"${child.name ? ` name="${child.name}"` : ""}) registered in the ${describePicker(props)} popup. ` +
24356
+ `A picker talks to ONE control: the first one receives the picker's whole value and is the only one read back, ` +
24357
+ `so this one is neither filled nor collected. ` +
24358
+ `A popup holding several values needs one group around them — wrap them in a <ControlGroup>, name each control inside it, and give the picker type="form".`,
24339
24359
  child,
24340
24360
  );
24341
24361
  } else {
@@ -24463,6 +24483,9 @@ const useUIFacadeStateController = (props, realUIStateController) => {
24463
24483
  return scope.controller;
24464
24484
  };
24465
24485
 
24486
+ const describePicker = (props) =>
24487
+ `<Picker${props.name ? ` name="${props.name}"` : ""}${props.type ? ` type="${props.type}"` : ""}>`;
24488
+
24466
24489
  /**
24467
24490
  * Returns true when `e` should trigger parent notification (child → parent bubbling).
24468
24491
  *
@@ -24612,12 +24635,19 @@ const ControlgroupChildrenWrapper = ({
24612
24635
  */
24613
24636
  const useControlProps = (props, {
24614
24637
  controlType,
24615
- allowNameless,
24638
+ allowNameless: allowNamelessByDefault,
24616
24639
  persists,
24617
24640
  uiActionInternal
24618
24641
  }) => {
24619
24642
  const debugUIState = useDebugUIState();
24620
24643
  const debugAction = useDebugAction();
24644
+
24645
+ // A control that is not a field: it opens something, it goes somewhere, and
24646
+ // the group around it must expect no value from it — no name, and no warning
24647
+ // about the missing name. Buttons and links say so from inside navi; the prop
24648
+ // is how a control used as a door says the same thing from the outside.
24649
+ const allowNameless = props.allowNameless ?? allowNamelessByDefault;
24650
+ delete props.allowNameless;
24621
24651
  const idDefault = useId();
24622
24652
  const controlId = useContext(ControlIdContext);
24623
24653
  props.id = props.id || controlId || idDefault;
@@ -25426,6 +25456,18 @@ const createControlInfo = (props, {
25426
25456
  // getDefaultEventReactionDefinitions.
25427
25457
  readOnlySupported = controlType === "picker" && INPUT_TYPE_SUPPORTING_READONLY_SET.has(typeProp);
25428
25458
  }
25459
+
25460
+ // The suggestion the control starts on, as opposed to what it holds — what a
25461
+ // reset goes back to, and what tells a field left on its default from one
25462
+ // carrying an answer (see isUIStateHeld in held_ui_state.js).
25463
+ let defaultValue;
25464
+ if (!hasStateProp) {
25465
+ if (signalHoldsChecked) {
25466
+ defaultValue = props.defaultChecked ? value : undefined;
25467
+ } else if (Object.hasOwn(props, "defaultValue")) {
25468
+ defaultValue = props.defaultValue;
25469
+ }
25470
+ }
25429
25471
  return {
25430
25472
  controlType,
25431
25473
  statePropName,
@@ -25434,6 +25476,7 @@ const createControlInfo = (props, {
25434
25476
  stateInitial,
25435
25477
  state: stateInitial,
25436
25478
  value,
25479
+ defaultValue,
25437
25480
  signal,
25438
25481
  signalHoldsChecked,
25439
25482
  stateFromSignal,
@@ -28339,6 +28382,20 @@ const css$V = /* css */`
28339
28382
  backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
28340
28383
  }
28341
28384
 
28385
+ /* backdropAppearance, keyed off the originating element (a
28386
+ pseudo-element carries no attributes of its own — same reasoning as
28387
+ the capture rule just above). After the rules it overrides: same
28388
+ specificity, so order is what decides. showModal() still makes the
28389
+ page inert either way — only the paint goes away. */
28390
+ &[data-backdrop-appearance="discrete"]::backdrop {
28391
+ background: var(--navi-backdrop-discrete-background);
28392
+ backdrop-filter: none;
28393
+ }
28394
+ &[data-backdrop-appearance="none"]::backdrop {
28395
+ background: transparent;
28396
+ backdrop-filter: none;
28397
+ }
28398
+
28342
28399
  /* Nested under &[navi-animation] (not the other way around) so every
28343
28400
  attribute selector compiles *before* ::backdrop, not after — a
28344
28401
  pseudo-element can't be qualified by an attribute of its own
@@ -28458,6 +28515,18 @@ const css$V = /* css */`
28458
28515
  backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
28459
28516
  }
28460
28517
 
28518
+ /* Same override as the via-attribute renderer's own ::backdrop rules
28519
+ above, on the real element this renderer uses instead — see them for
28520
+ the specificity/ordering reasoning. */
28521
+ &[data-backdrop-appearance="discrete"] {
28522
+ background: var(--navi-backdrop-discrete-background);
28523
+ backdrop-filter: none;
28524
+ }
28525
+ &[data-backdrop-appearance="none"] {
28526
+ background: transparent;
28527
+ backdrop-filter: none;
28528
+ }
28529
+
28461
28530
  &[navi-animation] {
28462
28531
  opacity: 1;
28463
28532
  transition-property: display, opacity;
@@ -28528,6 +28597,13 @@ const css$V = /* css */`
28528
28597
  * both just absorb the click without closing (visually dimmed backdrop vs.
28529
28598
  * not) — a dialog is always modal one way or another, so there's always
28530
28599
  * at least a click-absorbing backdrop regardless of this prop.
28600
+ * @param {"auto"|"discrete"|"none"} [props.backdropAppearance="auto"] - How
28601
+ * visible the backdrop is, independently of what it does. `"auto"`: the
28602
+ * paint `pointerInteractionOutsideEffect` implies (dimmed for
28603
+ * `"close"`/`"cancel"`, blurred glass for `"capture"`). `"discrete"`: a
28604
+ * barely-there dim. `"none"`: fully transparent. The dialog stays modal
28605
+ * either way — this only changes how much it insists visually, never what
28606
+ * an outside click does or whether the page behind stays reachable.
28531
28607
  * @param {boolean} [props.scrollCapture] - Traps scroll gestures inside the
28532
28608
  * dialog so the page/container behind it can't scroll while it's open.
28533
28609
  * A `layer="local"` dialog always locks its own positioned ancestor's
@@ -28775,6 +28851,11 @@ const useDialogProps = props => {
28775
28851
  // there's no native inert-ing, so the real backdrop below is what
28776
28852
  // actually makes "capture"/"none" behave the same way here too.
28777
28853
  pointerInteractionOutsideEffect = "close",
28854
+ // How loudly the backdrop says it is there — independent of what it
28855
+ // *does* (that's pointerInteractionOutsideEffect above). A dialog is
28856
+ // always modal, so "none" here never makes the page behind reachable:
28857
+ // it only stops the dim from being drawn.
28858
+ backdropAppearance = "auto",
28778
28859
  scrollCapture: scrollCaptureProp,
28779
28860
  // "auto" (default) → the dialog follows its content. "frozen" → measured
28780
28861
  // once, held at that size while open. See this prop's own JSDoc above.
@@ -29293,7 +29374,8 @@ const useDialogProps = props => {
29293
29374
  "navi-hidden": openController.opened ? undefined : "",
29294
29375
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
29295
29376
  "animationDuration": rest.animationDuration,
29296
- "data-pointer-interaction-outside": pointerInteractionOutsideEffect
29377
+ "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
29378
+ "data-backdrop-appearance": backdropAppearance
29297
29379
  });
29298
29380
  Object.assign(contentProps, {
29299
29381
  tabIndex,
@@ -29322,6 +29404,11 @@ const useDialogProps = props => {
29322
29404
  // real backdrop element already gets the same attribute via
29323
29405
  // backdropProps above, which is what its own CSS actually keys off).
29324
29406
  "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
29407
+ // Only load-bearing for the via-attribute renderer's own native
29408
+ // ::backdrop, same "a pseudo-element can't carry attributes" reasoning
29409
+ // as the prop just above (and harmless for the custom renderer, whose
29410
+ // real backdrop element gets it via backdropProps).
29411
+ "data-backdrop-appearance": backdropAppearance,
29325
29412
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
29326
29413
  ...rest,
29327
29414
  ...autoFocusProps,
@@ -29724,6 +29811,20 @@ const css$U = /* css */`
29724
29811
  backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
29725
29812
  }
29726
29813
 
29814
+ /* backdropAppearance overrides whatever the effect above picked — same
29815
+ specificity (class + one attribute), so these have to stay *after*
29816
+ them to win. Only the paint changes: the element is still rendered
29817
+ and still pointer-events: auto, so an outside click keeps doing
29818
+ exactly what pointerInteractionOutsideEffect says. */
29819
+ &[data-backdrop-appearance="discrete"] {
29820
+ background: var(--navi-backdrop-discrete-background);
29821
+ backdrop-filter: none;
29822
+ }
29823
+ &[data-backdrop-appearance="none"] {
29824
+ background: transparent;
29825
+ backdrop-filter: none;
29826
+ }
29827
+
29727
29828
  /* navi-animation mirrors the content popover's own resolved value (set
29728
29829
  imperatively in openEffect) — the backdrop only ever fades, regardless
29729
29830
  of which kind it is (translate/scale wouldn't mean anything on it).
@@ -29787,6 +29888,15 @@ const css$U = /* css */`
29787
29888
  * absorbs the click (dims the backdrop) without closing. Note this
29788
29889
  * default differs from `Dialog`'s own (`"close"`) — a popover is
29789
29890
  * typically a lightweight, non-modal affordance.
29891
+ * @param {"auto"|"discrete"|"none"} [props.backdropAppearance="auto"] - How
29892
+ * visible the backdrop is, independently of what it does. `"auto"`: the
29893
+ * paint `pointerInteractionOutsideEffect` implies (dimmed for
29894
+ * `"close"`/`"cancel"`, blurred glass for `"capture"`). `"discrete"`: a
29895
+ * barely-there dim. `"none"`: fully transparent. The backdrop is still
29896
+ * rendered and still catches outside clicks in every case — this only
29897
+ * changes how much the popover insists on being the thing you deal with.
29898
+ * Ignored when `pointerInteractionOutsideEffect="none"` (there is no
29899
+ * backdrop at all then, and outside clicks pass through).
29790
29900
  * @param {boolean} [props.scrollCapture] - Traps scroll gestures inside the
29791
29901
  * popover so the page/container behind it can't scroll while it's open.
29792
29902
  * @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
@@ -30031,6 +30141,11 @@ const usePopoverProps = props => {
30031
30141
  // "capture"→ absorb the press, stay open
30032
30142
  // "none" → no backdrop
30033
30143
  pointerInteractionOutsideEffect = "none",
30144
+ // How loudly the backdrop says it is there — independent of what it
30145
+ // *does* (that's pointerInteractionOutsideEffect above). "auto" keeps
30146
+ // the paint the effect implies; "discrete"/"none" tone it down or
30147
+ // remove it entirely without giving up the outside click.
30148
+ backdropAppearance = "auto",
30034
30149
  scrollCapture,
30035
30150
  focusCapture,
30036
30151
  // "auto" (default) → the popover follows its content. "frozen" → measured
@@ -30704,6 +30819,7 @@ const usePopoverProps = props => {
30704
30819
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
30705
30820
  "animationDuration": rest.animationDuration,
30706
30821
  "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
30822
+ "data-backdrop-appearance": backdropAppearance,
30707
30823
  "onMouseDown": mouseDownEvent => {
30708
30824
  if (mouseDownEvent.button !== 0) {
30709
30825
  return;
@@ -47374,6 +47490,86 @@ const Editable = props => {
47374
47490
  });
47375
47491
  };
47376
47492
 
47493
+ /**
47494
+ * What a control HOLDS, as opposed to what it is showing.
47495
+ *
47496
+ * A `value` is held: the control was given it, so handing it back says nothing
47497
+ * new. A `defaultValue` is only a suggestion — an age that is usually 18, a
47498
+ * duration that is usually 1h30 — so the control holds nothing, and confirming
47499
+ * the suggestion IS an answer ("yes, 18"). A control bound to a signal falls on
47500
+ * whichever side the signal put it: a signal with something in it is an answer
47501
+ * (restored from the url, set by whoever owns it), an empty one leaves the
47502
+ * control on its suggestion.
47503
+ *
47504
+ * The same distinction Form makes across its fields (see readHeldUIState in
47505
+ * form.jsx), asked of a single control — which is what lets a Picker tell "the
47506
+ * user re-confirmed what was already chosen" (nothing new) from "the user
47507
+ * accepted the proposal" (an answer).
47508
+ */
47509
+
47510
+ const isUIStateHeld = (controller) => {
47511
+ if (!controller) {
47512
+ return false;
47513
+ }
47514
+ // Given a value outright: held, whatever it is showing.
47515
+ if (controller.hasStateProp || controller.hasValueProp) {
47516
+ return true;
47517
+ }
47518
+ // A facade (a picker) shows what the control inside its popup holds, so that
47519
+ // is the one to ask — the facade itself was given nothing.
47520
+ const facadeChild = controller.facadeChild;
47521
+ if (facadeChild) {
47522
+ return isUIStateHeld(facadeChild);
47523
+ }
47524
+ const boundSignal = controller.props?.signal;
47525
+ if (boundSignal) {
47526
+ return boundSignal.value !== undefined;
47527
+ }
47528
+ // Uncontrolled with a suggestion: what it shows is that suggestion until it
47529
+ // differs from it.
47530
+ if (controller.defaultValue !== undefined) {
47531
+ return !compareTwoJsValues(controller.uiState, controller.defaultValue);
47532
+ }
47533
+ // A group holding nothing of its own is worth what its children are: two
47534
+ // wheels each on their own suggestion make a group still waiting for an
47535
+ // answer, one of them moved makes a group holding one.
47536
+ const childControllers = controller.getChildControllers?.() || [];
47537
+ if (childControllers.length > 0) {
47538
+ return childControllers.some((child) => isUIStateHeld(child));
47539
+ }
47540
+ return controller.uiState !== undefined;
47541
+ };
47542
+
47543
+ /**
47544
+ * Tell a control — and everything inside it — that what it is showing is now
47545
+ * the answer, without its state having to move.
47546
+ *
47547
+ * The state is already right; what has not happened is anyone saying so. A
47548
+ * control reports an answer through `onUIAction` (that is where a bound signal
47549
+ * is written, where `uiAction` fires), and a suggestion nobody touched never
47550
+ * got there. Confirming a picker is exactly that moment.
47551
+ *
47552
+ * Down the whole subtree because that is where the answer actually lives: a
47553
+ * picker holding a group of two wheels has one signal per wheel, and it is each
47554
+ * wheel that has to record what it is showing. Commands are skipped — a
47555
+ * `command` on a control is its reaction to being used, and this is a
47556
+ * confirmation happening elsewhere, whose own command (the picker's) is already
47557
+ * running.
47558
+ */
47559
+ const commitUIStateAsAnswer = (controller, e) => {
47560
+ if (!controller) {
47561
+ return;
47562
+ }
47563
+ const answering = controller.facadeChild || controller;
47564
+ commitSubtree(answering, e);
47565
+ };
47566
+ const commitSubtree = (controller, e) => {
47567
+ controller.onUIAction?.(e, { skipCommand: true });
47568
+ for (const child of controller.getChildControllers?.() || []) {
47569
+ commitSubtree(child, e);
47570
+ }
47571
+ };
47572
+
47377
47573
  /**
47378
47574
  *
47379
47575
  * Here we want the same behaviour as web standards:
@@ -47424,6 +47620,7 @@ const useFormGroup = props => {
47424
47620
  };
47425
47621
  delete propsForGroup.standalone;
47426
47622
  delete propsForGroup.canSendWhileUnchanged;
47623
+ delete propsForGroup.pristineKey;
47427
47624
  // Not the generic control `command`, which a control triggers on its own ui
47428
47625
  // actions — here it is what follows a SUCCESSFUL submission. So it is kept
47429
47626
  // out of the control machinery and left in the DOM for the send to read
@@ -47456,7 +47653,7 @@ const useFormGroup = props => {
47456
47653
  // microtask away, so typing and pressing Enter right after must not be read
47457
47654
  // against the state of the previous frame.
47458
47655
  uiStateController.shouldRequestAction = value => Boolean(props.canSendWhileUnchanged) || !compareTwoJsValues(withoutEmptyFields(value), uiStateController.sentUIState);
47459
- useFirstUIStateAsSent(uiStateController);
47656
+ useHeldUIStateAsSent(uiStateController, props.pristineKey);
47460
47657
  useUnregisteredControlWarning(props.ref);
47461
47658
  const {
47462
47659
  basePseudoState,
@@ -47554,13 +47751,10 @@ const FormNested = props => {
47554
47751
  });
47555
47752
  };
47556
47753
 
47557
- // What the form HOLDS, as opposed to what it is showing. A `value` is held: the
47558
- // form was given it, and sending it back says nothing new. A `defaultValue` is
47559
- // only a suggestion an age that is usually 18, a duration that is usually
47560
- // 1h30 so the form holds nothing for that field, and sending the suggestion
47561
- // back IS an answer ("yes, 18"). A field bound to a signal falls on whichever
47562
- // side the signal put it: one carrying a default seeds `defaultValue`, one
47563
- // without controls the field outright.
47754
+ // What the form HOLDS, as opposed to what it is showing field by field, the
47755
+ // question isUIStateHeld answers: a field it was given an answer for is held,
47756
+ // a field merely showing a suggestion is not, and confirming that suggestion IS
47757
+ // an answer ("yes, 18").
47564
47758
  const readHeldUIState = uiStateController => {
47565
47759
  const uiState = uiStateController.uiState;
47566
47760
  // A form given a value holds all of it, whatever its fields say.
@@ -47571,7 +47765,7 @@ const readHeldUIState = uiStateController => {
47571
47765
  ...uiState
47572
47766
  };
47573
47767
  for (const child of uiStateController.getChildControllers?.() || []) {
47574
- if (child.name && !child.hasStateProp) {
47768
+ if (child.name && !isUIStateHeld(child)) {
47575
47769
  delete held[child.name];
47576
47770
  }
47577
47771
  }
@@ -47601,10 +47795,22 @@ const withoutEmptyFields = uiState => {
47601
47795
  // register themselves in their own effects, which run first — this is the
47602
47796
  // earliest moment the form knows what it holds. Everything after this baseline
47603
47797
  // is a real send moving it forward (see useFormGroup's own onnavi_action_end).
47604
- const useFirstUIStateAsSent = uiStateController => {
47798
+ const useHeldUIStateAsSent = (uiStateController, pristineKey) => {
47799
+ // The render that brought a new pristineKey read `changed` against the
47800
+ // previous baseline, and nothing else is going to move: the button would stay
47801
+ // lit on a form that holds exactly what it was just given. So ask for the one
47802
+ // render that reads the new baseline — the first one has nobody to tell,
47803
+ // every field it is waiting for re-renders the form as it registers.
47804
+ const [, rereadBaseline] = useState(0);
47805
+ const isFirstRef = useRef(true);
47605
47806
  useLayoutEffect(() => {
47606
47807
  uiStateController.sentUIState = readHeldUIState(uiStateController);
47607
- }, [uiStateController]);
47808
+ if (isFirstRef.current) {
47809
+ isFirstRef.current = false;
47810
+ return;
47811
+ }
47812
+ rereadBaseline(count => count + 1);
47813
+ }, [uiStateController, pristineKey]);
47608
47814
  };
47609
47815
  const useUnregisteredControlWarning = ref => {
47610
47816
  // No dependency array: fields appear and disappear as the form re-renders,
@@ -47914,6 +48120,29 @@ installImportMetaCssBuild(import.meta);/**
47914
48120
  * container of ours could ever hold two of them side by side and translate the
47915
48121
  * pair. One popup holding slides of its own contents has no such problem — and
47916
48122
  * it is the same component in the document, in a dialog or in a popover.
48123
+ *
48124
+ * Padding belongs on the SLIDE, never on this box nor on anything above it.
48125
+ * Overflow clips at the PADDING edge, so a padding given to the container is a
48126
+ * band the clipping does not cover: a slide travelling through it is seen there
48127
+ * before it has reached the frame. And a padding above the slides does not
48128
+ * travel — the two contents crossing each other pass with nothing between them,
48129
+ * each flush against the other, instead of arriving already inset. Put on the
48130
+ * slide, the inset moves with what it insets, and a travel shows two paddings'
48131
+ * worth of gutter between the two.
48132
+ *
48133
+ * What SCROLLS is the slide too, and for the same reason read the other way
48134
+ * round: the box is as big as its largest slide, so a scroller placed above the
48135
+ * slides is always scrolling the tallest of them — stand on a short one and it
48136
+ * carries the scrollbar of a neighbour, scrolling through emptiness. The cap on
48137
+ * the height comes from above (a max-height on the popup, a column it has to
48138
+ * fit in) and must reach the slides as a CONSTRAINT rather than as a scroller:
48139
+ * this box shrinks into it (flex: 0 1 auto below), the grid hands that height
48140
+ * to every slide, and a slide with an overflow of its own scrolls only when ITS
48141
+ * content is taller than that. The tall slide scrolls; the short ones are tall
48142
+ * boxes with a short content in them, which is exactly what one wants — they
48143
+ * take the height the context imposes and ignore the height of their neighbour.
48144
+ * So: nothing scrollable between the cap and the slides (a shared [data-body]
48145
+ * around them IS a scroller, see box.jsx), and `overflow="auto"` on each Slide.
47917
48146
  */
47918
48147
  const css$A = /* css */`
47919
48148
  /* Where the picture stands relative to the slide that is current, in boxes
@@ -51238,6 +51467,11 @@ const css$z = /* css */`
51238
51467
  * is unavoidably *more* intrusive once it switches to dialog mode than
51239
51468
  * the exact same usage would be as a popover — worth keeping in mind for
51240
51469
  * anything that relies on `Popup` and can end up on a small screen.
51470
+ * @param {"auto"|"discrete"|"none"} [props.backdropAppearance] - Forwarded
51471
+ * as-is to whichever component renders (both understand it identically):
51472
+ * how visible the backdrop is, independently of what an outside click
51473
+ * does. Unlike `pointerInteractionOutsideEffect` above, this one needs no
51474
+ * default here — `"auto"` already means the same thing on both sides.
51241
51475
  * @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"expanding"|`slide-from-${string}`|`expand-${string}`} [props.animation]
51242
51476
  * - Forwarded as-is.
51243
51477
  * @param {string} [props.animationDuration] - Forwarded as-is.
@@ -51344,86 +51578,6 @@ const Popup = props => {
51344
51578
  });
51345
51579
  };
51346
51580
 
51347
- /**
51348
- * What a control HOLDS, as opposed to what it is showing.
51349
- *
51350
- * A `value` is held: the control was given it, so handing it back says nothing
51351
- * new. A `defaultValue` is only a suggestion — an age that is usually 18, a
51352
- * duration that is usually 1h30 — so the control holds nothing, and confirming
51353
- * the suggestion IS an answer ("yes, 18"). A control bound to a signal falls on
51354
- * whichever side the signal put it: a signal with something in it is an answer
51355
- * (restored from the url, set by whoever owns it), an empty one leaves the
51356
- * control on its suggestion.
51357
- *
51358
- * The same distinction Form makes across its fields (see readHeldUIState in
51359
- * form.jsx), asked of a single control — which is what lets a Picker tell "the
51360
- * user re-confirmed what was already chosen" (nothing new) from "the user
51361
- * accepted the proposal" (an answer).
51362
- */
51363
-
51364
- const isUIStateHeld = (controller) => {
51365
- if (!controller) {
51366
- return false;
51367
- }
51368
- // Given a value outright: held, whatever it is showing.
51369
- if (controller.hasStateProp || controller.hasValueProp) {
51370
- return true;
51371
- }
51372
- // A facade (a picker) shows what the control inside its popup holds, so that
51373
- // is the one to ask — the facade itself was given nothing.
51374
- const facadeChild = controller.facadeChild;
51375
- if (facadeChild) {
51376
- return isUIStateHeld(facadeChild);
51377
- }
51378
- const boundSignal = controller.props?.signal;
51379
- if (boundSignal) {
51380
- return boundSignal.value !== undefined;
51381
- }
51382
- // Uncontrolled with a suggestion: what it shows is that suggestion until it
51383
- // differs from it.
51384
- if (controller.defaultValue !== undefined) {
51385
- return !compareTwoJsValues(controller.uiState, controller.defaultValue);
51386
- }
51387
- // A group holding nothing of its own is worth what its children are: two
51388
- // wheels each on their own suggestion make a group still waiting for an
51389
- // answer, one of them moved makes a group holding one.
51390
- const childControllers = controller.getChildControllers?.() || [];
51391
- if (childControllers.length > 0) {
51392
- return childControllers.some((child) => isUIStateHeld(child));
51393
- }
51394
- return controller.uiState !== undefined;
51395
- };
51396
-
51397
- /**
51398
- * Tell a control — and everything inside it — that what it is showing is now
51399
- * the answer, without its state having to move.
51400
- *
51401
- * The state is already right; what has not happened is anyone saying so. A
51402
- * control reports an answer through `onUIAction` (that is where a bound signal
51403
- * is written, where `uiAction` fires), and a suggestion nobody touched never
51404
- * got there. Confirming a picker is exactly that moment.
51405
- *
51406
- * Down the whole subtree because that is where the answer actually lives: a
51407
- * picker holding a group of two wheels has one signal per wheel, and it is each
51408
- * wheel that has to record what it is showing. Commands are skipped — a
51409
- * `command` on a control is its reaction to being used, and this is a
51410
- * confirmation happening elsewhere, whose own command (the picker's) is already
51411
- * running.
51412
- */
51413
- const commitUIStateAsAnswer = (controller, e) => {
51414
- if (!controller) {
51415
- return;
51416
- }
51417
- const answering = controller.facadeChild || controller;
51418
- commitSubtree(answering, e);
51419
- };
51420
- const commitSubtree = (controller, e) => {
51421
- controller.onUIAction?.(e, { skipCommand: true });
51422
- for (const child of controller.getChildControllers?.() || []) {
51423
- commitSubtree(child, e);
51424
- }
51425
- };
51426
-
51427
51581
  installImportMetaCssBuild(import.meta);const css$y = /* css */`
51428
51582
  .navi_picker {
51429
51583
  /* Sizing ceilings (maxmax), background, box-shadow, outline, padding,
@@ -52039,6 +52193,9 @@ const PickerContentInsidePopup = props => {
52039
52193
  // action if the value changed) — Escape still cancels. Pass "cancel" to make
52040
52194
  // clicking outside revert instead, or "capture" to keep it open.
52041
52195
  pointerInteractionOutsideEffect = "close",
52196
+ // Named/forwarded rather than left in ...rest: rest goes to the picker
52197
+ // element itself, not the popup, and this belongs to the popup.
52198
+ backdropAppearance,
52042
52199
  dialogExpand,
52043
52200
  dialogExpandX,
52044
52201
  dialogExpandY,
@@ -52089,6 +52246,7 @@ const PickerContentInsidePopup = props => {
52089
52246
  marginWithContainer: marginWithContainer === undefined && isPopover ? popoverSpacing : marginWithContainer,
52090
52247
  scrollCapture: scrollCapture,
52091
52248
  pointerInteractionOutsideEffect: pointerLock ? "capture" : pointerInteractionOutsideEffect,
52249
+ backdropAppearance: backdropAppearance,
52092
52250
  focusCapture: isPopover ? focusCapture : undefined,
52093
52251
  expand: isPopover ? undefined : dialogExpand,
52094
52252
  expandX: isPopover ? undefined : dialogExpandX,
@@ -53711,6 +53869,28 @@ const css$v = /* css */`
53711
53869
  --list-border-width-default: 1px;
53712
53870
  --list-border-color: light-dark(#ccc, #555);
53713
53871
  --list-background-color: light-dark(#fff, #1e1e1e);
53872
+
53873
+ /* A sticky part paints over the rows only while it IS stuck — which is
53874
+ what --navi-z-index-sticky says it is for ("kept stuck while something
53875
+ scrolls under it"). At rest it is a block in the flow with nothing
53876
+ passing under it, and a 10 there is what slices whatever a neighbouring
53877
+ row lets out of its box: a focus ring, a badge, a stamp. See
53878
+ useStuckStickyParts for the navi-stuck attribute these read.
53879
+
53880
+ With "auto" at rest, a card whose badge overflows into the label
53881
+ below it gets past it by saying z-index: 1 on that badge — a literal,
53882
+ in the card, against its own neighbour, which is what docs/z_index.md
53883
+ asks for. These variables are the escape hatch for what that cannot
53884
+ reach, not the usual answer. Mind that a negative value here is
53885
+ compared against the page: it needs a stacking context between the
53886
+ label and the nearest opaque background, or the label goes behind that
53887
+ background and disappears. */
53888
+ --list-header-z-index: auto;
53889
+ --list-header-z-index-stuck: var(--navi-z-index-sticky);
53890
+ --list-footer-z-index: auto;
53891
+ --list-footer-z-index-stuck: var(--navi-z-index-sticky);
53892
+ --list-group-label-z-index: auto;
53893
+ --list-group-label-z-index-stuck: var(--navi-z-index-sticky);
53714
53894
  }
53715
53895
  .navi_list_item {
53716
53896
  --list-item-padding-x-default: 0px;
@@ -54092,8 +54272,12 @@ const css$v = /* css */`
54092
54272
  position: sticky;
54093
54273
  top: 0;
54094
54274
  left: 0;
54095
- z-index: var(--navi-z-index-sticky);
54275
+ z-index: var(--list-header-z-index);
54096
54276
  order: -2;
54277
+
54278
+ &[navi-stuck] {
54279
+ z-index: var(--list-header-z-index-stuck);
54280
+ }
54097
54281
  }
54098
54282
  .navi_list_fallback,
54099
54283
  .navi_list_search_fallback {
@@ -54199,8 +54383,12 @@ const css$v = /* css */`
54199
54383
  position: sticky;
54200
54384
  right: 0;
54201
54385
  bottom: 0;
54202
- z-index: var(--navi-z-index-sticky);
54386
+ z-index: var(--list-footer-z-index);
54203
54387
  order: 2;
54388
+
54389
+ &[navi-stuck] {
54390
+ z-index: var(--list-footer-z-index-stuck);
54391
+ }
54204
54392
  }
54205
54393
 
54206
54394
  ::highlight(navi-search-match) {
@@ -54215,11 +54403,15 @@ const css$v = /* css */`
54215
54403
  .navi_list_item_group_label {
54216
54404
  position: sticky;
54217
54405
  top: var(--list-group-label-top, var(--x-list-group-label-top, 0px));
54218
- z-index: var(--navi-z-index-sticky);
54406
+ z-index: var(--list-group-label-z-index);
54219
54407
  display: block;
54220
54408
  background-color: var(--list-group-label-background-color);
54221
54409
  user-select: none;
54222
54410
 
54411
+ &[navi-stuck] {
54412
+ z-index: var(--list-group-label-z-index-stuck);
54413
+ }
54414
+
54223
54415
  &[navi-default] {
54224
54416
  padding: 4px 12px 2px;
54225
54417
  color: light-dark(#888, #aaa);
@@ -54837,6 +55029,7 @@ const useListScrollSync = ({
54837
55029
  };
54838
55030
  useLayoutEffect(resolveScroller);
54839
55031
  useStickyScrollportWarning();
55032
+ useStuckStickyParts(ref, getScroller, scrollerElResolved, horizontal);
54840
55033
 
54841
55034
  // The row the scroll holds onto across a change of geometry, and where it
54842
55035
  // sat when that change was decided. Captured at the two moments the list
@@ -55614,6 +55807,14 @@ const getScrollerViewportRect = scrollerEl => {
55614
55807
  }
55615
55808
  return scrollerEl.getBoundingClientRect();
55616
55809
  };
55810
+ // What a sticky part of the list sticks to is the nearest scroll container in
55811
+ // the DOM; `scroller` has no say in it. A list told the page scrolls it can
55812
+ // therefore have its group labels and its header stuck to a wrapper that never
55813
+ // scrolls — and pushed down by that wrapper's scroll-padding on top of it. The
55814
+ // usual culprit is an app wrapper carrying `overflow-x: auto` to keep the
55815
+ // document from overflowing horizontally on mobile; `overflow-x: clip` keeps
55816
+ // that guarantee without making a scroll container.
55817
+ const STICKY_LIST_PART_SELECTOR = `.navi_list_item_header, .navi_list_item_footer, .navi_list_item_group_label`;
55617
55818
  const useStickyScrollportWarning = (ref, scroller) => {
55618
55819
  useRef(false);
55619
55820
  useLayoutEffect(() => {
@@ -55623,6 +55824,142 @@ const useStickyScrollportWarning = (ref, scroller) => {
55623
55824
  });
55624
55825
  };
55625
55826
 
55827
+ /**
55828
+ * "Am I stuck?" — the question a `position: sticky` element cannot ask about
55829
+ * itself. There is no selector for it, and `scroll-state(stuck: top)` does not
55830
+ * answer it either: that query styles a container's DESCENDANTS, so a part
55831
+ * cannot read its own stuck state, which is exactly the one a background, a
55832
+ * shadow or a stacking order has to depend on.
55833
+ *
55834
+ * So the list says it, on the three parts it makes sticky: `navi-stuck` while a
55835
+ * part sits at the edge it sticks to, gone while it rides along in the flow.
55836
+ * The list is the right place for it because it is the only one that knows
55837
+ * WHICH box its parts stick to — an app writing this outside would listen to
55838
+ * the window and be right only for `scroller="document"` (see getScrollerEl,
55839
+ * and useStickyScrollportWarning for the case where even the list is wrong
55840
+ * about it: a scroll container between the two, which dev mode reports).
55841
+ *
55842
+ * What reads it is navi's own z-index rule first (see --list-*-z-index above:
55843
+ * the sticky band is for a part with something scrolling under it, not for a
55844
+ * block at rest in the flow), and an app second, for anything it wants to say
55845
+ * about a part being stuck.
55846
+ */
55847
+ // Fractional layout is the rule, not the exception — zoom, screen density, a
55848
+ // scroller at a half-pixel offset. A part at its sticky offset can render a
55849
+ // fraction short of it, and without this slack it reads as being at rest: a
55850
+ // bug that shows up on one machine and not the next.
55851
+ const STUCK_SLACK = 1;
55852
+ // Which edge a part sticks to. The header and the footer stick along whichever
55853
+ // axis the list scrolls — their rules declare both insets (top/left, and
55854
+ // bottom/right) so the same markup works either way; a group label always caps
55855
+ // its group from the top.
55856
+ const getStickyEdge = (partEl, horizontal) => {
55857
+ if (partEl.classList.contains("navi_list_item_footer")) {
55858
+ return horizontal ? "right" : "bottom";
55859
+ }
55860
+ if (partEl.classList.contains("navi_list_item_header")) {
55861
+ return horizontal ? "left" : "top";
55862
+ }
55863
+ return "top";
55864
+ };
55865
+ // A sticky inset is measured from the scrollport — the padding box of the
55866
+ // scroller, or the viewport when the page scrolls. getScrollerViewportRect
55867
+ // gives the border box; the borders come off here, since a scroller with one
55868
+ // would otherwise read as a pixel of scrolling already done.
55869
+ const getScrollportRect = scrollerEl => {
55870
+ const rect = getScrollerViewportRect(scrollerEl);
55871
+ if (scrollerEl === document.scrollingElement) {
55872
+ return rect;
55873
+ }
55874
+ const top = rect.top + scrollerEl.clientTop;
55875
+ const left = rect.left + scrollerEl.clientLeft;
55876
+ return {
55877
+ top,
55878
+ left,
55879
+ bottom: top + scrollerEl.clientHeight,
55880
+ right: left + scrollerEl.clientWidth
55881
+ };
55882
+ };
55883
+ const isPartStuck = (partEl, edge, scrollportRect) => {
55884
+ // The inset is read computed, not from the rule: --list-group-label-top and
55885
+ // the FixedBar space behind it are what put the label where it sticks.
55886
+ const declared = parseFloat(getComputedStyle(partEl)[edge]);
55887
+ const inset = Number.isFinite(declared) ? declared : 0;
55888
+ const rect = partEl.getBoundingClientRect();
55889
+ if (edge === "top") {
55890
+ return rect.top - scrollportRect.top <= inset + STUCK_SLACK;
55891
+ }
55892
+ if (edge === "left") {
55893
+ return rect.left - scrollportRect.left <= inset + STUCK_SLACK;
55894
+ }
55895
+ if (edge === "bottom") {
55896
+ return scrollportRect.bottom - rect.bottom <= inset + STUCK_SLACK;
55897
+ }
55898
+ return scrollportRect.right - rect.right <= inset + STUCK_SLACK;
55899
+ };
55900
+ const useStuckStickyParts = (ref, getScroller, scrollerElResolved, horizontal) => {
55901
+ // Rewritten on every render so the listeners below, registered once per
55902
+ // scroller, always run against the current geometry.
55903
+ const updateRef = useRef(null);
55904
+ updateRef.current = () => {
55905
+ const listContainerEl = ref.current;
55906
+ if (!listContainerEl) {
55907
+ return;
55908
+ }
55909
+ const partEls = listContainerEl.querySelectorAll(STICKY_LIST_PART_SELECTOR);
55910
+ if (partEls.length === 0) {
55911
+ return;
55912
+ }
55913
+ const scrollerEl = getScroller();
55914
+ if (!scrollerEl) {
55915
+ return;
55916
+ }
55917
+ // One rect per RENDERED part: virtualization already bounds how many of
55918
+ // them exist, which is what keeps this affordable on every scroll event.
55919
+ const scrollportRect = getScrollportRect(scrollerEl);
55920
+ for (const partEl of partEls) {
55921
+ const edge = getStickyEdge(partEl, horizontal);
55922
+ partEl.toggleAttribute("navi-stuck", isPartStuck(partEl, edge, scrollportRect));
55923
+ }
55924
+ };
55925
+
55926
+ // Every commit, because a virtualized list changes which parts exist without
55927
+ // anything scrolling: group labels enter and leave the DOM as the window
55928
+ // moves, and one that arrives already at the edge has never been measured.
55929
+ useLayoutEffect(() => {
55930
+ updateRef.current();
55931
+ });
55932
+ useLayoutEffect(() => {
55933
+ const listContainerEl = ref.current;
55934
+ if (!listContainerEl) {
55935
+ return undefined;
55936
+ }
55937
+ const update = () => {
55938
+ // Synchronously, not on a rAF: scroll events are dispatched while the
55939
+ // frame is being put together, so the attribute lands in the same paint
55940
+ // as the scroll that caused it. A frame late is a frame of flicker.
55941
+ updateRef.current();
55942
+ };
55943
+ // A page-level scroller does not emit "scroll" on the element itself
55944
+ // (document.scrollingElement); the document does.
55945
+ const scrollerEl = getScroller();
55946
+ const scrollEventTarget = !scrollerEl || scrollerEl === document.scrollingElement ? document : scrollerEl;
55947
+ scrollEventTarget.addEventListener("scroll", update, {
55948
+ passive: true
55949
+ });
55950
+ window.addEventListener("resize", update);
55951
+ // The list growing under a scroller that has not moved — rows loaded by
55952
+ // scroll, a group unfolding — changes which parts sit at an edge.
55953
+ const observer = new ResizeObserver(update);
55954
+ observer.observe(listContainerEl);
55955
+ return () => {
55956
+ scrollEventTarget.removeEventListener("scroll", update);
55957
+ window.removeEventListener("resize", update);
55958
+ observer.disconnect();
55959
+ };
55960
+ }, [scrollerElResolved]);
55961
+ };
55962
+
55626
55963
  // The CSS needs to tell "the page scrolls me" from "some box around me
55627
55964
  // scrolls me": only the first one sticks to the viewport, where the fixed bars
55628
55965
  // are.
@@ -58958,110 +59295,6 @@ const PickerFirstResolver = props => {
58958
59295
  ...props
58959
59296
  });
58960
59297
  };
58961
-
58962
- /**
58963
- * Button-like trigger that opens a picker (native or custom popup) when clicked.
58964
- *
58965
- * Without `children`, opens the browser-native picker for the given `type`.
58966
- * With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
58967
- * Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
58968
- *
58969
- * @type {import("ignore:preact").FunctionComponent<{
58970
- * type?: "date" | "month" | "week" | "time" | "datetime" | "color" | "hour" | "navi_time" | "navi_number" | "navi_percentage",
58971
- * value?: any,
58972
- * defaultValue?: any,
58973
- * name?: string,
58974
- * placeholder?: import("ignore:preact").ComponentChildren,
58975
- * required?: boolean,
58976
- * min?: Date | string | number,
58977
- * max?: Date | string | number,
58978
- * step?: string | number,
58979
- * disabled?: boolean,
58980
- * readOnly?: boolean,
58981
- * error?: boolean | string,
58982
- * uiAction?: (value: any, event: Event) => void,
58983
- * action?: (value: any, event: Event) => void,
58984
- * children?: import("ignore:preact").ComponentChildren,
58985
- * mode?: "popover" | "dialog",
58986
- * popoverMode?: "nearby" | "overlay",
58987
- * positionArea?: string,
58988
- * popupWidthFitContent?: boolean,
58989
- * variant?: "icon" | "headless" | "discrete",
58990
- * rightSlotIcon?: import("ignore:preact").ComponentChildren,
58991
- * rightSlotIconSize?: number | string,
58992
- * maxLines?: number,
58993
- * slotSpacing?: number | string,
58994
- * popoverMaxHeight?: number | string,
58995
- * dialogMaxWidth?: number | string,
58996
- * dialogMaxHeight?: number | string,
58997
- * popupBackgroundColor?: string,
58998
- * popupBorderRadius?: number | string,
58999
- * clearable?: boolean,
59000
- * popupLayer?: "top" | "local",
59001
- * dialogExpand?: boolean,
59002
- * dialogExpandX?: boolean,
59003
- * dialogExpandY?: boolean,
59004
- * marginWithContainer?: number | string,
59005
- * escapeEffect?: "cancel" | "close",
59006
- * pointerInteractionOutsideEffect?: "close" | "cancel" | "capture",
59007
- * ref?: import("ignore:preact").RefObject<HTMLElement>,
59008
- * [key: string]: any,
59009
- * }>}
59010
- * @param {boolean|string} [error] Something went wrong around this picker (its
59011
- * content failed to load, its value could not be resolved…). Shown as a
59012
- * callout on the trigger, open or closed — the caller has nothing to place.
59013
- * Dismissing it discards that error; a new `error` value raises another one.
59014
- * @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
59015
- * over the trigger, "nearby" leaves a small gap below it.
59016
- * @param {string} [positionArea] Where the popup goes — relative to the trigger
59017
- * in popover mode, relative to the viewport in dialog mode. Same grammar as
59018
- * Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
59019
- * …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
59020
- * popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
59021
- * popover still flips to the opposite side on its own when there isn't
59022
- * enough room.
59023
- * @param {boolean} [popupWidthFitContent] By default the popup is at least as
59024
- * wide as the trigger. Set this to let the content size it instead, so a
59025
- * popup narrower than the trigger stays narrow.
59026
- * @param {import("ignore:preact").ComponentChildren} [rightSlotIcon] What the right
59027
- * slot draws in place of the chevron. It is the whole slot, not an addition
59028
- * to it: the picker then no longer says on its own that it opens, so pass
59029
- * something that does.
59030
- * @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
59031
- * right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
59032
- * cross. "inherit" takes the picker's own font size.
59033
- * @param {number|string} [slotSpacing] Gap kept between what sits in the right
59034
- * slot (the chevron, or the clear button) and the picker's own edge — same
59035
- * prop name Input uses for its own slots. Accepts a spacing token ("s",
59036
- * "m"…) like any other spacing prop, or a length. Defaults to half the
59037
- * horizontal padding.
59038
- * @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
59039
- * (default 300px). The popover shrinks below it when space is tight.
59040
- * @param {number|string} [dialogMaxWidth] Ceiling on the dialog's width, the
59041
- * one `dialogExpand`/`dialogExpandX` grows up to — what makes an expanded
59042
- * dialog a large sheet rather than a full screen. Capped in turn by the
59043
- * container minus `marginWithContainer`.
59044
- * @param {number|string} [dialogMaxHeight] Same, on the height.
59045
- * @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
59046
- * picker. "cancel" puts back the value the picker had at open, and a dialog
59047
- * picker also goes back in history — so anything written to the url while it
59048
- * was open (a route `stateSignal`, a search param) goes back with it. "close"
59049
- * makes Escape say what clicking outside says: keep what was chosen, close —
59050
- * a last resort, see docs/popup_open.md ("Escape cancels, the other gestures
59051
- * keep") for why Escape should go on meaning cancel, and for what the value
59052
- * at open is on the picker's very first open.
59053
- * @param {"close"|"cancel"|"capture"} [pointerInteractionOutsideEffect="close"]
59054
- * What a click outside the popup does: close and keep ("close"), close and
59055
- * put back the value at open ("cancel"), or nothing at all ("capture"). The
59056
- * default is what gives a popup with no confirm button its way out that
59057
- * keeps — see the same section.
59058
- * @param {number|string} [marginWithContainer] Minimum gap kept between the
59059
- * popup and the edges of what contains it (the viewport, or the picker's own
59060
- * positioned ancestor for `popupLayer="local"`). Caps the popup's size as
59061
- * well as its placement, so what an expanded dialog leaves visible around
59062
- * itself is set here. Defaults to `popoverSpacing` in popover mode, and to
59063
- * Dialog's own 3vvw in dialog mode.
59064
- */
59065
59298
  const Picker = createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
59066
59299
  Picker.UI = PickerDefaultUI;
59067
59300
  Picker.UI.Date = PickerDateUI;