@jsenv/navi 0.29.29 → 0.29.30

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.
@@ -3,7 +3,7 @@
3
3
  * using @jsenv/navi as intended.
4
4
  */
5
5
  import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, coarsePointerSignal } from "./jsenv_navi_side_effects.js";
6
- import { elementIsFocusable, createPubSub, dispatchInternalCustomEvent, dispatchCustomEvent, getElementSignature, findEvent, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, createIterableWeakSet, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, mergeTwoStyles, normalizeStyles, resolveCSSSize, hasCSSSizeUnit, resolveOklchLightness, contrastColor, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, startDragToTravel, scrollRoomTowards, findBefore, findAfter, initFocusGroup, scrollIntoViewScoped, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, stringifyStyle as stringifyStyle$1 } from "@jsenv/dom";
6
+ import { elementIsFocusable, createPubSub, dispatchInternalCustomEvent, dispatchCustomEvent, getElementSignature, findEvent, createValueEffect, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, startDragTo, createIterableWeakSet, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, mergeTwoStyles, normalizeStyles, resolveCSSSize, hasCSSSizeUnit, resolveOklchLightness, contrastColor, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, findBefore, findAfter, initFocusGroup, scrollIntoViewScoped, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, stringifyStyle as stringifyStyle$1 } from "@jsenv/dom";
7
7
  export { contrastColor, findEvent, startDragTo } from "@jsenv/dom";
8
8
  import { signal, computed, effect, batch, useSignal } from "@preact/signals";
9
9
  import { createContext, isValidElement, h, Fragment, render, toChildArray, options, cloneElement } from "preact";
