@jsenv/navi 0.29.304 → 0.29.306

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.
@@ -435,6 +435,8 @@ const css = /* css */ `@layer navi {
435
435
  --navi-backdrop-discrete-backdrop-filter: none;
436
436
  --navi-backdrop-capture-background: #ffffff14;
437
437
  --navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);
438
+ --navi-backdrop-grow-background: #060a14e0;
439
+ --navi-backdrop-grow-backdrop-filter: blur(4px);
438
440
  --navi-link-color: #00e;
439
441
  --navi-link-color-pressed: red;
440
442
  --navi-link-current-indicator-color: #cd3425;
@@ -13,8 +13,8 @@
13
13
  "import {\n getVirtualKeyboardOverlayHeight,\n subscribeVisualViewportResizeSettled,\n subscribeWindowResizeSettled,\n} from \"@jsenv/dom\";\nimport { computed, signal } from \"@preact/signals\";\n\nexport const windowWidthSignal = signal(window.innerWidth);\nexport const windowHeightSignal = signal(window.innerHeight);\n\n// Debounced (not a raw \"resize\" listener) — see window_size.js's own\n// module comment: mobile fires a transient \"resize\" when the browser's own\n// UI chrome (address bar, etc.) briefly shows/hides, and this needs to settle\n// on the same tick as Popover/Dialog's own repositioning, or one flickers a\n// moment out of sync with the others.\nsubscribeWindowResizeSettled(() => {\n windowWidthSignal.value = window.innerWidth;\n windowHeightSignal.value = window.innerHeight;\n});\n\n// Visual viewport dimensions — update when the virtual keyboard opens/closes or\n// when the browser UI (address bar) shows/hides.\n// When visualViewport is not available, derived from window signals so they\n// stay live without any extra listeners.\nconst vv = window.visualViewport;\nexport const visualViewportWidthSignal = vv\n ? signal(vv.width)\n : computed(() => windowWidthSignal.value);\nexport const visualViewportHeightSignal = vv\n ? signal(vv.height)\n : computed(() => windowHeightSignal.value);\n\nif (vv) {\n const update = () => {\n visualViewportWidthSignal.value = vv.width;\n visualViewportHeightSignal.value = vv.height;\n };\n // The two directions are not equally trustworthy, and treating them alike is\n // what makes one of the two bugs unavoidable.\n //\n // SMALLER is believed at once. Something now covers the screen, and what is\n // sized against these numbers — the dialog/popover ceilings, through\n // --navi-vvh (navi_css_vars.js) — has to answer the smaller screen in the\n // same frame the placement does. The placement reads the viewport live (see\n // getVisibleViewportRect in @jsenv/dom), so a ceiling arriving a debounce\n // later means a box sized for a screen that is gone, placed in the one that\n // replaced it.\n //\n // BIGGER waits for the resize to settle, because growing back is the reading\n // a mobile browser lies about: going straight from one field to the next\n // fires a blur/focus pair that briefly reports the full height again, with\n // the keyboard never having left. Believed, it flicks every popup back to\n // full height and down again between two taps — the \"two inputs\" case in\n // Dialog's own demo. Nothing is lost by waiting: a keyboard that really left\n // stays gone, and the settled event lands 100ms later.\n vv.addEventListener(\"resize\", () => {\n if (\n vv.width < visualViewportWidthSignal.peek() ||\n vv.height < visualViewportHeightSignal.peek()\n ) {\n update();\n }\n });\n subscribeVisualViewportResizeSettled(update);\n vv.addEventListener(\"scroll\", update);\n}\n\n// The app's own screen — the visual viewport, unless the app declared a\n// narrower one with --navi-app-max-width/--navi-app-height (see\n// navi_css_vars.js, which derives --navi-app-width/--navi-app-height from\n// these in CSS). Anything escaping normal flow is sized against this rather\n// than the viewport: an app that simulates a handheld screen keeps that width\n// even for what paints on top of it.\n//\n// The declaration stays in CSS and is read back from there rather than handed\n// to navi a second time in JS — a JS copy would be the one that goes stale.\n// Read on the spot rather than cached in a signal: the only caller is a popup\n// resolving its own margin as it places itself, which already reads far more\n// of the DOM than this, and nothing then has to be invalidated when the value\n// changes.\nconst unresolvableWarned = new Set();\nconst readAppMax = (propertyName) => {\n const declared = getComputedStyle(document.documentElement)\n .getPropertyValue(propertyName)\n .trim();\n if (!declared) {\n return Infinity;\n }\n // A custom property computes to a token stream, not to a length: \"40rem\"\n // arrives here as the string \"40rem\", and parseFloat would read it as 40\n // pixels. Only px is accepted — an app declaring the screen it simulates has\n // a pixel number to give, and resolving arbitrary lengths would mean laying\n // out a probe element on every read.\n const inPixels = /^([0-9.]+)px$/.exec(declared);\n if (!inPixels) {\n if (!unresolvableWarned.has(propertyName)) {\n unresolvableWarned.add(propertyName);\n // Not silently wrong, just partially applied: CSS still caps the popup's\n // size with the declared length, only the margin it keeps with the edges\n // falls back to a share of the viewport (the pre-token behavior).\n console.warn(\n `${propertyName}=\"${declared}\" must be a length in pixels (\"600px\"). Until then popups keep viewport-sized margins.`,\n );\n }\n return Infinity;\n }\n return parseFloat(inPixels[1]);\n};\nexport const getAppWidth = () =>\n Math.min(visualViewportWidthSignal.value, readAppMax(\"--navi-app-max-width\"));\n// The JS reading of --navi-app-inset-* (see safe_area.js): the centered bands\n// between the window's edges and the app's own rectangle. Handed to\n// @jsenv/dom (setPlacementViewportInsets, wired in navi_css_vars.js) so\n// placement keeps to the same rectangle the CSS size caps describe. The\n// keyboard is deliberately absent, unlike in the CSS twin: the placement\n// viewport already subtracts the keyboard overlay itself (see\n// getVisibleViewportRect in @jsenv/dom's visible_rect.js), so carrying it\n// here too would count it twice.\nexport const getAppInsets = () => {\n const vvWidth = visualViewportWidthSignal.value;\n const vvHeight = visualViewportHeightSignal.value;\n const appMaxWidth = readAppMax(\"--navi-app-max-width\");\n const appMaxHeight = readAppMax(\"--navi-app-max-height\");\n const bandX = appMaxWidth < vvWidth ? (vvWidth - appMaxWidth) / 2 : 0;\n const bandY = appMaxHeight < vvHeight ? (vvHeight - appMaxHeight) / 2 : 0;\n return { left: bandX, top: bandY, right: bandX, bottom: bandY };\n};\n// Minus what the keyboard covers, so this stays the JS reading of the very\n// same rectangle --navi-app-height describes in CSS (see safe_area.js's own\n// --navi-keyboard-inset-bottom). Zero unless the app opted into the keyboard\n// overlaying its content — otherwise the shrinking visual viewport above has\n// already accounted for it, and subtracting again would count it twice.\nexport const getAppHeight = () =>\n Math.max(\n 0,\n Math.min(\n visualViewportHeightSignal.value,\n readAppMax(\"--navi-app-max-height\"),\n ) - getVirtualKeyboardOverlayHeight(),\n );\n\n// Whether the primary input is a finger rather than a mouse. A pointer type is\n// not a size: a narrow desktop window is still a mouse, and a large tablet is\n// still a finger — so anything sized for the on-screen keyboard must key off\n// this, never off windowWidthSignal. Thumb reach needs both, which is what\n// smallTouchScreenSignal below answers.\nconst coarsePointerQuery = window.matchMedia\n ? window.matchMedia(\"(pointer: coarse)\")\n : null;\nexport const coarsePointerSignal = signal(\n coarsePointerQuery ? coarsePointerQuery.matches : false,\n);\nif (coarsePointerQuery) {\n coarsePointerQuery.addEventListener(\"change\", () => {\n coarsePointerSignal.value = coarsePointerQuery.matches;\n });\n}\n\n// Whether the screen is one a bottom sheet actually suits: a finger *and* a\n// screen whose bottom edge stays where the thumb already is. Touch alone is not\n// enough — a tall touch screen (a tablet, a kiosk panel) docks a sheet a whole\n// screen away from where the finger just tapped, which is worse than the\n// centered box it replaced.\n//\n// A phone is recognized by its SHAPE, not by a box of maximum dimensions: what\n// makes the bottom edge reachable is holding a narrow slab, and phones keep\n// growing along their long side (20:9, 21:9) while staying just as narrow. So\n// each orientation is answered on the short side plus the elongation:\n// - upright: narrow enough to be held in one hand, and taller than it is wide.\n// Its height is deliberately unbounded — a very tall narrow screen is the\n// case a bottom sheet is most for, not the case to exclude.\n// - on its side: short enough that the bottom edge is a thumb away whatever the\n// width, and wider than it is tall.\n// A tablet fails both: it is too wide upright, and too tall on its side — the\n// smallest one already starts around 740 CSS px on its short side, and the\n// bound below leaves that gap deliberately wide rather than cutting close to\n// the largest phone.\n//\n// Read off window, not visualViewport: the virtual keyboard shrinks the visual\n// viewport while the user types, and a dialog must not undock mid-interaction\n// because a keyboard opened under it.\nconst HANDHELD_MAX_SHORT_SIDE = 600;\n// Enough elongation to tell a slab from a square-ish panel; a phone is well\n// past it (1.7 and up) in either orientation.\nconst HANDHELD_MIN_RATIO = 1.2;\nexport const smallTouchScreenSignal = computed(() => {\n if (!coarsePointerSignal.value) {\n return false;\n }\n const width = windowWidthSignal.value;\n const height = windowHeightSignal.value;\n if (width <= HANDHELD_MAX_SHORT_SIDE) {\n return height >= width * HANDHELD_MIN_RATIO;\n }\n if (height <= HANDHELD_MAX_SHORT_SIDE) {\n return width >= height * HANDHELD_MIN_RATIO;\n }\n return false;\n});\n",
