@jsenv/navi 0.29.454 → 0.29.456

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.
@@ -33755,9 +33755,33 @@ const pushHeadThemeColors = (themeColors) => {
33755
33755
  };
33756
33756
  };
33757
33757
 
33758
+ /**
33759
+ * What a popup asks of the colour while it is open: its `themeColor` prop,
33760
+ * else the `--navi-popup-theme-color` it inherits — where the choice goes
33761
+ * when it is the app's, or a part of the app's, rather than one popup's.
33762
+ *
33763
+ * @param {Element} element - The popup element.
33764
+ * @param {string|false} [themeColor] - The popup's prop.
33765
+ * @returns {string} `"auto"` paints what the popup lays over the top edge,
33766
+ * `"none"` leaves the colour as it is, anything else is a colour forcing it.
33767
+ */
33768
+ const readPopupThemeColor = (element, themeColor) => {
33769
+ if (themeColor === false) {
33770
+ return "none";
33771
+ }
33772
+ if (themeColor) {
33773
+ return themeColor;
33774
+ }
33775
+ const token = getComputedStyle(element)
33776
+ .getPropertyValue("--navi-popup-theme-color")
33777
+ .trim();
33778
+ return token || "auto";
33779
+ };
33780
+
33758
33781
  /**
33759
33782
  * @param {object} paint
33760
- * @param {string} [paint.themeColor] - Forces the colour, nothing is painted.
33783
+ * @param {string} paint.themeColor - `"auto"` paints the two colours below,
33784
+ * a colour forces it and nothing is painted.
33761
33785
  * @param {string|null} [paint.backdropColor] - Laid over the page's colour.
33762
33786
  * @param {string|null} [paint.surfaceColor] - Laid over that, for a popup
33763
33787
  * flush with the top edge.
@@ -33769,7 +33793,7 @@ const pushPopupThemeColor = ({
33769
33793
  surfaceColor,
33770
33794
  }) => {
33771
33795
  let layer;
33772
- if (themeColor) {
33796
+ if (themeColor !== "auto") {
33773
33797
  layer = { color: themeColor };
33774
33798
  } else {
33775
33799
  const paints = [];
@@ -40613,18 +40637,20 @@ const callLiftOverCallbacks = () => {
40613
40637
  *
40614
40638
  * A picker's `action` waits for its popup to close: what is done in there is
40615
40639
  * one answer, given when the person is done. A change made while the popup is
40616
- * closed — a chip's cross, a paste, a cut, a value proposed from beside it
40617
- * (`--navi-update`) — has no close coming, so nothing would ever send it: the
40618
- * value moves on screen and the record behind it stays. Each of those gestures
40619
- * calls this right after setting the value; a failing action puts the value
40620
- * back (resetOnError), as it does after a close.
40640
+ * closed — the clear cross, a chip's cross, a paste, a cut, a value proposed
40641
+ * from beside it (`--navi-update`) — has no close coming, so nothing would ever
40642
+ * send it: the value moves on screen and the record behind it stays. Each of
40643
+ * those gestures calls this right after setting the value; a failing action
40644
+ * puts the value back (resetOnError), as it does after a close.
40621
40645
  *
40622
40646
  * Called by gestures only. A value set without one — a bound signal followed, a
40623
40647
  * cancel or a failure rolled back — is the picker being told, not asked, which
40624
40648
  * is why this is not a reaction to every value set on a closed picker.
40625
40649
  *
40626
- * The clear cross does the same through `--navi-send` (see --navi-clear in
40627
- * commands.js), and so does not come through here.
40650
+ * The action is requested the way the close requests it, not through
40651
+ * `--navi-send`: a send also answers the surface around the control (a slide
40652
+ * moves on, an open dialog closes — see resolveAfterSend in commands.js), and a
40653
+ * cross pressed on a picker in a dialog is not the dialog being answered.
40628
40654
  */
40629
40655
 
40630
40656
 
