@jsenv/navi 0.29.46 → 0.29.48

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.
@@ -16993,11 +16993,14 @@ const VISUAL_PROPS = {
16993
16993
  };
16994
16994
  const CONTENT_PROPS = {
16995
16995
  align: applyOnTwoProps("alignX", "alignY"),
16996
+ /* The value the caller named is always written out, even when it matches
16997
+ CSS's own initial value ("start" for justify-content, "stretch" for
16998
+ align-items). Skipping it as a no-op only holds for a bare element: a
16999
+ component whose stylesheet already centers its content (Picker in its icon
17000
+ variant, for one) would keep centering, and alignX="start" would silently
17001
+ do nothing — the one case where saying it explicitly matters most. */
16996
17002
  alignX: (value, { boxFlow }) => {
16997
17003
  if (boxFlow === "flex-y" || boxFlow === "inline-flex-y") {
16998
- if (value === "stretch") {
16999
- return undefined; // this is the default
17000
- }
17001
17004
  return { alignItems: value };
17002
17005
  }
17003
17006
  if (
@@ -17011,18 +17014,12 @@ const CONTENT_PROPS = {
17011
17014
  boxFlow === "grid" ||
17012
17015
  boxFlow === "inline-grid"
17013
17016
  ) {
17014
- if (value === "start") {
17015
- return undefined; // this is the default
17016
- }
17017
17017
  return { justifyContent: value };
17018
17018
  }
17019
17019
  return { textAlign: value };
17020
17020
  },
17021
17021
  alignY: (value, { boxFlow }) => {
17022
17022
  if (boxFlow === "flex-y" || boxFlow === "inline-flex-y") {
17023
- if (value === "start") {
17024
- return undefined;
17025
- }
17026
17023
  return { justifyContent: value };
17027
17024
  }
17028
17025
  if (
@@ -17035,9 +17032,6 @@ const CONTENT_PROPS = {
17035
17032
  boxFlow === "grid" ||
17036
17033
  boxFlow === "inline-grid"
17037
17034
  ) {
17038
- if (value === "stretch") {
17039
- return undefined;
17040
- }
17041
17035
  return { alignItems: value };
17042
17036
  }
17043
17037
  const verticalAlignMap = {
@@ -22147,7 +22141,10 @@ const useNavState = useNavStateBasic;
22147
22141
  * @param {Event} event What the user did.
22148
22142
  * @param {object} [options]
22149
22143
  * @param {boolean} [options.optional] No suitable target is not a warning.
22150
- * @param {any} [options.value] Carried to whoever answers.
22144
+ * @param {any} [options.value] What the command is about, carried to whoever
22145
+ * answers — and, for `--navi-open`/`--navi-toggle`, what the popup is opened
22146
+ * ON: it reaches its `onOpen` before the popup builds anything. Left out, a
22147
+ * source's own value (`<Button value={id}>`) is read instead.
22151
22148
  * @param {Element} [options.anchor] Where a popup this opens should be placed,
22152
22149
  * when that is not the element asking: a menu opened by a press belongs at the
22153
22150
  * point the press happened, and the row that was pressed is not that point.
@@ -22176,6 +22173,10 @@ const triggerNaviCommand = (
22176
22173
  // Whatever followed the colon: "--navi-go-to-slide:edit" → "edit".
22177
22174
  argument: command.includes(":") ? commandArgument(command) : undefined,
22178
22175
  anchor,
22176
+ // What the command is about, said by the caller rather than read off the
22177
+ // source: the attribute form has a `value` to read (`<Button value={id}>`),
22178
+ // a JS decision has none, and both must be able to say the same thing.
22179
+ value,
22179
22180
  });
22180
22181
  if (!execute) {
22181
22182
  if (optional) {
@@ -22812,28 +22813,42 @@ registerNaviCommand("--navi-nav-to", (source, event, { argument }) => {
22812
22813
  };
22813
22814
  });
22814
22815
 
22815
- registerNaviCommand("--navi-toggle", (source, event, { anchor } = {}) => {
22816
- const target =
22817
- resolveExplicitTarget(source) || resolveClosestExpandable(source);
22818
- if (!target) {
22819
- return undefined;
22820
- }
22821
- return {
22822
- target,
22823
- implementation: () => {
22824
- const isExpanded = target.getAttribute("aria-expanded") === "true";
22825
- const customEventName = isExpanded
22826
- ? "navi_request_close"
22827
- : "navi_request_open";
22828
- return dispatchCustomEvent(target, customEventName, {
22829
- event,
22830
- source: resolveCommandProxySource(source),
22831
- anchor,
22832
- });
22833
- },
22834
- };
22835
- });
22836
- registerNaviCommand("--navi-open", (source, event, { anchor } = {}) => {
22816
+ registerNaviCommand(
22817
+ "--navi-toggle",
22818
+ (source, event, { anchor, value } = {}) => {
22819
+ const target =
22820
+ resolveExplicitTarget(source) || resolveClosestExpandable(source);
22821
+ if (!target) {
22822
+ return undefined;
22823
+ }
22824
+ return {
22825
+ target,
22826
+ implementation: () => {
22827
+ const isExpanded = target.getAttribute("aria-expanded") === "true";
22828
+ const customEventName = isExpanded
22829
+ ? "navi_request_close"
22830
+ : "navi_request_open";
22831
+ return dispatchCustomEvent(target, customEventName, {
22832
+ event,
22833
+ source: resolveCommandProxySource(source),
22834
+ anchor,
22835
+ // Same as --navi-open below: the half of the toggle that opens says
22836
+ // what it opens ON, and the half that closes carries it too so the
22837
+ // popup never has to ask which half it just heard.
22838
+ value:
22839
+ value === undefined ? resolveCommandValue(source, event) : value,
22840
+ });
22841
+ },
22842
+ };
22843
+ },
22844
+ );
22845
+ // A popup that edits opens ON something, and the press is the only place that
22846
+ // knows which one. It says it with `value` — "what this is about", as
22847
+ // everywhere else — rather than with an argument, which says what the command
22848
+ // DOES: "open" is already a complete instruction, unlike --navi-go-to-slide.
22849
+ // <Button value={radar.id} command="--navi-open" commandfor="radar-dialog">
22850
+ // The popup is told before it opens — see Dialog/Popover's `onOpen`.
22851
+ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
22837
22852
  const target =
22838
22853
  resolveExplicitTarget(source) || resolveClosestExpandable(source);
22839
22854
  if (!target) {
@@ -22849,6 +22864,7 @@ registerNaviCommand("--navi-open", (source, event, { anchor } = {}) => {
22849
22864
  event,
22850
22865
  source: resolveCommandProxySource(source),
22851
22866
  anchor,
22867
+ value: value === undefined ? resolveCommandValue(source, event) : value,
22852
22868
  });
22853
22869
  },
22854
22870
  };
@@ -27021,37 +27037,45 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
27021
27037
  --x-button-border-color: var(--callout-color);
27022
27038
  }
27023
27039
 
27040
+ /* A variant states what the caller did NOT: the frameless ones below move
27041
+ the DEFAULTS (--button-*) and never the resolved values (--x-button-*),
27042
+ so a backgroundColor/background/borderColor prop — which lands inline on
27043
+ this same element — still wins. Two things come with that: the
27044
+ transparent default is written as a fallback of --button-background, so
27045
+ the background prop feeds --button-background-color through it; and the
27046
+ per-state defaults are re-pointed at the base one, otherwise the @layer
27047
+ formulas (hover = 5% black over the background, readonly = the same)
27048
+ would repaint a box the variant just took away. */
27049
+
27024
27050
  /* discrete: background on hover, and nothing else — no box at rest, and no
27025
27051
  shrink when pressed. What is drawn IS the content (a chevron, a number,
27026
27052
  a word), and shrinking it under the finger reads as the content itself
27027
27053
  flinching rather than as a button being pressed. */
27028
27054
  &[data-variant="discrete"] {
27029
27055
  --button-border-width: 0;
27030
- --x-button-background-color: transparent;
27031
- --x-button-border-color: transparent;
27056
+ --button-border-color: transparent;
27057
+ --button-border-color-hover: var(--button-border-color);
27058
+ --button-border-color-current: var(--button-border-color);
27059
+ --button-border-color-readonly: var(--button-border-color);
27060
+ --button-border-color-disabled: var(--button-border-color);
27061
+ --button-background-color: var(--button-background, transparent);
27062
+ /* The hover wash is mixed INTO the background instead of replacing it:
27063
+ over the transparent default it is exactly the 8% of currentColor it
27064
+ has always been, and over a backgroundColor the caller gave it darkens
27065
+ that color rather than erasing it. */
27066
+ --button-background-color-hover: color-mix(
27067
+ in srgb,
27068
+ currentColor 8%,
27069
+ var(--button-background-color)
27070
+ );
27071
+ --button-background-color-readonly: var(--button-background-color);
27072
+ --button-background-color-disabled: var(--button-background-color);
27032
27073
 
27033
27074
  &[data-pressed] {
27034
27075
  .navi_button_content {
27035
27076
  transform: none;
27036
27077
  }
27037
27078
  }
27038
-
27039
- &[data-hover] {
27040
- --x-button-border-color: transparent;
27041
- --x-button-background-color: color-mix(
27042
- in srgb,
27043
- currentColor 8%,
27044
- transparent
27045
- );
27046
- }
27047
- &[data-readonly] {
27048
- --x-button-border-color: transparent;
27049
- --x-button-background-color: transparent;
27050
- }
27051
- &[data-disabled] {
27052
- --x-button-border-color: transparent;
27053
- --x-button-background-color: transparent;
27054
- }
27055
27079
  }
27056
27080
  /* bare: discrete, minus the background on hover. For a control whose own
27057
27081
  drawing IS the button (a carousel bullet, a swatch), where a box lighting
@@ -27060,13 +27084,16 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
27060
27084
  on focus, commandable. */
27061
27085
  &[data-variant="bare"] {
27062
27086
  --button-border-width: 0;
27063
- --x-button-background-color: transparent;
27064
- --x-button-border-color: transparent;
27087
+ --button-border-color: transparent;
27088
+ --button-border-color-hover: var(--button-border-color);
27089
+ --button-border-color-current: var(--button-border-color);
27090
+ --button-border-color-readonly: var(--button-border-color);
27091
+ --button-border-color-disabled: var(--button-border-color);
27092
+ --button-background-color: var(--button-background, transparent);
27093
+ --button-background-color-hover: var(--button-background-color);
27094
+ --button-background-color-readonly: var(--button-background-color);
27095
+ --button-background-color-disabled: var(--button-background-color);
27065
27096
 
27066
- &[data-hover] {
27067
- --x-button-background-color: transparent;
27068
- --x-button-border-color: transparent;
27069
- }
27070
27097
  &[data-pressed] {
27071
27098
  .navi_button_content {
27072
27099
  transform: none;
@@ -27075,36 +27102,34 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
27075
27102
  }
27076
27103
  /* discrete-border: border on hover */
27077
27104
  &[data-variant="discrete-border"] {
27078
- --x-button-background-color: transparent;
27105
+ --button-background-color: var(--button-background, transparent);
27106
+ --button-background-color-hover: var(--button-background-color);
27107
+ --button-background-color-readonly: var(--button-background-color);
27108
+ --button-background-color-disabled: var(--button-background-color);
27109
+ /* The border is the whole point of this variant: it is absent at rest
27110
+ and drawn on hover, so only the resting color goes transparent — the
27111
+ hover one keeps the @layer formula, now mixed from whatever
27112
+ borderColor the caller gave. */
27079
27113
  --x-button-border-color: transparent;
27080
27114
 
27081
27115
  &[data-hover] {
27082
27116
  --x-button-border-color: var(--button-border-color-hover);
27083
27117
  }
27084
- &[data-readonly] {
27085
- --x-button-border-color: transparent;
27086
- }
27118
+ &[data-readonly],
27087
27119
  &[data-disabled] {
27088
27120
  --x-button-border-color: transparent;
27089
27121
  }
27090
27122
  }
27091
27123
  /* border variant: no background, border only */
27092
27124
  &[data-variant="border"] {
27093
- --x-button-background-color: transparent;
27094
-
27095
- &[data-hover] {
27096
- --x-button-background-color: color-mix(
27097
- in srgb,
27098
- currentColor 8%,
27099
- transparent
27100
- );
27101
- }
27102
- &[data-readonly] {
27103
- --x-button-background-color: transparent;
27104
- }
27105
- &[data-disabled] {
27106
- --x-button-background-color: transparent;
27107
- }
27125
+ --button-background-color: var(--button-background, transparent);
27126
+ --button-background-color-hover: color-mix(
27127
+ in srgb,
27128
+ currentColor 8%,
27129
+ var(--button-background-color)
27130
+ );
27131
+ --button-background-color-readonly: var(--button-background-color);
27132
+ --button-background-color-disabled: var(--button-background-color);
27108
27133
  }
27109
27134
  /* Last word on the shrink, over whatever the variant decided: the variant
27110
27135
  guesses from how the button is drawn, and that guess is wrong as soon as
@@ -27790,7 +27815,8 @@ const getFocusedBeforeTransfer = (e) => {
27790
27815
  * - `onClose(e)`: actually closing, not preventable — final reactions live here.
27791
27816
  *
27792
27817
  * The controller exposes matching action methods:
27793
- * - `open()`: requests opening — runs `openEffect`, then `openHandler`.
27818
+ * - `open()`: requests opening — calls the caller's `onOpen` (see below), then
27819
+ * `mountContent`/`openEffect`, then `openHandler`.
27794
27820
  * - `requestClose()`: requests closing — calls `onRequestClose` then `onClose`,
27795
27821
  * stopping after the first if denied. The popup may choose to stay open.
27796
27822
  * - `close()`: closes for real — calls only `onClose`, skipping
@@ -27960,6 +27986,13 @@ const createOpenController = (
27960
27986
  // content is still waiting for a first open to be built. Called below,
27961
27987
  // before openEffect, so the popup measures and positions the real thing.
27962
27988
  mountContent: null,
27989
+ // The caller's own `onOpen`, set by Dialog/Popover from their props on
27990
+ // every render (like openEffect). Called BEFORE mountContent, so whatever
27991
+ // it decides — which record this dialog is opening on — is already true by
27992
+ // the time the content is built, positioned and shown. That order is the
27993
+ // whole point: learning it afterwards means the content mounted on the
27994
+ // previous subject first.
27995
+ onOpen: null,
27963
27996
  // The counterpart, set only when the popup was told to throw its content
27964
27997
  // away on close (`unmountWhenClosed`). Called from performClose above.
27965
27998
  unmountContent: null,
@@ -28016,6 +28049,10 @@ const createOpenController = (
28016
28049
  }
28017
28050
  };
28018
28051
  };
28052
+ // Before mountContent, which builds the content, and before openEffect,
28053
+ // which shows it: what the popup opens ON has to be known before either
28054
+ // (see `onOpen` above).
28055
+ controller.onOpen?.(requestOpenEvent);
28019
28056
  // After prepareFocusTransfer, which has to record what held the focus
28020
28057
  // before anything inside the popup can claim it, and before openEffect,
28021
28058
  // which measures the popup to place it.
@@ -29026,16 +29063,16 @@ const css$V = /* css */`
29026
29063
  backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
29027
29064
  }
29028
29065
 
29029
- /* backdropAppearance, keyed off the originating element (a
29066
+ /* backdropVariant, keyed off the originating element (a
29030
29067
  pseudo-element carries no attributes of its own — same reasoning as
29031
29068
  the capture rule just above). After the rules it overrides: same
29032
29069
  specificity, so order is what decides. showModal() still makes the
29033
29070
  page inert either way — only the paint goes away. */
29034
- &[data-backdrop-appearance="discrete"]::backdrop {
29071
+ &[data-backdrop-variant="discrete"]::backdrop {
29035
29072
  background: var(--navi-backdrop-discrete-background);
29036
29073
  backdrop-filter: none;
29037
29074
  }
29038
- &[data-backdrop-appearance="none"]::backdrop {
29075
+ &[data-backdrop-variant="invisible"]::backdrop {
29039
29076
  background: transparent;
29040
29077
  backdrop-filter: none;
29041
29078
  }
@@ -29162,11 +29199,11 @@ const css$V = /* css */`
29162
29199
  /* Same override as the via-attribute renderer's own ::backdrop rules
29163
29200
  above, on the real element this renderer uses instead — see them for
29164
29201
  the specificity/ordering reasoning. */
29165
- &[data-backdrop-appearance="discrete"] {
29202
+ &[data-backdrop-variant="discrete"] {
29166
29203
  background: var(--navi-backdrop-discrete-background);
29167
29204
  backdrop-filter: none;
29168
29205
  }
29169
- &[data-backdrop-appearance="none"] {
29206
+ &[data-backdrop-variant="invisible"] {
29170
29207
  background: transparent;
29171
29208
  backdrop-filter: none;
29172
29209
  }
@@ -29245,11 +29282,11 @@ const css$V = /* css */`
29245
29282
  * both just absorb the click without closing (visually dimmed backdrop vs.
29246
29283
  * not) — a dialog is always modal one way or another, so there's always
29247
29284
  * at least a click-absorbing backdrop regardless of this prop.
29248
- * @param {"auto"|"discrete"|"none"} [props.backdropAppearance="auto"] - How
29285
+ * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant="auto"] - How
29249
29286
  * visible the backdrop is, independently of what it does. `"auto"`: the
29250
29287
  * paint `pointerInteractionOutsideEffect` implies (dimmed for
29251
29288
  * `"close"`/`"cancel"`, blurred glass for `"capture"`). `"discrete"`: a
29252
- * barely-there dim. `"none"`: fully transparent. The dialog stays modal
29289
+ * barely-there dim. `"invisible"`: fully transparent. The dialog stays modal
29253
29290
  * either way — this only changes how much it insists visually, never what
29254
29291
  * an outside click does or whether the page behind stays reachable.
29255
29292
  * @param {boolean} [props.scrollCapture] - Traps scroll gestures inside the
@@ -29305,6 +29342,13 @@ const css$V = /* css */`
29305
29342
  * for the user to see it transition away from. `"interaction"` says the
29306
29343
  * opposite — this dialog is mounted *because* the user just asked for it, so
29307
29344
  * the mount is the opening and the entrance plays like any other.
29345
+ * @param {(openEvent: CustomEvent) => void} [props.onOpen] - Called when it
29346
+ * opens, BEFORE its content is built, positioned or shown. What it opens ON
29347
+ * is in `openEvent.detail.value` — the value of whatever asked
29348
+ * (`<Button value={radar.id} command="--navi-open" commandfor="…">`), or the
29349
+ * `value` given to `triggerNaviCommand`. That order is the point: a dialog that
29350
+ * is "new" or "edit X" depending on the press must know which one it is
29351
+ * before what it holds is rendered.
29308
29352
  * @param {(event: Event) => void} [props.onClose] - Called when the dialog
29309
29353
  * actually closes — not preventable (see `open_controller.js`'s own
29310
29354
  * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
@@ -29379,7 +29423,11 @@ const UncontrolledDialog = props => {
29379
29423
  openController: openController,
29380
29424
  onnavi_request_open: e => {
29381
29425
  openController.open(e, {
29382
- anchor: e.detail?.anchor ?? e.detail?.source
29426
+ anchor: e.detail?.anchor ?? e.detail?.source,
29427
+ // What the command was about — a `<Button value={id}>` that opened
29428
+ // this popup ON that id. Handed to `onOpen` before anything is
29429
+ // built (see open_controller.js).
29430
+ value: e.detail?.value
29383
29431
  });
29384
29432
  },
29385
29433
  onnavi_request_close: e => {
@@ -29503,7 +29551,7 @@ const useDialogProps = props => {
29503
29551
  // *does* (that's pointerInteractionOutsideEffect above). A dialog is
29504
29552
  // always modal, so "none" here never makes the page behind reachable:
29505
29553
  // it only stops the dim from being drawn.
29506
- backdropAppearance = "auto",
29554
+ backdropVariant = "auto",
29507
29555
  scrollCapture: scrollCaptureProp,
29508
29556
  // "auto" (default) → the dialog follows its content. "frozen" → measured
29509
29557
  // once, held at that size while open. See this prop's own JSDoc above.
@@ -29522,11 +29570,20 @@ const useDialogProps = props => {
29522
29570
  // instead, so it's read here rather than left in `rest`.
29523
29571
  autoFocus = "last-resort",
29524
29572
  onKeyDown,
29573
+ // Read here (rather than left in `rest`) for two reasons: it must never
29574
+ // reach the DOM as an `onopen` attribute, and the controller — not this
29575
+ // render — is what calls it, at the one moment that makes it useful (see
29576
+ // openController.onOpen below).
29577
+ onOpen,
29525
29578
  children: childrenProp,
29526
29579
  mountWhenClosed,
29527
29580
  unmountWhenClosed,
29528
29581
  ...rest
29529
29582
  } = props;
29583
+ // Assigned on every render, like openEffect below, so it always closes over
29584
+ // the latest prop. Called by openController.open() before the content is
29585
+ // built: what this popup opens ON is known before anything reads it.
29586
+ openController.onOpen = onOpen || null;
29530
29587
  const children = usePopupContentMount(openController, props.ref, {
29531
29588
  children: childrenProp,
29532
29589
  mountWhenClosed,
@@ -30025,7 +30082,7 @@ const useDialogProps = props => {
30025
30082
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
30026
30083
  "animationDuration": rest.animationDuration,
30027
30084
  "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
30028
- "data-backdrop-appearance": backdropAppearance
30085
+ "data-backdrop-variant": backdropVariant
30029
30086
  });
30030
30087
  Object.assign(contentProps, {
30031
30088
  tabIndex,
@@ -30058,7 +30115,7 @@ const useDialogProps = props => {
30058
30115
  // ::backdrop, same "a pseudo-element can't carry attributes" reasoning
30059
30116
  // as the prop just above (and harmless for the custom renderer, whose
30060
30117
  // real backdrop element gets it via backdropProps).
30061
- "data-backdrop-appearance": backdropAppearance,
30118
+ "data-backdrop-variant": backdropVariant,
30062
30119
  "styleCSSVars": DIALOG_STYLE_CSS_VARS,
30063
30120
  ...rest,
30064
30121
  ...autoFocusProps,
@@ -30463,16 +30520,16 @@ const css$U = /* css */`
30463
30520
  backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
30464
30521
  }
30465
30522
 
30466
- /* backdropAppearance overrides whatever the effect above picked — same
30523
+ /* backdropVariant overrides whatever the effect above picked — same
30467
30524
  specificity (class + one attribute), so these have to stay *after*
30468
30525
  them to win. Only the paint changes: the element is still rendered
30469
30526
  and still pointer-events: auto, so an outside click keeps doing
30470
30527
  exactly what pointerInteractionOutsideEffect says. */
30471
- &[data-backdrop-appearance="discrete"] {
30528
+ &[data-backdrop-variant="discrete"] {
30472
30529
  background: var(--navi-backdrop-discrete-background);
30473
30530
  backdrop-filter: none;
30474
30531
  }
30475
- &[data-backdrop-appearance="none"] {
30532
+ &[data-backdrop-variant="invisible"] {
30476
30533
  background: transparent;
30477
30534
  backdrop-filter: none;
30478
30535
  }
@@ -30540,11 +30597,11 @@ const css$U = /* css */`
30540
30597
  * absorbs the click (dims the backdrop) without closing. Note this
30541
30598
  * default differs from `Dialog`'s own (`"close"`) — a popover is
30542
30599
  * typically a lightweight, non-modal affordance.
30543
- * @param {"auto"|"discrete"|"none"} [props.backdropAppearance="auto"] - How
30600
+ * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant="auto"] - How
30544
30601
  * visible the backdrop is, independently of what it does. `"auto"`: the
30545
30602
  * paint `pointerInteractionOutsideEffect` implies (dimmed for
30546
30603
  * `"close"`/`"cancel"`, blurred glass for `"capture"`). `"discrete"`: a
30547
- * barely-there dim. `"none"`: fully transparent. The backdrop is still
30604
+ * barely-there dim. `"invisible"`: fully transparent. The backdrop is still
30548
30605
  * rendered and still catches outside clicks in every case — this only
30549
30606
  * changes how much the popover insists on being the thing you deal with.
30550
30607
  * Ignored when `pointerInteractionOutsideEffect="none"` (there is no
@@ -30607,6 +30664,13 @@ const css$U = /* css */`
30607
30664
  * for the user to see it transition away from. `"interaction"` says the
30608
30665
  * opposite — this popover is mounted *because* the user just asked for it, so
30609
30666
  * the mount is the opening and the entrance plays like any other.
30667
+ * @param {(openEvent: CustomEvent) => void} [props.onOpen] - Called when it
30668
+ * opens, BEFORE its content is built, positioned or shown. What it opens ON
30669
+ * is in `openEvent.detail.value` — the value of whatever asked
30670
+ * (`<Button value={radar.id} command="--navi-open" commandfor="…">`), or the
30671
+ * `value` given to `triggerNaviCommand`. That order is the point: a popover that
30672
+ * is "new" or "edit X" depending on the press must know which one it is
30673
+ * before what it holds is rendered.
30610
30674
  * @param {(event: Event) => void} [props.onClose] - Called when the popover
30611
30675
  * actually closes — not preventable (see `open_controller.js`'s own
30612
30676
  * `onRequestClose`/`onClose` distinction; `onRequestClose` is where you'd
@@ -30679,7 +30743,11 @@ const UncontrolledPopover = props => {
30679
30743
  openController: openController,
30680
30744
  onnavi_request_open: e => {
30681
30745
  openController.open(e, {
30682
- anchor: e.detail?.anchor ?? e.detail?.source
30746
+ anchor: e.detail?.anchor ?? e.detail?.source,
30747
+ // What the command was about — a `<Button value={id}>` that opened
30748
+ // this popup ON that id. Handed to `onOpen` before anything is
30749
+ // built (see open_controller.js).
30750
+ value: e.detail?.value
30683
30751
  });
30684
30752
  },
30685
30753
  onnavi_request_close: e => {
@@ -30797,7 +30865,7 @@ const usePopoverProps = props => {
30797
30865
  // *does* (that's pointerInteractionOutsideEffect above). "auto" keeps
30798
30866
  // the paint the effect implies; "discrete"/"none" tone it down or
30799
30867
  // remove it entirely without giving up the outside click.
30800
- backdropAppearance = "auto",
30868
+ backdropVariant = "auto",
30801
30869
  scrollCapture,
30802
30870
  focusCapture,
30803
30871
  // "auto" (default) → the popover follows its content. "frozen" → measured
@@ -30817,11 +30885,20 @@ const usePopoverProps = props => {
30817
30885
  // instead, so it's read here rather than left in `rest`.
30818
30886
  autoFocus = "last-resort",
30819
30887
  onKeyDown,
30888
+ // Read here (rather than left in `rest`) for two reasons: it must never
30889
+ // reach the DOM as an `onopen` attribute, and the controller — not this
30890
+ // render — is what calls it, at the one moment that makes it useful (see
30891
+ // openController.onOpen below).
30892
+ onOpen,
30820
30893
  children: childrenProp,
30821
30894
  mountWhenClosed,
30822
30895
  unmountWhenClosed,
30823
30896
  ...rest
30824
30897
  } = props;
30898
+ // Assigned on every render, like openEffect below, so it always closes over
30899
+ // the latest prop. Called by openController.open() before the content is
30900
+ // built: what this popup opens ON is known before anything reads it.
30901
+ openController.onOpen = onOpen || null;
30825
30902
  const children = usePopupContentMount(openController, props.ref, {
30826
30903
  children: childrenProp,
30827
30904
  mountWhenClosed,
@@ -31471,7 +31548,7 @@ const usePopoverProps = props => {
31471
31548
  "styleCSSVars": POPUP_STYLE_CSS_VARS,
31472
31549
  "animationDuration": rest.animationDuration,
31473
31550
  "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
31474
- "data-backdrop-appearance": backdropAppearance,
31551
+ "data-backdrop-variant": backdropVariant,
31475
31552
  "onMouseDown": mouseDownEvent => {
31476
31553
  if (mouseDownEvent.button !== 0) {
31477
31554
  return;
@@ -51824,7 +51901,7 @@ const css$z = /* css */`
51824
51901
  * is unavoidably *more* intrusive once it switches to dialog mode than
51825
51902
  * the exact same usage would be as a popover — worth keeping in mind for
51826
51903
  * anything that relies on `Popup` and can end up on a small screen.
51827
- * @param {"auto"|"discrete"|"none"} [props.backdropAppearance] - Forwarded
51904
+ * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant] - Forwarded
51828
51905
  * as-is to whichever component renders (both understand it identically):
51829
51906
  * how visible the backdrop is, independently of what an outside click
51830
51907
  * does. Unlike `pointerInteractionOutsideEffect` above, this one needs no
@@ -52552,7 +52629,7 @@ const PickerContentInsidePopup = props => {
52552
52629
  pointerInteractionOutsideEffect = "close",
52553
52630
  // Named/forwarded rather than left in ...rest: rest goes to the picker
52554
52631
  // element itself, not the popup, and this belongs to the popup.
52555
- backdropAppearance,
52632
+ backdropVariant,
52556
52633
  dialogExpand,
52557
52634
  dialogExpandX,
52558
52635
  dialogExpandY,
@@ -52603,7 +52680,7 @@ const PickerContentInsidePopup = props => {
52603
52680
  marginWithContainer: marginWithContainer === undefined && isPopover ? popoverSpacing : marginWithContainer,
52604
52681
  scrollCapture: scrollCapture,
52605
52682
  pointerInteractionOutsideEffect: pointerLock ? "capture" : pointerInteractionOutsideEffect,
52606
- backdropAppearance: backdropAppearance,
52683
+ backdropVariant: backdropVariant,
52607
52684
  focusCapture: isPopover ? focusCapture : undefined,
52608
52685
  expand: isPopover ? undefined : dialogExpand,
52609
52686
  expandX: isPopover ? undefined : dialogExpandX,
@@ -59102,6 +59179,12 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59102
59179
  transparent
59103
59180
  );
59104
59181
  --picker-icon-color-disabled: var(--picker-icon-color-readonly);
59182
+ /* Where the slots sit INSIDE the box, visible only once the box is bigger
59183
+ than what it holds (a width/height the caller gave it). Distinct from
59184
+ textAlign, which places the text inside the value slot; this places the
59185
+ slots themselves. */
59186
+ --picker-align-x-default: flex-start;
59187
+ --picker-align-y-default: center;
59105
59188
  }
59106
59189
  }
59107
59190
 
@@ -59138,6 +59221,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59138
59221
  );
59139
59222
  --x-picker-color: var(--picker-color);
59140
59223
  --x-picker-icon-color: var(--picker-icon-color);
59224
+ --x-picker-align-x: var(--picker-align-x, var(--picker-align-x-default));
59225
+ --x-picker-align-y: var(--picker-align-y, var(--picker-align-y-default));
59141
59226
 
59142
59227
  /* Deliberately NOT positioned: the popup children live in here, and a
59143
59228
  layer="local" Popover/Dialog takes its nearest positioned ancestor as
@@ -59177,7 +59262,8 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59177
59262
  padding-left: 0;
59178
59263
  flex: 1 1 auto;
59179
59264
  flex-direction: row;
59180
- align-items: center;
59265
+ align-items: var(--x-picker-align-y);
59266
+ justify-content: var(--x-picker-align-x);
59181
59267
  background-color: var(--x-picker-background-color);
59182
59268
  border-width: var(--picker-border-width);
59183
59269
  border-style: solid;
@@ -59279,7 +59365,7 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59279
59365
  }
59280
59366
  &[navi-single-line] {
59281
59367
  .navi_picker_right_slot {
59282
- align-self: center;
59368
+ align-self: var(--x-picker-align-y);
59283
59369
  }
59284
59370
  }
59285
59371
  .navi_picker_input {
@@ -59353,14 +59439,29 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59353
59439
  --x-picker-border-color: var(--callout-color);
59354
59440
  }
59355
59441
 
59442
+ /* A variant states what the caller did NOT: it moves the DEFAULTS
59443
+ (--picker-*) and never the resolved values (--x-picker-*), so a
59444
+ backgroundColor/borderColor/padding prop — which lands inline on this
59445
+ same element — still wins. The per-state defaults are re-pointed at the
59446
+ base one too, otherwise the @layer formulas (hover = 5% black over the
59447
+ background, disabled = 5% grey) would repaint a box the variant just
59448
+ took away. */
59356
59449
  &[data-variant="icon"] {
59357
- --x-picker-padding-top: 0;
59358
- --x-picker-padding-right: 0;
59359
- --x-picker-padding-bottom: 0;
59360
- --x-picker-padding-left: 0;
59450
+ --picker-padding-x-default: 0;
59451
+ --picker-padding-y-default: 0;
59452
+ /* Nothing but the icon is drawn here, so a width/height the caller gave
59453
+ it is a target area, not a text column: the icon belongs in its middle.
59454
+ A default, like everything else a variant moves, so alignX still wins. */
59455
+ --picker-align-x-default: center;
59361
59456
  --picker-border-width: 0px; /* must carry a unit (px) — used in calc() to offset the custom input overlay */
59362
- --x-picker-border-color: transparent;
59363
- --x-picker-background-color: transparent;
59457
+ --picker-border-color: transparent;
59458
+ --picker-border-color-hover: var(--picker-border-color);
59459
+ --picker-border-color-readonly: var(--picker-border-color);
59460
+ --picker-border-color-disabled: var(--picker-border-color);
59461
+ --picker-background-color: transparent;
59462
+ --picker-background-color-hover: var(--picker-background-color);
59463
+ --picker-background-color-readonly: var(--picker-background-color);
59464
+ --picker-background-color-disabled: var(--picker-background-color);
59364
59465
  --x-picker-icon-color: currentColor;
59365
59466
  }
59366
59467
  /* discrete: no box at rest, a background on hover — the same word Button
@@ -59368,31 +59469,35 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
59368
59469
  around it; the chevron in the right slot is what still says it opens. */
59369
59470
  &[data-variant="discrete"] {
59370
59471
  --picker-border-width: 0px; /* must carry a unit (px) — used in calc() to offset the custom input overlay */
59371
- --x-picker-border-color: transparent;
59372
- --x-picker-background-color: transparent;
59373
-
59374
- &[data-hover] {
59375
- --x-picker-border-color: transparent;
59376
- --x-picker-background-color: color-mix(
59377
- in srgb,
59378
- currentColor 8%,
59379
- transparent
59380
- );
59381
- }
59382
- &[data-readonly],
59383
- &[data-disabled] {
59384
- --x-picker-border-color: transparent;
59385
- --x-picker-background-color: transparent;
59386
- }
59472
+ --picker-border-color: transparent;
59473
+ --picker-border-color-hover: var(--picker-border-color);
59474
+ --picker-border-color-readonly: var(--picker-border-color);
59475
+ --picker-border-color-disabled: var(--picker-border-color);
59476
+ --picker-background-color: transparent;
59477
+ /* The hover wash is mixed INTO the background instead of replacing it:
59478
+ over the transparent default it is exactly the 8% of currentColor it
59479
+ has always been, and over a backgroundColor the caller gave it darkens
59480
+ that color rather than erasing it. */
59481
+ --picker-background-color-hover: color-mix(
59482
+ in srgb,
59483
+ currentColor 8%,
59484
+ var(--picker-background-color)
59485
+ );
59486
+ --picker-background-color-readonly: var(--picker-background-color);
59487
+ --picker-background-color-disabled: var(--picker-background-color);
59387
59488
  }
59388
59489
  &[data-variant="headless"] {
59389
- --x-picker-padding-top: 0;
59390
- --x-picker-padding-right: 0;
59391
- --x-picker-padding-bottom: 0;
59392
- --x-picker-padding-left: 0;
59490
+ --picker-padding-x-default: 0;
59491
+ --picker-padding-y-default: 0;
59393
59492
  --picker-border-width: 0px; /* must carry a unit (px) — used in calc() to offset the custom input overlay */
59394
- --x-picker-border-color: transparent;
59395
- --x-picker-background-color: transparent;
59493
+ --picker-border-color: transparent;
59494
+ --picker-border-color-hover: var(--picker-border-color);
59495
+ --picker-border-color-readonly: var(--picker-border-color);
59496
+ --picker-border-color-disabled: var(--picker-border-color);
59497
+ --picker-background-color: transparent;
59498
+ --picker-background-color-hover: var(--picker-background-color);
59499
+ --picker-background-color-readonly: var(--picker-background-color);
59500
+ --picker-background-color-disabled: var(--picker-background-color);
59396
59501
  --x-picker-icon-color: currentColor;
59397
59502
 
59398
59503
  .navi_picker_box {
@@ -59449,7 +59554,15 @@ const PickerButton = props => {
59449
59554
  usePickerErrorCallout(uiStateController, error);
59450
59555
  return jsxs(Box, {
59451
59556
  as: "div",
59452
- ref: ref,
59557
+ ref: ref
59558
+ // The flow this element really has (.navi_picker is display:inline-flex).
59559
+ // Left unsaid, Box reads a <div> as block and resolves alignX into a
59560
+ // text-align — which is a different intention entirely (that one is the
59561
+ // textAlign prop, placing the text INSIDE the value slot).
59562
+ ,
59563
+
59564
+ inline: true,
59565
+ flex: "x",
59453
59566
  baseClassName: "navi_picker",
59454
59567
  pseudoClasses: PICKER_BUTTON_PSEUDO_CLASSES,
59455
59568
  "data-variant": variant,
@@ -59707,6 +59820,11 @@ const PickerStyleCSSVars = {
59707
59820
  "popupBorderRadius": "--picker-popup-border-radius",
59708
59821
  "dialogBorderWidth": "--picker-dialog-border-width",
59709
59822
  "slotSpacing": "--picker-slot-spacing",
59823
+ // alignX/alignY resolve to these two on a flex-x box; naming the CSS style
59824
+ // (not the prop) is what styleCSSVars matches, so justifyContent/alignItems
59825
+ // passed directly land in the same variables.
59826
+ "justifyContent": "--picker-align-x",
59827
+ "alignItems": "--picker-align-y",
59710
59828
  "padding": "--picker-padding",
59711
59829
  "paddingX": "--picker-padding-x",
59712
59830
  "paddingY": "--picker-padding-y",