14
14
  "/**\n * The part of the window an app actually has, in two levels.\n *\n * Two, and not one, for a reason worth stating up front: a fixed bar is one of\n * the things that reduce the free region, so it cannot ALSO be placed against\n * that region — it would push itself off the edge it is pinned to. What is\n * anchored and what is anchored-inside are two different rectangles.\n *\n * 1. `--navi-app-inset-{top,right,bottom,left}` — from the window's edges to\n * the app's own rectangle. Whatever is pinned to an edge (a fixed bar, a\n * side panel, a popup aimed at a corner) is pinned to THAT, so an app\n * pretending to be a 600px handheld inside a 1500px window stays one\n * rectangle instead of a column with its furniture spread across the glass.\n *\n * 2. `--navi-safe-area-inset-{top,right,bottom,left}` — from the window's edges\n * to the band left free INSIDE that rectangle. Whatever flows, scrolls, or\n * gets painted keeps to it.\n *\n * Level 2 is a sum, and the contract for taking part in it is only \"publish\n * what you take on one edge\": the device's own notch (`env(safe-area-inset-*)`,\n * which is the browser's version of this very idea), the fixed bars\n * (fixed_bar_space.js), and anything an app adds. That is the point of naming\n * it at all — a component that must stay clear of what covers the screen reads\n * ONE set of numbers, and never has to learn what is covering it.\n *\n * `max()` between the notch and the bars rather than a sum: a bar pinned to an\n * edge already reaches under the notch and counts it in its own size (see\n * fixed_bar.jsx), so adding both would reserve it twice.\n *\n * JS placement answers to the level-1 rectangle too: getAppInsets\n * (layout/responsive.js) is its reading of these same bands, handed to\n * pickPositionRelativeTo via setPlacementViewportInsets (see\n * navi_css_vars.js).\n */\n\nconst SAFE_AREA_CSS = /* css */ `\n /* Declared as lengths so that they COMPUTE to one: an unregistered custom\n property keeps the calc() it was written as, and the sum below is then a\n string no one can read back. Reading it off the computed style — which a\n route transition does, to keep the band the page being left had (see\n nav/transition_window.js) — only works for a registered property. */\n @property --navi-safe-area-inset-top {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n }\n @property --navi-safe-area-inset-right {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n }\n @property --navi-safe-area-inset-bottom {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n }\n @property --navi-safe-area-inset-left {\n syntax: \"<length>\";\n inherits: true;\n initial-value: 0px;\n }\n\n @layer navi {\n /* Layered whole, rules included: the two rules below are offers, not\n structure. [data-navi-safe-area] is an attribute the app puts on its own\n scroller, so the app's own padding on that element has to win over what\n navi suggests for it. */\n :root {\n /* The room each kind of furniture takes, declared here at zero and\n written by whoever takes it. A slot rather than a value: the sum below\n has to be readable whether or not the app ever mounts a fixed bar. */\n --navi-fixed-bar-space-top: 0px;\n --navi-fixed-bar-space-right: 0px;\n --navi-fixed-bar-space-bottom: 0px;\n --navi-fixed-bar-space-left: 0px;\n\n /* What the on-screen keyboard covers — and ONLY where it overlays the\n content rather than shrinking the viewport, which is navi's default\n wherever the browser has the VirtualKeyboard API (see\n layout/virtual_keyboard.js). Zero on Firefox/Safari, which have no\n such API, and zero for an app that called\n disableVirtualKeyboardOverlay(): both get a keyboard that shrinks the\n visual viewport instead, which --navi-vvh already tracks. Reading\n env() rather than a JS-written value keeps it live: the keyboard\n slides in over several frames and this follows it without a\n listener. */\n --navi-keyboard-inset-bottom: env(keyboard-inset-height, 0px);\n\n /* Level 1. Centered bands, so that declaring one ceiling\n (--navi-app-max-width) is all an app has to do to be a narrow screen in\n a wide window; an app that wants them uneven writes these directly. */\n --navi-app-inset-top: max(\n 0px,\n (var(--navi-vvh) - var(--navi-app-max-height, var(--navi-vvh))) / 2\n );\n /* The keyboard on top of the band, and on this edge only: it eats into\n the app's own rectangle exactly like a viewport that shrank, which is\n what makes both paths end up at the same --navi-app-height (and so at\n the same dialog/popover ceiling). Not part of the centering, hence\n added here rather than folded into --navi-app-inset-top: a keyboard\n takes the bottom, it doesn't re-center anything. */\n --navi-app-inset-bottom: calc(\n var(--navi-app-inset-top) + var(--navi-keyboard-inset-bottom)\n );\n --navi-app-inset-left: max(\n 0px,\n (var(--navi-vvw) - var(--navi-app-max-width, var(--navi-vvw))) / 2\n );\n --navi-app-inset-right: var(--navi-app-inset-left);\n\n /* Level 2. */\n --navi-safe-area-inset-top: calc(\n var(--navi-app-inset-top) +\n max(env(safe-area-inset-top), var(--navi-fixed-bar-space-top))\n );\n --navi-safe-area-inset-right: calc(\n var(--navi-app-inset-right) +\n max(env(safe-area-inset-right), var(--navi-fixed-bar-space-right))\n );\n --navi-safe-area-inset-bottom: calc(\n var(--navi-app-inset-bottom) +\n max(env(safe-area-inset-bottom), var(--navi-fixed-bar-space-bottom))\n );\n --navi-safe-area-inset-left: calc(\n var(--navi-app-inset-left) +\n max(env(safe-area-inset-left), var(--navi-fixed-bar-space-left))\n );\n\n /* The document is the scrollport in the common case, and something the\n browser scrolls to — an anchor, a focused field, a restored position —\n landing under a bar is never what anyone wants. */\n scroll-padding-top: var(--navi-safe-area-inset-top);\n scroll-padding-right: var(--navi-safe-area-inset-right);\n scroll-padding-bottom: var(--navi-safe-area-inset-bottom);\n scroll-padding-left: var(--navi-safe-area-inset-left);\n }\n\n /* Put this on whatever scrolls under the furniture.\n\n There are TWO rooms to give back, and forgetting the second one is the\n classic bug:\n\n - padding, so the end of the content can be scrolled out from under it.\n Without it the last screenful stays covered, unreachable.\n - scroll-padding, so anything the browser scrolls TO lands in front of it\n rather than under. The padding above does not help here: it moves the\n content, not the place the browser scrolls the target to.\n\n Marked by the app rather than picked by navi: which element scrolls is\n the app's business, and an app with more than one would have to fight a\n component that chose for it. An app is free to read the variables itself\n instead. */\n [data-navi-safe-area] {\n padding-top: var(--navi-safe-area-inset-top);\n padding-right: var(--navi-safe-area-inset-right);\n padding-bottom: var(--navi-safe-area-inset-bottom);\n padding-left: var(--navi-safe-area-inset-left);\n\n scroll-padding-top: var(--navi-safe-area-inset-top);\n scroll-padding-right: var(--navi-safe-area-inset-right);\n scroll-padding-bottom: var(--navi-safe-area-inset-bottom);\n scroll-padding-left: var(--navi-safe-area-inset-left);\n }\n }\n`;\nimport.meta.css = SAFE_AREA_CSS;\n",
15
15
  "/**\n * navi's stance on the on-screen keyboard: it overlays the app rather than\n * resizing the viewport, wherever the browser can be told so (the\n * VirtualKeyboard API — Chromium only). See virtual_keyboard.js in @jsenv/dom\n * for what that trades away and what it gives back.\n *\n * Turned on rather than offered, because navi already sizes everything that\n * escapes normal flow against the app's own rectangle rather than against the\n * window (--navi-app-width/height, see navi_css_vars.js), and\n * --navi-keyboard-inset-bottom (safe_area.js) puts the keyboard into exactly\n * that rectangle. So the two mechanisms reach the same numbers here, and the\n * overlay reaches them without reflowing the page underneath — a resizing\n * viewport is a resize of everything, fired transiently every time focus goes\n * from one input to the next.\n *\n * An app that built its own layout around the viewport shrinking can say so\n * with disableVirtualKeyboardOverlay(), and gets the behavior Firefox and\n * Safari give it anyway.\n *\n * The one thing it hands back to the app: scrolling the focused field into\n * view. A viewport that shrinks makes the browser do it; a keyboard that\n * merely paints over the page leaves whatever is under it under it. navi\n * answers that for what it places itself — a popup is sized and positioned\n * against the app rectangle the keyboard was just subtracted from — and for\n * anything marked [data-navi-safe-area], whose scroll-padding-bottom counts\n * the keyboard in (safe_area.js). A field in a scroller the app never marked\n * is the app's own to handle.\n */\n\nimport { setVirtualKeyboardOverlaysContent } from \"@jsenv/dom\";\n\nsetVirtualKeyboardOverlaysContent(true);\n\nexport const disableVirtualKeyboardOverlay = () => {\n setVirtualKeyboardOverlaysContent(false);\n};\n",