@@ -40647,6 +40673,13 @@ const sendClosedPickerChange = (element, { event, name, requester }) => {
40647
40673
  if (props.picksNothing) {
40648
40674
  return;
40649
40675
  }
40676
+ // A source with an action of its own made the request already: `<Button
40677
+ // action={remove} command="--navi-clear">` runs the removal, and the command
40678
+ // that follows only brings the picker in line with it. Sending the picker's
40679
+ // action on top would ask twice.
40680
+ if (requester?.__uiStateController__?.props.action) {
40681
+ return;
40682
+ }
40650
40683
  // `requester`: the source of a command may have claimed the press to send
40651
40684
  // this very change (see isAimedAtSelfInteractionsBelow).
40652
40685
  dispatchRequestAction(controlHost, { event, name, requester });
@@ -41060,20 +41093,6 @@ registerNaviCommand("--navi-clear", (source, event) => {
41060
41093
  return undefined;
41061
41094
  }
41062
41095
  const fromInput = source.closest(`[navi-control="input"]`);
41063
- // A control that commits on an explicit send — a picker, whose list sends the
41064
- // moment a value is chosen — has nothing that would commit a clear: its
41065
- // action never runs on a ui state change. Left alone, the field goes empty
41066
- // while the caller still holds the value it gave, and renders it right back.
41067
- //
41068
- // Unless the source committed the clear itself: a clear button given its own
41069
- // action (`<Button action={remove} command="--navi-clear">`) already made the
41070
- // request — the ui state is being brought in line with what just happened, and
41071
- // sending the picker's own action on top would ask twice.
41072
- const sourceController = source.__uiStateController__;
41073
- const fromSendOnlyControl = Boolean(
41074
- source.closest?.(`[navi-control=picker]`) &&
41075
- !sourceController?.props.action,
41076
- );
41077
41096
 
41078
41097
  const performClear = (clearEvent) => {
41079
41098
  dispatchRequestInteraction(target, {
@@ -41084,46 +41103,11 @@ registerNaviCommand("--navi-clear", (source, event) => {
41084
41103
  requester: source,
41085
41104
  prevented: () => clearEvent.preventDefault(),
41086
41105
  allowed: () => {
41087
- // What the control holds, before it holds nothing: the clear is
41088
- // optimistic — the field empties now and the send that commits it may
41089
- // still fail — so what it emptied has to be kept to be put back.
41090
- const uiStateBefore = getUIStateFromElement(target);
41091
41106
  dispatchRequestClearUIState(target, clearEvent);
41092
- if (!fromSendOnlyControl) {
41093
- return;
41094
- }
41095
- // After the clear, never before: the action is bound to the ui
41096
- // state signal, so this sends the value the control now holds.
41097
- const actionHost = findControlHost(target) || target;
41098
- const completion = watchActionCompletion(actionHost, () => {
41099
- triggerNaviCommand(source, "--navi-send", clearEvent, {
41100
- optional: true,
41101
- });
41102
- });
41103
- completion.whenSettled(({ error, aborted }) => {
41104
- if (!error && !aborted) {
41105
- return;
41106
- }
41107
- // The removal did not happen, so the field must stop saying it did.
41108
- // The error itself stays where the action put it — on the control,
41109
- // which is still there, unlike the cross that has just gone with the
41110
- // value it cleared (see addErrorMessage in use_execute_action.js).
41111
- //
41112
- // Put back from the inside ("clear_rollback" is an internal event
41113
- // type, see ui_state_controller.js) rather than asked for the way a
41114
- // user would: nobody acted, the control is being returned to the
41115
- // state its caller still holds. Asked from the outside it would be
41116
- // answered with "this element is busy" — the action is still
41117
- // settling — over the very error that explains why the value is back.
41118
- const controller = actionHost.__uiStateController__;
41119
- if (!controller) {
41120
- return;
41121
- }
41122
- const rollbackEvent = new CustomEvent("clear_rollback", {
41123
- detail: {},
41124
- });
41125
- chainEvent(rollbackEvent, clearEvent);
41126
- controller.setUIState(uiStateBefore, rollbackEvent);
41107
+ sendClosedPickerChange(target, {
41108
+ event: clearEvent,
41109
+ name: "--navi-clear",
41110
+ requester: source,
41127
41111
  });
41128
41112
  },
41129
41113
  });
@@ -41863,6 +41847,7 @@ registerNaviCommand("--navi-select", (source, event) => {
41863
41847
  return dispatchCustomEvent(target, "navi_request_select", {
41864
41848
  event,
41865
41849
  id: resolveCommandValue(source, event),
41850
+ requester: source,
41866
41851
  });
41867
41852
  },
41868
41853
  };
@@ -41879,6 +41864,7 @@ registerNaviCommand("--navi-unselect", (source, event) => {
41879
41864
  return dispatchCustomEvent(target, "navi_request_unselect", {
41880
41865
  event,
41881
41866
  id: resolveCommandValue(source, event),
41867
+ requester: source,
41882
41868
  });
41883
41869
  },
41884
41870
  };
@@ -45782,12 +45768,6 @@ const INTERNAL_EVENT_SET = new Set([
45782
45768
  // on registration, and group pushing value/defaultValue to children on registerChild.
45783
45769
  // Equivalent to defaultValue initialization: no uiAction, no commands, no parent notification.
45784
45770
  "initial_state_push",
45785
- // navi undoing its own optimistic write: the clear cross emptied the control
45786
- // before the send that commits it, the send failed, and the value it emptied
45787
- // goes back where it was (see the --navi-clear command). Nothing acted — the
45788
- // control is being put back on the state the caller still holds — so this
45789
- // must not fire a uiAction, a command, or a report on the way.
45790
- "clear_rollback",
45791
45771
  // A picker's popup closed on a cancel (the no button, Escape, a click
45792
45772
  // outside): what the picker held at open goes back. Nobody acted — no
45793
45773
  // command — but the value really did move back, so uiAction and the parent
@@ -67952,16 +67932,17 @@ const css$E = /* css */`
67952
67932
  * `saturate()`/`grayscale()` work too. How far what is behind withdraws is
67953
67933
  * a question of paint, not of what an outside click does — a backdrop that
67954
67934
  * closes can blur.
67955
- * @param {string|false} [props.themeColor] - The colour the browser paints
67956
- * above the page (`<meta name="theme-color">`: Chrome's address bar on
67957
- * Android, an installed app's status bar) while this dialog is open.
67958
- * Left out, it is what the dialog lays over the top edge of the screen: its
67935
+ * @param {"auto"|string|false} [props.themeColor] - The colour the browser
67936
+ * paints above the page (`<meta name="theme-color">`: Chrome's address bar
67937
+ * on Android, an installed app's status bar) while this dialog is open.
67938
+ * `"auto"` is what the dialog lays over the top edge of the screen: its
67959
67939
  * backdrop over the colour the page asked for (`<Head>`), and its own
67960
67940
  * background over that when it is flush with the top edge
67961
67941
  * (`marginWithContainer={0}` with `expand`/`expandY` or a top
67962
67942
  * `positionArea`). A colour forces it, for a surface whose colour cannot be
67963
67943
  * read (an image, a gradient); `false` leaves the page's colour alone.
67964
- * `layer="top"` only.
67944
+ * Left out, the `--navi-popup-theme-color` token decides, `"auto"` unless
67945
+ * the app says otherwise. `layer="top"` only.
67965
67946
  * @param {boolean} [props.scrollCapture] - Traps scroll gestures inside the
67966
67947
  * dialog so the page/container behind it can't scroll while it's open.
67967
67948
  * A `layer="local"` dialog always locks its own positioned ancestor's
@@ -69131,12 +69112,15 @@ const useDialogProps = props => {
69131
69112
  // What the browser paints above the page goes with what is laid over its
69132
69113
  // top edge, from the start of the opening to the start of the closing: a
69133
69114
  // local dialog lays nothing there.
69134
- if (isTopLayer && themeColor !== false) {
69135
- addCleanup(pushPopupThemeColor({
69136
- themeColor,
69137
- backdropColor: backdrop ? readBackdropColor(dialogEl) : null,
69138
- surfaceColor: flushEdges.top ? getComputedStyle(dialogEl).backgroundColor : null
69139
- }));
69115
+ if (isTopLayer) {
69116
+ const popupThemeColor = readPopupThemeColor(dialogEl, themeColor);
69117
+ if (popupThemeColor !== "none") {
69118
+ addCleanup(pushPopupThemeColor({
69119
+ themeColor: popupThemeColor,
69120
+ backdropColor: backdrop ? readBackdropColor(dialogEl) : null,
69121
+ surfaceColor: flushEdges.top ? getComputedStyle(dialogEl).backgroundColor : null
69122
+ }));
69123
+ }
69140
69124
  }
69141
69125
  const cancelOpenInteractionSuppression = !silent && hasCssTransitionAnimation ? suppressPointerEventsDuringTransition(dialogEl) : null;
69142
69126
  // Handing the focus to a field is what raises the on-screen keyboard, and
@@ -70002,12 +69986,14 @@ const css$D = /* css */`
70002
69986
  * `saturate()`/`grayscale()` work too. How far what is behind withdraws is
70003
69987
  * a question of paint, not of what an outside click does — a backdrop that
70004
69988
  * closes can blur.
70005
- * @param {string|false} [props.themeColor] - The colour the browser paints
70006
- * above the page (`<meta name="theme-color">`: Chrome's address bar on
70007
- * Android, an installed app's status bar) while this popover is open.
70008
- * Left out, it is the backdrop laid over the colour the page asked for
69989
+ * @param {"auto"|string|false} [props.themeColor] - The colour the browser
69990
+ * paints above the page (`<meta name="theme-color">`: Chrome's address bar
69991
+ * on Android, an installed app's status bar) while this popover is open.
69992
+ * `"auto"` is the backdrop laid over the colour the page asked for
70009
69993
  * (`<Head>`), and the page's colour when there is no backdrop. A colour
70010
- * forces it; `false` leaves the page's colour alone. `layer="top"` only.
69994
+ * forces it; `false` leaves the page's colour alone. Left out, the
69995
+ * `--navi-popup-theme-color` token decides, `"auto"` unless the app says
69996
+ * otherwise. `layer="top"` only.
70011
69997
  * @param {boolean} [props.scrollCapture] - Traps scroll gestures inside the
70012
69998
  * popover so the page/container behind it can't scroll while it's open.
70013
69999
  * @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
@@ -70968,11 +70954,14 @@ const usePopoverProps = props => {
70968
70954
  // What the browser paints above the page goes with what is laid over its
70969
70955
  // top edge, from the start of the opening to the start of the closing: a
70970
70956
  // local popover's backdrop stops at its container.
70971
- if (isTopLayer && themeColor !== false) {
70972
- addCleanup(pushPopupThemeColor({
70973
- themeColor,
70974
- backdropColor: backdropEl ? readBackdropColor(backdropEl) : null
70975
- }));
70957
+ if (isTopLayer) {
70958
+ const popupThemeColor = readPopupThemeColor(popoverEl, themeColor);
70959
+ if (popupThemeColor !== "none") {
70960
+ addCleanup(pushPopupThemeColor({
70961
+ themeColor: popupThemeColor,
70962
+ backdropColor: backdropEl ? readBackdropColor(backdropEl) : null
70963
+ }));
70964
+ }
70976
70965
  }
70977
70966
  const cancelOpenInteractionSuppression = !silent && hasCssTransitionAnimation ? suppressPointerEventsDuringTransition(popoverEl) : null;
70978
70967
  // autoFocus={false}: opening moves no focus at all — whoever has the
@@ -71390,10 +71379,11 @@ const css$C = /* css */`@layer navi {
71390
71379
  * identically): the wash the backdrop paints over what is behind.
71391
71380
  * @param {string} [props.backdropFilter] - Forwarded as-is: what that wash
71392
71381
  * does to the picture underneath, `"blur(4px)"` and the like.
71393
- * @param {string|false} [props.themeColor] - Forwarded as-is: the colour the
71394
- * browser paints above the page while the popup is open, worked out from
71395
- * what it lays over the top edge when left out. A colour forces it, `false`
71396
- * leaves the page's colour alone.
71382
+ * @param {"auto"|string|false} [props.themeColor] - Forwarded as-is: the
71383
+ * colour the browser paints above the page while the popup is open.
71384
+ * `"auto"` works it out from what the popup lays over the top edge, a
71385
+ * colour forces it, `false` leaves the page's colour alone. Left out, the
71386
+ * `--navi-popup-theme-color` token decides.
71397
71387
  * @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`|"lifting"|{open: boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`, close: "lifting"}} [props.animation]
71398
71388
  * - Forwarded to whichever renders — see either component's own doc.
71399
71389
  * `"expanding"`/`"expand-*"` are `Popover`'s own; `"lifting"` and
@@ -84127,7 +84117,8 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
84127
84117
  return;
84128
84118
  }
84129
84119
  const {
84130
- id: entry
84120
+ id: entry,
84121
+ requester
84131
84122
  } = e.detail;
84132
84123
  const isThere = uiState.some(item => compareTwoJsValues(item, entry));
84133
84124
  if (goal === "select" ? isThere : !isThere) {
@@ -84144,7 +84135,8 @@ const requestPickerListEntry = (pickerEl, pickerInputEl, e, goal) => {
84144
84135
  });
84145
84136
  sendClosedPickerChange(pickerInputEl, {
84146
84137
  event: e,
84147
- name: goal
84138
+ name: goal,
84139
+ requester
84148
84140
  });
84149
84141
  }
84150
84142
  });
@@ -84618,7 +84610,9 @@ const PickerFirstResolver = props => {
84618
84610
  * the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
84619
84611
  * BEFORE the ui state is emptied, so answering no leaves the field exactly
84620
84612
  * as it was; answering yes clears and sends, and the picker's own action
84621
- * receives the cleared value like any other choice.
84613
+ * receives the cleared value like any other choice. A cut (Ctrl+X) on such a
84614
+ * picker copies the value and leaves it in place: no clear without the
84615
+ * question.
84622
84616
  * @param {"icon"|"headless"|"discrete"|"button"|"text"|"bare"|"picker"} [variant]
84623
84617
  * How the trigger is drawn. `"button"` is a Button's drawing — surface,
84624
84618
  * padding, a centered label, no chevron — and what a `type="confirm"` picker