@jsenv/navi 0.29.490 → 0.29.492

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.
@@ -15804,11 +15804,19 @@ const addInputEffect = (
15804
15804
  (e) => {
15805
15805
  onEvent(e, { skipDebounce: true });
15806
15806
  },
15807
- { getState, changeOnEnter: true },
15807
+ { getState },
15808
15808
  );
15809
15809
  addTeardown(() => {
15810
15810
  stop();
15811
15811
  });
15812
+ // What the user gave up on (addInputCancel) does not go out later either.
15813
+ const oncancel = () => {
15814
+ clearTimeout(debounceTimeout);
15815
+ };
15816
+ input.addEventListener("navi_cancel", oncancel);
15817
+ addTeardown(() => {
15818
+ input.removeEventListener("navi_cancel", oncancel);
15819
+ });
15812
15820
  } else {
15813
15821
  onEvent = (e) => {
15814
15822
  clearTimeout(timeout);
@@ -15891,36 +15899,38 @@ const addInputEffect = (
15891
15899
  return teardown;
15892
15900
  };
15893
15901
 
15894
- const listenInputStateChange = (
15895
- input,
15896
- callback,
15897
- { getState, changeOnEnter },
15898
- ) => {
15902
+ const listenInputStateChange = (input, callback, { getState }) => {
15899
15903
  const [teardown, addTeardown] = createPubSub();
15900
15904
 
15905
+ // Enter commits on its own keydown, not through the "change" the browser
15906
+ // fires after it: a field answering Enter for itself takes the key from its
15907
+ // <form> (keyDownDefaultOnInput in control_hooks.jsx), and the browser then
15908
+ // holds that "change" until the blur. Committed only when the user typed
15909
+ // since the last commit, which is when the browser would fire one.
15910
+ let typedSinceCommit = false;
15911
+ // A "change" carrying this value was answered by a key already: committed by
15912
+ // Enter, or given up by Escape.
15901
15913
  let stateAtInteraction;
15902
- const oninput = () => {
15914
+ const commit = (e) => {
15915
+ typedSinceCommit = false;
15916
+ callback(e);
15917
+ };
15918
+ const oninput = (e) => {
15903
15919
  stateAtInteraction = undefined;
15920
+ if (e.isTrusted) {
15921
+ typedSinceCommit = true;
15922
+ }
15904
15923
  };
15905
15924
  const onkeydown = (e) => {
15906
15925
  if (e.key === "Enter") {
15907
- /**
15908
- * Browser trigger a "change" event right after the enter is pressed
15909
- * if the input value has changed.
15910
- * We need to prevent the next change event otherwise we would request action twice
15911
- */
15912
15926
  stateAtInteraction = getState();
15913
- if (changeOnEnter) {
15914
- onchange(e);
15927
+ if (typedSinceCommit) {
15928
+ commit(e);
15915
15929
  }
15930
+ return;
15916
15931
  }
15917
15932
  if (e.key === "Escape") {
15918
- /**
15919
- * Browser trigger a "change" event right after the escape is pressed
15920
- * if the input value has changed.
15921
- * We need to prevent the next change event otherwise we would request action when
15922
- * we actually want to cancel
15923
- */
15933
+ typedSinceCommit = false;
15924
15934
  stateAtInteraction = getState();
15925
15935
  }
15926
15936
  };
@@ -15929,7 +15939,7 @@ const listenInputStateChange = (
15929
15939
  stateAtInteraction = undefined;
15930
15940
  return;
15931
15941
  }
15932
- callback(e);
15942
+ commit(e);
15933
15943
  };
15934
15944
  input.addEventListener("input", oninput);
15935
15945
  // Capture: the guard is set before the control reacts to the key. Its
@@ -51342,26 +51352,22 @@ const useControlProps = (props, {
51342
51352
  }
51343
51353
 
51344
51354
  // A field with an action of its own has Enter answered already: the
51345
- // input effect (input_effect.js) commits it — through the change Enter
51346
- // fires, or on the keydown itself under actionDebounce — and an action
51355
+ // input effect (input_effect.js) commits it on the keydown, and an action
51347
51356
  // run on every keystroke has nothing left to commit. --navi-send would
51348
51357
  // run that same action a second time. Nothing answers it under
51349
51358
  // actionEvent="custom", where the field's caller does.
51350
51359
  const enterAnsweredByInputEffect = Boolean(actionDebounce) || props.action !== undefined && actionEvent !== "custom";
51351
51360
  const keyDownDefaultOnInput = e => {
51352
51361
  if (e.key === "Enter") {
51353
- if (props.enterSendsNothing) {
51354
- // Left to the browser, so its own Enter still happens (a datalist
51355
- // choice) — except inside a <form>, where that Enter is the form's
51356
- // implicit submission.
51362
+ if (props.enterSendsNothing || enterAnsweredByInputEffect) {
51363
+ // Nothing around the field hears it. Left to the browser, so its
51364
+ // own Enter still happens (a datalist choice) — except inside a
51365
+ // <form>, where that Enter is the form's implicit submission.
51357
51366
  if (ref.current.form) {
51358
51367
  e.preventDefault();
51359
51368
  }
51360
51369
  return null;
51361
51370
  }
51362
- if (enterAnsweredByInputEffect) {
51363
- return null;
51364
- }
51365
51371
  return enterToSend(e);
51366
51372
  }
51367
51373
  return keyDownDefault(e);
@@ -80425,12 +80431,28 @@ const PickerCustom = props => {
80425
80431
  // person's edit. Until they change something, what was there at open
80426
80432
  // follows it: a look and a close then send nothing, and a cancel goes
80427
80433
  // back to the record as it arrived.
80434
+ // A write the app makes in answer to the person — a seat chosen in a
80435
+ // sheet, a drag landed — arrives as the same event as that record, so
80436
+ // the event alone cannot end the following: a press or a key in the
80437
+ // picker does (its popup and the sheets opened from it stand in its
80438
+ // root). The press that opened it ended before these listeners.
80428
80439
  let editedSinceOpen = false;
80440
+ let actedSinceOpen = false;
80441
+ const noteActed = () => {
80442
+ actedSinceOpen = true;
80443
+ };
80444
+ const pickerRoot = ref.current;
80445
+ pickerRoot.addEventListener("pointerdown", noteActed, {
80446
+ capture: true
80447
+ });
80448
+ pickerRoot.addEventListener("keydown", noteActed, {
80449
+ capture: true
80450
+ });
80429
80451
  const followOutsideUntilEdited = e => {
80430
80452
  if (editedSinceOpen) {
80431
80453
  return;
80432
80454
  }
80433
- if (!isWrittenByTheOutside(e.detail.event)) {
80455
+ if (actedSinceOpen || !isWrittenByTheOutside(e.detail.event)) {
80434
80456
  editedSinceOpen = true;
80435
80457
  return;
80436
80458
  }
@@ -80491,6 +80513,12 @@ const PickerCustom = props => {
80491
80513
  },
80492
80514
  onClose: closeEvent => {
80493
80515
  pickerInput?.removeEventListener("navi_ui_state_change", followOutsideUntilEdited);
80516
+ pickerRoot.removeEventListener("pointerdown", noteActed, {
80517
+ capture: true
80518
+ });
80519
+ pickerRoot.removeEventListener("keydown", noteActed, {
80520
+ capture: true
80521
+ });
80494
80522
  if (closeEvent.detail.isCancel) {
80495
80523
  const pickerEl = ref.current;
80496
80524
  const inputEl = getPickerInput(pickerEl);
@@ -81016,12 +81044,13 @@ const getTriggerLabelIds = pickerEl => {
81016
81044
  }
81017
81045
  return ids.length === 0 ? undefined : ids.join(" ");
81018
81046
  };
81019
- // What the picker holds without the person having changed it: its `value` (or
81020
- // signal) moving, a default moving under it, and what its popup's controls are
81021
- // placed on as they mount. Read along the chain, never off the event alone: a
81022
- // control in the popup carries its change up to the picker as an event of its
81047
+ // What reaches the picker without a gesture of one of its controls: its `value`
81048
+ // (or signal) moving, a default moving under it, and what its popup's controls
81049
+ // are placed on as they mount. Read along the chain, never off the event alone:
81050
+ // a control in the popup carries its change up to the picker as an event of its
81023
81051
  // own (see the facade in ui_state_controller.js), chained to what caused it —
81024
- // an app writing that control's bound signal included.
81052
+ // an app writing that control's bound signal included. Whether that write
81053
+ // answers the person is not in the event (see actedSinceOpen).
81025
81054
  const OUTSIDE_WRITE_TYPE_SET = new Set(["state_prop_change", "initial_state_push", "facade_child_mount_sync", "outside_value_follow"]);
81026
81055
  const isWrittenByTheOutside = e => {
81027
81056
  if (!e) {