@jsenv/navi 0.29.238 → 0.29.298

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.
@@ -20677,16 +20677,16 @@ const useComposeElementRef = (syncElement, externalRef) => {
20677
20677
  const cleanupRef = useRef(null);
20678
20678
  const elRef = useRef(null);
20679
20679
  const prevSyncElementRef = useRef(undefined);
20680
- const stableRef = useRef(null);
20680
+ const holderRef = useRef(null);
20681
20681
  const externalRefRef = useRef(externalRef);
20682
20682
  const syncElementRef = useRef(syncElement);
20683
20683
  syncElementRef.current = syncElement;
20684
- // Detect external ref identity change between renders. The refCallback is
20685
- // stable across renders, so when the parent passes a new ref object (or
20686
- // switches from null to a ref), Preact does NOT re-fire the callback while
20687
- // the DOM element is unchanged. We must manually clear the old ref and
20688
- // populate the new one with the current element to avoid leaving the new
20689
- // ref's `.current` stuck at `null`.
20684
+ // Detect external ref identity change between renders. The refCallback keeps
20685
+ // its identity once it holds the element, so when the parent passes a new
20686
+ // ref object (or switches from null to a ref), Preact does NOT re-fire the
20687
+ // callback while the DOM element is unchanged. We must manually clear the
20688
+ // old ref and populate the new one with the current element to avoid
20689
+ // leaving the new ref's `.current` stuck at `null`.
20690
20690
  const prevExternalRefRef = useRef(externalRef);
20691
20691
  if (prevExternalRefRef.current !== externalRef) {
20692
20692
  const previous = prevExternalRefRef.current;
@@ -20704,12 +20704,12 @@ const useComposeElementRef = (syncElement, externalRef) => {
20704
20704
  }
20705
20705
  externalRefRef.current = externalRef;
20706
20706
 
20707
- if (!stableRef.current) {
20708
- // Created once, like the ref callback that calls it, and reading the sync
20709
- // function through a ref for that reason: the element can be replaced long
20710
- // after the first render — a tag that changes, a box hidden then shown —
20711
- // and what the new element gets must be the current render's sync, not
20712
- // the one the first render closed over.
20707
+ if (!holderRef.current) {
20708
+ // Created once, and reading the sync function through a ref for that
20709
+ // reason: the element can be replaced long after the first render — a tag
20710
+ // that changes, a box hidden then shown — and what the new element gets
20711
+ // must be the current render's sync, not the one the first render closed
20712
+ // over.
20713
20713
  const runSync = (el) => {
20714
20714
  if (cleanupRef.current) {
20715
20715
  cleanupRef.current();
@@ -20722,6 +20722,18 @@ const useComposeElementRef = (syncElement, externalRef) => {
20722
20722
  cleanupRef.current = cleanup;
20723
20723
  }
20724
20724
  };
20725
+ holderRef.current = { runSync, refCallback: null };
20726
+ }
20727
+ const { runSync } = holderRef.current;
20728
+
20729
+ // A new callback on every render until Preact has handed it the element,
20730
+ // the same one afterwards. Preact re-applies a ref only when its identity
20731
+ // changes, and a diff that throws with no error boundary above skips
20732
+ // commitRoot: its refs are dropped while its DOM stays and is adopted as
20733
+ // mounted by the next render. A callback that never fired would then never
20734
+ // fire, and a Box would stay raw HTML for the life of the document. Free on
20735
+ // a healthy mount, where the first callback is the only one ever created.
20736
+ if (!elRef.current) {
20725
20737
  const refCallback = (el) => {
20726
20738
  elRef.current = el;
20727
20739
  // Keep .current in sync immediately so useEffect callbacks that read
@@ -20745,9 +20757,9 @@ const useComposeElementRef = (syncElement, externalRef) => {
20745
20757
  prevSyncElementRef.current = undefined;
20746
20758
  }
20747
20759
  };
20748
- stableRef.current = { refCallback, runSync };
20760
+ holderRef.current.refCallback = refCallback;
20749
20761
  }
20750
- const { refCallback, runSync } = stableRef.current;
20762
+ const { refCallback } = holderRef.current;
20751
20763
 
20752
20764
  // If element already mounted, re-sync when syncElement reference changed.
20753
20765
  if (elRef.current && syncElement !== prevSyncElementRef.current) {
@@ -32258,6 +32270,17 @@ registerNaviCommand("--navi-send", (source, event, { requester }) => {
32258
32270
  return sent;
32259
32271
  }
32260
32272
  if (isRunning) {
32273
+ // An optimistic control takes its send as done on its own word: what
32274
+ // follows it runs at once and the run goes on detached — still
32275
+ // watched, still able to fail, its error callout then drawn on what
32276
+ // surrounds the closed surface it was typed in (see openCallout's
32277
+ // anchor resolution in callout.js).
32278
+ const sendController = (findControlHost(target) || target)
32279
+ .__uiStateController__;
32280
+ if (sendController?.optimistic) {
32281
+ runAfterSend();
32282
+ return sent;
32283
+ }
32261
32284
  // The send is committing but has not finished: leaving now would take
32262
32285
  // the form off the screen mid-submission (a popup closing over its own
32263
32286
  // running action, a slide moving on before it is answered). What
@@ -36849,6 +36872,16 @@ const useControlProps = (props, {
36849
36872
  if (!deferredCommand) {
36850
36873
  return;
36851
36874
  }
36875
+ if (uiStateController.optimistic) {
36876
+ // An optimistic button takes its action as done on its own word:
36877
+ // the command runs at once and the run goes on detached, the same
36878
+ // release the form counterpart gives the send (see --navi-send in
36879
+ // commands.js). Only a refused press has nothing to follow.
36880
+ if (completion.result !== false) {
36881
+ deferredCommand();
36882
+ }
36883
+ return;
36884
+ }
36852
36885
  runWhenActionSucceeded(completion, deferredCommand);
36853
36886
  };
36854
36887
  return {
@@ -48795,6 +48828,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
48795
48828
  * replace?: boolean,
48796
48829
  * actionStandalone?: boolean,
48797
48830
  * actionAbortable?: boolean,
48831
+ * optimistic?: boolean,
48798
48832
  * [key: string]: any,
48799
48833
  * }>}
48800
48834
  * @param {boolean} [replace] Go where the press leads — an `href`, a
@@ -48838,6 +48872,12 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
48838
48872
  * running, which the app watches from somewhere else; never for one holding
48839
48873
  * an answer the screen is the only place to read (see
48840
48874
  * docs/interactions.md#the-fourth-question-whose-wait-is-it).
48875
+ * @param {boolean} [optimistic] The action is taken as done on the button's own
48876
+ * word: no busy state, a second press queues behind the run, and its
48877
+ * `command` runs at once — the popup it sits in closes over the run, which
48878
+ * goes on behind. A failure then has no popup to be read in: `resetOnError`
48879
+ * puts the button back and the error callout is drawn on what surrounds the
48880
+ * closed popup (see docs/popup_open.md#the-popup-owns-its-open-state).
48841
48881
  * @param {boolean} [actionAbortable] The person waiting may give up on this
48842
48882
  * button's action: closing the popup the run holds calls it off and goes
48843
48883
  * through, instead of being refused. For a run whose answer may never come —