@jsenv/navi 0.29.29 → 0.29.31
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.
- package/dist/jsenv_navi.js +911 -58
- package/dist/jsenv_navi.js.map +23 -13
- package/dist/jsenv_navi_side_effects.js +0 -1
- package/dist/jsenv_navi_side_effects.js.map +2 -2
- package/docs/AI_INSTRUCTIONS.md +5 -1
- package/docs/control_group.md +135 -0
- package/docs/interactions.md +40 -9
- package/docs/z_index.md +40 -16
- package/package.json +5 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -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,
|
|
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:
|
|
7644
|
+
--callout-z-index: var(--navi-z-index-callout);
|
|
7587
7645
|
}
|
|
7588
7646
|
}
|
|
7589
7647
|
|
|
@@ -11697,6 +11755,657 @@ 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. It takes it away AFTER, though —
|
|
11767
|
+
* a native interaction IS its own event, the very one the gate reads, and a gate
|
|
11768
|
+
* refuses what is already `defaultPrevented`. Cancelling first would therefore
|
|
11769
|
+
* make the interaction refuse itself, and no right click could ever get through.
|
|
11770
|
+
* So: ask, and only cover the browser's menu once something answered.
|
|
11771
|
+
*/
|
|
11772
|
+
|
|
11773
|
+
|
|
11774
|
+
const NATIVE_TYPE_SET = new Set([
|
|
11775
|
+
"mousedown",
|
|
11776
|
+
"mouseup",
|
|
11777
|
+
"click",
|
|
11778
|
+
"dblclick",
|
|
11779
|
+
"contextmenu",
|
|
11780
|
+
]);
|
|
11781
|
+
|
|
11782
|
+
defineInteractionDetector({
|
|
11783
|
+
name: "native",
|
|
11784
|
+
claims: (type) => NATIVE_TYPE_SET.has(type),
|
|
11785
|
+
setup: (element, trigger, { types }) => {
|
|
11786
|
+
const listeners = types.map((type) => {
|
|
11787
|
+
const listener = (nativeEvent) => {
|
|
11788
|
+
const performed = trigger(type, nativeEvent);
|
|
11789
|
+
if (type === "contextmenu" && performed) {
|
|
11790
|
+
// Something answered the request, so the browser's own menu would only
|
|
11791
|
+
// cover it. Nothing answered (the gate refused, a listener said "not
|
|
11792
|
+
// this time"): the right click stays what it was, and opens the menu.
|
|
11793
|
+
nativeEvent.preventDefault();
|
|
11794
|
+
}
|
|
11795
|
+
};
|
|
11796
|
+
element.addEventListener(type, listener);
|
|
11797
|
+
return [type, listener];
|
|
11798
|
+
});
|
|
11799
|
+
return () => {
|
|
11800
|
+
for (const [type, listener] of listeners) {
|
|
11801
|
+
element.removeEventListener(type, listener);
|
|
11802
|
+
}
|
|
11803
|
+
};
|
|
11804
|
+
},
|
|
11805
|
+
});
|
|
11806
|
+
|
|
11807
|
+
installImportMetaCssBuild(import.meta);/**
|
|
11808
|
+
* What a press can turn out to be: a swipe, or a hold.
|
|
11809
|
+
*
|
|
11810
|
+
* One detector for both, because they dispute the SAME press and something has to
|
|
11811
|
+
* arbitrate them in one place — read apart, a hold that drifts three pixels both
|
|
11812
|
+
* opens the menu and starts putting the row away. A finger lands; it leaves
|
|
11813
|
+
* sideways (a swipe), it stays still (a hold), it lifts at once (a click, which is
|
|
11814
|
+
* not this detector's business).
|
|
11815
|
+
*
|
|
11816
|
+
* Naming the interactions in `interactions` rather than letting one callback read
|
|
11817
|
+
* the pointer is what lets this know, BEFORE the first pixel, which of them the
|
|
11818
|
+
* element takes. Two things depend on knowing that early:
|
|
11819
|
+
*
|
|
11820
|
+
* - **The arbitration between nested boxes.** A row swiped sideways lives inside a
|
|
11821
|
+
* container that travels sideways too, and the innermost box must take the axis
|
|
11822
|
+
* it walks — read from the DOM at the press, and only from there (see axesLeftBy
|
|
11823
|
+
* in @jsenv/dom). The axis comes from the name (`swipe_left` and `swipe_right`
|
|
11824
|
+
* say `x`), so the attribute is written at render time. Written during the
|
|
11825
|
+
* gesture it would arrive too late: a browser decides what a touch may do when
|
|
11826
|
+
* the touch BEGINS. Same for `touch-action` and iOS's callout below, which is
|
|
11827
|
+
* why they are a stylesheet and not a line of JS in the pointerdown.
|
|
11828
|
+
*
|
|
11829
|
+
* - **The click a gesture leaves behind.** A hold ends with a `pointerup`, so the
|
|
11830
|
+
* browser follows it with a `click` on whatever the finger was over — a link,
|
|
11831
|
+
* and the page navigates as well as opening the menu.
|
|
11832
|
+
*
|
|
11833
|
+
* A swipe makes the element follow the finger — there is nothing to decide about
|
|
11834
|
+
* that — and says where it is up to, for the caller to draw the rest with:
|
|
11835
|
+
*
|
|
11836
|
+
* - `--swipe-pulled`: how far it has come, signed, in px.
|
|
11837
|
+
* - `--swipe-progress`: the same as a fraction of the element, signed.
|
|
11838
|
+
*
|
|
11839
|
+
* Both inherit, so anything inside the element can read them.
|
|
11840
|
+
* - `[data-swiping="left|right|up|down"]`: which way, while a finger holds it.
|
|
11841
|
+
* - `[data-swipe-past-threshold]`: letting go now would go through with it.
|
|
11842
|
+
*
|
|
11843
|
+
* WHAT is revealed behind is the caller's: navi does not know what putting a row
|
|
11844
|
+
* away looks like. A trail is usually a child of the swiped element sized off
|
|
11845
|
+
* `--swipe-pulled`, which is what makes those values reachable from CSS at all — a
|
|
11846
|
+
* sibling could not read them.
|
|
11847
|
+
*
|
|
11848
|
+
* A swipe is its own gesture, not the drag-to-travel of a slide container: it
|
|
11849
|
+
* borrows the same reader (`startDragToTravel`, whose axis lock, resistance, flick
|
|
11850
|
+
* and click-swallowing are exactly what a swipe needs) with its own settings, and
|
|
11851
|
+
* it is those settings that differ — a third of a row rather than a third of a
|
|
11852
|
+
* screen, tuned per element with `data-swipe-threshold`.
|
|
11853
|
+
*
|
|
11854
|
+
* A hold does NOT take the context menu with it. Declaring one says what a held
|
|
11855
|
+
* FINGER does — a finger held down being the system's own context-menu gesture,
|
|
11856
|
+
* which is why that one is refused while the wait runs (see waitForPressHeld). A
|
|
11857
|
+
* right click is not that press: it comes from the other button and it is the
|
|
11858
|
+
* user asking for the browser's menu, so it keeps opening it. An element that
|
|
11859
|
+
* wants the right click to do what the hold does says so, with `contextmenu`
|
|
11860
|
+
* beside it.
|
|
11861
|
+
*
|
|
11862
|
+
* A hold CAN open a popup while the finger is still down — a menu appearing under
|
|
11863
|
+
* a waiting finger, which is the native gesture. navi's Popover is `popover="manual"`
|
|
11864
|
+
* and owns its own dismissal, so the `pointerup` that ends the press is not read as
|
|
11865
|
+
* an interaction outside it (the browser's light dismiss, which would close it on
|
|
11866
|
+
* that very event, only applies to `popover="auto"`).
|
|
11867
|
+
*/
|
|
11868
|
+
|
|
11869
|
+
// The axis each swipe names, which is the whole reason they are named rather than
|
|
11870
|
+
// counted: an element that takes a horizontal swipe has to say so in the DOM
|
|
11871
|
+
// before it is touched.
|
|
11872
|
+
const AXIS_BY_SWIPE_TYPE = {
|
|
11873
|
+
swipe_left: "x",
|
|
11874
|
+
swipe_right: "x",
|
|
11875
|
+
swipe_up: "y",
|
|
11876
|
+
swipe_down: "y"
|
|
11877
|
+
};
|
|
11878
|
+
// Which way the content goes. A drag towards positive x moves the row right, which
|
|
11879
|
+
// is a swipe right — and, for the travel underneath, the direction that brings in
|
|
11880
|
+
// what comes BEFORE (`travelBack`).
|
|
11881
|
+
const SWIPE_TYPE_BY_AXIS = {
|
|
11882
|
+
x: {
|
|
11883
|
+
positive: "swipe_right",
|
|
11884
|
+
negative: "swipe_left"
|
|
11885
|
+
},
|
|
11886
|
+
y: {
|
|
11887
|
+
positive: "swipe_down",
|
|
11888
|
+
negative: "swipe_up"
|
|
11889
|
+
}
|
|
11890
|
+
};
|
|
11891
|
+
|
|
11892
|
+
// How much of the element has to be pulled for letting go to go through with it. A
|
|
11893
|
+
// FRACTION and never a distance: the same gesture must mean the same thing on a
|
|
11894
|
+
// phone and on a wide screen. Speed answers on its own on top of this — a brief
|
|
11895
|
+
// flick counts whatever the distance covered.
|
|
11896
|
+
const SWIPE_THRESHOLD_DEFAULT = 0.33;
|
|
11897
|
+
// Under the system context-menu delay, so the press is answered before the menu it
|
|
11898
|
+
// would otherwise open.
|
|
11899
|
+
const LONGPRESS_DELAY_DEFAULT = 450;
|
|
11900
|
+
// Past this the finger is going somewhere: it swipes, it does not hold.
|
|
11901
|
+
const LONGPRESS_SLOP_DEFAULT = 8;
|
|
11902
|
+
// How long the element takes to reach where the gesture leaves it, or to come
|
|
11903
|
+
// back. Written into the CSS below from here: the state is cleaned up when the
|
|
11904
|
+
// movement is over, so a duration living only in the stylesheet would be a timing
|
|
11905
|
+
// JS has to know and cannot read reliably.
|
|
11906
|
+
const SETTLE_DURATION_MS = 200;
|
|
11907
|
+
|
|
11908
|
+
// Read off the element or off any ancestor carrying it, so a whole list is tuned
|
|
11909
|
+
// in one place and a stylesheet can read the same value.
|
|
11910
|
+
const SWIPE_THRESHOLD_ATTRIBUTE = "data-swipe-threshold";
|
|
11911
|
+
const LONGPRESS_DELAY_ATTRIBUTE = "data-longpress-delay";
|
|
11912
|
+
const LONGPRESS_SLOP_ATTRIBUTE = "data-longpress-slop";
|
|
11913
|
+
|
|
11914
|
+
// Which axes this element takes a swipe on, and that it takes a hold: said in the
|
|
11915
|
+
// DOM at render time, for the CSS below and for the boxes above to read.
|
|
11916
|
+
const SWIPE_AXES_ATTRIBUTE = "data-swipe";
|
|
11917
|
+
const LONGPRESS_ATTRIBUTE = "data-longpress";
|
|
11918
|
+
import.meta.css = [/* css */`
|
|
11919
|
+
/* Declared, so the browser sees a NUMBER it can interpolate and calculate with:
|
|
11920
|
+
what a swipe reveals behind the element is drawn from this, and an undeclared
|
|
11921
|
+
custom property only ever jumps from one value to the next. Both inherited,
|
|
11922
|
+
because what is drawn from them is drawn by a CHILD of the swiped element:
|
|
11923
|
+
the trail behind a row is inside the row, and a value that stopped at the
|
|
11924
|
+
element itself would reach 0 exactly where it is read. */
|
|
11925
|
+
@property --swipe-progress {
|
|
11926
|
+
syntax: "<number>";
|
|
11927
|
+
inherits: true;
|
|
11928
|
+
initial-value: 0;
|
|
11929
|
+
}
|
|
11930
|
+
@property --swipe-pulled {
|
|
11931
|
+
syntax: "<length>";
|
|
11932
|
+
inherits: true;
|
|
11933
|
+
initial-value: 0px;
|
|
11934
|
+
}
|
|
11935
|
+
|
|
11936
|
+
/* What a touch may do on an element that takes a swipe: the axis the swipe walks
|
|
11937
|
+
is taken, the other is left to the page — so a row is swiped sideways and the
|
|
11938
|
+
list still scrolls under the same finger. */
|
|
11939
|
+
[${SWIPE_AXES_ATTRIBUTE}="x"] {
|
|
11940
|
+
touch-action: pan-y;
|
|
11941
|
+
}
|
|
11942
|
+
[${SWIPE_AXES_ATTRIBUTE}="y"] {
|
|
11943
|
+
touch-action: pan-x;
|
|
11944
|
+
}
|
|
11945
|
+
[${SWIPE_AXES_ATTRIBUTE}="xy"] {
|
|
11946
|
+
touch-action: none;
|
|
11947
|
+
}
|
|
11948
|
+
|
|
11949
|
+
/* iOS shows its callout (Copy / Look Up) and selects the word under the finger on
|
|
11950
|
+
a press held still, and does not always route that through an event that can be
|
|
11951
|
+
refused. Same reason as the drag sources in @jsenv/dom: it has to be true
|
|
11952
|
+
before the finger lands. */
|
|
11953
|
+
[${LONGPRESS_ATTRIBUTE}] {
|
|
11954
|
+
-webkit-touch-callout: none;
|
|
11955
|
+
}
|
|
11956
|
+
|
|
11957
|
+
/* The element follows the finger. The translate property rather than a transform,
|
|
11958
|
+
so whatever transform the element (or its theme) already has is left alone. */
|
|
11959
|
+
[data-swiping="left"],
|
|
11960
|
+
[data-swiping="right"] {
|
|
11961
|
+
translate: var(--swipe-pulled) 0;
|
|
11962
|
+
}
|
|
11963
|
+
[data-swiping="up"],
|
|
11964
|
+
[data-swiping="down"] {
|
|
11965
|
+
translate: 0 var(--swipe-pulled);
|
|
11966
|
+
}
|
|
11967
|
+
/* No transition while the finger holds it — the element is where the hand put
|
|
11968
|
+
it — and one when the hand lets go. */
|
|
11969
|
+
[data-swipe-settling] {
|
|
11970
|
+
transition: translate ${SETTLE_DURATION_MS}ms ease-out;
|
|
11971
|
+
}
|
|
11972
|
+
`, "@jsenv/navi/src/control/interaction/interaction_press.js"];
|
|
11973
|
+
defineInteractionDetector({
|
|
11974
|
+
name: "press",
|
|
11975
|
+
claims: type => type in AXIS_BY_SWIPE_TYPE || type === "longpress",
|
|
11976
|
+
setup: (element, trigger, {
|
|
11977
|
+
types,
|
|
11978
|
+
readConfig
|
|
11979
|
+
}) => {
|
|
11980
|
+
let axes = "";
|
|
11981
|
+
for (const type of types) {
|
|
11982
|
+
const axis = AXIS_BY_SWIPE_TYPE[type];
|
|
11983
|
+
if (axis && !axes.includes(axis)) {
|
|
11984
|
+
axes += axis;
|
|
11985
|
+
}
|
|
11986
|
+
}
|
|
11987
|
+
const hasLongPress = types.includes("longpress");
|
|
11988
|
+
const undo = [];
|
|
11989
|
+
const mark = (attribute, value) => {
|
|
11990
|
+
element.setAttribute(attribute, value);
|
|
11991
|
+
undo.push(() => {
|
|
11992
|
+
element.removeAttribute(attribute);
|
|
11993
|
+
});
|
|
11994
|
+
};
|
|
11995
|
+
if (axes) {
|
|
11996
|
+
mark(SWIPE_AXES_ATTRIBUTE, axes);
|
|
11997
|
+
// Read by the boxes ABOVE this one: an axis swiped here is not theirs to
|
|
11998
|
+
// travel (see axesLeftBy).
|
|
11999
|
+
mark("data-travel-by-drag", axes);
|
|
12000
|
+
// …and nothing inside hands its leftover scroll to the page while the
|
|
12001
|
+
// element is being pulled.
|
|
12002
|
+
mark("data-drag-travel", axes);
|
|
12003
|
+
|
|
12004
|
+
// A link and an image are draggable without anyone asking, and a native
|
|
12005
|
+
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
12006
|
+
// events with it and paints a ghost of the row the hand is trying to swipe.
|
|
12007
|
+
// Two things are needed, and neither covers the other:
|
|
12008
|
+
// - `draggable` refuses it on the element itself, before it starts;
|
|
12009
|
+
// - a swipe is usually a row with a link or a thumbnail INSIDE it, and
|
|
12010
|
+
// those are draggable in their own right. `dragstart` bubbles, so
|
|
12011
|
+
// refusing it here refuses theirs too — and it is the only refusal every
|
|
12012
|
+
// browser honours (`-webkit-user-drag` is one engine's).
|
|
12013
|
+
// The cost is stated rather than worked around: an element that takes a
|
|
12014
|
+
// swipe cannot also be dragged out of the page, because there is one
|
|
12015
|
+
// gesture and it cannot mean both.
|
|
12016
|
+
mark("draggable", "false");
|
|
12017
|
+
const onDragStart = dragStartEvent => {
|
|
12018
|
+
dragStartEvent.preventDefault();
|
|
12019
|
+
};
|
|
12020
|
+
element.addEventListener("dragstart", onDragStart);
|
|
12021
|
+
undo.push(() => {
|
|
12022
|
+
element.removeEventListener("dragstart", onDragStart);
|
|
12023
|
+
});
|
|
12024
|
+
}
|
|
12025
|
+
if (hasLongPress) {
|
|
12026
|
+
mark(LONGPRESS_ATTRIBUTE, "");
|
|
12027
|
+
}
|
|
12028
|
+
const onPointerDown = pointerDownEvent => {
|
|
12029
|
+
if (pointerDownEvent.button !== 0) {
|
|
12030
|
+
return;
|
|
12031
|
+
}
|
|
12032
|
+
let swipe = null;
|
|
12033
|
+
let press = null;
|
|
12034
|
+
if (axes) {
|
|
12035
|
+
swipe = startSwipe(pointerDownEvent, {
|
|
12036
|
+
element,
|
|
12037
|
+
axes,
|
|
12038
|
+
types,
|
|
12039
|
+
trigger,
|
|
12040
|
+
threshold: readConfig(SWIPE_THRESHOLD_ATTRIBUTE, SWIPE_THRESHOLD_DEFAULT),
|
|
12041
|
+
// The axis is the definitive word on what this press is: a finger that
|
|
12042
|
+
// has picked one is swiping, not holding. The slop below cancels most
|
|
12043
|
+
// holds before this (it is the smaller distance), but a press that
|
|
12044
|
+
// resolves the axis without drifting — a mouse, a flick — has to be
|
|
12045
|
+
// taken from the wait too.
|
|
12046
|
+
onSwipeStart: () => {
|
|
12047
|
+
press?.cancel();
|
|
12048
|
+
press = null;
|
|
12049
|
+
}
|
|
12050
|
+
});
|
|
12051
|
+
}
|
|
12052
|
+
if (hasLongPress) {
|
|
12053
|
+
press = waitForPressHeld(pointerDownEvent, {
|
|
12054
|
+
delay: readConfig(LONGPRESS_DELAY_ATTRIBUTE, LONGPRESS_DELAY_DEFAULT),
|
|
12055
|
+
slop: readConfig(LONGPRESS_SLOP_ATTRIBUTE, LONGPRESS_SLOP_DEFAULT),
|
|
12056
|
+
onPressHeld: (pressEvent, {
|
|
12057
|
+
endPress
|
|
12058
|
+
}) => {
|
|
12059
|
+
// The hold won the arbitration: the swipe never got the distance it
|
|
12060
|
+
// needed, and must not get it from whatever the finger does next.
|
|
12061
|
+
swipe?.stop();
|
|
12062
|
+
swipe = null;
|
|
12063
|
+
const clickSuppressionIsOver = suppressClickAfterGesture();
|
|
12064
|
+
const onPointerEnd = () => {
|
|
12065
|
+
window.removeEventListener("pointerup", onPointerEnd, true);
|
|
12066
|
+
window.removeEventListener("pointercancel", onPointerEnd, true);
|
|
12067
|
+
endPress();
|
|
12068
|
+
clickSuppressionIsOver();
|
|
12069
|
+
};
|
|
12070
|
+
window.addEventListener("pointerup", onPointerEnd, true);
|
|
12071
|
+
window.addEventListener("pointercancel", onPointerEnd, true);
|
|
12072
|
+
trigger("longpress", pressEvent, {
|
|
12073
|
+
pointerType: pressEvent.pointerType
|
|
12074
|
+
});
|
|
12075
|
+
}
|
|
12076
|
+
});
|
|
12077
|
+
}
|
|
12078
|
+
};
|
|
12079
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
12080
|
+
undo.push(() => {
|
|
12081
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
12082
|
+
});
|
|
12083
|
+
return () => {
|
|
12084
|
+
for (const undoOne of undo) {
|
|
12085
|
+
undoOne();
|
|
12086
|
+
}
|
|
12087
|
+
};
|
|
12088
|
+
}
|
|
12089
|
+
});
|
|
12090
|
+
const startSwipe = (pointerDownEvent, {
|
|
12091
|
+
element,
|
|
12092
|
+
axes,
|
|
12093
|
+
types,
|
|
12094
|
+
trigger,
|
|
12095
|
+
threshold,
|
|
12096
|
+
onSwipeStart
|
|
12097
|
+
}) => {
|
|
12098
|
+
let settleTimeout = null;
|
|
12099
|
+
const paint = ({
|
|
12100
|
+
pulled,
|
|
12101
|
+
progress,
|
|
12102
|
+
type
|
|
12103
|
+
}) => {
|
|
12104
|
+
element.style.setProperty("--swipe-pulled", `${pulled}px`);
|
|
12105
|
+
element.style.setProperty("--swipe-progress", progress);
|
|
12106
|
+
element.setAttribute("data-swiping", type.slice("swipe_".length));
|
|
12107
|
+
element.toggleAttribute("data-swipe-past-threshold", Math.abs(progress) >= threshold);
|
|
12108
|
+
};
|
|
12109
|
+
const forget = () => {
|
|
12110
|
+
clearTimeout(settleTimeout);
|
|
12111
|
+
element.removeAttribute("data-swiping");
|
|
12112
|
+
element.removeAttribute("data-swipe-past-threshold");
|
|
12113
|
+
element.removeAttribute("data-swipe-settling");
|
|
12114
|
+
element.style.removeProperty("--swipe-pulled");
|
|
12115
|
+
element.style.removeProperty("--swipe-progress");
|
|
12116
|
+
};
|
|
12117
|
+
// Where the gesture leaves the element, and later back to rest. Cleaned up on a
|
|
12118
|
+
// timeout rather than on transitionend: a swipe let go of at the very edge has
|
|
12119
|
+
// nothing left to move (the pull is clamped to the element's size), and a
|
|
12120
|
+
// movement that does not happen reports no end.
|
|
12121
|
+
const settleTo = ({
|
|
12122
|
+
pulled,
|
|
12123
|
+
progress,
|
|
12124
|
+
type
|
|
12125
|
+
}, {
|
|
12126
|
+
thenForget = true
|
|
12127
|
+
} = {}) => {
|
|
12128
|
+
clearTimeout(settleTimeout);
|
|
12129
|
+
element.setAttribute("data-swipe-settling", "");
|
|
12130
|
+
paint({
|
|
12131
|
+
pulled,
|
|
12132
|
+
progress,
|
|
12133
|
+
type
|
|
12134
|
+
});
|
|
12135
|
+
if (thenForget) {
|
|
12136
|
+
settleTimeout = setTimeout(forget, SETTLE_DURATION_MS);
|
|
12137
|
+
}
|
|
12138
|
+
};
|
|
12139
|
+
return startDragToTravel(pointerDownEvent, {
|
|
12140
|
+
element,
|
|
12141
|
+
axes,
|
|
12142
|
+
commitRatio: threshold,
|
|
12143
|
+
onStart: ({
|
|
12144
|
+
axis
|
|
12145
|
+
}) => {
|
|
12146
|
+
const {
|
|
12147
|
+
positive,
|
|
12148
|
+
negative
|
|
12149
|
+
} = SWIPE_TYPE_BY_AXIS[axis];
|
|
12150
|
+
const hasPositive = types.includes(positive);
|
|
12151
|
+
const hasNegative = types.includes(negative);
|
|
12152
|
+
if (!hasPositive && !hasNegative) {
|
|
12153
|
+
return false;
|
|
12154
|
+
}
|
|
12155
|
+
const {
|
|
12156
|
+
width,
|
|
12157
|
+
height
|
|
12158
|
+
} = element.getBoundingClientRect();
|
|
12159
|
+
const size = axis === "x" ? width : height;
|
|
12160
|
+
if (!size) {
|
|
12161
|
+
return false;
|
|
12162
|
+
}
|
|
12163
|
+
onSwipeStart();
|
|
12164
|
+
clearTimeout(settleTimeout);
|
|
12165
|
+
element.removeAttribute("data-swipe-settling");
|
|
12166
|
+
// A side nothing is declared for is not refused, it resists: the gesture is
|
|
12167
|
+
// answered (something moves) while saying there is nothing that way.
|
|
12168
|
+
return {
|
|
12169
|
+
size,
|
|
12170
|
+
travelBack: hasPositive,
|
|
12171
|
+
travelOn: hasNegative
|
|
12172
|
+
};
|
|
12173
|
+
},
|
|
12174
|
+
onPull: ({
|
|
12175
|
+
axis,
|
|
12176
|
+
pulled,
|
|
12177
|
+
progress
|
|
12178
|
+
}) => {
|
|
12179
|
+
paint({
|
|
12180
|
+
pulled,
|
|
12181
|
+
progress,
|
|
12182
|
+
type: swipeTypeOf(axis, pulled)
|
|
12183
|
+
});
|
|
12184
|
+
},
|
|
12185
|
+
onEnd: ({
|
|
12186
|
+
axis,
|
|
12187
|
+
pulled,
|
|
12188
|
+
size,
|
|
12189
|
+
sign,
|
|
12190
|
+
travels,
|
|
12191
|
+
event
|
|
12192
|
+
}) => {
|
|
12193
|
+
const type = swipeTypeOf(axis, pulled);
|
|
12194
|
+
const restingPlace = {
|
|
12195
|
+
pulled: 0,
|
|
12196
|
+
progress: 0,
|
|
12197
|
+
type
|
|
12198
|
+
};
|
|
12199
|
+
if (!travels) {
|
|
12200
|
+
settleTo(restingPlace);
|
|
12201
|
+
return;
|
|
12202
|
+
}
|
|
12203
|
+
// Out, and out is where it stays for as long as the answer takes: a row that
|
|
12204
|
+
// comes back and leaves again says the gesture was not understood.
|
|
12205
|
+
settleTo({
|
|
12206
|
+
pulled: sign * size,
|
|
12207
|
+
progress: sign,
|
|
12208
|
+
type
|
|
12209
|
+
}, {
|
|
12210
|
+
thenForget: false
|
|
12211
|
+
});
|
|
12212
|
+
const pending = trigger(type, event, {
|
|
12213
|
+
axis,
|
|
12214
|
+
sign,
|
|
12215
|
+
pulled,
|
|
12216
|
+
size,
|
|
12217
|
+
progress: pulled / size
|
|
12218
|
+
});
|
|
12219
|
+
if (!pending) {
|
|
12220
|
+
settleTo(restingPlace);
|
|
12221
|
+
return;
|
|
12222
|
+
}
|
|
12223
|
+
// However it ended, the element comes back: a failure leaves the row in
|
|
12224
|
+
// place and it can be tried again, and a success is the caller's to answer
|
|
12225
|
+
// (a list that redemands its rows, a row that leaves). Nothing here makes it
|
|
12226
|
+
// disappear — navi does not know what "put away" means.
|
|
12227
|
+
const comeBack = () => {
|
|
12228
|
+
settleTo(restingPlace);
|
|
12229
|
+
};
|
|
12230
|
+
pending.then(comeBack, comeBack);
|
|
12231
|
+
},
|
|
12232
|
+
// Nothing to put back: onGiveUp only ever comes from a press that never became
|
|
12233
|
+
// a swipe, so nothing was painted by it.
|
|
12234
|
+
onGiveUp: () => {}
|
|
12235
|
+
});
|
|
12236
|
+
};
|
|
12237
|
+
const swipeTypeOf = (axis, pulled) => {
|
|
12238
|
+
const {
|
|
12239
|
+
positive,
|
|
12240
|
+
negative
|
|
12241
|
+
} = SWIPE_TYPE_BY_AXIS[axis];
|
|
12242
|
+
return pulled > 0 ? positive : negative;
|
|
12243
|
+
};
|
|
12244
|
+
|
|
12245
|
+
/**
|
|
12246
|
+
* `move`, `reorder`, `toss` — one grab, and what letting go of it means.
|
|
12247
|
+
*
|
|
12248
|
+
* All three are the same gesture: the element is picked up and carried. What
|
|
12249
|
+
* differs is the answer at the release, so one detector reads them all — it is one
|
|
12250
|
+
* press, and something has to arbitrate it.
|
|
12251
|
+
*
|
|
12252
|
+
* interactions={{ reorder: moveBefore, toss: remove }}
|
|
12253
|
+
* interactions={{ move: remember }}
|
|
12254
|
+
*
|
|
12255
|
+
* `reorder` and `toss` combine: a task dragged onto another changes places, the
|
|
12256
|
+
* same task thrown far and fast is gotten rid of. `move` does not combine with
|
|
12257
|
+
* `reorder` — an element either goes where it is put or takes a place in a list,
|
|
12258
|
+
* and the two answers cannot both be true of one release.
|
|
12259
|
+
*
|
|
12260
|
+
* `move` carries the element ITSELF and leaves it where it was put; the other two
|
|
12261
|
+
* carry a copy and put the original back. That is the same difference said in
|
|
12262
|
+
* layout terms: something moved has a new place of its own, something reordered
|
|
12263
|
+
* had its place taken by the list.
|
|
12264
|
+
*
|
|
12265
|
+
* Nothing of the gesture is decided here. `startDragTo` owns all of it — the
|
|
12266
|
+
* copy carried above the page while the original keeps its place in the layout, the
|
|
12267
|
+
* drop hint, the drop targets found by intersection, the no-op drops filtered out,
|
|
12268
|
+
* the flight of a thrown copy and its return when the answer refuses. This says
|
|
12269
|
+
* which elements are the items, how they are named, and what a given release means.
|
|
12270
|
+
*
|
|
12271
|
+
* WHICH ELEMENTS. Every element declaring `reorder` marks itself, so the set of
|
|
12272
|
+
* items IS the set of elements that declared it — no selector to pass, nothing to
|
|
12273
|
+
* keep in sync with the markup, and an item that must not move simply does not
|
|
12274
|
+
* declare it. An element that only declares `toss` marks nothing: it is not a place
|
|
12275
|
+
* anything lands.
|
|
12276
|
+
*
|
|
12277
|
+
* HOW THEY ARE NAMED: by `id`. A reorder is answered in terms of what moved and
|
|
12278
|
+
* what it landed before, and a DOM index cannot say it — a list draws fewer rows
|
|
12279
|
+
* than it has, and a search reorders them.
|
|
12280
|
+
*
|
|
12281
|
+
* WHAT COMES BACK, in the interaction's own detail:
|
|
12282
|
+
*
|
|
12283
|
+
* interactions={{
|
|
12284
|
+
* reorder: (event) => {
|
|
12285
|
+
* const { fromId, toId, syncCloneWithDropTarget } = event.detail;
|
|
12286
|
+
* return document.startViewTransition(() => {
|
|
12287
|
+
* syncCloneWithDropTarget();
|
|
12288
|
+
* setOrder(moveBefore(order, fromId, toId));
|
|
12289
|
+
* }).finished;
|
|
12290
|
+
* },
|
|
12291
|
+
* toss: (event) => remove(event.detail.id),
|
|
12292
|
+
* }}
|
|
12293
|
+
*
|
|
12294
|
+
* `toId` is null for a drop at the end. `syncCloneWithDropTarget` must be called
|
|
12295
|
+
* synchronously inside the transition callback, next to the state change, so the
|
|
12296
|
+
* copy is captured where it lands rather than where it was let go of.
|
|
12297
|
+
*
|
|
12298
|
+
* And the promise matters in both cases: the gesture holds its copy until the
|
|
12299
|
+
* answer settles. Returning the transition is what makes a landing continuous; a
|
|
12300
|
+
* `toss` that rejects brings the copy back, because the thing still exists and the
|
|
12301
|
+
* screen has to say so.
|
|
12302
|
+
*
|
|
12303
|
+
* Starting a document transition is the application's call and not navi's: a
|
|
12304
|
+
* `view-transition-name` must be unique per document, so only the application can
|
|
12305
|
+
* name what moves.
|
|
12306
|
+
*
|
|
12307
|
+
* What the copy LOOKS like is the application's too. A copy of a transparent
|
|
12308
|
+
* element is invisible — a row usually gets its background from the list around it,
|
|
12309
|
+
* which the copy has left — so it is dressed through the attributes the gesture
|
|
12310
|
+
* writes: `navi-drag-clone` on the copy, `navi-drag-clone-source` on the original.
|
|
12311
|
+
*/
|
|
12312
|
+
|
|
12313
|
+
|
|
12314
|
+
const MOVE = "move";
|
|
12315
|
+
const REORDER = "reorder";
|
|
12316
|
+
const TOSS = "toss";
|
|
12317
|
+
|
|
12318
|
+
// What makes an element a place something can land, written by the detector itself.
|
|
12319
|
+
const REORDERABLE_ATTRIBUTE = "data-reorderable";
|
|
12320
|
+
// Which axes the drag walks: "x", "y" or "xy". Its default is not the same for
|
|
12321
|
+
// every outcome — a list runs one way, and something being put somewhere goes
|
|
12322
|
+
// wherever it is put.
|
|
12323
|
+
const AXIS_ATTRIBUTE = "data-drag-axis";
|
|
12324
|
+
const DELAY_ATTRIBUTE = "data-drag-delay";
|
|
12325
|
+
const SLOP_ATTRIBUTE = "data-drag-slop";
|
|
12326
|
+
const THRESHOLD_ATTRIBUTE = "data-drag-threshold";
|
|
12327
|
+
const TOSS_DISTANCE_ATTRIBUTE = "data-toss-distance";
|
|
12328
|
+
const TOSS_SPEED_ATTRIBUTE = "data-toss-speed";
|
|
12329
|
+
|
|
12330
|
+
defineInteractionDetector({
|
|
12331
|
+
name: "drag",
|
|
12332
|
+
claims: (type) => type === MOVE || type === REORDER || type === TOSS,
|
|
12333
|
+
setup: (element, trigger, { types, readConfig }) => {
|
|
12334
|
+
const canMove = types.includes(MOVE);
|
|
12335
|
+
const canReorder = types.includes(REORDER);
|
|
12336
|
+
const canToss = types.includes(TOSS);
|
|
12337
|
+
// Read at setup rather than at the press: a container can say it, and it is
|
|
12338
|
+
// what the gesture is about rather than something it discovers.
|
|
12339
|
+
const axisHolder = element.closest(`[${AXIS_ATTRIBUTE}]`);
|
|
12340
|
+
const axes =
|
|
12341
|
+
axisHolder?.getAttribute(AXIS_ATTRIBUTE) ||
|
|
12342
|
+
// A list runs one way, and reordering walks it. Anything else goes wherever
|
|
12343
|
+
// the hand takes it: a thing put somewhere has two axes to be put along, and
|
|
12344
|
+
// a throw goes where it was thrown.
|
|
12345
|
+
(canReorder && !canToss ? "y" : "xy");
|
|
12346
|
+
|
|
12347
|
+
if (canReorder) {
|
|
12348
|
+
element.setAttribute(REORDERABLE_ATTRIBUTE, "");
|
|
12349
|
+
}
|
|
12350
|
+
// What @jsenv/dom puts on a drag source: no iOS callout, and the touch left to
|
|
12351
|
+
// the scroll until the press becomes a grab. Its value is the axis the
|
|
12352
|
+
// SURROUNDINGS scroll on, which for a list is the axis the list runs on.
|
|
12353
|
+
element.setAttribute("data-drag-source", axes === "x" ? "x" : "");
|
|
12354
|
+
|
|
12355
|
+
const onPointerDown = (pointerDownEvent) => {
|
|
12356
|
+
// What this element says a release can mean. The gesture then runs only what
|
|
12357
|
+
// those need — no copy for a move, no drop hint for something that can only
|
|
12358
|
+
// be thrown away.
|
|
12359
|
+
startDragTo(pointerDownEvent, types, {
|
|
12360
|
+
draggedElement: element,
|
|
12361
|
+
// Nothing to land on when nothing reorders.
|
|
12362
|
+
itemSelector: canReorder ? `[${REORDERABLE_ATTRIBUTE}]` : undefined,
|
|
12363
|
+
getItemId: (itemElement) => itemElement.id,
|
|
12364
|
+
direction: { x: axes.includes("x"), y: axes.includes("y") },
|
|
12365
|
+
// Where it may go, said in the DOM. A thing that is put somewhere stays
|
|
12366
|
+
// inside what one can SEE of its container ("scrollport", not "scroll":
|
|
12367
|
+
// the scrollable area can be far larger than the box, and "inside the box"
|
|
12368
|
+
// is what a hand expects). `data-drag-free` lifts that. Left alone for a
|
|
12369
|
+
// throw, which frees the area on its own — it has to be able to leave.
|
|
12370
|
+
areaConstraint: element.closest(`[data-drag-free]`)
|
|
12371
|
+
? "none"
|
|
12372
|
+
: canMove
|
|
12373
|
+
? "scrollport"
|
|
12374
|
+
: undefined,
|
|
12375
|
+
threshold: readConfig(THRESHOLD_ATTRIBUTE, undefined),
|
|
12376
|
+
longPressDelay: readConfig(DELAY_ATTRIBUTE, undefined),
|
|
12377
|
+
longPressSlop: readConfig(SLOP_ATTRIBUTE, undefined),
|
|
12378
|
+
tossDistance: readConfig(TOSS_DISTANCE_ATTRIBUTE, undefined),
|
|
12379
|
+
tossSpeed: readConfig(TOSS_SPEED_ATTRIBUTE, undefined),
|
|
12380
|
+
// Handed straight back in all three cases: what `trigger` returns is a
|
|
12381
|
+
// promise while the answer is still going, which is what the gesture waits
|
|
12382
|
+
// on before it lets go of what it carries.
|
|
12383
|
+
onReorder: (fromId, toId, syncCloneWithDropTarget) =>
|
|
12384
|
+
trigger(REORDER, pointerDownEvent, {
|
|
12385
|
+
fromId,
|
|
12386
|
+
toId,
|
|
12387
|
+
syncCloneWithDropTarget,
|
|
12388
|
+
}),
|
|
12389
|
+
onToss: ({ gestureInfo }) =>
|
|
12390
|
+
trigger(TOSS, pointerDownEvent, {
|
|
12391
|
+
id: element.id,
|
|
12392
|
+
velocity: gestureInfo.velocity,
|
|
12393
|
+
x: gestureInfo.layout.xDelta,
|
|
12394
|
+
y: gestureInfo.layout.yDelta,
|
|
12395
|
+
}),
|
|
12396
|
+
onMove: ({ x, y }) => trigger(MOVE, pointerDownEvent, { x, y }),
|
|
12397
|
+
});
|
|
12398
|
+
};
|
|
12399
|
+
element.addEventListener("pointerdown", onPointerDown);
|
|
12400
|
+
|
|
12401
|
+
return () => {
|
|
12402
|
+
element.removeAttribute(REORDERABLE_ATTRIBUTE);
|
|
12403
|
+
element.removeAttribute("data-drag-source");
|
|
12404
|
+
element.removeEventListener("pointerdown", onPointerDown);
|
|
12405
|
+
};
|
|
12406
|
+
},
|
|
12407
|
+
});
|
|
12408
|
+
|
|
11700
12409
|
const isSignal = (value) => {
|
|
11701
12410
|
return getSignalType(value) !== null;
|
|
11702
12411
|
};
|
|
@@ -15399,6 +16108,43 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
15399
16108
|
return false;
|
|
15400
16109
|
};
|
|
15401
16110
|
|
|
16111
|
+
/**
|
|
16112
|
+
* `"keyboard:ctrl+backspace"` — an interaction asked for with keys.
|
|
16113
|
+
*
|
|
16114
|
+
* The name carries the shortcut, so a control says what a shortcut DOES in the
|
|
16115
|
+
* same place it says what a swipe does, rather than in a separate shortcut list
|
|
16116
|
+
* whose entries then have to find their way back to the action.
|
|
16117
|
+
*
|
|
16118
|
+
* The shortcut itself is not parsed here: `createOnKeyDownForShortcuts` already
|
|
16119
|
+
* knows what "ctrl+backspace" means, which keys a control may take without
|
|
16120
|
+
* swallowing what the browser owed them, and which it may not.
|
|
16121
|
+
*
|
|
16122
|
+
* The interaction is dispatched under its full name, colon included, so
|
|
16123
|
+
* `findEvent(event, "keyboard:ctrl+backspace")` gets back to it — the shortcut
|
|
16124
|
+
* that asked is what the action wants to know, not that a key went down.
|
|
16125
|
+
*/
|
|
16126
|
+
|
|
16127
|
+
|
|
16128
|
+
const KEYBOARD_PREFIX = "keyboard:";
|
|
16129
|
+
|
|
16130
|
+
defineInteractionDetector({
|
|
16131
|
+
name: "keyboard",
|
|
16132
|
+
claims: (type) => type.startsWith(KEYBOARD_PREFIX),
|
|
16133
|
+
setup: (element, trigger, { types }) => {
|
|
16134
|
+
const shortcuts = {};
|
|
16135
|
+
for (const type of types) {
|
|
16136
|
+
shortcuts[type.slice(KEYBOARD_PREFIX.length)] = (keyboardEvent) => {
|
|
16137
|
+
trigger(type, keyboardEvent);
|
|
16138
|
+
};
|
|
16139
|
+
}
|
|
16140
|
+
const onKeyDown = createOnKeyDownForShortcuts(shortcuts);
|
|
16141
|
+
element.addEventListener("keydown", onKeyDown);
|
|
16142
|
+
return () => {
|
|
16143
|
+
element.removeEventListener("keydown", onKeyDown);
|
|
16144
|
+
};
|
|
16145
|
+
},
|
|
16146
|
+
});
|
|
16147
|
+
|
|
15402
16148
|
/**
|
|
15403
16149
|
* Merges a component's base className with className received from props.
|
|
15404
16150
|
*
|
|
@@ -24989,7 +25735,28 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
|
|
|
24989
25735
|
color: var(--x-button-color);
|
|
24990
25736
|
background: none;
|
|
24991
25737
|
border: none;
|
|
24992
|
-
|
|
25738
|
+
/* Squared from the outside, corner by corner: a Group asks the member it
|
|
25739
|
+
joins for square corners along the seam, and the button it means may
|
|
25740
|
+
arrive wrapped (in a tooltip, in a link), so the ask travels down as
|
|
25741
|
+
inherited custom properties rather than as a selector aimed at the
|
|
25742
|
+
button. Each corner falls back to the button's own radius when nothing
|
|
25743
|
+
asks for anything. */
|
|
25744
|
+
border-top-left-radius: var(
|
|
25745
|
+
--x-corner-top-left-radius,
|
|
25746
|
+
var(--button-border-radius)
|
|
25747
|
+
);
|
|
25748
|
+
border-top-right-radius: var(
|
|
25749
|
+
--x-corner-top-right-radius,
|
|
25750
|
+
var(--button-border-radius)
|
|
25751
|
+
);
|
|
25752
|
+
border-bottom-right-radius: var(
|
|
25753
|
+
--x-corner-bottom-right-radius,
|
|
25754
|
+
var(--button-border-radius)
|
|
25755
|
+
);
|
|
25756
|
+
border-bottom-left-radius: var(
|
|
25757
|
+
--x-corner-bottom-left-radius,
|
|
25758
|
+
var(--button-border-radius)
|
|
25759
|
+
);
|
|
24993
25760
|
outline: none;
|
|
24994
25761
|
cursor: var(--x-button-cursor);
|
|
24995
25762
|
touch-action: manipulation;
|
|
@@ -24997,6 +25764,14 @@ installImportMetaCssBuild(import.meta);const css$W = /* css */`
|
|
|
24997
25764
|
-webkit-tap-highlight-color: var(--navi-control-tap-highlight-color);
|
|
24998
25765
|
|
|
24999
25766
|
.navi_button_content {
|
|
25767
|
+
/* The ask stops here: this element is the button's frame, so what is
|
|
25768
|
+
inside it (a popover the button opens, a button of its own) is no
|
|
25769
|
+
longer at the seam. */
|
|
25770
|
+
--x-corner-top-left-radius: initial;
|
|
25771
|
+
--x-corner-top-right-radius: initial;
|
|
25772
|
+
--x-corner-bottom-right-radius: initial;
|
|
25773
|
+
--x-corner-bottom-left-radius: initial;
|
|
25774
|
+
|
|
25000
25775
|
position: relative;
|
|
25001
25776
|
display: inherit;
|
|
25002
25777
|
box-sizing: border-box;
|
|
@@ -27009,6 +27784,15 @@ const css$V = /* css */`
|
|
|
27009
27784
|
}
|
|
27010
27785
|
|
|
27011
27786
|
.navi_dialog {
|
|
27787
|
+
/* A popup renders inside its opener's own subtree, so a corner claimed from
|
|
27788
|
+
the outside (see group.jsx) would otherwise reach the controls in here.
|
|
27789
|
+
It stops at the popup: what a popup holds is never at a seam of the group
|
|
27790
|
+
its opener belongs to. */
|
|
27791
|
+
--x-corner-top-left-radius: initial;
|
|
27792
|
+
--x-corner-top-right-radius: initial;
|
|
27793
|
+
--x-corner-bottom-right-radius: initial;
|
|
27794
|
+
--x-corner-bottom-left-radius: initial;
|
|
27795
|
+
|
|
27012
27796
|
/* Computed once, reused by both max-width itself and min-width's own
|
|
27013
27797
|
clamp below (see its comment for why) — avoids repeating the same
|
|
27014
27798
|
min(..., ...) expression twice. */
|
|
@@ -27033,7 +27817,7 @@ const css$V = /* css */`
|
|
|
27033
27817
|
position: absolute;
|
|
27034
27818
|
inset: unset;
|
|
27035
27819
|
/* Custom renderer only — see openLocalDialogCount above */
|
|
27036
|
-
z-index: calc(var(--navi-
|
|
27820
|
+
z-index: calc(var(--navi-z-index-popup) + var(--dialog-stack-order, 0));
|
|
27037
27821
|
min-width: min(
|
|
27038
27822
|
max(var(--anchor-width, 0px), var(--dialog-min-width, 0px)),
|
|
27039
27823
|
var(--x-dialog-max-width)
|
|
@@ -28278,6 +29062,15 @@ const css$U = /* css */`
|
|
|
28278
29062
|
}
|
|
28279
29063
|
|
|
28280
29064
|
.navi_popover {
|
|
29065
|
+
/* A popup renders inside its opener's own subtree, so a corner claimed from
|
|
29066
|
+
the outside (see group.jsx) would otherwise reach the controls in here.
|
|
29067
|
+
It stops at the popup: what a popup holds is never at a seam of the group
|
|
29068
|
+
its opener belongs to. */
|
|
29069
|
+
--x-corner-top-left-radius: initial;
|
|
29070
|
+
--x-corner-top-right-radius: initial;
|
|
29071
|
+
--x-corner-bottom-right-radius: initial;
|
|
29072
|
+
--x-corner-bottom-left-radius: initial;
|
|
29073
|
+
|
|
28281
29074
|
--x-popover-max-width: min(
|
|
28282
29075
|
var(--popover-max-width, var(--popover-maxmax-width)),
|
|
28283
29076
|
var(--container-position-remaining-width, var(--popover-maxmax-width)),
|
|
@@ -28302,7 +29095,7 @@ const css$U = /* css */`
|
|
|
28302
29095
|
most-recently-opened local popover always outranks an earlier one,
|
|
28303
29096
|
regardless of DOM position — a plain positioned div gets no free
|
|
28304
29097
|
"last shown wins" the way the top-layer renderer does. */
|
|
28305
|
-
z-index: calc(var(--navi-
|
|
29098
|
+
z-index: calc(var(--navi-z-index-popup) + var(--popover-stack-order, 0));
|
|
28306
29099
|
min-width: min(var(--popover-min-width, 0px), var(--x-popover-max-width));
|
|
28307
29100
|
max-width: var(--x-popover-max-width);
|
|
28308
29101
|
min-height: min(
|
|
@@ -28435,7 +29228,7 @@ const css$U = /* css */`
|
|
|
28435
29228
|
instance still resolve via DOM order (content rendered after its own
|
|
28436
29229
|
backdrop, see this file's top comment), so this only needs to beat
|
|
28437
29230
|
*other* popovers' own backdrop/content, not its own. */
|
|
28438
|
-
z-index: calc(var(--navi-
|
|
29231
|
+
z-index: calc(var(--navi-z-index-popup) + var(--popover-stack-order, 0));
|
|
28439
29232
|
margin: 0;
|
|
28440
29233
|
padding: 0;
|
|
28441
29234
|
background: transparent;
|
|
@@ -41486,7 +42279,7 @@ const css$L = /* css */`
|
|
|
41486
42279
|
|
|
41487
42280
|
.navi_fixed_bar {
|
|
41488
42281
|
position: fixed;
|
|
41489
|
-
z-index:
|
|
42282
|
+
z-index: var(--navi-z-index-bar);
|
|
41490
42283
|
display: flex;
|
|
41491
42284
|
box-sizing: border-box;
|
|
41492
42285
|
margin: auto;
|
|
@@ -44933,6 +45726,14 @@ const inputCss = /* css */`
|
|
|
44933
45726
|
}
|
|
44934
45727
|
|
|
44935
45728
|
.navi_input_slot {
|
|
45729
|
+
/* A corner claimed from the outside (see group.jsx) is the frame's, and
|
|
45730
|
+
what lives in here is not the frame — a button in a slot, the content
|
|
45731
|
+
of a popup — so the ask stops at this boundary. */
|
|
45732
|
+
--x-corner-top-left-radius: initial;
|
|
45733
|
+
--x-corner-top-right-radius: initial;
|
|
45734
|
+
--x-corner-bottom-right-radius: initial;
|
|
45735
|
+
--x-corner-bottom-left-radius: initial;
|
|
45736
|
+
|
|
44936
45737
|
margin-right: var(--slot-spacing, calc(2px + 0.1em));
|
|
44937
45738
|
margin-left: var(--slot-spacing, calc(2px + 0.1em));
|
|
44938
45739
|
color: #5e4e4e;
|
|
@@ -46354,92 +47155,117 @@ HTMLFormElement.prototype.requestSubmit = function (submitter) {
|
|
|
46354
47155
|
// form.dispatchEvent(customEvent);
|
|
46355
47156
|
// };
|
|
46356
47157
|
|
|
46357
|
-
installImportMetaCssBuild(import.meta)
|
|
47158
|
+
installImportMetaCssBuild(import.meta);/**
|
|
47159
|
+
* A row or column of controls sharing one frame: the borders where two of them
|
|
47160
|
+
* meet are drawn once instead of twice, and only the outer corners stay
|
|
47161
|
+
* rounded. See docs/control_group.md.
|
|
47162
|
+
*/
|
|
47163
|
+
const css$B = /* css */`
|
|
46358
47164
|
.navi_group {
|
|
46359
|
-
--group-border-width:
|
|
46360
|
-
|
|
47165
|
+
--group-border-width: var(--navi-control-border-width);
|
|
47166
|
+
|
|
47167
|
+
/* Two ways of asking for a square corner, and no selector ever reaching
|
|
47168
|
+
inside a member:
|
|
47169
|
+
- the property, on the member itself: a navi control declares the radius
|
|
47170
|
+
of its frame on its own root, and the inner element that draws that
|
|
47171
|
+
frame inherits it;
|
|
47172
|
+
- the custom property, which travels: a member can be an enrobage (a
|
|
47173
|
+
tooltip, a link) around the control that carries the frame, so the ask
|
|
47174
|
+
also goes down as --x-corner-*-radius, which a control reads as an
|
|
47175
|
+
override of its own radius. Private (the --x- prefix): navi's own
|
|
47176
|
+
controls answer it, an app never writes it. Whoever answers it also
|
|
47177
|
+
stops it (see .navi_button_content in button_ui.jsx), so a button
|
|
47178
|
+
deeper in — the clear cross in a picker's slot, the Save of a form in
|
|
47179
|
+
a popup the member opens — never mistakes itself for the seam. */
|
|
47180
|
+
|
|
47181
|
+
/* Members overlap by the width of one border, so along each seam one of the
|
|
47182
|
+
two borders covers the other. Whichever member the user is on has to be
|
|
47183
|
+
the one on top: it is the one whose border changes color, and the one
|
|
47184
|
+
whose focus ring goes all the way around — half a ring, cut by the
|
|
47185
|
+
neighbour painted after it, is what this avoids. z-index needs a
|
|
47186
|
+
positioned element to mean anything, hence position: relative.
|
|
47187
|
+
Deliberately not paired with isolation: isolate — a stacking context
|
|
47188
|
+
here would also trap the popup of a picker held in the group, which
|
|
47189
|
+
counts on its own band reaching the whole page. What keeps these two
|
|
47190
|
+
values from escaping is instead that everything they could reach is a
|
|
47191
|
+
band above them (see navi_z_indexes.js). */
|
|
46361
47192
|
> *:hover,
|
|
46362
47193
|
> *[data-hover] {
|
|
46363
47194
|
position: relative;
|
|
46364
|
-
z-index:
|
|
46365
|
-
}
|
|
47195
|
+
z-index: var(--navi-z-index-control-hovered);
|
|
47196
|
+
}
|
|
47197
|
+
/* Three spellings for one thing — the member showing a focus ring. Some
|
|
47198
|
+
controls take the focus on their own root (a button); others wrap a real
|
|
47199
|
+
input and draw the ring on their frame while the keyboard is held
|
|
47200
|
+
somewhere inside (a picker, a spin). The ring is what must not be sliced,
|
|
47201
|
+
so the member holding it is raised whether it wears the state itself or
|
|
47202
|
+
merely contains it. */
|
|
46366
47203
|
> *:focus-visible,
|
|
46367
|
-
> *[data-focus-visible]
|
|
47204
|
+
> *[data-focus-visible],
|
|
47205
|
+
> *:has([data-focus-visible]) {
|
|
46368
47206
|
position: relative;
|
|
46369
|
-
z-index:
|
|
47207
|
+
z-index: var(--navi-z-index-control-focused);
|
|
46370
47208
|
}
|
|
46371
47209
|
|
|
46372
47210
|
/* Horizontal (default): Cumulative margin for border overlap */
|
|
46373
47211
|
&:not([data-vertical]) {
|
|
46374
47212
|
> *:not(:first-child) {
|
|
46375
|
-
margin-left: calc(var(--border-width) * -1);
|
|
47213
|
+
margin-left: calc(var(--border-width, var(--group-border-width)) * -1);
|
|
46376
47214
|
}
|
|
46377
47215
|
> *:first-child:not(:only-child) {
|
|
47216
|
+
--x-corner-top-right-radius: 0;
|
|
47217
|
+
--x-corner-bottom-right-radius: 0;
|
|
47218
|
+
|
|
46378
47219
|
border-top-right-radius: 0 !important;
|
|
46379
47220
|
border-bottom-right-radius: 0 !important;
|
|
46380
|
-
|
|
46381
|
-
.navi_button_content,
|
|
46382
|
-
.navi_native_input {
|
|
46383
|
-
border-top-right-radius: 0 !important;
|
|
46384
|
-
border-bottom-right-radius: 0 !important;
|
|
46385
|
-
}
|
|
46386
47221
|
}
|
|
46387
47222
|
|
|
46388
47223
|
> *:last-child:not(:only-child) {
|
|
47224
|
+
--x-corner-top-left-radius: 0;
|
|
47225
|
+
--x-corner-bottom-left-radius: 0;
|
|
47226
|
+
|
|
46389
47227
|
border-top-left-radius: 0 !important;
|
|
46390
47228
|
border-bottom-left-radius: 0 !important;
|
|
46391
|
-
|
|
46392
|
-
.navi_button_content,
|
|
46393
|
-
.navi_native_input {
|
|
46394
|
-
border-top-left-radius: 0 !important;
|
|
46395
|
-
border-bottom-left-radius: 0 !important;
|
|
46396
|
-
}
|
|
46397
47229
|
}
|
|
46398
47230
|
|
|
46399
47231
|
> *:not(:first-child):not(:last-child) {
|
|
46400
|
-
|
|
47232
|
+
--x-corner-top-left-radius: 0;
|
|
47233
|
+
--x-corner-top-right-radius: 0;
|
|
47234
|
+
--x-corner-bottom-right-radius: 0;
|
|
47235
|
+
--x-corner-bottom-left-radius: 0;
|
|
46401
47236
|
|
|
46402
|
-
|
|
46403
|
-
.navi_native_input {
|
|
46404
|
-
border-radius: 0 !important;
|
|
46405
|
-
}
|
|
47237
|
+
border-radius: 0 !important;
|
|
46406
47238
|
}
|
|
46407
47239
|
}
|
|
46408
47240
|
|
|
46409
47241
|
/* Vertical: Cumulative margin for border overlap */
|
|
46410
47242
|
&[data-vertical] {
|
|
46411
47243
|
> *:not(:first-child) {
|
|
46412
|
-
margin-top: calc(var(--group-border-width) * -1);
|
|
47244
|
+
margin-top: calc(var(--border-width, var(--group-border-width)) * -1);
|
|
46413
47245
|
}
|
|
46414
47246
|
> *:first-child:not(:only-child) {
|
|
47247
|
+
--x-corner-bottom-right-radius: 0;
|
|
47248
|
+
--x-corner-bottom-left-radius: 0;
|
|
47249
|
+
|
|
46415
47250
|
border-bottom-right-radius: 0 !important;
|
|
46416
47251
|
border-bottom-left-radius: 0 !important;
|
|
46417
|
-
|
|
46418
|
-
.navi_button_content,
|
|
46419
|
-
.navi_native_input {
|
|
46420
|
-
border-bottom-right-radius: 0 !important;
|
|
46421
|
-
border-bottom-left-radius: 0 !important;
|
|
46422
|
-
}
|
|
46423
47252
|
}
|
|
46424
47253
|
|
|
46425
47254
|
> *:last-child:not(:only-child) {
|
|
47255
|
+
--x-corner-top-left-radius: 0;
|
|
47256
|
+
--x-corner-top-right-radius: 0;
|
|
47257
|
+
|
|
46426
47258
|
border-top-left-radius: 0 !important;
|
|
46427
47259
|
border-top-right-radius: 0 !important;
|
|
46428
|
-
|
|
46429
|
-
.navi_button_content,
|
|
46430
|
-
.navi_native_input {
|
|
46431
|
-
border-top-left-radius: 0 !important;
|
|
46432
|
-
border-top-right-radius: 0 !important;
|
|
46433
|
-
}
|
|
46434
47260
|
}
|
|
46435
47261
|
|
|
46436
47262
|
> *:not(:first-child):not(:last-child) {
|
|
46437
|
-
|
|
47263
|
+
--x-corner-top-left-radius: 0;
|
|
47264
|
+
--x-corner-top-right-radius: 0;
|
|
47265
|
+
--x-corner-bottom-right-radius: 0;
|
|
47266
|
+
--x-corner-bottom-left-radius: 0;
|
|
46438
47267
|
|
|
46439
|
-
|
|
46440
|
-
.navi_native_input {
|
|
46441
|
-
border-radius: 0 !important;
|
|
46442
|
-
}
|
|
47268
|
+
border-radius: 0 !important;
|
|
46443
47269
|
}
|
|
46444
47270
|
}
|
|
46445
47271
|
}
|
|
@@ -46453,8 +47279,15 @@ const Group = ({
|
|
|
46453
47279
|
import.meta.css = [css$B, "@jsenv/navi/src/control/group.jsx"];
|
|
46454
47280
|
return jsx(Box, {
|
|
46455
47281
|
baseClassName: "navi_group",
|
|
46456
|
-
"data-vertical": vertical ? "" : undefined
|
|
46457
|
-
|
|
47282
|
+
"data-vertical": vertical ? "" : undefined
|
|
47283
|
+
/* A group draws one frame around its members, so they must share their
|
|
47284
|
+
top and bottom edges. A block box lays them out inline, where they
|
|
47285
|
+
align on baselines instead: a member whose content carries no text —
|
|
47286
|
+
the swatch of a color picker — has no baseline of its own, the one
|
|
47287
|
+
synthesized from its bottom edge does not land where its neighbours'
|
|
47288
|
+
text does, and the member rides a few pixels off, slicing the frame.
|
|
47289
|
+
Flex aligns the edges, in both directions. */,
|
|
47290
|
+
flex: vertical ? "y" : "x",
|
|
46458
47291
|
...props,
|
|
46459
47292
|
children: children
|
|
46460
47293
|
});
|
|
@@ -52387,7 +53220,7 @@ const css$v = /* css */`
|
|
|
52387
53220
|
position: sticky;
|
|
52388
53221
|
top: 0;
|
|
52389
53222
|
left: 0;
|
|
52390
|
-
z-index:
|
|
53223
|
+
z-index: var(--navi-z-index-sticky);
|
|
52391
53224
|
order: -2;
|
|
52392
53225
|
}
|
|
52393
53226
|
.navi_list_fallback,
|
|
@@ -52494,7 +53327,7 @@ const css$v = /* css */`
|
|
|
52494
53327
|
position: sticky;
|
|
52495
53328
|
right: 0;
|
|
52496
53329
|
bottom: 0;
|
|
52497
|
-
z-index:
|
|
53330
|
+
z-index: var(--navi-z-index-sticky);
|
|
52498
53331
|
order: 2;
|
|
52499
53332
|
}
|
|
52500
53333
|
|
|
@@ -52510,7 +53343,7 @@ const css$v = /* css */`
|
|
|
52510
53343
|
.navi_list_item_group_label {
|
|
52511
53344
|
position: sticky;
|
|
52512
53345
|
top: var(--list-group-label-top, var(--x-list-group-label-top, 0px));
|
|
52513
|
-
z-index:
|
|
53346
|
+
z-index: var(--navi-z-index-sticky);
|
|
52514
53347
|
display: block;
|
|
52515
53348
|
background-color: var(--list-group-label-background-color);
|
|
52516
53349
|
user-select: none;
|
|
@@ -56622,6 +57455,11 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
56622
57455
|
font-size: var(--picker-font-size);
|
|
56623
57456
|
font-family: var(--picker-font-family);
|
|
56624
57457
|
text-align: inherit;
|
|
57458
|
+
/* The frame is drawn by the box, but its radius is declared here, on the
|
|
57459
|
+
control root, like every other navi control does — so anything styling
|
|
57460
|
+
the picker from the outside (a Group squaring the corners it joins) has
|
|
57461
|
+
one element to talk to, and the box follows. */
|
|
57462
|
+
border-radius: var(--picker-border-radius);
|
|
56625
57463
|
|
|
56626
57464
|
.navi_picker_box {
|
|
56627
57465
|
position: relative;
|
|
@@ -56643,7 +57481,7 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
56643
57481
|
border-width: var(--picker-border-width);
|
|
56644
57482
|
border-style: solid;
|
|
56645
57483
|
border-color: var(--x-picker-border-color);
|
|
56646
|
-
border-radius:
|
|
57484
|
+
border-radius: inherit;
|
|
56647
57485
|
outline-width: var(--picker-outline-width);
|
|
56648
57486
|
outline-style: none;
|
|
56649
57487
|
outline-color: var(--picker-outline-color);
|
|
@@ -56674,6 +57512,14 @@ installImportMetaCssBuild(import.meta);const css$r = /* css */`
|
|
|
56674
57512
|
}
|
|
56675
57513
|
}
|
|
56676
57514
|
.navi_picker_right_slot {
|
|
57515
|
+
/* A corner claimed from the outside (see group.jsx) is the frame's, and
|
|
57516
|
+
what lives in here is not the frame — a button in a slot, the content
|
|
57517
|
+
of a popup — so the ask stops at this boundary. */
|
|
57518
|
+
--x-corner-top-left-radius: initial;
|
|
57519
|
+
--x-corner-top-right-radius: initial;
|
|
57520
|
+
--x-corner-bottom-right-radius: initial;
|
|
57521
|
+
--x-corner-bottom-left-radius: initial;
|
|
57522
|
+
|
|
56677
57523
|
display: inline-flex;
|
|
56678
57524
|
height: 1em;
|
|
56679
57525
|
height: 1lh;
|
|
@@ -62536,6 +63382,13 @@ const createIsolatedItemTracker = () => {
|
|
|
62536
63382
|
return [useIsolatedItemTrackerProvider, useTrackIsolatedItem, useTrackedIsolatedItem, useTrackedIsolatedItems];
|
|
62537
63383
|
};
|
|
62538
63384
|
|
|
63385
|
+
/**
|
|
63386
|
+
* The table's own scale: these order the table's parts among themselves and are
|
|
63387
|
+
* derived from each other, never written as literals twice. They stay under the
|
|
63388
|
+
* bands of src/navi_z_indexes.js, which is where anything competing with
|
|
63389
|
+
* another component belongs.
|
|
63390
|
+
*/
|
|
63391
|
+
|
|
62539
63392
|
const Z_INDEX_EDITING = 1; /* To go above neighbours, but should not be too big to stay under the sticky cells */
|
|
62540
63393
|
|
|
62541
63394
|
/* needed because cell uses position:relative, sticky must win even if before in DOM order */
|
|
@@ -67954,7 +68807,7 @@ const css = /* css */`
|
|
|
67954
68807
|
.navi_side_panel_head,
|
|
67955
68808
|
.navi_side_panel_foot {
|
|
67956
68809
|
position: sticky;
|
|
67957
|
-
z-index:
|
|
68810
|
+
z-index: var(--navi-z-index-sticky);
|
|
67958
68811
|
background-color: var(--navi-popup-background-color);
|
|
67959
68812
|
}
|
|
67960
68813
|
/* The separator token, not the popup border: these lines split two
|