@jsenv/navi 0.29.60 → 0.29.62

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.
@@ -12622,10 +12622,10 @@ defineInteractionDetector({
12622
12622
  if (canReorder) {
12623
12623
  element.setAttribute(REORDERABLE_ATTRIBUTE, "");
12624
12624
  }
12625
- // What @jsenv/dom puts on a drag source: no iOS callout, the touch left to
12626
- // the scroll until the press becomes a grab, and the listener that lets the
12627
- // grab take it back. Its argument is the axis the SURROUNDINGS scroll on,
12628
- // which for a list is the axis the list runs on.
12625
+ // What @jsenv/dom puts on a drag source: the axes written in the DOM for
12626
+ // whoever else answers this press (a sheet pushed down to close it, a row of
12627
+ // slides), no iOS callout, the touch left to the scroll until the press
12628
+ // becomes a grab, and the listener that lets the grab take it back.
12629
12629
  const unmarkDragSource = markDragSource(element, axes);
12630
12630
 
12631
12631
  // What a release can mean, which is not all of what was declared: "grab" is a
@@ -24189,20 +24189,26 @@ const useUIStateController = (
24189
24189
  e,
24190
24190
  "dispatching synthetic input event without data for checkbox/radio",
24191
24191
  );
24192
- el.dispatchEvent(new Event("input", { bubbles: true }));
24192
+ dispatchSyntheticInput(
24193
+ el,
24194
+ new Event("input", { bubbles: true }),
24195
+ e,
24196
+ );
24193
24197
  syntheticInputFired = true;
24194
24198
  } else {
24195
24199
  debugUIState(
24196
24200
  e,
24197
24201
  `dispatching synthetic input event with data "${newUIState}" for input`,
24198
24202
  );
24199
- el.dispatchEvent(
24203
+ dispatchSyntheticInput(
24204
+ el,
24200
24205
  new InputEvent("input", {
24201
24206
  bubbles: true,
24202
24207
  cancelable: true,
24203
24208
  inputType: "insertText",
24204
24209
  data: newUIState,
24205
24210
  }),
24211
+ e,
24206
24212
  );
24207
24213
  syntheticInputFired = true;
24208
24214
  }
@@ -24214,7 +24220,11 @@ const useUIStateController = (
24214
24220
  // A plain Event, not an InputEvent: that is what the browser
24215
24221
  // itself fires on a select, and input_effect reads the value off
24216
24222
  // the element anyway.
24217
- el.dispatchEvent(new Event("input", { bubbles: true }));
24223
+ dispatchSyntheticInput(
24224
+ el,
24225
+ new Event("input", { bubbles: true }),
24226
+ e,
24227
+ );
24218
24228
  syntheticInputFired = true;
24219
24229
  }
24220
24230
  // TODO: textarea
@@ -25317,14 +25327,16 @@ const useUIFacadeStateController = (props, realUIStateController) => {
25317
25327
  }
25318
25328
  if (
25319
25329
  silent &&
25320
- child.uiState === undefined &&
25321
- s.realUIStateController.uiState !== undefined
25330
+ uiStateHoldsNothing(child.uiState) &&
25331
+ !uiStateHoldsNothing(s.realUIStateController.uiState)
25322
25332
  ) {
25323
25333
  // A silent sync means the child's own structure changed (children
25324
25334
  // mounted/unmounted), not that the user acted. A child that ends up
25325
25335
  // with no value there is one that currently *cannot* express one —
25326
- // a <List loading> holds no items yet which must not read as the
25327
- // user clearing the picker, nor fire its uiAction.
25336
+ // a <List loading> holds no items yet, a popup whose items are gone
25337
+ // aggregates to the empty array its stateType falls back to — which
25338
+ // must not read as the user clearing the picker, nor fire its
25339
+ // uiAction.
25328
25340
  return;
25329
25341
  }
25330
25342
  updatingRef.current = true;
@@ -25445,6 +25457,33 @@ const isInternalEvent = (e) => {
25445
25457
  return INTERNAL_EVENT_SET.has(e.type);
25446
25458
  };
25447
25459
 
25460
+ /**
25461
+ * The synthetic "input" event is how the new state reaches the outside world
25462
+ * (`uiAction` is called from the input handler it triggers). It carries what
25463
+ * caused the state change so a listener can tell a gesture apart from navi
25464
+ * talking to itself: `findEvent(e, "navi_clear_ui_state")` answers "the user
25465
+ * pressed the clear cross", the same way the facade hops are recognized.
25466
+ */
25467
+ const dispatchSyntheticInput = (el, inputEvent, causeEvent) => {
25468
+ chainEvent(inputEvent, causeEvent);
25469
+ el.dispatchEvent(inputEvent);
25470
+ };
25471
+
25472
+ // What a control says when it has nothing to say: no value at all, or the empty
25473
+ // array/object a group falls back to while it has no child to aggregate.
25474
+ const uiStateHoldsNothing = (uiState) => {
25475
+ if (uiState === undefined) {
25476
+ return true;
25477
+ }
25478
+ if (Array.isArray(uiState)) {
25479
+ return uiState.length === 0;
25480
+ }
25481
+ if (uiState !== null && typeof uiState === "object") {
25482
+ return Object.keys(uiState).length === 0;
25483
+ }
25484
+ return false;
25485
+ };
25486
+
25448
25487
  /**
25449
25488
  * Core hooks for wiring navi field components to the action/validation system.
25450
25489
  *
@@ -30296,8 +30335,40 @@ const useDialogProps = props => {
30296
30335
  // argument at all) docks it against the viewport (layer="top"/isModal)
30297
30336
  // or its own positioned ancestor (layer="local", the same
30298
30337
  // positionedAncestor computed above), same mechanism as Popover's own
30299
- // custom renderer. applyNewPosition sets --container-position-remaining-height/-width
30300
- // from the result, same as popover.jsx.
30338
+ // custom renderer. applyDialogPosition sets --container-position-remaining-height/-width
30339
+ // from the result, same as popover.jsx — except for layer="top", see its
30340
+ // own comment just above.
30341
+ // The placement is a snapshot taken on a debounce; the size caps must not
30342
+ // be one too. applyNewPosition writes --container-position-remaining-
30343
+ // height/width on every reposition, from what pickPositionRelativeTo
30344
+ // measured the container as at that instant. For layer="top" that value
30345
+ // says nothing --dialog-maxmax-height/width doesn't already say live: the
30346
+ // container IS the visual viewport and Dialog is never anchored, so the
30347
+ // "remaining" space is always the whole container net of its own margins
30348
+ // — the very definition of --dialog-maxmax-*, which tracks the visual
30349
+ // viewport through --navi-vvh/--navi-app-height without waiting for
30350
+ // anything. Identical values, one of them one debounce late.
30351
+ //
30352
+ // That lag stays invisible while the viewport SHRINKS — the two are
30353
+ // combined with min(), so the live var is the one that binds and the
30354
+ // dialog follows the mobile keyboard down, its top edge pinned. On the
30355
+ // way back up the stale pixel value is the smaller one, so it binds
30356
+ // instead and the dialog stays keyboard-sized until the debounced
30357
+ // reposition fires — which then places a still-shrunk box in a
30358
+ // full-height viewport (centered: visibly lower), grows it in the same
30359
+ // call, and has to slide it back up: the bounce. Dropping the property
30360
+ // for layer="top" leaves the live var alone in charge, so the height
30361
+ // comes back exactly the way it left, from the bottom edge.
30362
+ //
30363
+ // Kept for layer="local": there the container is a real element, its own
30364
+ // box is what the caps must read, and nothing in CSS tracks it.
30365
+ const applyDialogPosition = position => {
30366
+ applyNewPosition(dialogEl, position);
30367
+ if (isModal) {
30368
+ dialogEl.style.removeProperty("--container-position-remaining-height");
30369
+ dialogEl.style.removeProperty("--container-position-remaining-width");
30370
+ }
30371
+ };
30301
30372
  const positionDialog = triggerEvent => {
30302
30373
  const {
30303
30374
  positionArea,
@@ -30327,8 +30398,8 @@ const useDialogProps = props => {
30327
30398
  event: triggerEvent
30328
30399
  };
30329
30400
  let position = pickPositionRelativeTo(dialogEl, null, pickOptions);
30330
- applyNewPosition(dialogEl, position);
30331
- // applyNewPosition above just set --container-position-remaining-
30401
+ applyDialogPosition(position);
30402
+ // applyDialogPosition above just set --container-position-remaining-
30332
30403
  // width/height to the real available space — narrower than whatever
30333
30404
  // dialogEl measured at just before (nothing, on a first open — see
30334
30405
  // popover.jsx's own identical comment on its own positionPopover for
@@ -30341,7 +30412,7 @@ const useDialogProps = props => {
30341
30412
  // correct it — that one only reacts on the *next* animation frame.
30342
30413
  if (dialogEl.offsetWidth !== position.width || dialogEl.offsetHeight !== position.height) {
30343
30414
  position = pickPositionRelativeTo(dialogEl, null, pickOptions);
30344
- applyNewPosition(dialogEl, position);
30415
+ applyDialogPosition(position);
30345
30416
  }
30346
30417
  // A descendant's own visibleRectEffect (visible_rect.js — e.g. a
30347
30418
  // Callout anchored to something inside this Dialog) knowing to