16
- "/**\n * Regroup CSS vars that makes sense to share across all navi components.\n */\nimport { setPlacementViewportInsets } from \"@jsenv/dom\";\nimport { effect } from \"@preact/signals\";\n\nimport {\n getAppInsets,\n visualViewportHeightSignal,\n visualViewportWidthSignal,\n} from \"./layout/responsive.js\";\nimport \"./layout/safe_area.js\";\n// Side-effect import: turns the on-screen keyboard into something that\n// overlays the app instead of resizing the viewport, which is navi's default\n// (see that module for why, and safe_area.js's own\n// --navi-keyboard-inset-bottom for what then reads the geometry). Here rather\n// than in each component: it is one decision about the whole window, and this\n// is the module that already makes those.\nimport \"./layout/virtual_keyboard.js\";\n\nconst button = document.createElement(\"button\");\nbutton.style.display = \"none\";\ndocument.body.appendChild(button);\nconst computedStyle = getComputedStyle(button);\nconst controlDefaultFontFamily = computedStyle.fontFamily;\nconst controlDefaultFontSize = computedStyle.fontSize;\ndocument.body.removeChild(button);\n\nconst css = /* css */ `\n @layer navi {\n :root {\n /* Overridden at runtime with precise VisualViewport pixel values so that dvw/dvh \n (which don't track the virtual keyboard dimensions) are never used in practice on supported browsers. */\n --navi-vvw: 100dvw;\n --navi-vvh: 100dvh;\n\n /* What navi treats as \"the screen\" when it sizes something that escapes\n normal flow (a dialog in the top layer, a popover, anything built on\n them): the app's own rectangle, which is the visual viewport minus the\n bands an app asks for. An app that never spans the whole window says so\n ONCE, without ever naming a component:\n\n :root {\n --navi-app-max-width: 600px;\n }\n\n In pixels — see readAppMax in layout/responsive.js for why.\n\n Typically an app simulating a handheld screen: a column centered in a\n wide window with bands on the sides. A dialog is in the top layer, so\n it answers to the viewport, not to that column — left alone it would\n paint 1500px of modal over a 600px app. Declaring the ceiling here is\n what makes the app's own width apply everywhere, including on top.\n\n It stays a ceiling and nothing else: on a screen narrower than the app\n it never binds, so popups keep shrinking with the phone, and the gap\n each popup keeps with the edges (marginWithContainer) is subtracted\n from it as before. A single popup that genuinely needs more can still\n raise its own maxWidth/maxHeight prop.\n\n Read from the insets rather than as a min() of its own so that the\n width and the placement come from ONE description of where the app is\n (see layout/safe_area.js). Placement follows the same rectangle: see\n setPlacementViewportInsets at the bottom of this file. */\n --navi-app-width: calc(\n var(--navi-vvw) - var(--navi-app-inset-left) - var(\n --navi-app-inset-right\n )\n );\n --navi-app-height: calc(\n var(--navi-vvh) - var(--navi-app-inset-top) - var(\n --navi-app-inset-bottom\n )\n );\n\n --navi-focus-outline-width: 2px;\n --navi-focus-outline-color: light-dark(#4476ff, #3b82f6);\n --navi-loader-color: light-dark(#355fcc, #3b82f6);\n /* The grey rectangle a mobile browser flashes under a finger: we draw\n what a press does ourselves (background, ring, chevron), and the\n browser's rectangle is a square painted over rounded corners.\n Declared here alone because the property is inherited: :root covers\n every element, in and out of navi, so nothing has to remember it.\n Set the token to a color to get the flash back, on the whole app or\n on one subtree. */\n -webkit-tap-highlight-color: var(--navi-tap-highlight-color);\n --navi-tap-highlight-color: transparent;\n\n /* A face of their own for the controls, and nothing else on the page.\n Undeclared here on purpose: controls write in inherit, so what a\n container says reaches the button, the field and the picker inside it,\n and a value on :root would resolve here and wall that off again. Set\n it to give the controls their own face, on the whole app or on one\n subtree:\n\n :root {\n --navi-control-font-family: system-ui;\n }\n */\n --navi-control-font-size: ${controlDefaultFontSize};\n --navi-control-border-radius: 2px;\n /* A checkbox is only ~1em wide: applying a control radius meant for\n buttons/inputs (say 8px) would turn it into a circle and make it read\n as a radio. So it follows the control radius but capped at a quarter\n of its own size. Override this token alone to opt out of the cap. */\n --navi-checkbox-border-radius: min(\n var(--navi-control-border-radius),\n 0.25em\n );\n /* The color a control uses to say \"this one is on\": a checked checkbox,\n a checked radio, an enabled switch. Kept apart from --navi-accent-color\n (the brand color for CTA and selection) because a control that is on\n must stay readable as a control, which usually means a brighter, more\n saturated color than a brand accent.\n The light value matches the browser's own accent so a navi control and\n a native one can sit side by side without reading as two blues. */\n --navi-control-accent-color: light-dark(rgb(24, 117, 255), #3b82f6);\n --navi-control-border-width: 1px;\n --navi-control-border-color: light-dark(#767676, #8e8e93);\n --navi-control-padding-x-default: 2px;\n --navi-control-padding-y-default: 1px;\n /* Global padding defaults — override these to change all button paddings. */\n /* Use --button-padding, --button-padding-x, --button-padding-y for per-button overrides. */\n --navi-button-padding-x-default: 6px;\n --navi-button-padding-y-default: 1px;\n /* Read by Button, and by whatever is drawn as one (a picker under\n variant=\"button\"), so the two keep the same surface. */\n --navi-button-background-color: light-dark(#f3f4f6, #2d3748);\n\n /* What a callout says about its message, as a color — and what draws\n as one outside the callout too (the icon a picker in callout mode\n presses, see CalloutStatusIcon). */\n --navi-callout-success-color: #4caf50;\n --navi-callout-info-color: #2196f3;\n --navi-callout-warning-color: #ff9800;\n --navi-callout-error-color: #f44336;\n /* No status: the message is an aside, said without an icon and framed\n like a control rather than like a warning. */\n --navi-callout-neutral-color: var(--navi-control-border-color);\n /* For list item we need slightly more padding to be able to see radio/checkbox outline */\n --navi-list-item-padding-x-default: 4px;\n --navi-list-item-padding-y-default: 1px;\n /* default */\n --navi-picker-padding-x-default: var(--navi-control-padding-x-default);\n --navi-picker-padding-y-default: var(--navi-control-padding-y-default);\n\n --navi-popup-border-radius: 8px;\n --navi-popup-border-color: light-dark(#d0d0d0, #3b3b3b);\n --navi-popup-box-shadow:\n 0 4px 8px rgba(0, 0, 0, 0.08), 0 12px 40px rgba(0, 0, 0, 0.22);\n /* A popup's paper is the same paper as everything else's: derived from\n the surface token so one override themes fields, layouts and popups\n together. Still its own token, so popups alone can be re-papered. */\n --navi-popup-background-color: var(--navi-surface-color);\n /* The ink written on that paper. A popup declares both on itself (see\n dialog.jsx / popover.jsx): it is a new surface, so it does not write\n in whatever the container it was declared in writes in. */\n --navi-popup-color: var(--navi-surface-text-color);\n /* A backdrop's paint is a colour AND a filter, one pair per kind of\n backdrop: how much the page behind must stop competing for the eye is\n a different question from what an outside click does, so a backdrop\n that closes on that click can blur too. Only \"capture\" blurs by\n default. */\n --navi-backdrop-close-background: rgba(0, 0, 0, 0.08);\n --navi-backdrop-close-backdrop-filter: none;\n /* backdropVariant=\"discrete\": the popup still catches every outside\n click, it just stops announcing that it did. For an affordance one\n reaches past rather than through — the dim is there to mark the layer,\n not to push the page behind it away. */\n --navi-backdrop-discrete-background: rgba(0, 0, 0, 0.02);\n --navi-backdrop-discrete-backdrop-filter: none;\n /* \"capture\" means the rest of the page is fully non-interactive —\n blurred, not just dimmed, so it reads as clearly secondary and\n pulls visual focus onto the popover's own content. */\n --navi-backdrop-capture-background: rgb(255 255 255 / 0.08);\n --navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);\n\n /* Link colors. They live here rather than only on .navi_link because a\n var declared on the element itself always beats the same var inherited\n from an ancestor: a page setting --link-color-pressed on :root would\n never reach a link. These :root tokens are the theme-level surface;\n --link-color-* stays the per-link (or per-subtree) override. */\n --navi-link-color: rgb(0, 0, 238);\n /* --navi-link-color-visited is intentionally left undefined: by default a\n visited link is derived from whatever --link-color ended up being on\n the link itself (see link.jsx), which a :root token cannot see. Set it\n here from an app to pin one visited color for every link. */\n --navi-link-color-pressed: red;\n --navi-link-current-indicator-color: rgb(205, 52, 37);\n\n --navi-selection-border-color: #0078d4;\n --navi-selection-background-color: #eaf1fd;\n /* Accent color — used for call-to-action buttons and selected list items.\n Override this single variable to apply a consistent brand color across\n all components that need to stand out. */\n --navi-accent-color: rgb(3, 30, 60);\n /* The surface a control's content sits on — the \"paper\" behind the text.\n Fields use it whenever they need a solid background (their default\n background, a transparent field being edited, …). */\n --navi-surface-color: light-dark(#ffffff, #1c1c1e);\n /* What is written on that paper. The browser's own text color rather\n than a literal, so it follows color-scheme the way the paper does. */\n --navi-surface-text-color: CanvasText;\n /* The plane the app's own frame is painted on — a top bar, a side menu, a\n toolbar: everything that surrounds a screen rather than being in it.\n A step away from the paper, because a bar painted in the paper's own\n color on a page of that color is not a bar, it is a line. Derived from\n the paper rather than written as a literal so it follows the theme by\n construction: a step toward the dark on a light page, toward the light\n on a dark one. Reached as background=\"chrome\" on a Box. */\n --navi-chrome-color: color-mix(\n in srgb,\n var(--navi-surface-color) 94%,\n var(--navi-color-dark)\n );\n /* The line that separates two regions of one surface — a scrolling area's\n header from what scrolls under it, for instance. Not a border: the\n separation belongs to the layout, not to the box that draws it. */\n --navi-separator-color-default: #d1d9e0;\n --navi-color-white: white;\n --navi-color-dark: rgb(55, 60, 69);\n\n --navi-info-color-light: #eaf6fc;\n --navi-info-color: #376cc2;\n --navi-success-color-light: #ecf9ef;\n --navi-success-color: #50c464;\n --navi-warning-color-light: #fdf6e3;\n --navi-warning-color: #f19c05;\n --navi-error-color-light: #fcebed;\n --navi-error-color: #eb364b;\n\n --navi-xxs: 0.125em; /* = 2px at 16px base */\n --navi-xs: 0.25em; /* = 4px at 16px base */\n --navi-s: 0.5em; /* = 8px at 16px base */\n --navi-m: 1em; /* = 16px at 16px base (base font size) */\n --navi-l: 1.5em; /* = 24px at 16px base */\n --navi-xl: 2em; /* = 32px at 16px base */\n --navi-xxl: 3em; /* 48px at 16px base */\n\n --navi-typo-xxs: 0.625rem; /* 10px at 16px base */\n --navi-typo-xs: 0.75rem; /* 12px at 16px base */\n --navi-typo-s: 0.875rem; /* 14px at 16px base */\n --navi-typo-m: 1rem; /* 16px at 16px base (base font size) */\n --navi-typo-l: 1.125rem; /* 18px at 16px base */\n --navi-typo-xl: 1.25rem; /* 20px at 16px base */\n --navi-typo-xxl: 1.5rem; /* 24px at 16px base */\n\n /* The line every text is written on, controls included. A number\n rather than \"normal\", because of the emoji: its box is taller than a\n letter's, so under \"normal\" the one line carrying one stands taller\n than the lines around it, and under a tighter line its top is cut.\n 1.25 is where neither happens — 1 clips the glyph, 1.5 spaces the rows\n out more than reading them asks for — which also makes it the floor\n for an app that displays what people typed.\n The document is written on it; the components that come with a line of\n their own from the browser (Button, Input, Textarea, Select) are handed\n it by name. One number everywhere is what keeps a value on the same\n line whether it is typed in a field or drawn in the page.\n See docs/typography.md. */\n --navi-line-height: 1.25;\n /* The face everything is written in, controls included — they inherit\n it rather than carrying one of their own, so an app names its typeface\n once, wherever it wants it to start: on the document, on a section, on\n a popup opened with font=\"…\". The browser's own control face rather\n than the document's default, so that a page saying nothing gets its\n text and its buttons in one face instead of two.\n Layered like the line above, so the app's own rule wins. */\n font-family: ${controlDefaultFontFamily};\n /* Layered: the document's line is what navi writes on when the app says\n nothing, so the app's own body rule wins. */\n line-height: var(--navi-line-height);\n /* The same line for a control, snapped to the pixel. The browser lays a\n line out at its exact height but paints the glyph on a pixel row: at\n the default control size (13.333px) the line is 16.666px, and the\n two-thirds that do not fit go entirely under the glyph, which then\n sits a pixel above the middle of its field. A whole number of pixels\n has no remainder to put anywhere. Written with em so it resolves on\n the control that uses it, at that control's own size. */\n --navi-control-line-height: round(\n calc(var(--navi-line-height) * 1em),\n 1px\n );\n\n /* Color keywords.\n primary: the ink of the paper, at full strength. An absolute\n rather than currentColor: it is what brings a run back\n to plain text inside a muted one (a value in a secondary\n label). Themed through --navi-surface-text-color.\n secondary: supporting text, captions, less important labels\n emphasis: reinforce meaning, make content stand out more\n discrete: unobtrusive elements that shouldn't compete for attention\n hint: barely-there color, watermarks, ghost placeholders\n The last four mix currentColor toward transparent or black, so they\n follow whatever ink a container writes in: a dark card sets color and\n nothing else. The share of ink in each is the theme's knob, a -mix\n token read wherever the formulas are declared — here and on each\n surface that re-declares them (rules below) — so a ratio set on :root\n reaches the page and its popups alike. Set on a container it reaches\n none of that container's own text, a var() resolving where its custom\n property is declared; a container that wants one keyword otherwise\n for ITS paper (a white at 88% where 80% of white reads too faint on a\n colored resin) pins the keyword itself, and that value stops at the\n next surface. */\n --navi-color-secondary-mix: 80%;\n --navi-color-emphasis-mix: 50%;\n --navi-color-discrete-mix: 60%;\n --navi-color-hint-mix: 25%;\n --navi-color-primary: var(--navi-surface-text-color);\n\n /* What a control shows while it holds nothing: the ::placeholder of an\n input, the empty value slot of a picker. One place for the whole app:\n\n :root {\n --navi-placeholder-color: #8a94a6;\n --navi-placeholder-font-style: italic;\n }\n */\n --navi-placeholder-color: var(--navi-color-discrete);\n --navi-placeholder-font-style: normal;\n }\n\n /* A surface is a new paper: it writes in an ink of its own, and the\n keywords below are computed against that ink rather than the\n container's. A popup writes in --navi-popup-color. */\n .navi_popover,\n .navi_dialog {\n --navi-color-primary: var(--navi-popup-color);\n }\n /* A callout writes in the UA's own ink: callout.js sets color: revert on\n a [popover] element, which the UA styles CanvasText. */\n .navi_callout {\n --navi-color-primary: CanvasText;\n }\n\n /* The keywords derived from the ink, declared on every paper — :root, and\n each surface that is a new one — so what a container pinned for its own\n paper stops at the surface. The -mix ratios (see :root) are read where\n this is declared, and each paper resolves currentColor as its own. On\n the element itself, so a surface beats the container's value whatever\n the layer; an app that wants a surface to keep its container's ink says\n so on the surface, unlayered, and wins in turn. */\n :root,\n .navi_popover,\n .navi_dialog,\n .navi_callout {\n --navi-color-secondary: color-mix(\n in srgb,\n currentColor var(--navi-color-secondary-mix),\n transparent\n );\n --navi-color-emphasis: color-mix(\n in srgb,\n currentColor var(--navi-color-emphasis-mix),\n black\n );\n --navi-color-discrete: color-mix(\n in srgb,\n currentColor var(--navi-color-discrete-mix),\n transparent\n );\n --navi-color-hint: color-mix(\n in srgb,\n currentColor var(--navi-color-hint-mix),\n transparent\n );\n }\n }\n\n /* Hidden appearance */\n input[navi-visually-hidden],\n button[navi-visually-hidden],\n div[navi-visually-hidden] {\n position: absolute;\n top: 0;\n left: 0;\n z-index: -1;\n /* Important to take full size so that scrollIntoView work as expected */\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n white-space: nowrap;\n border: 0;\n clip-path: inset(50%);\n appearance: none;\n overflow: hidden;\n\n &[navi-debug] {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n clip-path: none;\n appearance: auto;\n }\n }\n`;\nimport.meta.css = css;\n\neffect(() => {\n document.documentElement.style.setProperty(\n \"--navi-vvw\",\n `${visualViewportWidthSignal.value}px`,\n );\n document.documentElement.style.setProperty(\n \"--navi-vvh\",\n `${visualViewportHeightSignal.value}px`,\n );\n});\n\n// Placement follows the same rectangle the size caps above describe: the JS\n// that positions popups (pickPositionRelativeTo in @jsenv/dom) computes\n// against the visual viewport narrowed by the app's own bands, so a dialog\n// centers on the app column and a side panel docks flush against the app's\n// edge rather than the window's. Here rather than in each component, for the\n// same reason as virtual_keyboard.js above: one decision about the whole\n// window.\nsetPlacementViewportInsets(getAppInsets);\n"
16
+ "/**\n * Regroup CSS vars that makes sense to share across all navi components.\n */\nimport { setPlacementViewportInsets } from \"@jsenv/dom\";\nimport { effect } from \"@preact/signals\";\n\nimport {\n getAppInsets,\n visualViewportHeightSignal,\n visualViewportWidthSignal,\n} from \"./layout/responsive.js\";\nimport \"./layout/safe_area.js\";\n// Side-effect import: turns the on-screen keyboard into something that\n// overlays the app instead of resizing the viewport, which is navi's default\n// (see that module for why, and safe_area.js's own\n// --navi-keyboard-inset-bottom for what then reads the geometry). Here rather\n// than in each component: it is one decision about the whole window, and this\n// is the module that already makes those.\nimport \"./layout/virtual_keyboard.js\";\n\nconst button = document.createElement(\"button\");\nbutton.style.display = \"none\";\ndocument.body.appendChild(button);\nconst computedStyle = getComputedStyle(button);\nconst controlDefaultFontFamily = computedStyle.fontFamily;\nconst controlDefaultFontSize = computedStyle.fontSize;\ndocument.body.removeChild(button);\n\nconst css = /* css */ `\n @layer navi {\n :root {\n /* Overridden at runtime with precise VisualViewport pixel values so that dvw/dvh \n (which don't track the virtual keyboard dimensions) are never used in practice on supported browsers. */\n --navi-vvw: 100dvw;\n --navi-vvh: 100dvh;\n\n /* What navi treats as \"the screen\" when it sizes something that escapes\n normal flow (a dialog in the top layer, a popover, anything built on\n them): the app's own rectangle, which is the visual viewport minus the\n bands an app asks for. An app that never spans the whole window says so\n ONCE, without ever naming a component:\n\n :root {\n --navi-app-max-width: 600px;\n }\n\n In pixels — see readAppMax in layout/responsive.js for why.\n\n Typically an app simulating a handheld screen: a column centered in a\n wide window with bands on the sides. A dialog is in the top layer, so\n it answers to the viewport, not to that column — left alone it would\n paint 1500px of modal over a 600px app. Declaring the ceiling here is\n what makes the app's own width apply everywhere, including on top.\n\n It stays a ceiling and nothing else: on a screen narrower than the app\n it never binds, so popups keep shrinking with the phone, and the gap\n each popup keeps with the edges (marginWithContainer) is subtracted\n from it as before. A single popup that genuinely needs more can still\n raise its own maxWidth/maxHeight prop.\n\n Read from the insets rather than as a min() of its own so that the\n width and the placement come from ONE description of where the app is\n (see layout/safe_area.js). Placement follows the same rectangle: see\n setPlacementViewportInsets at the bottom of this file. */\n --navi-app-width: calc(\n var(--navi-vvw) - var(--navi-app-inset-left) - var(\n --navi-app-inset-right\n )\n );\n --navi-app-height: calc(\n var(--navi-vvh) - var(--navi-app-inset-top) - var(\n --navi-app-inset-bottom\n )\n );\n\n --navi-focus-outline-width: 2px;\n --navi-focus-outline-color: light-dark(#4476ff, #3b82f6);\n --navi-loader-color: light-dark(#355fcc, #3b82f6);\n /* The grey rectangle a mobile browser flashes under a finger: we draw\n what a press does ourselves (background, ring, chevron), and the\n browser's rectangle is a square painted over rounded corners.\n Declared here alone because the property is inherited: :root covers\n every element, in and out of navi, so nothing has to remember it.\n Set the token to a color to get the flash back, on the whole app or\n on one subtree. */\n -webkit-tap-highlight-color: var(--navi-tap-highlight-color);\n --navi-tap-highlight-color: transparent;\n\n /* A face of their own for the controls, and nothing else on the page.\n Undeclared here on purpose: controls write in inherit, so what a\n container says reaches the button, the field and the picker inside it,\n and a value on :root would resolve here and wall that off again. Set\n it to give the controls their own face, on the whole app or on one\n subtree:\n\n :root {\n --navi-control-font-family: system-ui;\n }\n */\n --navi-control-font-size: ${controlDefaultFontSize};\n --navi-control-border-radius: 2px;\n /* A checkbox is only ~1em wide: applying a control radius meant for\n buttons/inputs (say 8px) would turn it into a circle and make it read\n as a radio. So it follows the control radius but capped at a quarter\n of its own size. Override this token alone to opt out of the cap. */\n --navi-checkbox-border-radius: min(\n var(--navi-control-border-radius),\n 0.25em\n );\n /* The color a control uses to say \"this one is on\": a checked checkbox,\n a checked radio, an enabled switch. Kept apart from --navi-accent-color\n (the brand color for CTA and selection) because a control that is on\n must stay readable as a control, which usually means a brighter, more\n saturated color than a brand accent.\n The light value matches the browser's own accent so a navi control and\n a native one can sit side by side without reading as two blues. */\n --navi-control-accent-color: light-dark(rgb(24, 117, 255), #3b82f6);\n --navi-control-border-width: 1px;\n --navi-control-border-color: light-dark(#767676, #8e8e93);\n --navi-control-padding-x-default: 2px;\n --navi-control-padding-y-default: 1px;\n /* Global padding defaults — override these to change all button paddings. */\n /* Use --button-padding, --button-padding-x, --button-padding-y for per-button overrides. */\n --navi-button-padding-x-default: 6px;\n --navi-button-padding-y-default: 1px;\n /* Read by Button, and by whatever is drawn as one (a picker under\n variant=\"button\"), so the two keep the same surface. */\n --navi-button-background-color: light-dark(#f3f4f6, #2d3748);\n\n /* What a callout says about its message, as a color — and what draws\n as one outside the callout too (the icon a picker in callout mode\n presses, see CalloutStatusIcon). */\n --navi-callout-success-color: #4caf50;\n --navi-callout-info-color: #2196f3;\n --navi-callout-warning-color: #ff9800;\n --navi-callout-error-color: #f44336;\n /* No status: the message is an aside, said without an icon and framed\n like a control rather than like a warning. */\n --navi-callout-neutral-color: var(--navi-control-border-color);\n /* For list item we need slightly more padding to be able to see radio/checkbox outline */\n --navi-list-item-padding-x-default: 4px;\n --navi-list-item-padding-y-default: 1px;\n /* default */\n --navi-picker-padding-x-default: var(--navi-control-padding-x-default);\n --navi-picker-padding-y-default: var(--navi-control-padding-y-default);\n\n --navi-popup-border-radius: 8px;\n --navi-popup-border-color: light-dark(#d0d0d0, #3b3b3b);\n --navi-popup-box-shadow:\n 0 4px 8px rgba(0, 0, 0, 0.08), 0 12px 40px rgba(0, 0, 0, 0.22);\n /* A popup's paper is the same paper as everything else's: derived from\n the surface token so one override themes fields, layouts and popups\n together. Still its own token, so popups alone can be re-papered. */\n --navi-popup-background-color: var(--navi-surface-color);\n /* The ink written on that paper. A popup declares both on itself (see\n dialog.jsx / popover.jsx): it is a new surface, so it does not write\n in whatever the container it was declared in writes in. */\n --navi-popup-color: var(--navi-surface-text-color);\n /* A backdrop's paint is a colour AND a filter, one pair per kind of\n backdrop: how much the page behind must stop competing for the eye is\n a different question from what an outside click does, so a backdrop\n that closes on that click can blur too. Only \"capture\" blurs by\n default. */\n --navi-backdrop-close-background: rgba(0, 0, 0, 0.08);\n --navi-backdrop-close-backdrop-filter: none;\n /* backdropVariant=\"discrete\": the popup still catches every outside\n click, it just stops announcing that it did. For an affordance one\n reaches past rather than through — the dim is there to mark the layer,\n not to push the page behind it away. */\n --navi-backdrop-discrete-background: rgba(0, 0, 0, 0.02);\n --navi-backdrop-discrete-backdrop-filter: none;\n /* \"capture\" means the rest of the page is fully non-interactive —\n blurred, not just dimmed, so it reads as clearly secondary and\n pulls visual focus onto the popover's own content. */\n --navi-backdrop-capture-background: rgb(255 255 255 / 0.08);\n --navi-backdrop-capture-backdrop-filter: blur(30px) saturate(180%);\n /* animation=\"growing\": the popup is what the anchor became, so the page\n it came out of is not a context to keep readable — it is the thing the\n movement leaves. A card, a plan, a picture grown to be looked at wants\n nothing legible competing with it, and a document still crisp behind a\n box that is still travelling reads as two movements at once. Dark in\n both schemes on purpose: what withdraws here is the page itself, the\n way it does around a photo, not a surface tinted to match a theme. */\n --navi-backdrop-grow-background: rgb(6 10 20 / 88%);\n --navi-backdrop-grow-backdrop-filter: blur(4px);\n\n /* Link colors. They live here rather than only on .navi_link because a\n var declared on the element itself always beats the same var inherited\n from an ancestor: a page setting --link-color-pressed on :root would\n never reach a link. These :root tokens are the theme-level surface;\n --link-color-* stays the per-link (or per-subtree) override. */\n --navi-link-color: rgb(0, 0, 238);\n /* --navi-link-color-visited is intentionally left undefined: by default a\n visited link is derived from whatever --link-color ended up being on\n the link itself (see link.jsx), which a :root token cannot see. Set it\n here from an app to pin one visited color for every link. */\n --navi-link-color-pressed: red;\n --navi-link-current-indicator-color: rgb(205, 52, 37);\n\n --navi-selection-border-color: #0078d4;\n --navi-selection-background-color: #eaf1fd;\n /* Accent color — used for call-to-action buttons and selected list items.\n Override this single variable to apply a consistent brand color across\n all components that need to stand out. */\n --navi-accent-color: rgb(3, 30, 60);\n /* The surface a control's content sits on — the \"paper\" behind the text.\n Fields use it whenever they need a solid background (their default\n background, a transparent field being edited, …). */\n --navi-surface-color: light-dark(#ffffff, #1c1c1e);\n /* What is written on that paper. The browser's own text color rather\n than a literal, so it follows color-scheme the way the paper does. */\n --navi-surface-text-color: CanvasText;\n /* The plane the app's own frame is painted on — a top bar, a side menu, a\n toolbar: everything that surrounds a screen rather than being in it.\n A step away from the paper, because a bar painted in the paper's own\n color on a page of that color is not a bar, it is a line. Derived from\n the paper rather than written as a literal so it follows the theme by\n construction: a step toward the dark on a light page, toward the light\n on a dark one. Reached as background=\"chrome\" on a Box. */\n --navi-chrome-color: color-mix(\n in srgb,\n var(--navi-surface-color) 94%,\n var(--navi-color-dark)\n );\n /* The line that separates two regions of one surface — a scrolling area's\n header from what scrolls under it, for instance. Not a border: the\n separation belongs to the layout, not to the box that draws it. */\n --navi-separator-color-default: #d1d9e0;\n --navi-color-white: white;\n --navi-color-dark: rgb(55, 60, 69);\n\n --navi-info-color-light: #eaf6fc;\n --navi-info-color: #376cc2;\n --navi-success-color-light: #ecf9ef;\n --navi-success-color: #50c464;\n --navi-warning-color-light: #fdf6e3;\n --navi-warning-color: #f19c05;\n --navi-error-color-light: #fcebed;\n --navi-error-color: #eb364b;\n\n --navi-xxs: 0.125em; /* = 2px at 16px base */\n --navi-xs: 0.25em; /* = 4px at 16px base */\n --navi-s: 0.5em; /* = 8px at 16px base */\n --navi-m: 1em; /* = 16px at 16px base (base font size) */\n --navi-l: 1.5em; /* = 24px at 16px base */\n --navi-xl: 2em; /* = 32px at 16px base */\n --navi-xxl: 3em; /* 48px at 16px base */\n\n --navi-typo-xxs: 0.625rem; /* 10px at 16px base */\n --navi-typo-xs: 0.75rem; /* 12px at 16px base */\n --navi-typo-s: 0.875rem; /* 14px at 16px base */\n --navi-typo-m: 1rem; /* 16px at 16px base (base font size) */\n --navi-typo-l: 1.125rem; /* 18px at 16px base */\n --navi-typo-xl: 1.25rem; /* 20px at 16px base */\n --navi-typo-xxl: 1.5rem; /* 24px at 16px base */\n\n /* The line every text is written on, controls included. A number\n rather than \"normal\", because of the emoji: its box is taller than a\n letter's, so under \"normal\" the one line carrying one stands taller\n than the lines around it, and under a tighter line its top is cut.\n 1.25 is where neither happens — 1 clips the glyph, 1.5 spaces the rows\n out more than reading them asks for — which also makes it the floor\n for an app that displays what people typed.\n The document is written on it; the components that come with a line of\n their own from the browser (Button, Input, Textarea, Select) are handed\n it by name. One number everywhere is what keeps a value on the same\n line whether it is typed in a field or drawn in the page.\n See docs/typography.md. */\n --navi-line-height: 1.25;\n /* The face everything is written in, controls included — they inherit\n it rather than carrying one of their own, so an app names its typeface\n once, wherever it wants it to start: on the document, on a section, on\n a popup opened with font=\"…\". The browser's own control face rather\n than the document's default, so that a page saying nothing gets its\n text and its buttons in one face instead of two.\n Layered like the line above, so the app's own rule wins. */\n font-family: ${controlDefaultFontFamily};\n /* Layered: the document's line is what navi writes on when the app says\n nothing, so the app's own body rule wins. */\n line-height: var(--navi-line-height);\n /* The same line for a control, snapped to the pixel. The browser lays a\n line out at its exact height but paints the glyph on a pixel row: at\n the default control size (13.333px) the line is 16.666px, and the\n two-thirds that do not fit go entirely under the glyph, which then\n sits a pixel above the middle of its field. A whole number of pixels\n has no remainder to put anywhere. Written with em so it resolves on\n the control that uses it, at that control's own size. */\n --navi-control-line-height: round(\n calc(var(--navi-line-height) * 1em),\n 1px\n );\n\n /* Color keywords.\n primary: the ink of the paper, at full strength. An absolute\n rather than currentColor: it is what brings a run back\n to plain text inside a muted one (a value in a secondary\n label). Themed through --navi-surface-text-color.\n secondary: supporting text, captions, less important labels\n emphasis: reinforce meaning, make content stand out more\n discrete: unobtrusive elements that shouldn't compete for attention\n hint: barely-there color, watermarks, ghost placeholders\n The last four mix currentColor toward transparent or black, so they\n follow whatever ink a container writes in: a dark card sets color and\n nothing else. The share of ink in each is the theme's knob, a -mix\n token read wherever the formulas are declared — here and on each\n surface that re-declares them (rules below) — so a ratio set on :root\n reaches the page and its popups alike. Set on a container it reaches\n none of that container's own text, a var() resolving where its custom\n property is declared; a container that wants one keyword otherwise\n for ITS paper (a white at 88% where 80% of white reads too faint on a\n colored resin) pins the keyword itself, and that value stops at the\n next surface. */\n --navi-color-secondary-mix: 80%;\n --navi-color-emphasis-mix: 50%;\n --navi-color-discrete-mix: 60%;\n --navi-color-hint-mix: 25%;\n --navi-color-primary: var(--navi-surface-text-color);\n\n /* What a control shows while it holds nothing: the ::placeholder of an\n input, the empty value slot of a picker. One place for the whole app:\n\n :root {\n --navi-placeholder-color: #8a94a6;\n --navi-placeholder-font-style: italic;\n }\n */\n --navi-placeholder-color: var(--navi-color-discrete);\n --navi-placeholder-font-style: normal;\n }\n\n /* A surface is a new paper: it writes in an ink of its own, and the\n keywords below are computed against that ink rather than the\n container's. A popup writes in --navi-popup-color. */\n .navi_popover,\n .navi_dialog {\n --navi-color-primary: var(--navi-popup-color);\n }\n /* A callout writes in the UA's own ink: callout.js sets color: revert on\n a [popover] element, which the UA styles CanvasText. */\n .navi_callout {\n --navi-color-primary: CanvasText;\n }\n\n /* The keywords derived from the ink, declared on every paper — :root, and\n each surface that is a new one — so what a container pinned for its own\n paper stops at the surface. The -mix ratios (see :root) are read where\n this is declared, and each paper resolves currentColor as its own. On\n the element itself, so a surface beats the container's value whatever\n the layer; an app that wants a surface to keep its container's ink says\n so on the surface, unlayered, and wins in turn. */\n :root,\n .navi_popover,\n .navi_dialog,\n .navi_callout {\n --navi-color-secondary: color-mix(\n in srgb,\n currentColor var(--navi-color-secondary-mix),\n transparent\n );\n --navi-color-emphasis: color-mix(\n in srgb,\n currentColor var(--navi-color-emphasis-mix),\n black\n );\n --navi-color-discrete: color-mix(\n in srgb,\n currentColor var(--navi-color-discrete-mix),\n transparent\n );\n --navi-color-hint: color-mix(\n in srgb,\n currentColor var(--navi-color-hint-mix),\n transparent\n );\n }\n }\n\n /* Hidden appearance */\n input[navi-visually-hidden],\n button[navi-visually-hidden],\n div[navi-visually-hidden] {\n position: absolute;\n top: 0;\n left: 0;\n z-index: -1;\n /* Important to take full size so that scrollIntoView work as expected */\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n white-space: nowrap;\n border: 0;\n clip-path: inset(50%);\n appearance: none;\n overflow: hidden;\n\n &[navi-debug] {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n clip-path: none;\n appearance: auto;\n }\n }\n`;\nimport.meta.css = css;\n\neffect(() => {\n document.documentElement.style.setProperty(\n \"--navi-vvw\",\n `${visualViewportWidthSignal.value}px`,\n );\n document.documentElement.style.setProperty(\n \"--navi-vvh\",\n `${visualViewportHeightSignal.value}px`,\n );\n});\n\n// Placement follows the same rectangle the size caps above describe: the JS\n// that positions popups (pickPositionRelativeTo in @jsenv/dom) computes\n// against the visual viewport narrowed by the app's own bands, so a dialog\n// centers on the app column and a side panel docks flush against the app's\n// edge rather than the window's. Here rather than in each component, for the\n// same reason as virtual_keyboard.js above: one decision about the whole\n// window.\nsetPlacementViewportInsets(getAppInsets);\n"
17
17
  ],
