@jsenv/navi 0.29.60 → 0.29.61

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
  *