@@ -11,8 +11,66 @@ import { useContext, useLayoutEffect, useCallback, useRef, useState, useEffect,
11
11
  import { jsx, jsxs, Fragment as Fragment$1 } from "preact/jsx-runtime";
12
12
  import { createValidity, parseDuration, durationContainsNaN, compareTwoDurations, durationToSeconds, durationToISOString } from "@jsenv/validity";
13
13
  export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
14
- import { createPortal, Suspense, forwardRef } from "preact/compat";
15
14
  import { prefixFirstAndIndentRemainingLines } from "@jsenv/humanize";
15
+ import { createPortal, Suspense, forwardRef } from "preact/compat";
16
+
17
+ installImportMetaCssBuild(import.meta);/**
18
+ * Every z-index navi can be seen competing on, in one place, ordered.
19
+ *
20
+ * The point is the overview: reading this file must be enough to know what
21
+ * paints over what, and adding a value here forces the question "against
22
+ * what?" to be answered before a number is written. These are not meant as a
23
+ * theming surface — override one only to get out of a conflict with an app
24
+ * that cannot move.
25
+ *
26
+ * What belongs here: anything whose stacking is decided against an element
27
+ * from ANOTHER component — a bar, a popup, a control raising itself above the
28
+ * neighbour it overlaps. A z-index that only orders a component's own parts
29
+ * (a shadow under its own content, a checkbox mark over its own box) stays a
30
+ * literal next to the rule that needs it: it says "above my sibling", nothing
31
+ * more, and routing it through a token would only dilute the overview.
32
+ *
33
+ * The bands are decades apart so a band can grow without reaching the next
34
+ * one, and so a value read in a devtools panel says which band it came from.
35
+ *
36
+ * Read docs/z_index.md first — DOM order and `isolation: isolate` come before
37
+ * any of these, and a number is the last resort, not the first tool.
38
+ */
39
+
40
+ const css$10 = /* css */`
41
+ @layer navi {
42
+ :root {
43
+ /* A control that overlaps its neighbours (the members of a Group share
44
+ one border along each seam) and has to paint in front of them while
45
+ the user is on it: its border color change, then its focus ring, which
46
+ a neighbour painted later would otherwise slice in half. Focus above
47
+ hover, since a focused member can sit next to a hovered one. */
48
+ --navi-z-index-control-hovered: 1;
49
+ --navi-z-index-control-focused: 2;
50
+
51
+ /* Kept stuck while something scrolls under it: a list header, the head
52
+ and foot of a side panel, a table's sticky cells. Above raised
53
+ controls — a control scrolling past must go under the header that
54
+ pins the column it belongs to, never over it. */
55
+ --navi-z-index-sticky: 10;
56
+
57
+ /* Pinned to the viewport, over the whole page: FixedBar. A decade of its
58
+ own and well above the sticky band, so that no sticky cell and no
59
+ hovered control can ever be seen crossing it. */
60
+ --navi-z-index-bar: 100;
61
+
62
+ /* Popups: Dialog/Popover with layer="local", their backdrop, and the
63
+ validation callouts. Above everything the page can produce, which is
64
+ the whole point of the gap — a popup never has to guess. Each opened
65
+ popup adds its stack order on top, so the last one opened wins.
66
+ Dialog/Popover with layer="top" use the browser top layer instead and
67
+ appear in no scale at all. */
68
+ --navi-z-index-popup: 1000;
69
+ --navi-z-index-callout: var(--navi-z-index-popup);
70
+ }
71
+ }
72
+ `;
73
+ import.meta.css = [css$10, "@jsenv/navi/src/navi_z_indexes.js"];
16
74
 
17
75
  const addIntoArray = (array, ...valuesToAdd) => {
18
76
  if (valuesToAdd.length === 1) {
@@ -7583,7 +7641,7 @@ const css$_ = /* css */`
7583
7641
  --callout-background-color: white;
7584
7642
  --callout-icon-color: black;
7585
7643
  --callout-padding: 8px;
7586
- --callout-z-index: 1000;
7644
+ --callout-z-index: var(--navi-z-index-callout);
7587
7645
  }
7588
7646
  }
7589
7647
 
@@ -11697,6 +11755,647 @@ const readNumberFromDom = (element, attribute, defaultValue) => {
11697
11755
  return value;
11698
11756
  };
11699
11757
 
11758
+ /**
11759
+ * The interactions the browser already has an event for.
11760
+ *
11761
+ * Nothing to detect: the name IS the event type, so this listens and says it
11762
+ * happened. Registered through the same door an application uses (see
11763
+ * interaction_registry.js) — navi has no private one.
11764
+ *
11765
+ * `contextmenu` is the only one that takes something away: the browser's own menu
11766
+ * would cover the answer to the request it is.
11767
+ */
11768
+
11769
+
11770
+ const NATIVE_TYPE_SET = new Set([
11771
+ "mousedown",
11772
+ "mouseup",
11773
+ "click",
11774
+ "dblclick",
11775
+ "contextmenu",
11776
+ ]);
11777
+
11778
+ defineInteractionDetector({
11779
+ name: "native",
11780
+ claims: (type) => NATIVE_TYPE_SET.has(type),
11781
+ setup: (element, trigger, { types }) => {
11782
+ const listeners = types.map((type) => {
11783
+ const listener = (nativeEvent) => {
11784
+ if (type === "contextmenu") {
11785
+ nativeEvent.preventDefault();
11786
+ }
11787
+ trigger(type, nativeEvent);
11788
+ };
11789
+ element.addEventListener(type, listener);
11790
+ return [type, listener];
11791
+ });
11792
+ return () => {
11793
+ for (const [type, listener] of listeners) {
11794
+ element.removeEventListener(type, listener);
11795
+ }
11796
+ };
11797
+ },
11798
+ });
11799
+
11800
+ installImportMetaCssBuild(import.meta);/**
11801
+ * What a press can turn out to be: a swipe, or a hold.
11802
+ *
11803
+ * One detector for both, because they dispute the SAME press and something has to
11804
+ * arbitrate them in one place — read apart, a hold that drifts three pixels both
11805
+ * opens the menu and starts putting the row away. A finger lands; it leaves
11806
+ * sideways (a swipe), it stays still (a hold), it lifts at once (a click, which is
11807
+ * not this detector's business).
11808
+ *
11809
+ * Naming the interactions in `interactions` rather than letting one callback read
11810
+ * the pointer is what lets this know, BEFORE the first pixel, which of them the
11811
+ * element takes. Two things depend on knowing that early:
11812
+ *
11813
+ * - **The arbitration between nested boxes.** A row swiped sideways lives inside a
11814
+ * container that travels sideways too, and the innermost box must take the axis
11815
+ * it walks — read from the DOM at the press, and only from there (see axesLeftBy
11816
+ * in @jsenv/dom). The axis comes from the name (`swipe_left` and `swipe_right`
11817
+ * say `x`), so the attribute is written at render time. Written during the
11818
+ * gesture it would arrive too late: a browser decides what a touch may do when
11819
+ * the touch BEGINS. Same for `touch-action` and iOS's callout below, which is
11820
+ * why they are a stylesheet and not a line of JS in the pointerdown.
11821
+ *
11822
+ * - **The click a gesture leaves behind.** A hold ends with a `pointerup`, so the
11823
+ * browser follows it with a `click` on whatever the finger was over — a link,
11824
+ * and the page navigates as well as opening the menu.
11825
+ *
11826
+ * A swipe makes the element follow the finger — there is nothing to decide about
11827
+ * that — and says where it is up to, for the caller to draw the rest with:
11828
+ *
11829
+ * - `--swipe-pulled`: how far it has come, signed, in px.
11830
+ * - `--swipe-progress`: the same as a fraction of the element, signed. Inherited,
11831
+ * so anything inside the element can read it.
11832
+ * - `[data-swiping="left|right|up|down"]`: which way, while a finger holds it.
11833
+ * - `[data-swipe-past-threshold]`: letting go now would go through with it.
11834
+ *
11835
+ * WHAT is revealed behind is the caller's: navi does not know what putting a row
11836
+ * away looks like. A trail is usually a child of the swiped element sized off
11837
+ * `--swipe-pulled`, which is what makes those values reachable from CSS at all — a
11838
+ * sibling could not read them.
11839
+ *
11840
+ * A swipe is its own gesture, not the drag-to-travel of a slide container: it
11841
+ * borrows the same reader (`startDragToTravel`, whose axis lock, resistance, flick
11842
+ * and click-swallowing are exactly what a swipe needs) with its own settings, and
11843
+ * it is those settings that differ — a third of a row rather than a third of a
11844
+ * screen, tuned per element with `data-swipe-threshold`.
11845
+ *
11846
+ * A hold does NOT take the context menu with it. Declaring one says what a held
11847
+ * FINGER does — a finger held down being the system's own context-menu gesture,
11848
+ * which is why that one is refused while the wait runs (see waitForPressHeld). A
11849
+ * right click is not that press: it comes from the other button and it is the
11850
+ * user asking for the browser's menu, so it keeps opening it. An element that
11851
+ * wants the right click to do what the hold does says so, with `contextmenu`
11852
+ * beside it.
11853
+ *
11854
+ * A hold CAN open a popup while the finger is still down — a menu appearing under
11855
+ * a waiting finger, which is the native gesture. navi's Popover is `popover="manual"`
11856
+ * and owns its own dismissal, so the `pointerup` that ends the press is not read as
11857
+ * an interaction outside it (the browser's light dismiss, which would close it on
11858
+ * that very event, only applies to `popover="auto"`).
11859
+ */
11860
+
11861
+ // The axis each swipe names, which is the whole reason they are named rather than
11862
+ // counted: an element that takes a horizontal swipe has to say so in the DOM
11863
+ // before it is touched.
11864
+ const AXIS_BY_SWIPE_TYPE = {
11865
+ swipe_left: "x",
11866
+ swipe_right: "x",
11867
+ swipe_up: "y",
11868
+ swipe_down: "y"
11869
+ };
11870
+ // Which way the content goes. A drag towards positive x moves the row right, which
11871
+ // is a swipe right — and, for the travel underneath, the direction that brings in
11872
+ // what comes BEFORE (`travelBack`).
11873
+ const SWIPE_TYPE_BY_AXIS = {
11874
+ x: {
11875
+ positive: "swipe_right",
11876
+ negative: "swipe_left"
11877
+ },
11878
+ y: {
11879
+ positive: "swipe_down",
11880
+ negative: "swipe_up"
11881
+ }
11882
+ };
11883
+
11884
+ // How much of the element has to be pulled for letting go to go through with it. A
11885
+ // FRACTION and never a distance: the same gesture must mean the same thing on a
11886
+ // phone and on a wide screen. Speed answers on its own on top of this — a brief
11887
+ // flick counts whatever the distance covered.
11888
+ const SWIPE_THRESHOLD_DEFAULT = 0.33;
11889
+ // Under the system context-menu delay, so the press is answered before the menu it
11890
+ // would otherwise open.
11891
+ const LONGPRESS_DELAY_DEFAULT = 450;
11892
+ // Past this the finger is going somewhere: it swipes, it does not hold.
11893
+ const LONGPRESS_SLOP_DEFAULT = 8;
11894
+ // How long the element takes to reach where the gesture leaves it, or to come
11895
+ // back. Written into the CSS below from here: the state is cleaned up when the
11896
+ // movement is over, so a duration living only in the stylesheet would be a timing
11897
+ // JS has to know and cannot read reliably.
11898
+ const SETTLE_DURATION_MS = 200;
11899
+
11900
+ // Read off the element or off any ancestor carrying it, so a whole list is tuned
11901
+ // in one place and a stylesheet can read the same value.
11902
+ const SWIPE_THRESHOLD_ATTRIBUTE = "data-swipe-threshold";
11903
+ const LONGPRESS_DELAY_ATTRIBUTE = "data-longpress-delay";
11904
+ const LONGPRESS_SLOP_ATTRIBUTE = "data-longpress-slop";
11905
+
11906
+ // Which axes this element takes a swipe on, and that it takes a hold: said in the
11907
+ // DOM at render time, for the CSS below and for the boxes above to read.
11908
+ const SWIPE_AXES_ATTRIBUTE = "data-swipe";
11909
+ const LONGPRESS_ATTRIBUTE = "data-longpress";
11910
+ import.meta.css = [/* css */`
11911
+ /* Declared, so the browser sees a NUMBER it can interpolate and calculate with:
11912
+ what a swipe reveals behind the element is drawn from this, and an undeclared
11913
+ custom property only ever jumps from one value to the next. Inherited, so
11914
+ anything inside the element can read it. */
11915
+ @property --swipe-progress {
11916
+ syntax: "<number>";
11917
+ inherits: true;
11918
+ initial-value: 0;
11919
+ }
11920
+ @property --swipe-pulled {
11921
+ syntax: "<length>";
11922
+ inherits: false;
11923
+ initial-value: 0px;
11924
+ }
11925
+
11926
+ /* What a touch may do on an element that takes a swipe: the axis the swipe walks
11927
+ is taken, the other is left to the page — so a row is swiped sideways and the
11928
+ list still scrolls under the same finger. */
11929
+ [${SWIPE_AXES_ATTRIBUTE}="x"] {
11930
+ touch-action: pan-y;
11931
+ }
11932
+ [${SWIPE_AXES_ATTRIBUTE}="y"] {
11933
+ touch-action: pan-x;
11934
+ }
11935
+ [${SWIPE_AXES_ATTRIBUTE}="xy"] {
11936
+ touch-action: none;
11937
+ }
11938
+
11939
+ /* iOS shows its callout (Copy / Look Up) and selects the word under the finger on
11940
+ a press held still, and does not always route that through an event that can be
11941
+ refused. Same reason as the drag sources in @jsenv/dom: it has to be true
11942
+ before the finger lands. */
11943
+ [${LONGPRESS_ATTRIBUTE}] {
11944
+ -webkit-touch-callout: none;
11945
+ }
11946
+
11947
+ /* The element follows the finger. The translate property rather than a transform,
11948
+ so whatever transform the element (or its theme) already has is left alone. */
11949
+ [data-swiping="left"],
11950
+ [data-swiping="right"] {
11951
+ translate: var(--swipe-pulled) 0;
11952
+ }
11953
+ [data-swiping="up"],
11954
+ [data-swiping="down"] {
11955
+ translate: 0 var(--swipe-pulled);
11956
+ }
11957
+ /* No transition while the finger holds it — the element is where the hand put
11958
+ it — and one when the hand lets go. */
11959
+ [data-swipe-settling] {
11960
+ transition: translate ${SETTLE_DURATION_MS}ms ease-out;
11961
+ }
11962
+ `, "@jsenv/navi/src/control/interaction/interaction_press.js"];
11963
+ defineInteractionDetector({
11964
+ name: "press",
11965
+ claims: type => type in AXIS_BY_SWIPE_TYPE || type === "longpress",
11966
+ setup: (element, trigger, {
11967
+ types,
11968
+ readConfig
11969
+ }) => {
11970
+ let axes = "";
11971
+ for (const type of types) {
11972
+ const axis = AXIS_BY_SWIPE_TYPE[type];
11973
+ if (axis && !axes.includes(axis)) {
11974
+ axes += axis;
11975
+ }
11976
+ }
11977
+ const hasLongPress = types.includes("longpress");
11978
+ const undo = [];
11979
+ const mark = (attribute, value) => {
11980
+ element.setAttribute(attribute, value);
11981
+ undo.push(() => {
11982
+ element.removeAttribute(attribute);
11983
+ });
11984
+ };
11985
+ if (axes) {
11986
+ mark(SWIPE_AXES_ATTRIBUTE, axes);
11987
+ // Read by the boxes ABOVE this one: an axis swiped here is not theirs to
11988
+ // travel (see axesLeftBy).
11989
+ mark("data-travel-by-drag", axes);
11990
+ // …and nothing inside hands its leftover scroll to the page while the
11991
+ // element is being pulled.
11992
+ mark("data-drag-travel", axes);
11993
+
11994
+ // A link and an image are draggable without anyone asking, and a native
11995
+ // drag IS press-and-move: the browser claims the gesture, takes the pointer
11996
+ // events with it and paints a ghost of the row the hand is trying to swipe.
11997
+ // Two things are needed, and neither covers the other:
11998
+ // - `draggable` refuses it on the element itself, before it starts;
11999
+ // - a swipe is usually a row with a link or a thumbnail INSIDE it, and
12000
+ // those are draggable in their own right. `dragstart` bubbles, so
12001
+ // refusing it here refuses theirs too — and it is the only refusal every
12002
+ // browser honours (`-webkit-user-drag` is one engine's).
12003
+ // The cost is stated rather than worked around: an element that takes a
12004
+ // swipe cannot also be dragged out of the page, because there is one
12005
+ // gesture and it cannot mean both.
12006
+ mark("draggable", "false");
12007
+ const onDragStart = dragStartEvent => {
12008
+ dragStartEvent.preventDefault();
12009
+ };
12010
+ element.addEventListener("dragstart", onDragStart);
12011
+ undo.push(() => {
12012
+ element.removeEventListener("dragstart", onDragStart);
12013
+ });
12014
+ }
12015
+ if (hasLongPress) {
12016
+ mark(LONGPRESS_ATTRIBUTE, "");
12017
+ }
12018
+ const onPointerDown = pointerDownEvent => {
12019
+ if (pointerDownEvent.button !== 0) {
12020
+ return;
12021
+ }
12022
+ let swipe = null;
12023
+ let press = null;
12024
+ if (axes) {
12025
+ swipe = startSwipe(pointerDownEvent, {
12026
+ element,
12027
+ axes,
12028
+ types,
12029
+ trigger,
12030
+ threshold: readConfig(SWIPE_THRESHOLD_ATTRIBUTE, SWIPE_THRESHOLD_DEFAULT),
12031
+ // The axis is the definitive word on what this press is: a finger that
12032
+ // has picked one is swiping, not holding. The slop below cancels most
12033
+ // holds before this (it is the smaller distance), but a press that
12034
+ // resolves the axis without drifting — a mouse, a flick — has to be
12035
+ // taken from the wait too.
12036
+ onSwipeStart: () => {
12037
+ press?.cancel();
12038
+ press = null;
12039
+ }
12040
+ });
12041
+ }
12042
+ if (hasLongPress) {
12043
+ press = waitForPressHeld(pointerDownEvent, {
12044
+ delay: readConfig(LONGPRESS_DELAY_ATTRIBUTE, LONGPRESS_DELAY_DEFAULT),
12045
+ slop: readConfig(LONGPRESS_SLOP_ATTRIBUTE, LONGPRESS_SLOP_DEFAULT),
12046
+ onPressHeld: (pressEvent, {
12047
+ endPress
12048
+ }) => {
12049
+ // The hold won the arbitration: the swipe never got the distance it
12050
+ // needed, and must not get it from whatever the finger does next.
12051
+ swipe?.stop();
12052
+ swipe = null;
12053
+ const clickSuppressionIsOver = suppressClickAfterGesture();
12054
+ const onPointerEnd = () => {
12055
+ window.removeEventListener("pointerup", onPointerEnd, true);
12056
+ window.removeEventListener("pointercancel", onPointerEnd, true);
12057
+ endPress();
12058
+ clickSuppressionIsOver();
12059
+ };
12060
+ window.addEventListener("pointerup", onPointerEnd, true);
12061
+ window.addEventListener("pointercancel", onPointerEnd, true);
12062
+ trigger("longpress", pressEvent, {
12063
+ pointerType: pressEvent.pointerType
12064
+ });
12065
+ }
12066
+ });
12067
+ }
12068
+ };
12069
+ element.addEventListener("pointerdown", onPointerDown);
12070
+ undo.push(() => {
12071
+ element.removeEventListener("pointerdown", onPointerDown);
12072
+ });
12073
+ return () => {
12074
+ for (const undoOne of undo) {
12075
+ undoOne();
12076
+ }
12077
+ };
12078
+ }
12079
+ });
12080
+ const startSwipe = (pointerDownEvent, {
12081
+ element,
12082
+ axes,
12083
+ types,
12084
+ trigger,
12085
+ threshold,
12086
+ onSwipeStart
12087
+ }) => {
12088
+ let settleTimeout = null;
12089
+ const paint = ({
12090
+ pulled,
12091
+ progress,
12092
+ type
12093
+ }) => {
12094
+ element.style.setProperty("--swipe-pulled", `${pulled}px`);
12095
+ element.style.setProperty("--swipe-progress", progress);
12096
+ element.setAttribute("data-swiping", type.slice("swipe_".length));
12097
+ element.toggleAttribute("data-swipe-past-threshold", Math.abs(progress) >= threshold);
12098
+ };
12099
+ const forget = () => {
12100
+ clearTimeout(settleTimeout);
12101
+ element.removeAttribute("data-swiping");
12102
+ element.removeAttribute("data-swipe-past-threshold");
12103
+ element.removeAttribute("data-swipe-settling");
12104
+ element.style.removeProperty("--swipe-pulled");
12105
+ element.style.removeProperty("--swipe-progress");
12106
+ };
12107
+ // Where the gesture leaves the element, and later back to rest. Cleaned up on a
12108
+ // timeout rather than on transitionend: a swipe let go of at the very edge has
12109
+ // nothing left to move (the pull is clamped to the element's size), and a
12110
+ // movement that does not happen reports no end.
12111
+ const settleTo = ({
12112
+ pulled,
12113
+ progress,
12114
+ type
12115
+ }, {
12116
+ thenForget = true
12117
+ } = {}) => {
12118
+ clearTimeout(settleTimeout);
12119
+ element.setAttribute("data-swipe-settling", "");
12120
+ paint({
12121
+ pulled,
12122
+ progress,
12123
+ type
12124
+ });
12125
+ if (thenForget) {
12126
+ settleTimeout = setTimeout(forget, SETTLE_DURATION_MS);
12127
+ }
12128
+ };
12129
+ return startDragToTravel(pointerDownEvent, {
12130
+ element,
12131
+ axes,
12132
+ commitRatio: threshold,
12133
+ onStart: ({
12134
+ axis
12135
+ }) => {
12136
+ const {
12137
+ positive,
12138
+ negative
12139
+ } = SWIPE_TYPE_BY_AXIS[axis];
12140
+ const hasPositive = types.includes(positive);
12141
+ const hasNegative = types.includes(negative);
12142
+ if (!hasPositive && !hasNegative) {
12143
+ return false;
12144
+ }
12145
+ const {
12146
+ width,
12147
+ height
12148
+ } = element.getBoundingClientRect();
12149
+ const size = axis === "x" ? width : height;
12150
+ if (!size) {
12151
+ return false;
12152
+ }
12153
+ onSwipeStart();
12154
+ clearTimeout(settleTimeout);
12155
+ element.removeAttribute("data-swipe-settling");
12156
+ // A side nothing is declared for is not refused, it resists: the gesture is
12157
+ // answered (something moves) while saying there is nothing that way.
12158
+ return {
12159
+ size,
12160
+ travelBack: hasPositive,
12161
+ travelOn: hasNegative
12162
+ };
12163
+ },
12164
+ onPull: ({
12165
+ axis,
12166
+ pulled,
12167
+ progress
12168
+ }) => {
12169
+ paint({
12170
+ pulled,
12171
+ progress,
12172
+ type: swipeTypeOf(axis, pulled)
12173
+ });
12174
+ },
12175
+ onEnd: ({
12176
+ axis,
12177
+ pulled,
12178
+ size,
12179
+ sign,
12180
+ travels,
12181
+ event
12182
+ }) => {
12183
+ const type = swipeTypeOf(axis, pulled);
12184
+ const restingPlace = {
12185
+ pulled: 0,
12186
+ progress: 0,
12187
+ type
12188
+ };
12189
+ if (!travels) {
12190
+ settleTo(restingPlace);
12191
+ return;
12192
+ }
12193
+ // Out, and out is where it stays for as long as the answer takes: a row that
12194
+ // comes back and leaves again says the gesture was not understood.
12195
+ settleTo({
12196
+ pulled: sign * size,
12197
+ progress: sign,
12198
+ type
12199
+ }, {
12200
+ thenForget: false
12201
+ });
12202
+ const pending = trigger(type, event, {
12203
+ axis,
12204
+ sign,
12205
+ pulled,
12206
+ size,
12207
+ progress: pulled / size
12208
+ });
12209
+ if (!pending) {
12210
+ settleTo(restingPlace);
12211
+ return;
12212
+ }
12213
+ // However it ended, the element comes back: a failure leaves the row in
12214
+ // place and it can be tried again, and a success is the caller's to answer
12215
+ // (a list that redemands its rows, a row that leaves). Nothing here makes it
12216
+ // disappear — navi does not know what "put away" means.
12217
+ const comeBack = () => {
12218
+ settleTo(restingPlace);
12219
+ };
12220
+ pending.then(comeBack, comeBack);
12221
+ },
12222
+ // Nothing to put back: onGiveUp only ever comes from a press that never became
12223
+ // a swipe, so nothing was painted by it.
12224
+ onGiveUp: () => {}
12225
+ });
12226
+ };
12227
+ const swipeTypeOf = (axis, pulled) => {
12228
+ const {
12229
+ positive,
12230
+ negative
12231
+ } = SWIPE_TYPE_BY_AXIS[axis];
12232
+ return pulled > 0 ? positive : negative;
12233
+ };
12234
+
12235
+ /**
12236
+ * `move`, `reorder`, `toss` — one grab, and what letting go of it means.
12237
+ *
12238
+ * All three are the same gesture: the element is picked up and carried. What
12239
+ * differs is the answer at the release, so one detector reads them all — it is one
12240
+ * press, and something has to arbitrate it.
12241
+ *
12242
+ * interactions={{ reorder: moveBefore, toss: remove }}
12243
+ * interactions={{ move: remember }}
12244
+ *
12245
+ * `reorder` and `toss` combine: a task dragged onto another changes places, the
12246
+ * same task thrown far and fast is gotten rid of. `move` does not combine with
12247
+ * `reorder` — an element either goes where it is put or takes a place in a list,
12248
+ * and the two answers cannot both be true of one release.
12249
+ *
12250
+ * `move` carries the element ITSELF and leaves it where it was put; the other two
12251
+ * carry a copy and put the original back. That is the same difference said in
12252
+ * layout terms: something moved has a new place of its own, something reordered
12253
+ * had its place taken by the list.
12254
+ *
12255
+ * Nothing of the gesture is decided here. `startDragTo` owns all of it — the
12256
+ * copy carried above the page while the original keeps its place in the layout, the
12257
+ * drop hint, the drop targets found by intersection, the no-op drops filtered out,
12258
+ * the flight of a thrown copy and its return when the answer refuses. This says
12259
+ * which elements are the items, how they are named, and what a given release means.
12260
+ *
12261
+ * WHICH ELEMENTS. Every element declaring `reorder` marks itself, so the set of
12262
+ * items IS the set of elements that declared it — no selector to pass, nothing to
12263
+ * keep in sync with the markup, and an item that must not move simply does not
12264
+ * declare it. An element that only declares `toss` marks nothing: it is not a place
12265
+ * anything lands.
12266
+ *
12267
+ * HOW THEY ARE NAMED: by `id`. A reorder is answered in terms of what moved and
12268
+ * what it landed before, and a DOM index cannot say it — a list draws fewer rows
12269
+ * than it has, and a search reorders them.
12270
+ *
12271
+ * WHAT COMES BACK, in the interaction's own detail:
12272
+ *
12273
+ * interactions={{
12274
+ * reorder: (event) => {
12275
+ * const { fromId, toId, syncCloneWithDropTarget } = event.detail;
12276
+ * return document.startViewTransition(() => {
12277
+ * syncCloneWithDropTarget();
12278
+ * setOrder(moveBefore(order, fromId, toId));
12279
+ * }).finished;
12280
+ * },
12281
+ * toss: (event) => remove(event.detail.id),
12282
+ * }}
12283
+ *
12284
+ * `toId` is null for a drop at the end. `syncCloneWithDropTarget` must be called
12285
+ * synchronously inside the transition callback, next to the state change, so the
12286
+ * copy is captured where it lands rather than where it was let go of.
12287
+ *
12288
+ * And the promise matters in both cases: the gesture holds its copy until the
12289
+ * answer settles. Returning the transition is what makes a landing continuous; a
12290
+ * `toss` that rejects brings the copy back, because the thing still exists and the
12291
+ * screen has to say so.
12292
+ *
12293
+ * Starting a document transition is the application's call and not navi's: a
12294
+ * `view-transition-name` must be unique per document, so only the application can
12295
+ * name what moves.
12296
+ *
12297
+ * What the copy LOOKS like is the application's too. A copy of a transparent
12298
+ * element is invisible — a row usually gets its background from the list around it,
12299
+ * which the copy has left — so it is dressed through the attributes the gesture
12300
+ * writes: `navi-drag-clone` on the copy, `navi-drag-clone-source` on the original.
12301
+ */
12302
+
12303
+
12304
+ const MOVE = "move";
12305
+ const REORDER = "reorder";
12306
+ const TOSS = "toss";
12307
+
12308
+ // What makes an element a place something can land, written by the detector itself.
12309
+ const REORDERABLE_ATTRIBUTE = "data-reorderable";
12310
+ // Which axes the drag walks: "x", "y" or "xy". Its default is not the same for
12311
+ // every outcome — a list runs one way, and something being put somewhere goes
12312
+ // wherever it is put.
12313
+ const AXIS_ATTRIBUTE = "data-drag-axis";
12314
+ const DELAY_ATTRIBUTE = "data-drag-delay";
12315
+ const SLOP_ATTRIBUTE = "data-drag-slop";
12316
+ const THRESHOLD_ATTRIBUTE = "data-drag-threshold";
12317
+ const TOSS_DISTANCE_ATTRIBUTE = "data-toss-distance";
12318
+ const TOSS_SPEED_ATTRIBUTE = "data-toss-speed";
12319
+
12320
+ defineInteractionDetector({
12321
+ name: "drag",
12322
+ claims: (type) => type === MOVE || type === REORDER || type === TOSS,
12323
+ setup: (element, trigger, { types, readConfig }) => {
12324
+ const canMove = types.includes(MOVE);
12325
+ const canReorder = types.includes(REORDER);
12326
+ const canToss = types.includes(TOSS);
12327
+ // Read at setup rather than at the press: a container can say it, and it is
12328
+ // what the gesture is about rather than something it discovers.
12329
+ const axisHolder = element.closest(`[${AXIS_ATTRIBUTE}]`);
12330
+ const axes =
12331
+ axisHolder?.getAttribute(AXIS_ATTRIBUTE) ||
12332
+ // A list runs one way, and reordering walks it. Anything else goes wherever
12333
+ // the hand takes it: a thing put somewhere has two axes to be put along, and
12334
+ // a throw goes where it was thrown.
12335
+ (canReorder && !canToss ? "y" : "xy");
12336
+
12337
+ if (canReorder) {
12338
+ element.setAttribute(REORDERABLE_ATTRIBUTE, "");
12339
+ }
12340
+ // What @jsenv/dom puts on a drag source: no iOS callout, and the touch left to
12341
+ // the scroll until the press becomes a grab. Its value is the axis the
12342
+ // SURROUNDINGS scroll on, which for a list is the axis the list runs on.
12343
+ element.setAttribute("data-drag-source", axes === "x" ? "x" : "");
12344
+
12345
+ const onPointerDown = (pointerDownEvent) => {
12346
+ // What this element says a release can mean. The gesture then runs only what
12347
+ // those need — no copy for a move, no drop hint for something that can only
12348
+ // be thrown away.
12349
+ startDragTo(pointerDownEvent, types, {
12350
+ draggedElement: element,
12351
+ // Nothing to land on when nothing reorders.
12352
+ itemSelector: canReorder ? `[${REORDERABLE_ATTRIBUTE}]` : undefined,
12353
+ getItemId: (itemElement) => itemElement.id,
12354
+ direction: { x: axes.includes("x"), y: axes.includes("y") },
12355
+ // Where it may go, said in the DOM. A thing that is put somewhere stays
12356
+ // inside what one can SEE of its container ("scrollport", not "scroll":
12357
+ // the scrollable area can be far larger than the box, and "inside the box"
12358
+ // is what a hand expects). `data-drag-free` lifts that. Left alone for a
12359
+ // throw, which frees the area on its own — it has to be able to leave.
12360
+ areaConstraint: element.closest(`[data-drag-free]`)
12361
+ ? "none"
12362
+ : canMove
12363
+ ? "scrollport"
12364
+ : undefined,
12365
+ threshold: readConfig(THRESHOLD_ATTRIBUTE, undefined),
12366
+ longPressDelay: readConfig(DELAY_ATTRIBUTE, undefined),
12367
+ longPressSlop: readConfig(SLOP_ATTRIBUTE, undefined),
12368
+ tossDistance: readConfig(TOSS_DISTANCE_ATTRIBUTE, undefined),
12369
+ tossSpeed: readConfig(TOSS_SPEED_ATTRIBUTE, undefined),
12370
+ // Handed straight back in all three cases: what `trigger` returns is a
12371
+ // promise while the answer is still going, which is what the gesture waits
12372
+ // on before it lets go of what it carries.
12373
+ onReorder: (fromId, toId, syncCloneWithDropTarget) =>
12374
+ trigger(REORDER, pointerDownEvent, {
12375
+ fromId,
12376
+ toId,
12377
+ syncCloneWithDropTarget,
12378
+ }),
12379
+ onToss: ({ gestureInfo }) =>
12380
+ trigger(TOSS, pointerDownEvent, {
12381
+ id: element.id,
12382
+ velocity: gestureInfo.velocity,
12383
+ x: gestureInfo.layout.xDelta,
12384
+ y: gestureInfo.layout.yDelta,
12385
+ }),
12386
+ onMove: ({ x, y }) => trigger(MOVE, pointerDownEvent, { x, y }),
12387
+ });
12388
+ };
12389
+ element.addEventListener("pointerdown", onPointerDown);
12390
+
12391
+ return () => {
12392
+ element.removeAttribute(REORDERABLE_ATTRIBUTE);
12393
+ element.removeAttribute("data-drag-source");
12394
+ element.removeEventListener("pointerdown", onPointerDown);
12395
+ };
12396
+ },
12397
+ });
12398
+
11700
12399
  const isSignal = (value) => {
11701
12400
  return getSignalType(value) !== null;
11702
12401
  };
@@ -15399,6 +16098,43 @@ const isSameKey = (browserEventKey, key) => {
15399
16098
  return false;
15400
16099
  };
15401
16100
 
16101
+ /**
16102
+ * `"keyboard:ctrl+backspace"` — an interaction asked for with keys.
16103
+ *
16104
+ * The name carries the shortcut, so a control says what a shortcut DOES in the
16105
+ * same place it says what a swipe does, rather than in a separate shortcut list
16106
+ * whose entries then have to find their way back to the action.
16107
+ *
16108
+ * The shortcut itself is not parsed here: `createOnKeyDownForShortcuts` already
16109
+ * knows what "ctrl+backspace" means, which keys a control may take without
16110
+ * swallowing what the browser owed them, and which it may not.
16111
+ *
16112
+ * The interaction is dispatched under its full name, colon included, so
16113
+ * `findEvent(event, "keyboard:ctrl+backspace")` gets back to it — the shortcut
16114
+ * that asked is what the action wants to know, not that a key went down.
16115
+ */
16116
+
16117
+
16118
+ const KEYBOARD_PREFIX = "keyboard:";
16119
+
16120
+ defineInteractionDetector({
16121
+ name: "keyboard",
16122
+ claims: (type) => type.startsWith(KEYBOARD_PREFIX),
16123
+ setup: (element, trigger, { types }) => {
16124
+ const shortcuts = {};
16125
+ for (const type of types) {
16126
+ shortcuts[type.slice(KEYBOARD_PREFIX.length)] = (keyboardEvent) => {
16127
+ trigger(type, keyboardEvent);
16128
+ };
16129
+ }
16130
+ const onKeyDown = createOnKeyDownForShortcuts(shortcuts);
16131
+ element.addEventListener("keydown", onKeyDown);
16132
+ return () => {
16133
+ element.removeEventListener("keydown", onKeyDown);
16134
+ };
16135
+ },
16136
+ });
16137
+
15402
16138
  /**
15403
16139
  * Merges a component's base className with className received from props.
15404
16140
  *
@@ -27033,7 +27769,7 @@ const css$V = /* css */`
27033
27769
  position: absolute;
27034
27770
  inset: unset;
27035
27771
  /* Custom renderer only — see openLocalDialogCount above */
27036
- z-index: calc(var(--navi-popup-z-index) + var(--dialog-stack-order, 0));
27772
+ z-index: calc(var(--navi-z-index-popup) + var(--dialog-stack-order, 0));
27037
27773
  min-width: min(
27038
27774
  max(var(--anchor-width, 0px), var(--dialog-min-width, 0px)),
27039
27775
  var(--x-dialog-max-width)
@@ -28302,7 +29038,7 @@ const css$U = /* css */`
28302
29038
  most-recently-opened local popover always outranks an earlier one,
28303
29039
  regardless of DOM position — a plain positioned div gets no free
28304
29040
  "last shown wins" the way the top-layer renderer does. */
28305
- z-index: calc(var(--navi-popup-z-index) + var(--popover-stack-order, 0));
29041
+ z-index: calc(var(--navi-z-index-popup) + var(--popover-stack-order, 0));
28306
29042
  min-width: min(var(--popover-min-width, 0px), var(--x-popover-max-width));
28307
29043
  max-width: var(--x-popover-max-width);
28308
29044
  min-height: min(
@@ -28435,7 +29171,7 @@ const css$U = /* css */`
28435
29171
  instance still resolve via DOM order (content rendered after its own
28436
29172
  backdrop, see this file's top comment), so this only needs to beat
28437
29173
  *other* popovers' own backdrop/content, not its own. */
28438
- z-index: calc(var(--navi-popup-z-index) + var(--popover-stack-order, 0));
29174
+ z-index: calc(var(--navi-z-index-popup) + var(--popover-stack-order, 0));
28439
29175
  margin: 0;
28440
29176
  padding: 0;
28441
29177
  background: transparent;
@@ -41486,7 +42222,7 @@ const css$L = /* css */`
41486
42222
 
41487
42223
  .navi_fixed_bar {
41488
42224
  position: fixed;
41489
- z-index: 1;
42225
+ z-index: var(--navi-z-index-bar);
41490
42226
  display: flex;
41491
42227
  box-sizing: border-box;
41492
42228
  margin: auto;
@@ -46354,32 +47090,53 @@ HTMLFormElement.prototype.requestSubmit = function (submitter) {
46354
47090
  // form.dispatchEvent(customEvent);
46355
47091
  // };
46356
47092
 
46357
- installImportMetaCssBuild(import.meta);const css$B = /* css */`
47093
+ installImportMetaCssBuild(import.meta);/**
47094
+ * A row or column of controls sharing one frame: the borders where two of them
47095
+ * meet are drawn once instead of twice, and only the outer corners stay
47096
+ * rounded. See docs/control_group.md.
47097
+ */
47098
+ const css$B = /* css */`
46358
47099
  .navi_group {
46359
- --group-border-width: 1px;
46360
-
47100
+ --group-border-width: var(--navi-control-border-width);
47101
+
47102
+ /* Squaring the joined corners is said on the direct child alone: a navi
47103
+ control declares the radius of its frame on its own root, and whatever
47104
+ inner element actually draws the frame inherits it. .navi_button_content
47105
+ is the exception — a button's frame sits on it and a button can arrive
47106
+ wrapped (a tooltip, a link), so it is named on its own. */
47107
+
47108
+ /* Members overlap by the width of one border, so along each seam one of the
47109
+ two borders covers the other. Whichever member the user is on has to be
47110
+ the one on top: it is the one whose border changes color, and the one
47111
+ whose focus ring goes all the way around — half a ring, cut by the
47112
+ neighbour painted after it, is what this avoids. z-index needs a
47113
+ positioned element to mean anything, hence position: relative.
47114
+ Deliberately not paired with isolation: isolate — a stacking context
47115
+ here would also trap the popup of a picker held in the group, which
47116
+ counts on its own band reaching the whole page. What keeps these two
47117
+ values from escaping is instead that everything they could reach is a
47118
+ band above them (see navi_z_indexes.js). */
46361
47119
  > *:hover,
46362
47120
  > *[data-hover] {
46363
47121
  position: relative;
46364
- z-index: 1;
47122
+ z-index: var(--navi-z-index-control-hovered);
46365
47123
  }
46366
47124
  > *:focus-visible,
46367
47125
  > *[data-focus-visible] {
46368
47126
  position: relative;
46369
- z-index: 1;
47127
+ z-index: var(--navi-z-index-control-focused);
46370
47128
  }
46371
47129
 
46372
47130
  /* Horizontal (default): Cumulative margin for border overlap */
46373
47131
  &:not([data-vertical]) {
46374
47132
  > *:not(:first-child) {
46375
- margin-left: calc(var(--border-width) * -1);
47133
+ margin-left: calc(var(--border-width, var(--group-border-width)) * -1);
46376
47134
  }
46377
47135
  > *:first-child:not(:only-child) {
46378
47136
  border-top-right-radius: 0 !important;
46379
47137
  border-bottom-right-radius: 0 !important;
46380
47138
 
46381
- .navi_button_content,
46382
- .navi_native_input {
47139
+ .navi_button_content {
46383
47140
  border-top-right-radius: 0 !important;
46384
47141
  border-bottom-right-radius: 0 !important;
46385
47142
  }
@@ -46389,8 +47146,7 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
46389
47146
  border-top-left-radius: 0 !important;
46390
47147
  border-bottom-left-radius: 0 !important;
46391
47148
 
46392
- .navi_button_content,
46393
- .navi_native_input {
47149
+ .navi_button_content {
46394
47150
  border-top-left-radius: 0 !important;
46395
47151
  border-bottom-left-radius: 0 !important;
46396
47152
  }
@@ -46399,8 +47155,7 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
46399
47155
  > *:not(:first-child):not(:last-child) {
46400
47156
  border-radius: 0 !important;
46401
47157
 
46402
- .navi_button_content,
46403
- .navi_native_input {
47158
+ .navi_button_content {
46404
47159
  border-radius: 0 !important;
46405
47160
  }
46406
47161
  }
@@ -46409,14 +47164,13 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
46409
47164
  /* Vertical: Cumulative margin for border overlap */
46410
47165
  &[data-vertical] {
46411
47166
  > *:not(:first-child) {
46412
- margin-top: calc(var(--group-border-width) * -1);
47167
+ margin-top: calc(var(--border-width, var(--group-border-width)) * -1);
46413
47168
  }
46414
47169
  > *:first-child:not(:only-child) {
46415
47170
  border-bottom-right-radius: 0 !important;
46416
47171
  border-bottom-left-radius: 0 !important;
46417
47172
 
46418
- .navi_button_content,
46419
- .navi_native_input {
47173
+ .navi_button_content {
46420
47174
  border-bottom-right-radius: 0 !important;
46421
47175
  border-bottom-left-radius: 0 !important;
46422
47176
  }
@@ -46426,8 +47180,7 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
46426
47180
  border-top-left-radius: 0 !important;
46427
47181
  border-top-right-radius: 0 !important;
46428
47182
 
46429
- .navi_button_content,
46430
- .navi_native_input {
47183
+ .navi_button_content {
46431
47184
  border-top-left-radius: 0 !important;
46432
47185
  border-top-right-radius: 0 !important;
46433
47186
  }
@@ -46436,8 +47189,7 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
46436
47189
  > *:not(:first-child):not(:last-child) {
46437
47190
  border-radius: 0 !important;
46438
47191
 
46439
- .navi_button_content,
46440
- .navi_native_input {
47192
+ .navi_button_content {
46441
47193
  border-radius: 0 !important;
46442
47194
  }
46443
47195
  }
@@ -52387,7 +53139,7 @@ const css$v = /* css */`
52387
53139
  position: sticky;
52388
53140
  top: 0;
52389
53141
  left: 0;
52390
- z-index: 1;
53142
+ z-index: var(--navi-z-index-sticky);
52391
53143
  order: -2;
52392
53144
  }
52393
53145
  .navi_list_fallback,
@@ -52494,7 +53246,7 @@ const css$v = /* css */`
52494
53246
  position: sticky;
52495
53247
  right: 0;
52496
53248
  bottom: 0;
52497
- z-index: 1;
53249
+ z-index: var(--navi-z-index-sticky);
52498
53250
  order: 2;
52499
53251
  }
52500
53252
 
@@ -52510,7 +53262,7 @@ const css$v = /* css */`
52510
53262
  .navi_list_item_group_label {
52511
53263
  position: sticky;
52512
53264
  top: var(--list-group-label-top, var(--x-list-group-label-top, 0px));
52513
- z-index: 1;
53265
+ z-index: var(--navi-z-index-sticky);
52514
53266
  display: block;
52515
53267
  background-color: var(--list-group-label-background-color);
52516
53268
  user-select: none;
@@ -56622,6 +57374,11 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
56622
57374
  font-size: var(--picker-font-size);
56623
57375
  font-family: var(--picker-font-family);
56624
57376
  text-align: inherit;
57377
+ /* The frame is drawn by the box, but its radius is declared here, on the
57378
+ control root, like every other navi control does — so anything styling
57379
+ the picker from the outside (a Group squaring the corners it joins) has
57380
+ one element to talk to, and the box follows. */
57381
+ border-radius: var(--picker-border-radius);
56625
57382
 
56626
57383
  .navi_picker_box {
56627
57384
  position: relative;
@@ -56643,7 +57400,7 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
56643
57400
  border-width: var(--picker-border-width);
56644
57401
  border-style: solid;
56645
57402
  border-color: var(--x-picker-border-color);
56646
- border-radius: var(--picker-border-radius);
57403
+ border-radius: inherit;
56647
57404
  outline-width: var(--picker-outline-width);
56648
57405
  outline-style: none;
56649
57406
  outline-color: var(--picker-outline-color);
@@ -62536,6 +63293,13 @@ const createIsolatedItemTracker = () => {
62536
63293
  return [useIsolatedItemTrackerProvider, useTrackIsolatedItem, useTrackedIsolatedItem, useTrackedIsolatedItems];
62537
63294
  };
62538
63295
 
63296
+ /**
63297
+ * The table's own scale: these order the table's parts among themselves and are
63298
+ * derived from each other, never written as literals twice. They stay under the
63299
+ * bands of src/navi_z_indexes.js, which is where anything competing with
63300
+ * another component belongs.
63301
+ */
63302
+
62539
63303
  const Z_INDEX_EDITING = 1; /* To go above neighbours, but should not be too big to stay under the sticky cells */
62540
63304
 
62541
63305
  /* needed because cell uses position:relative, sticky must win even if before in DOM order */
@@ -67954,7 +68718,7 @@ const css = /* css */`
67954
68718
  .navi_side_panel_head,
67955
68719
  .navi_side_panel_foot {
67956
68720
  position: sticky;
67957
- z-index: 1;
68721
+ z-index: var(--navi-z-index-sticky);
67958
68722
  background-color: var(--navi-popup-background-color);
67959
68723
  }
67960
68724
  /* The separator token, not the popup border: these lines split two