18
18
  "names": [],
19
- "mappings": "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"
19
+ "mappings": "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"
20
20
  }
@@ -4910,8 +4910,15 @@ const onUIStateControllerCreated = (uiStateController) => {
4910
4910
  const onUIStateControllerDestroyed = (uiStateController) => {
4911
4911
  const { id, name, controlType } = uiStateController;
4912
4912
  if (id) {
4913
- controllersById.delete(id);
4914
- naviJsRegistry.delete(id);
4913
+ // Only the controller the id currently points at may take the entry away:
4914
+ // when two controls share an id, the one leaving would otherwise unregister
4915
+ // the one staying (see the warning in onUIStateControllerCreated), and the
4916
+ // same holds while a control is being replaced by its successor, which
4917
+ // registers during its render, before this cleanup runs.
4918
+ if (controllersById.get(id) === uiStateController) {
4919
+ controllersById.delete(id);
4920
+ naviJsRegistry.delete(id);
4921
+ }
4915
4922
  }
4916
4923
  const proxyFor = uiStateController.props["navi-control-proxy-for"];
4917
4924
  if (proxyFor) {
@@ -34723,17 +34730,21 @@ const useUIStateController = (
34723
34730
  if (el) {
34724
34731
  el.__uiStateController__ = controller;
34725
34732
  }
34726
- // Re-register so the radio registry stays in sync when props.ref changes
34727
- // identity (e.g. across a Suspense boundary). The render-phase call in
34728
- // control_hooks.jsx handles the initial mount; this call handles re-runs.
34729
- onUIStateControllerCreated(controller);
34730
34733
  return () => {
34731
34734
  if (el && el.__uiStateController__ === controller) {
34732
34735
  delete el.__uiStateController__;
34733
34736
  }
34734
- onUIStateControllerDestroyed(controller);
34735
34737
  };
34736
34738
  }, [controllerRef]);
34739
+ // The registry entry lives as long as the component does. Registration is
34740
+ // done by the render (see useInteractiveProps); this cleanup is the only
34741
+ // thing that takes the entry away, so it must not follow the ref: a ref
34742
+ // identity change would run it right after the render that registered.
34743
+ useLayoutEffect(() => {
34744
+ return () => {
34745
+ onUIStateControllerDestroyed(controller);
34746
+ };
34747
+ }, []);
34737
34748
 
34738
34749
  const { parentUIStateController: parentController } = scope;
34739
34750
  useLayoutEffect(() => {
@@ -37704,6 +37715,15 @@ const useInteractiveProps = (props, {
37704
37715
  const debugAction = useDebugAction();
37705
37716
  const debugInteraction = useDebugInteraction();
37706
37717
  const debugFocus = useDebugFocus();
37718
+
37719
+ // The very instance a run of this control is using, held in a SIGNAL because
37720
+ // the busy state below follows it: it is captured at navi_action_start and
37721
+ // released when the run settles (see onnavi_action_start).
37722
+ const runningActionSignalRef = useRef(null);
37723
+ if (runningActionSignalRef.current === null) {
37724
+ runningActionSignalRef.current = signal(null);
37725
+ }
37726
+ uiStateController.runningActionSignal = runningActionSignalRef.current;
37707
37727
  {
37708
37728
  const {
37709
37729
  autoFocus,
@@ -37742,7 +37762,15 @@ const useInteractiveProps = (props, {
37742
37762
  const isActionRequesterComputed = useMemo(() => parentActionRequesterSignal ? computed(() => parentActionRequesterSignal.value === ref.current) : null, [parentActionRequesterSignal]);
37743
37763
  const parallelGuard = useContext(ParallelGuardContext);
37744
37764
  const parentAction = useContext(ActionContext);
37745
- const actionStatus = useActionStatus(boundAction);
37765
+ // The instance the run in flight is using when there is one, and only
37766
+ // then boundAction: boundAction can be a proxy following the UI state
37767
+ // signal, and that state moves while a run is out — a popup content
37768
+ // unmounting takes the group's value with it. The proxy would resolve to
37769
+ // the instance for the new value, which is idle, and the control would
37770
+ // stop waiting on a run that is still going. What it waits on is the run
37771
+ // it started, from navi_action_start to its settlement.
37772
+ const runningAction = uiStateController.runningActionSignal.value;
37773
+ const actionStatus = useActionStatus(runningAction || boundAction);
37746
37774
  const networkPolicyReason = useNetworkPolicyReason();
37747
37775
  const {
37748
37776
  disabled,
@@ -38103,7 +38131,7 @@ const useInteractiveProps = (props, {
38103
38131
  // state has already moved to the new value by now — the proxy would
38104
38132
  // resolve to the instance for that new value, which is not the one
38105
38133
  // running.
38106
- uiStateController.runningAction?.abort(`superseded by a newer request on this control`);
38134
+ uiStateController.runningActionSignal.peek()?.abort(`superseded by a newer request on this control`);
38107
38135
  return;
38108
38136
  }
38109
38137
  debugAction(e, `executing action ${e.detail.action.callSource}`);
@@ -38125,14 +38153,14 @@ const useInteractiveProps = (props, {
38125
38153
  // this run (see the optimistic queue above), the state — and the
38126
38154
  // proxy's resolution — will have moved on.
38127
38155
  const runAction = e.detail.action;
38128
- uiStateController.runningAction = runAction.getCurrentAction?.() ?? runAction;
38156
+ uiStateController.runningActionSignal.value = runAction.getCurrentAction?.() ?? runAction;
38129
38157
  // Fires when the run's underlying work has settled — even for an
38130
38158
  // aborted run, whose promise is awaited to completion (see
38131
38159
  // performRun in actions.js) — which is exactly what "the server is
38132
38160
  // done with it" means, and therefore when the queued request may go.
38133
38161
  e.detail.addSideEffect(outcome => {
38134
38162
  uiStateController.actionInFlight = false;
38135
- uiStateController.runningAction = null;
38163
+ uiStateController.runningActionSignal.value = null;
38136
38164
  uiStateController.parallelGuard?.release(uiStateController);
38137
38165
  const queuedEvent = uiStateController.queuedActionAllowedEvent;
38138
38166
  if (!queuedEvent) {
@@ -38237,11 +38265,13 @@ const useInteractiveProps = (props, {
38237
38265
  // an action's own completion side effect — has to read the signal to get an
38238
38266
  // answer that is not one frame late (see BUSY_CONSTRAINT).
38239
38267
  uiStateController.boundAction = boundAction;
38268
+ // "This id is that controller", stated on every render: a Suspense boundary
38269
+ // parking a subtree runs every hook cleanup below it — the unregistration
38270
+ // included — while the controllers themselves survive, and readers look a
38271
+ // controller up by id while rendering, before any effect can run again.
38272
+ // Placed after controlHostProps is set, which the constraints read.
38273
+ onUIStateControllerCreated(uiStateController);
38240
38274
  if (firstRender) {
38241
- // Deferred from the factory so these run after controlHostProps is set.
38242
- // Constraints like READONLY_CONSTRAINT and findControlProxyTargetController
38243
- // read controlHostProps — calling these earlier would throw or produce wrong results.
38244
- onUIStateControllerCreated(uiStateController);
38245
38275
  uiStateController.rules.validation.checkValidity();
38246
38276
  }
38247
38277
  return [controlRootProps, controlHostProps];
@@ -58332,14 +58362,22 @@ const clipOf = (side, distance) => {
58332
58362
  // before the next one takes it (see releaseGrowInProgress).
58333
58363
  const NAME = "navi-popup-grow";
58334
58364
  const NAME_PROPERTY = "view-transition-name";
58335
- // Worn by the root for the length of the movement — what the CSS keys the
58336
- // page's own opt-out on, and the movement's only trace in the document.
58365
+ // Worn by the root for the length of the movement, saying which way it goes
58366
+ // ("opening" | "closing") — what the CSS keys the page's own opt-out and the
58367
+ // clip near the anchor's end on, and the movement's only trace in the document.
58337
58368
  const ROOT_ATTRIBUTE = "data-navi-popup-grow";
58369
+ // What the two boxes are to each other (Dialog's `grow`: "box" | "scene"),
58370
+ // worn by the root too — it decides how each picture sits in the moving box.
58371
+ const KIND_ATTRIBUTE = "data-navi-popup-grow-kind";
58338
58372
  // Inside the popup, the one node that IS the anchor once it has grown.
58339
58373
  const TARGET_SELECTOR = "[data-grow]";
58340
58374
  // The popup's own animation duration, published on the root because the
58341
58375
  // ::view-transition tree hangs off it and inherits from nowhere else.
58342
58376
  const DURATION_PROPERTY = "--navi-popup-grow-duration";
58377
+ // The corners of the box being left, published the same way: the pictures are
58378
+ // clipped to the moving box (dialog.jsx), and a card with rounded corners must
58379
+ // not travel with square ones.
58380
+ const BORDER_RADIUS_PROPERTY = "--navi-popup-grow-border-radius";
58343
58381
 
58344
58382
  let releaseGrowInProgress = null;
58345
58383
 
@@ -58350,13 +58388,13 @@ let releaseGrowInProgress = null;
58350
58388
  * `opened` says which way: the box being left is the anchor when the popup is
58351
58389
  * opening and the popup when it is closing, and the arriving one is only known
58352
58390
  * once the change has been made (the content a popup grows into is built by
58353
- * that very change).
58391
+ * that very change). `grow` is Dialog's own prop of that name.
58354
58392
  */
58355
58393
  const growPopupFromAnchor = (
58356
58394
  popupEl,
58357
58395
  anchorElement,
58358
58396
  applyChange,
58359
- { opened },
58397
+ { opened, grow },
58360
58398
  ) => {
58361
58399
  const startViewTransition = ensureDocumentStartViewTransition();
58362
58400
  // A movement still wearing the name would make the name two elements wide,
@@ -58367,7 +58405,8 @@ const growPopupFromAnchor = (
58367
58405
  const elementLeaving = opened ? anchorElement : resolveGrowTarget(popupEl);
58368
58406
  const giveBackNameLeaving = wearGrowName(elementLeaving);
58369
58407
  const root = document.documentElement;
58370
- root.setAttribute(ROOT_ATTRIBUTE, "");
58408
+ root.setAttribute(ROOT_ATTRIBUTE, opened ? "opening" : "closing");
58409
+ root.setAttribute(KIND_ATTRIBUTE, grow);
58371
58410
  const duration = getComputedStyle(popupEl)
58372
58411
  .getPropertyValue("--popup-animation-duration")
58373
58412
  .trim();
@@ -58377,6 +58416,10 @@ const growPopupFromAnchor = (
58377
58416
  // browser's own pace.
58378
58417
  root.style.setProperty(DURATION_PROPERTY, duration);
58379
58418
  }
58419
+ const borderRadius = getComputedStyle(elementLeaving).borderRadius;
58420
+ if (borderRadius) {
58421
+ root.style.setProperty(BORDER_RADIUS_PROPERTY, borderRadius);
58422
+ }
58380
58423
 
58381
58424
  let giveBackNameArriving = null;
58382
58425
  const release = () => {
@@ -58387,7 +58430,9 @@ const growPopupFromAnchor = (
58387
58430
  giveBackNameLeaving();
58388
58431
  giveBackNameArriving?.();
58389
58432
  root.removeAttribute(ROOT_ATTRIBUTE);
58433
+ root.removeAttribute(KIND_ATTRIBUTE);
58390
58434
  root.style.removeProperty(DURATION_PROPERTY);
58435
+ root.style.removeProperty(BORDER_RADIUS_PROPERTY);
58391
58436
  };
58392
58437
  releaseGrowInProgress = release;
58393
58438
 
@@ -58733,6 +58778,15 @@ const css$E = /* css */`
58733
58778
  to filter). An entrance animation moves the dialog through scale and
58734
58779
  transform instead, which compose under it: see popup_css.js. */
58735
58780
 
58781
+ /* animation="growing" brings its own wall: the popup is the anchor
58782
+ continued, not a surface shown over a page that goes on being read (see
58783
+ navi_css_vars.js for the paint). Before the rules below, so everything
58784
+ said out loud — an outside press that captures, a backdropVariant, the
58785
+ two paint props — still wins over what the animation assumes. */
58786
+ &[data-growing] {
58787
+ --backdrop-background: var(--navi-backdrop-grow-background);
58788
+ --backdrop-filter: var(--navi-backdrop-grow-backdrop-filter);
58789
+ }
58736
58790
  &[data-pointer-interaction-outside="capture"] {
58737
58791
  --backdrop-background: var(--navi-backdrop-capture-background);
58738
58792
  --backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
@@ -58903,6 +58957,15 @@ const css$E = /* css */`
58903
58957
  --backdrop-background: var(--navi-backdrop-close-background);
58904
58958
  --backdrop-filter: var(--navi-backdrop-close-backdrop-filter);
58905
58959
  }
58960
+ /* animation="growing" brings its own wall: the popup is the anchor
58961
+ continued, not a surface shown over a page that goes on being read (see
58962
+ navi_css_vars.js for the paint). Before the rules below, so everything
58963
+ said out loud — an outside press that captures, a backdropVariant, the
58964
+ two paint props — still wins over what the animation assumes. */
58965
+ &[data-growing] {
58966
+ --backdrop-background: var(--navi-backdrop-grow-background);
58967
+ --backdrop-filter: var(--navi-backdrop-grow-backdrop-filter);
58968
+ }
58906
58969
  &[data-pointer-interaction-outside="capture"] {
58907
58970
  --backdrop-background: var(--navi-backdrop-capture-background);
58908
58971
  --backdrop-filter: var(--navi-backdrop-capture-backdrop-filter);
@@ -58951,6 +59014,73 @@ const css$E = /* css */`
58951
59014
  &::view-transition-new(navi-popup-grow) {
58952
59015
  animation-duration: var(--navi-popup-grow-duration, 0.25s);
58953
59016
  }
59017
+
59018
+ /* Each picture is drawn as wide as the box and as tall as it is, so a box
59019
+ that grows in height alone (a card keeping its width) would show the
59020
+ whole taller picture from the first frame, and the movement would read
59021
+ as a fade. Clipped to the box, the picture is uncovered as the box
59022
+ grows and covered back as it shrinks — behind the corners the box
59023
+ has in the page, published by popup_grow.js. */
59024
+ &::view-transition-image-pair(navi-popup-grow) {
59025
+ border-radius: var(--navi-popup-grow-border-radius, 0);
59026
+ overflow: clip;
59027
+ }
59028
+
59029
+ /* One scene through two frames (Dialog's grow="scene"): each picture
59030
+ covers the box, cropped around its centre, so what both frames show
59031
+ lands on itself. */
59032
+ &[data-navi-popup-grow-kind="scene"] {
59033
+ &::view-transition-old(navi-popup-grow),
59034
+ &::view-transition-new(navi-popup-grow) {
59035
+ height: 100%;
59036
+ object-fit: cover;
59037
+ }
59038
+ }
59039
+
59040
+ /* The anchor lives in the page, under the fixed bars; the popup lives in
59041
+ the top layer, over them. The pictures are painted above everything,
59042
+ bars included, so near the anchor's end of the movement the whole tree
59043
+ is clipped to the room between the bars — a thumbnail half under the
59044
+ top bar leaves from under it and comes back under it — and freed near
59045
+ the popup's end, where a tall dialog may stand over them. Clipped for
59046
+ the 65% of the time nearest the anchor: with the group's own ease that
59047
+ leaves the last 35% of the time to cover more than half of the trip,
59048
+ so the picture is out of the bars before the clip closes on it. */
59049
+ &::view-transition {
59050
+ animation: navi-popup-grow-clip var(--navi-popup-grow-duration, 0.25s)
59051
+ ease both;
59052
+ }
59053
+ &[data-navi-popup-grow="closing"]::view-transition {
59054
+ animation-direction: reverse;
59055
+ }
59056
+ }
59057
+ /* The bars sit at the app's own inset (fixed_bar.jsx), so the room they
59058
+ take starts there; both tokens are 0px where nothing takes any. */
59059
+ @keyframes navi-popup-grow-clip {
59060
+ 0%,
59061
+ 65% {
59062
+ clip-path: inset(
59063
+ calc(
59064
+ var(--navi-app-inset-top, 0px) +
59065
+ var(--navi-fixed-bar-space-top, 0px)
59066
+ )
59067
+ calc(
59068
+ var(--navi-app-inset-right, 0px) +
59069
+ var(--navi-fixed-bar-space-right, 0px)
59070
+ )
59071
+ calc(
59072
+ var(--navi-app-inset-bottom, 0px) +
59073
+ var(--navi-fixed-bar-space-bottom, 0px)
59074
+ )
59075
+ calc(
59076
+ var(--navi-app-inset-left, 0px) +
59077
+ var(--navi-fixed-bar-space-left, 0px)
59078
+ )
59079
+ );
59080
+ }
59081
+ 100% {
59082
+ clip-path: inset(0);
59083
+ }
58954
59084
  }
58955
59085
 
58956
59086
  ${surfaceTextCss}
@@ -59053,7 +59183,8 @@ const css$E = /* css */`
59053
59183
  * @param {"auto"|"discrete"|"invisible"} [props.backdropVariant="auto"] - How
59054
59184
  * visible the backdrop is, independently of what it does. `"auto"`: the
59055
59185
  * paint `pointerInteractionOutsideEffect` implies (dimmed for
59056
- * `"close"`/`"cancel"`, blurred glass for `"capture"`). `"discrete"`: a
59186
+ * `"close"`/`"cancel"`, blurred glass for `"capture"`), or the opaque wall
59187
+ * `animation="growing"` asks for. `"discrete"`: a
59057
59188
  * barely-there dim. `"invisible"`: fully transparent — a wall that is not
59058
59189
  * seen, still catching every press. This only changes how much the dialog
59059
59190
  * insists visually, never what an outside click does; whether there is a
@@ -59086,7 +59217,20 @@ const css$E = /* css */`
59086
59217
  * over it. It needs an anchor (whatever opened the dialog, or the `anchor`
59087
59218
  * prop) and grows into whatever inside the dialog carries `data-grow`, the
59088
59219
  * dialog itself when nothing does. `"auto"` never picks it: only the caller
59089
- * knows the two boxes are one object. See `popup_grow.js`.
59220
+ * knows the two boxes are one object. It also brings its own backdrop —
59221
+ * opaque and blurred (`--navi-backdrop-grow-*`), the page it came out of
59222
+ * being what the movement leaves rather than a context to keep readable;
59223
+ * `backdropVariant="discrete"` asks for the light wash back. See
59224
+ * `popup_grow.js`.
59225
+ * @param {"box"|"scene"} [props.grow="box"] - Under `animation="growing"`,
59226
+ * what the anchor and what it grows into are to each other, which decides
59227
+ * how their pictures sit in the box moving between them. `"box"`: one
59228
+ * object at two sizes — a card gaining fields. Each picture is drawn at the
59229
+ * box's width from its top edge, and a box growing in height uncovers more
59230
+ * of it: the header stays where it is, the rest extends. `"scene"`: one
59231
+ * scene through two frames — a thumbnail and the map it is cut from. Each
59232
+ * picture covers the box, cropped around its centre and never distorted, so
59233
+ * what both frames show lands on itself.
59090
59234
  * @param {string} [props.animationDuration] - Maps to
59091
59235
  * `--popup-animation-duration`.
59092
59236
  * @param {Element|{current: Element}|string} [props.anchor] - Never used for
@@ -59429,6 +59573,7 @@ const useDialogProps = props => {
59429
59573
  // once, held at that size while open. See this prop's own JSDoc above.
59430
59574
  sizing = "auto",
59431
59575
  animation,
59576
+ grow = "box",
59432
59577
  // Inert unless sizeFromAnchor below (see this file's top comment) —
59433
59578
  // Dialog's own positioning is never relative to it.
59434
59579
  anchor,
@@ -59652,7 +59797,8 @@ const useDialogProps = props => {
59652
59797
  return;
59653
59798
  }
59654
59799
  growPopupFromAnchor(dialogEl, anchorElement, applyChange, {
59655
- opened
59800
+ opened,
59801
+ grow
59656
59802
  });
59657
59803
  } : null;
59658
59804
 
@@ -60096,6 +60242,7 @@ const useDialogProps = props => {
60096
60242
  "animationDuration": rest.animationDuration,
60097
60243
  "data-pointer-interaction-outside": pointerInteractionOutsideEffect,
60098
60244
  "data-backdrop-variant": backdropVariant,
60245
+ "data-growing": growing ? "" : undefined,
60099
60246
  backdropColor,
60100
60247
  backdropFilter
60101
60248
  });
@@ -60131,6 +60278,11 @@ const useDialogProps = props => {
60131
60278
  // (and harmless for a local dialog, whose real backdrop element gets it
60132
60279
  // via backdropProps).
60133
60280
  "data-backdrop-variant": backdropVariant,
60281
+ // That this popup is the anchor continued rather than a surface shown
60282
+ // over the page, which is what its backdrop is painted from (see this
60283
+ // file's CSS). It cannot be read off navi-animation: growing arms no CSS
60284
+ // animation of its own, so that attribute is deliberately absent here.
60285
+ "data-growing": growing ? "" : undefined,
60134
60286
  // That this dialog has no wall, said in the DOM: the browser generates a
60135
60287
  // ::backdrop for the popover a wall-less top-layer dialog is shown as,
60136
60288
  // and the CSS above keys off this to leave it unpainted. Also the only
@@ -61979,6 +62131,7 @@ const css$C = /* css */`@layer navi {
61979
62131
  * popup meant to dock on a phone must not declare itself compact — see
61980
62132
  * `maxWidth` below.
61981
62133
  * @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
62134
+ * @param {"box"|"scene"} [props.grow] - **Dialog-only**, same guard.
61982
62135
  * @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
61983
62136
  * `Popover` have different own defaults (`"center"` vs. `"bottom"`),
61984
62137
  * deliberately not homogenized here (each reads best for its own typical
@@ -62085,6 +62238,7 @@ const Popup = props => {
62085
62238
  // in the dialog branch, so they never reach the popover element.
62086
62239
  dockedOnSmallTouchScreen,
62087
62240
  sizeFromAnchor,
62241
+ grow,
62088
62242
  ...rest
62089
62243
  } = props;
62090
62244
  const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
@@ -62103,6 +62257,7 @@ const Popup = props => {
62103
62257
  ...rest,
62104
62258
  dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
62105
62259
  sizeFromAnchor: sizeFromAnchor,
62260
+ grow: grow,
62106
62261
  maxWidth: maxWidth,
62107
62262
  pointerInteractionOutsideEffect: pointerInteractionOutsideEffect,
62108
62263
  backdrop: backdrop,
@@ -62562,19 +62717,26 @@ const PickerCustom = props => {
62562
62717
  commitUIStateAsAnswer(controller, closeEvent);
62563
62718
  }
62564
62719
  }
62565
- leaveExpanded({
62566
- isBack: closeEvent.detail.isCancel
62567
- });
62568
- // Reset so the next opening re-evaluates screen size
62569
- resetMode();
62570
62720
  const confirmEvent = confirmEventRef.current;
62571
62721
  confirmEventRef.current = null;
62572
62722
  if (confirmEvent && !closeEvent.detail.isCancel) {
62573
62723
  onConfirm?.(confirmEvent);
62574
62724
  }
62575
- // Last, after the value bookkeeping above: whoever listens reads the
62576
- // picker's value as it ends up — committed, or restored on a cancel.
62725
+ // After the value bookkeeping above — whoever listens reads the
62726
+ // picker's value as it ends up, committed or restored on a cancel —
62727
+ // and before leaveExpanded below, which is where the caller's
62728
+ // reaction runs for a Dialog too. A close that keeps goes back onto
62729
+ // the entry the popup was opened from while KEEPING the url as it
62730
+ // stands at that moment (see useNavState's leave()), so what onClose
62731
+ // spells into the address — a route param naming what was being
62732
+ // edited, cleared now that nothing is — has to be written before,
62733
+ // or the landing puts it back.
62577
62734
  onClose?.(closeEvent);
62735
+ leaveExpanded({
62736
+ isBack: closeEvent.detail.isCancel
62737
+ });
62738
+ // Reset so the next opening re-evaluates screen size
62739
+ resetMode();
62578
62740
  }
62579
62741
  };
62580
62742
  });
@@ -62968,6 +63130,7 @@ const PickerContentInsidePopup = props => {
62968
63130
  // its own width once lifted. Dialog's own `sizeFromAnchor`.
62969
63131
  dialogSizeFromAnchor,
62970
63132
  animation,
63133
+ grow,
62971
63134
  animationDuration,
62972
63135
  // mode="callout": what the callout says about what it holds, and paints
62973
63136
  // in its border and icon — "none" for a plain tooltip (see the callout
@@ -63046,6 +63209,7 @@ const PickerContentInsidePopup = props => {
63046
63209
  expandY: isPopover ? undefined : dialogExpandY,
63047
63210
  dockedOnSmallTouchScreen: isPopover ? undefined : dockedOnSmallTouchScreen,
63048
63211
  sizeFromAnchor: isPopover ? undefined : dialogSizeFromAnchor,
63212
+ grow: isPopover ? undefined : grow,
63049
63213
  children: jsx(PopupModeContext.Provider, {
63050
63214
  value: mode,
63051
63215
  children: children