@jsenv/navi 0.29.116 → 0.29.118
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 +251 -63
- package/dist/jsenv_navi.js.map +33 -13
- package/docs/AI_INSTRUCTIONS.md +3 -3
- package/docs/control_value.md +43 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -26618,6 +26618,49 @@ const RouteUI = ({
|
|
|
26618
26618
|
return element;
|
|
26619
26619
|
};
|
|
26620
26620
|
|
|
26621
|
+
/**
|
|
26622
|
+
* Where a route movement is heading, said before its first picture is taken.
|
|
26623
|
+
*
|
|
26624
|
+
* A view transition pairs, across its two pictures, whatever wears the same
|
|
26625
|
+
* name on both — and a name is read off the DOM as it stands when the
|
|
26626
|
+
* transition starts. So whoever names an element for a movement between two
|
|
26627
|
+
* pages (the bar under the current tab of a <Nav>, which glides from one tab
|
|
26628
|
+
* to the next) has to decide whether this movement is one it takes part in
|
|
26629
|
+
* before that reading: on a page the row is not on, its bar must leave with the
|
|
26630
|
+
* page, unnamed.
|
|
26631
|
+
*
|
|
26632
|
+
* Everything that decision needs is on the page itself, except one thing:
|
|
26633
|
+
* where the movement goes. That is what is published here, by whoever starts
|
|
26634
|
+
* the movement — route_transition.jsx for a relation between two pages,
|
|
26635
|
+
* route_travel.jsx for a row a finger pushes — before their transition starts.
|
|
26636
|
+
* The navigation's own announcement (before_routing.js) cannot stand in for
|
|
26637
|
+
* it: a travel a finger sets off starts its transition first and navigates
|
|
26638
|
+
* from inside it.
|
|
26639
|
+
*
|
|
26640
|
+
* Only one movement plays at a time (one view transition per document), which
|
|
26641
|
+
* is what lets both say it in one place. Taken down by the one that put it up,
|
|
26642
|
+
* and by it alone: a movement ending after another has replaced it must not
|
|
26643
|
+
* wipe what the new one stands on.
|
|
26644
|
+
*/
|
|
26645
|
+
|
|
26646
|
+
|
|
26647
|
+
const [publishDestination, observeTransitionDestination] = createPubSub();
|
|
26648
|
+
|
|
26649
|
+
let destinationOwner = null;
|
|
26650
|
+
|
|
26651
|
+
const holdTransitionDestination = (owner, url) => {
|
|
26652
|
+
destinationOwner = owner;
|
|
26653
|
+
publishDestination(new URL(url, window.location.href).href);
|
|
26654
|
+
};
|
|
26655
|
+
|
|
26656
|
+
const releaseTransitionDestination = (owner) => {
|
|
26657
|
+
if (owner !== destinationOwner) {
|
|
26658
|
+
return;
|
|
26659
|
+
}
|
|
26660
|
+
destinationOwner = null;
|
|
26661
|
+
publishDestination(null);
|
|
26662
|
+
};
|
|
26663
|
+
|
|
26621
26664
|
/**
|
|
26622
26665
|
* The window two pages are seen through while one replaces the other, measured
|
|
26623
26666
|
* once and published for the length of the movement.
|
|
@@ -27498,6 +27541,7 @@ const rebuildWatcher = () => {
|
|
|
27498
27541
|
}
|
|
27499
27542
|
beginTransition({
|
|
27500
27543
|
page: pages[index],
|
|
27544
|
+
url: navigationUrl,
|
|
27501
27545
|
// Which way it plays: what the navigation itself said first — the link
|
|
27502
27546
|
// being pressed is where the way the app is being walked is known — then
|
|
27503
27547
|
// the relation, and forward for a navigation that asked for a movement
|
|
@@ -27518,11 +27562,12 @@ const rebuildWatcher = () => {
|
|
|
27518
27562
|
|
|
27519
27563
|
// What plays when no relation matched (see defineRouteDefaultTransition), what
|
|
27520
27564
|
// the navigation now landing asked for on its own (see readNavigationRequest),
|
|
27521
|
-
// and whether it found an answer already — a relation's
|
|
27522
|
-
// a RouteTravel travel. The last
|
|
27523
|
-
// navigation, so they are always about the latest one.
|
|
27565
|
+
// where it goes, and whether it found an answer already — a relation's
|
|
27566
|
+
// transition, a "none", a RouteTravel travel. The last three are read at the
|
|
27567
|
+
// start of every navigation, so they are always about the latest one.
|
|
27524
27568
|
let defaultTransition = null;
|
|
27525
27569
|
let navigationRequest = null;
|
|
27570
|
+
let navigationUrl = null;
|
|
27526
27571
|
let navigationAnimated = false;
|
|
27527
27572
|
|
|
27528
27573
|
// The two ends of every navigation, watched from here on. The picture of the
|
|
@@ -27534,6 +27579,7 @@ let navigationAnimated = false;
|
|
|
27534
27579
|
observeBeforeRouting(details => {
|
|
27535
27580
|
navigationAnimated = false;
|
|
27536
27581
|
navigationRequest = readNavigationRequest(details);
|
|
27582
|
+
navigationUrl = details.url;
|
|
27537
27583
|
if (relations.length === 0 && !defaultTransition && !navigationRequest) {
|
|
27538
27584
|
return;
|
|
27539
27585
|
}
|
|
@@ -27541,9 +27587,11 @@ observeBeforeRouting(details => {
|
|
|
27541
27587
|
});
|
|
27542
27588
|
observeAfterRouting(() => {
|
|
27543
27589
|
const request = navigationRequest;
|
|
27590
|
+
const url = navigationUrl;
|
|
27544
27591
|
// Read here and dropped here: a request answers for the navigation it was
|
|
27545
27592
|
// made on, and the next one is back to the relations.
|
|
27546
27593
|
navigationRequest = null;
|
|
27594
|
+
navigationUrl = null;
|
|
27547
27595
|
if (!navigationAnimated && (request || defaultTransition)) {
|
|
27548
27596
|
const {
|
|
27549
27597
|
type,
|
|
@@ -27552,6 +27600,7 @@ observeAfterRouting(() => {
|
|
|
27552
27600
|
if (type !== "none") {
|
|
27553
27601
|
beginTransition({
|
|
27554
27602
|
page: null,
|
|
27603
|
+
url,
|
|
27555
27604
|
// A default has no direction: nothing says which of two arbitrary
|
|
27556
27605
|
// pages is before the other, and the attribute is then worn empty —
|
|
27557
27606
|
// present for whoever keys on "one of ours is playing", silent on the
|
|
@@ -27599,6 +27648,7 @@ const findRelation = (fromPage, toPage) => {
|
|
|
27599
27648
|
let currentTransition = null;
|
|
27600
27649
|
const beginTransition = ({
|
|
27601
27650
|
page,
|
|
27651
|
+
url,
|
|
27602
27652
|
direction,
|
|
27603
27653
|
type,
|
|
27604
27654
|
duration
|
|
@@ -27615,6 +27665,9 @@ const beginTransition = ({
|
|
|
27615
27665
|
}
|
|
27616
27666
|
const transition = {};
|
|
27617
27667
|
currentTransition = transition;
|
|
27668
|
+
// Said before the picture is taken: whoever names something for a movement
|
|
27669
|
+
// between two pages decides on it now (see transition_destination.js).
|
|
27670
|
+
holdTransitionDestination(transition, url);
|
|
27618
27671
|
documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
|
|
27619
27672
|
if (type) {
|
|
27620
27673
|
documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
|
|
@@ -27719,6 +27772,7 @@ const beginTransition = ({
|
|
|
27719
27772
|
documentElement.removeAttribute(TRANSITION_TYPE_ATTRIBUTE);
|
|
27720
27773
|
documentElement.removeAttribute(TRANSITION_TARGET_ATTRIBUTE);
|
|
27721
27774
|
releaseTransitionWindow(transition);
|
|
27775
|
+
releaseTransitionDestination(transition);
|
|
27722
27776
|
if (restoreDuration) {
|
|
27723
27777
|
restoreDuration();
|
|
27724
27778
|
}
|
|
@@ -28380,8 +28434,12 @@ const RouteTravel = ({
|
|
|
28380
28434
|
travelRef.current = travel;
|
|
28381
28435
|
// Taken before the picture is: the browser reads the name off the DOM as it
|
|
28382
28436
|
// stands when the transition starts, and this box is only a picture of its
|
|
28383
|
-
// own for as long as it is the one travelling.
|
|
28437
|
+
// own for as long as it is the one travelling. Where it travels to is said
|
|
28438
|
+
// at the same moment and for the same reason: whoever names something for
|
|
28439
|
+
// a movement between two pages decides on it now (see
|
|
28440
|
+
// transition_destination.js).
|
|
28384
28441
|
nameForTravel(elementRef.current);
|
|
28442
|
+
holdTransitionDestination(travel, page.route.buildUrl(page.params));
|
|
28385
28443
|
document.documentElement.setAttribute(TRAVEL_ATTRIBUTE, direction);
|
|
28386
28444
|
document.documentElement.setAttribute(TRAVEL_AXIS_ATTRIBUTE, axis);
|
|
28387
28445
|
if (scrub) {
|
|
@@ -28737,6 +28795,7 @@ const RouteTravel = ({
|
|
|
28737
28795
|
document.documentElement.removeAttribute(DRAGGED_ATTRIBUTE);
|
|
28738
28796
|
document.documentElement.removeAttribute(TURNED_ATTRIBUTE);
|
|
28739
28797
|
releaseTransitionWindow(travel);
|
|
28798
|
+
releaseTransitionDestination(travel);
|
|
28740
28799
|
}
|
|
28741
28800
|
};
|
|
28742
28801
|
|
|
@@ -29628,7 +29687,11 @@ registerNaviCommand("--navi-void", (source) => {
|
|
|
29628
29687
|
};
|
|
29629
29688
|
});
|
|
29630
29689
|
|
|
29631
|
-
|
|
29690
|
+
// "--navi-update:smooth" asks the control to be SEEN moving to the value — a
|
|
29691
|
+
// wheel scrolls to it rather than swapping its digits — so a shortcut under
|
|
29692
|
+
// two wheels shows which one it changed, and by how much. The value itself is
|
|
29693
|
+
// set at once either way; only the drawing is concerned.
|
|
29694
|
+
registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
29632
29695
|
const target =
|
|
29633
29696
|
resolveExplicitTarget(source) || resolveFirstParentControl(source);
|
|
29634
29697
|
if (!target) {
|
|
@@ -29645,6 +29708,7 @@ registerNaviCommand("--navi-update", (source, event) => {
|
|
|
29645
29708
|
const commandValue = resolveCommandValue(source, event);
|
|
29646
29709
|
dispatchRequestSetUIState(target, commandValue, {
|
|
29647
29710
|
event,
|
|
29711
|
+
behavior: argument,
|
|
29648
29712
|
});
|
|
29649
29713
|
},
|
|
29650
29714
|
});
|
|
@@ -38719,8 +38783,7 @@ const BinderItemContext = createContext(null);
|
|
|
38719
38783
|
|
|
38720
38784
|
/**
|
|
38721
38785
|
* What a <Link> learns from the <Nav> around it: where to draw the bar that
|
|
38722
|
-
* says "you are here",
|
|
38723
|
-
* bar from one page to the next, and — for a row of tabs that are slides — which
|
|
38786
|
+
* says "you are here", and — for a row of tabs that are slides — which
|
|
38724
38787
|
* <SlideContainer> they are about and which of its slides is on screen (see
|
|
38725
38788
|
* nav.jsx).
|
|
38726
38789
|
*/
|
|
@@ -39416,14 +39479,7 @@ const LinkPlain = props => {
|
|
|
39416
39479
|
// around this link.
|
|
39417
39480
|
const currentIndicatorAsked = currentIndicator ?? nav?.currentIndicator;
|
|
39418
39481
|
const currentIndicatorPosition = currentIndicatorAsked === true ? "bottom" : currentIndicatorAsked;
|
|
39419
|
-
const currentIndicatorEl = currentIndicatorPosition === "left" || currentIndicatorPosition === "right" || currentIndicatorPosition === "top" || currentIndicatorPosition === "bottom" ? jsx(LinkCurrentIndicator
|
|
39420
|
-
// Only the bar one can actually see carries the row's name, because a
|
|
39421
|
-
// name belongs to one element at a time and every tab holds a bar. The
|
|
39422
|
-
// browser then has the same thing in two places from one page to the
|
|
39423
|
-
// next, and moves it — which is the whole of "the bar slides".
|
|
39424
|
-
, {
|
|
39425
|
-
viewTransitionName: innerCurrent ? nav?.indicatorName : null
|
|
39426
|
-
}) : null;
|
|
39482
|
+
const currentIndicatorEl = currentIndicatorPosition === "left" || currentIndicatorPosition === "right" || currentIndicatorPosition === "top" || currentIndicatorPosition === "bottom" ? jsx(LinkCurrentIndicator, {}) : null;
|
|
39427
39483
|
const {
|
|
39428
39484
|
onClick,
|
|
39429
39485
|
preventDefault
|
|
@@ -39542,14 +39598,12 @@ const LinkPlain = props => {
|
|
|
39542
39598
|
children: [startIconEl, innerChildren, endIconEl]
|
|
39543
39599
|
});
|
|
39544
39600
|
};
|
|
39545
|
-
|
|
39546
|
-
|
|
39547
|
-
|
|
39601
|
+
|
|
39602
|
+
// Named by the <Nav> around the link when the link is current, from its CSS:
|
|
39603
|
+
// that is what makes the bar glide from one tab to the next (see nav.jsx).
|
|
39604
|
+
const LinkCurrentIndicator = () => {
|
|
39548
39605
|
return jsx("span", {
|
|
39549
|
-
className: "navi_current_indicator"
|
|
39550
|
-
style: viewTransitionName ? {
|
|
39551
|
-
viewTransitionName
|
|
39552
|
-
} : undefined
|
|
39606
|
+
className: "navi_current_indicator"
|
|
39553
39607
|
});
|
|
39554
39608
|
};
|
|
39555
39609
|
markAsOutsideTextFlow(LinkCurrentIndicator);
|
|
@@ -39559,6 +39613,9 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
39559
39613
|
* https://dribbble.com/search/tabs
|
|
39560
39614
|
*/
|
|
39561
39615
|
let navCount = 0;
|
|
39616
|
+
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
39617
|
+
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
39618
|
+
const BETWEEN_TABS_ATTRIBUTE = "data-nav-between-tabs";
|
|
39562
39619
|
const css$V = /* css */`
|
|
39563
39620
|
@layer navi {
|
|
39564
39621
|
.navi_nav {
|
|
@@ -39638,6 +39695,30 @@ const css$V = /* css */`
|
|
|
39638
39695
|
}
|
|
39639
39696
|
}
|
|
39640
39697
|
|
|
39698
|
+
/* The bar under the current tab of a nav made of routes is NAMED: a change
|
|
39699
|
+
played as a view transition then finds it on both pictures and moves it
|
|
39700
|
+
from the tab it was under to the tab it is under, which is all "the bar
|
|
39701
|
+
slides" is. The name is the row's (--nav-indicator-name, absent when the
|
|
39702
|
+
row asked for no slide) and only the bar one can see wears it: every tab
|
|
39703
|
+
holds a bar, and a name belongs to one element at a time. */
|
|
39704
|
+
.navi_nav .navi_link[data-href-current] .navi_current_indicator {
|
|
39705
|
+
view-transition-name: var(--nav-indicator-name);
|
|
39706
|
+
}
|
|
39707
|
+
/* Named for a movement it takes part in, and for that alone. While the PAGES
|
|
39708
|
+
are the ones moving — a route transition, a route travel — a name lifts the
|
|
39709
|
+
bar out of its page's picture into a picture of its own, and a picture of
|
|
39710
|
+
its own is precisely what does not travel: it stands where it was captured,
|
|
39711
|
+
fading, while the row slides away under it. Right when the row is on both
|
|
39712
|
+
sides and the bar has a tab to glide to; wrong anywhere else, where the bar
|
|
39713
|
+
must leave or arrive with its row. So the name is dropped unless the
|
|
39714
|
+
movement goes from a tab of this row to another one of its tabs (see
|
|
39715
|
+
markIndicatorTakesPart). */
|
|
39716
|
+
:root:is([data-navi-route-transition], [data-navi-route-travel])
|
|
39717
|
+
.navi_nav:not([${BETWEEN_TABS_ATTRIBUTE}])
|
|
39718
|
+
.navi_current_indicator {
|
|
39719
|
+
view-transition-name: none;
|
|
39720
|
+
}
|
|
39721
|
+
|
|
39641
39722
|
.navi_nav {
|
|
39642
39723
|
display: flex;
|
|
39643
39724
|
width: fit-content;
|
|
@@ -39819,9 +39900,12 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
39819
39900
|
* does so by being NAMED, which is all the browser needs: any change played as
|
|
39820
39901
|
* a view transition animates it on the same clock as everything else in that
|
|
39821
39902
|
* transition. Inside a `RouteTravel` that means it follows the pages, and the
|
|
39822
|
-
* thumb dragging them, without either of them being told about the other.
|
|
39823
|
-
* a
|
|
39824
|
-
*
|
|
39903
|
+
* thumb dragging them, without either of them being told about the other. It
|
|
39904
|
+
* is named for a movement between two tabs of the row and for nothing else:
|
|
39905
|
+
* when the pages move and the row is on one side only, the bar leaves or
|
|
39906
|
+
* arrives with its row. For a nav made of slides (`slideContainer`) the bar
|
|
39907
|
+
* is one element for the whole row, and it reads the travel the container
|
|
39908
|
+
* publishes.
|
|
39825
39909
|
* @param {string} [props.slideContainer] - the id of a `<SlideContainer>` these
|
|
39826
39910
|
* tabs are about: each one says which slide it is (`<Link slide="…">`), the
|
|
39827
39911
|
* container says which one is on screen, and pressing a tab travels there.
|
|
@@ -39954,16 +40038,47 @@ const Nav = ({
|
|
|
39954
40038
|
useLayoutEffect(() => {
|
|
39955
40039
|
paintIndicatorGeometry();
|
|
39956
40040
|
});
|
|
40041
|
+
|
|
40042
|
+
// Whether the bar keeps its name for the route movement about to be pictured
|
|
40043
|
+
// (see the CSS above). Written on the DOM as the movement is announced rather
|
|
40044
|
+
// than rendered: rendering is held from a navigation's first word until the
|
|
40045
|
+
// movement's first picture is taken (see rendering_hold.js), so a render
|
|
40046
|
+
// would land on the second picture with the first already taken.
|
|
40047
|
+
useLayoutEffect(() => {
|
|
40048
|
+
const navElement = navRef.current;
|
|
40049
|
+
const markIndicatorTakesPart = destinationUrl => {
|
|
40050
|
+
if (destinationUrl === null) {
|
|
40051
|
+
navElement.removeAttribute(BETWEEN_TABS_ATTRIBUTE);
|
|
40052
|
+
return;
|
|
40053
|
+
}
|
|
40054
|
+
// From a tab of this row: the row still shows the page being left, and
|
|
40055
|
+
// the tab it is on. To a tab of this row: one of its links aims at the
|
|
40056
|
+
// page the movement goes to. A row mounted while the movement plays hears
|
|
40057
|
+
// nothing here and stays unnamed, rightly: its bar is on the second
|
|
40058
|
+
// picture alone, with nothing on the first to glide from.
|
|
40059
|
+
const fromTab = navElement.querySelector("[data-href-current]");
|
|
40060
|
+
let toTab = null;
|
|
40061
|
+
for (const linkElement of navElement.querySelectorAll("a[href]")) {
|
|
40062
|
+
if (linkElement.href === destinationUrl) {
|
|
40063
|
+
toTab = linkElement;
|
|
40064
|
+
break;
|
|
40065
|
+
}
|
|
40066
|
+
}
|
|
40067
|
+
if (fromTab && toTab) {
|
|
40068
|
+
navElement.setAttribute(BETWEEN_TABS_ATTRIBUTE, "");
|
|
40069
|
+
} else {
|
|
40070
|
+
navElement.removeAttribute(BETWEEN_TABS_ATTRIBUTE);
|
|
40071
|
+
}
|
|
40072
|
+
};
|
|
40073
|
+
return observeTransitionDestination(markIndicatorTakesPart);
|
|
40074
|
+
}, []);
|
|
39957
40075
|
const navContextValue = useMemo(() => ({
|
|
39958
40076
|
// The bar belongs to the row itself when the tabs are slides, so the
|
|
39959
40077
|
// links draw none of their own.
|
|
39960
40078
|
currentIndicator: slideContainer ? undefined : currentIndicator,
|
|
39961
|
-
// Read by the link that is current, and by it alone: a name belongs to
|
|
39962
|
-
// one element at a time, and the bar exists in every tab.
|
|
39963
|
-
indicatorName: currentIndicatorSlides ? indicatorNameRef.current : null,
|
|
39964
40079
|
slideContainer,
|
|
39965
40080
|
currentSlideArea
|
|
39966
|
-
}), [currentIndicator,
|
|
40081
|
+
}), [currentIndicator, slideContainer, currentSlideArea]);
|
|
39967
40082
|
children = toChildArray(children);
|
|
39968
40083
|
return jsxs(Box, {
|
|
39969
40084
|
as: "nav",
|
|
@@ -39993,6 +40108,13 @@ const Nav = ({
|
|
|
39993
40108
|
expandY: expandY,
|
|
39994
40109
|
spacing: spacing,
|
|
39995
40110
|
...props,
|
|
40111
|
+
// The name the bar of the current tab wears, handed to the CSS above
|
|
40112
|
+
// rather than to the links: which bar wears it, and when, is decided
|
|
40113
|
+
// there.
|
|
40114
|
+
style: currentIndicatorSlides && !slideContainer ? {
|
|
40115
|
+
...props.style,
|
|
40116
|
+
"--nav-indicator-name": indicatorNameRef.current
|
|
40117
|
+
} : props.style,
|
|
39996
40118
|
styleCSSVars: NavStyleCSSVars,
|
|
39997
40119
|
children: [indicatorPosition && jsx("span", {
|
|
39998
40120
|
className: "navi_nav_indicator"
|
|
@@ -50058,8 +50180,12 @@ const SlideContainer = ({
|
|
|
50058
50180
|
// after it — a step that has been answered is not a step to stay on.
|
|
50059
50181
|
const done = (area, event) => {
|
|
50060
50182
|
markAnswered(area);
|
|
50061
|
-
if (!moveNext(event
|
|
50062
|
-
|
|
50183
|
+
if (!moveNext(event, {
|
|
50184
|
+
released: true
|
|
50185
|
+
})) {
|
|
50186
|
+
movePrevious(event, {
|
|
50187
|
+
released: true
|
|
50188
|
+
});
|
|
50063
50189
|
}
|
|
50064
50190
|
};
|
|
50065
50191
|
|
|
@@ -50377,6 +50503,7 @@ const SlideContainer = ({
|
|
|
50377
50503
|
forward,
|
|
50378
50504
|
event,
|
|
50379
50505
|
value,
|
|
50506
|
+
released,
|
|
50380
50507
|
dx = 0,
|
|
50381
50508
|
dy = 0
|
|
50382
50509
|
} = {}) => {
|
|
@@ -50391,6 +50518,7 @@ const SlideContainer = ({
|
|
|
50391
50518
|
forward,
|
|
50392
50519
|
event,
|
|
50393
50520
|
value,
|
|
50521
|
+
released,
|
|
50394
50522
|
dx,
|
|
50395
50523
|
dy
|
|
50396
50524
|
});
|
|
@@ -50427,7 +50555,11 @@ const SlideContainer = ({
|
|
|
50427
50555
|
// event dispatched by hand: a slide that holds on to the user holds them
|
|
50428
50556
|
// whatever they press. Read off the slide being LEFT, because that is what
|
|
50429
50557
|
// has a reason to keep them (an answer still missing, a step not taken).
|
|
50430
|
-
|
|
50558
|
+
// `released` is the slide letting go itself (--navi-done, see Slide): the
|
|
50559
|
+
// one departure the forward hold does not apply to. Said here rather than
|
|
50560
|
+
// by dropping the attribute, which stays the render's to write — a hold
|
|
50561
|
+
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
50562
|
+
if (forward ? !released && currentElement?.hasAttribute("data-prevent-nav-next") : currentElement?.hasAttribute("data-prevent-nav-previous")) {
|
|
50431
50563
|
return false;
|
|
50432
50564
|
}
|
|
50433
50565
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
@@ -50576,15 +50708,20 @@ const SlideContainer = ({
|
|
|
50576
50708
|
dx,
|
|
50577
50709
|
dy,
|
|
50578
50710
|
event,
|
|
50579
|
-
value
|
|
50711
|
+
value,
|
|
50712
|
+
released
|
|
50580
50713
|
} = pendingRollsRef.current.shift();
|
|
50581
50714
|
if (dx || dy) {
|
|
50582
|
-
move(dx, dy, event,
|
|
50715
|
+
move(dx, dy, event, {
|
|
50716
|
+
value,
|
|
50717
|
+
released
|
|
50718
|
+
});
|
|
50583
50719
|
return;
|
|
50584
50720
|
}
|
|
50585
50721
|
goToArea(area, {
|
|
50586
50722
|
event,
|
|
50587
|
-
value
|
|
50723
|
+
value,
|
|
50724
|
+
released
|
|
50588
50725
|
});
|
|
50589
50726
|
}, [noTravel]);
|
|
50590
50727
|
|
|
@@ -50703,10 +50840,14 @@ const SlideContainer = ({
|
|
|
50703
50840
|
return area;
|
|
50704
50841
|
}
|
|
50705
50842
|
};
|
|
50706
|
-
const move = (dx, dy, event,
|
|
50843
|
+
const move = (dx, dy, event, {
|
|
50844
|
+
value,
|
|
50845
|
+
released
|
|
50846
|
+
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
50707
50847
|
forward: dx > 0 || dy > 0,
|
|
50708
50848
|
event,
|
|
50709
50849
|
value,
|
|
50850
|
+
released,
|
|
50710
50851
|
dx,
|
|
50711
50852
|
dy
|
|
50712
50853
|
});
|
|
@@ -50716,8 +50857,8 @@ const SlideContainer = ({
|
|
|
50716
50857
|
// slides are. On a map they mean the same thing, and fall back to the other
|
|
50717
50858
|
// axis when there is nothing that way — a step onwards, however the screens
|
|
50718
50859
|
// happen to be arranged.
|
|
50719
|
-
const moveNext = event => vertical ? move(0, 1, event) || move(1, 0, event) : move(1, 0, event) || move(0, 1, event);
|
|
50720
|
-
const movePrevious = event => vertical ? move(0, -1, event) || move(-1, 0, event) : move(-1, 0, event) || move(0, -1, event);
|
|
50860
|
+
const moveNext = (event, options) => vertical ? move(0, 1, event, options) || move(1, 0, event, options) : move(1, 0, event, options) || move(0, 1, event, options);
|
|
50861
|
+
const movePrevious = (event, options) => vertical ? move(0, -1, event, options) || move(-1, 0, event, options) : move(-1, 0, event, options) || move(0, -1, event, options);
|
|
50721
50862
|
|
|
50722
50863
|
// Where the track is right now, as the gesture left it: the resting place of
|
|
50723
50864
|
// the slide being dragged, plus what the pointer has pulled since.
|
|
@@ -51364,7 +51505,9 @@ const SlideContainer = ({
|
|
|
51364
51505
|
// (a --navi-right command carries the click that ran it, that click its
|
|
51365
51506
|
// own mousedown), and the focus transfer reads the whole chain to know
|
|
51366
51507
|
// where the interaction started.
|
|
51367
|
-
move(dx, dy, e,
|
|
51508
|
+
move(dx, dy, e, {
|
|
51509
|
+
value
|
|
51510
|
+
});
|
|
51368
51511
|
}
|
|
51369
51512
|
// By name rather than by direction (--navi-go-to-slide): the caller says
|
|
51370
51513
|
// where, the map says nothing about it.
|
|
@@ -51524,11 +51667,6 @@ const Slide = ({
|
|
|
51524
51667
|
,
|
|
51525
51668
|
|
|
51526
51669
|
onnavi_done: e => {
|
|
51527
|
-
// Dropped imperatively rather than left to the re-render this
|
|
51528
|
-
// schedules: moving on happens in this same handler, and the gate it
|
|
51529
|
-
// goes through reads this attribute off the DOM (see goToArea) — a
|
|
51530
|
-
// render is a microtask away, the move is not.
|
|
51531
|
-
e.currentTarget.removeAttribute("data-prevent-nav-next");
|
|
51532
51670
|
container?.done(slideArea);
|
|
51533
51671
|
rest.onnavi_done?.(e);
|
|
51534
51672
|
},
|
|
@@ -57182,7 +57320,12 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
|
|
|
57182
57320
|
--picker-popup-border-radius,
|
|
57183
57321
|
var(--picker-border-radius)
|
|
57184
57322
|
);
|
|
57185
|
-
|
|
57323
|
+
/* Explicit fallback, for the same reason as --popover-background-color
|
|
57324
|
+
above: the dialog paints border-width from this var with no fallback
|
|
57325
|
+
of its own, so an unset dialogBorderWidth would leave the var
|
|
57326
|
+
guaranteed-invalid and border-width at its initial "medium" (3px).
|
|
57327
|
+
0px is the dialog's own default. */
|
|
57328
|
+
--dialog-border-width: var(--picker-dialog-border-width, 0px);
|
|
57186
57329
|
--dialog-border-color: var(--x-picker-border-color);
|
|
57187
57330
|
/* The picker's own surface is not this one — see the popover branch,
|
|
57188
57331
|
including why the fallback is spelled out. */
|
|
@@ -70558,7 +70701,7 @@ const WheelGroupContext = createContext(null);
|
|
|
70558
70701
|
* @param {boolean} [props.glass] - Frost the neighbouring rows so the center reads as a clear "window" (iOS-picker style). Inherited from a WheelGroup.
|
|
70559
70702
|
* @param {boolean} [props.frameBorder] - Line the center-window edges with a faint frame (off by default; independent of glass). Tune via --wheel-frame-color.
|
|
70560
70703
|
* @param {boolean|number} [props.zoom] - Make the centered value stand out by size: the neighbours are drawn smaller, so a value grows as it slides into the window and shrinks as it leaves (the centered one keeps its natural size). `true` uses the default ratio (1.3); a number is the ratio itself (1.8 = the center reads 80% bigger than its neighbours). Inherited from a WheelGroup.
|
|
70561
|
-
* @param {number} [props.glideSpeed=0.16] - Speed (px/ms) of the programmatic glide used by arrow keys, taps and the navi_scroll "smooth" behavior. Lower = slower, more visible transitions; ≈0.16 covers one 32px row in 200ms.
|
|
70704
|
+
* @param {number} [props.glideSpeed=0.16] - Speed (px/ms) of the programmatic glide used by arrow keys, taps, a value set with `--navi-update:smooth` and the navi_scroll "smooth" behavior. Lower = slower, more visible transitions; ≈0.16 covers one 32px row in 200ms.
|
|
70562
70705
|
* @param {string} [props.type] - Informative value kind (e.g. "integer", "day"). Used only for rendering hints, like tabular figures for "integer".
|
|
70563
70706
|
*/
|
|
70564
70707
|
const Wheel = props => {
|
|
@@ -71280,6 +71423,23 @@ function WheelUI(props) {
|
|
|
71280
71423
|
// faster, so a second press mid-glide reads as accelerating, not restarting.
|
|
71281
71424
|
const targetPosRef = useRef(null);
|
|
71282
71425
|
const glideRef = useRef(null);
|
|
71426
|
+
// Who aimed the running glide. A value set from outside (`--navi-update:smooth`)
|
|
71427
|
+
// is already committed by whoever set it: the wheel only catches up with it,
|
|
71428
|
+
// and its arrival must not be reported as a settle — no navi_wheel_settle, no
|
|
71429
|
+
// action. A user input is the opposite: its arrival IS the settle. Written at
|
|
71430
|
+
// every motion start (settle, glideTo).
|
|
71431
|
+
const glideFromOutsideRef = useRef(false);
|
|
71432
|
+
// How the value that just arrived asked to be shown. A set request can say
|
|
71433
|
+
// `behavior: "smooth"` (what `--navi-update:smooth` puts on it); the wheel
|
|
71434
|
+
// then scrolls to the value instead of swapping the digits. Read off the
|
|
71435
|
+
// navi_ui_state_change the controller dispatches — the request may have
|
|
71436
|
+
// reached this wheel through a group distributing to its children, so it is
|
|
71437
|
+
// looked up along the event chain. Consumed by the sync effect below.
|
|
71438
|
+
const pendingBehaviorRef = useRef(null);
|
|
71439
|
+
const onUIStateChange = e => {
|
|
71440
|
+
const setRequest = findEvent(e, "navi_set_ui_state");
|
|
71441
|
+
pendingBehaviorRef.current = setRequest ? setRequest.detail.behavior : null;
|
|
71442
|
+
};
|
|
71283
71443
|
// The glide loop is bound once (mount effect) but the speed can change live
|
|
71284
71444
|
// (e.g. a demo control) — read it through a ref so it uses the latest.
|
|
71285
71445
|
const glideSpeedRef = useRef(glideSpeed);
|
|
@@ -71470,11 +71630,11 @@ function WheelUI(props) {
|
|
|
71470
71630
|
posRef.current = index * size;
|
|
71471
71631
|
renderPos(vp);
|
|
71472
71632
|
}
|
|
71473
|
-
|
|
71633
|
+
centeredIndexRef.current = index;
|
|
71634
|
+
if (!interactive || glideFromOutsideRef.current) {
|
|
71474
71635
|
return;
|
|
71475
71636
|
}
|
|
71476
71637
|
const settleEvent = new CustomEvent("navi_wheel_settle");
|
|
71477
|
-
centeredIndexRef.current = index;
|
|
71478
71638
|
debugScroll(`settle: committed → ${trackedItemsRef.current[index].value}`);
|
|
71479
71639
|
requestSelectValue(trackedItemsRef.current[index].value, settleEvent);
|
|
71480
71640
|
// The value is now stable → commit the action. The wheel runs actionEvent
|
|
@@ -71557,13 +71717,21 @@ function WheelUI(props) {
|
|
|
71557
71717
|
// Spring stiffness (fraction of remaining distance per ~frame). Scales with the
|
|
71558
71718
|
// glide speed so slower = gentler chase; clamped so it never crawls or snaps.
|
|
71559
71719
|
const glideSpringFactor = () => clampNumber(glideSpeedRef.current * 1.4, 0.06, 0.45);
|
|
71560
|
-
const glideTo = (vp, target
|
|
71720
|
+
const glideTo = (vp, target, {
|
|
71721
|
+
fromOutside = false
|
|
71722
|
+
} = {}) => {
|
|
71561
71723
|
// A discrete glide overrides any fling momentum.
|
|
71562
71724
|
if (momentumRef.current !== null) {
|
|
71563
71725
|
cancelAnimationFrame(momentumRef.current);
|
|
71564
71726
|
momentumRef.current = null;
|
|
71565
71727
|
}
|
|
71566
71728
|
targetPosRef.current = target;
|
|
71729
|
+
// A user input anywhere in the movement makes its arrival a user settle;
|
|
71730
|
+
// an outside value re-aiming a glide the user started never takes that
|
|
71731
|
+
// away from them (their tap still has to be answered with a settle).
|
|
71732
|
+
if (glideRef.current === null || !fromOutside) {
|
|
71733
|
+
glideFromOutsideRef.current = fromOutside;
|
|
71734
|
+
}
|
|
71567
71735
|
if (glideRef.current === null) {
|
|
71568
71736
|
debugScroll("glide: start");
|
|
71569
71737
|
glideRef.current = requestAnimationFrame(() => glideStep(vp, performance.now()));
|
|
@@ -71579,6 +71747,7 @@ function WheelUI(props) {
|
|
|
71579
71747
|
// overshoots only a handful of rows (a picker isn't a free-scrolling list).
|
|
71580
71748
|
const settle = (vp, velocity) => {
|
|
71581
71749
|
cancelAnim();
|
|
71750
|
+
glideFromOutsideRef.current = false;
|
|
71582
71751
|
debugScroll(`settle: momentum start (v=${velocity}px/ms)`);
|
|
71583
71752
|
// A drag fling: allow the full swipe velocity (see WHEEL_FLING_MAX_VELOCITY)
|
|
71584
71753
|
// so a hard swipe carries across the list instead of being clipped to a few
|
|
@@ -71643,13 +71812,19 @@ function WheelUI(props) {
|
|
|
71643
71812
|
glideTo(vp, target);
|
|
71644
71813
|
};
|
|
71645
71814
|
|
|
71646
|
-
// Center value `index`
|
|
71647
|
-
//
|
|
71815
|
+
// Center value `index` for a value the wheel did not choose itself (first
|
|
71816
|
+
// display, a value set from outside). "smooth" glides to the nearest copy
|
|
71817
|
+
// (glideTargetFor) so a wrap goes the short way, and arrives silently (see
|
|
71818
|
+
// glideFromOutsideRef); "auto" puts the row in place at once, stopping
|
|
71819
|
+
// whatever was moving so no glide lands on top of it a frame later.
|
|
71648
71820
|
const centerOnIndex = (vp, index, behavior) => {
|
|
71649
71821
|
const target = glideTargetFor(vp, index);
|
|
71650
71822
|
if (behavior === "smooth") {
|
|
71651
|
-
glideTo(vp, target
|
|
71823
|
+
glideTo(vp, target, {
|
|
71824
|
+
fromOutside: true
|
|
71825
|
+
});
|
|
71652
71826
|
} else {
|
|
71827
|
+
cancelAnim();
|
|
71653
71828
|
setPos(vp, target);
|
|
71654
71829
|
}
|
|
71655
71830
|
};
|
|
@@ -71661,7 +71836,7 @@ function WheelUI(props) {
|
|
|
71661
71836
|
const glideToIndex = (vp, index, event) => {
|
|
71662
71837
|
centeredIndexRef.current = index;
|
|
71663
71838
|
requestSelectValue(trackedItemsRef.current[index].value, event);
|
|
71664
|
-
|
|
71839
|
+
glideTo(vp, glideTargetFor(vp, index));
|
|
71665
71840
|
};
|
|
71666
71841
|
// Index we are heading to (the target, not the mid-glide visual center).
|
|
71667
71842
|
const currentTargetIndex = vp => {
|
|
@@ -71690,14 +71865,12 @@ function WheelUI(props) {
|
|
|
71690
71865
|
// Sync the center with the current value — used on first display and whenever
|
|
71691
71866
|
// the controlled value changes from outside.
|
|
71692
71867
|
const syncCenterToSelection = (viewportEl, behavior) => {
|
|
71693
|
-
// A
|
|
71694
|
-
//
|
|
71695
|
-
//
|
|
71696
|
-
//
|
|
71697
|
-
//
|
|
71698
|
-
|
|
71699
|
-
// is honoured once it settles (centeredIndexRef then differs from selection).
|
|
71700
|
-
if (glideRef.current !== null || momentumRef.current !== null) {
|
|
71868
|
+
// A fling in flight reports its own value at every row crossing and commits
|
|
71869
|
+
// on settle: the finger that threw it owns the wheel until then, and a value
|
|
71870
|
+
// arriving meanwhile is re-stated by the next crossing anyway. A glide is
|
|
71871
|
+
// different: its target is re-aimed below (glideTo never restarts the
|
|
71872
|
+
// loop), so a value set from outside mid-glide is where the wheel ends up.
|
|
71873
|
+
if (momentumRef.current !== null) {
|
|
71701
71874
|
return;
|
|
71702
71875
|
}
|
|
71703
71876
|
if (trackedItemsRef.current.length === 0) {
|
|
@@ -71739,13 +71912,27 @@ function WheelUI(props) {
|
|
|
71739
71912
|
};
|
|
71740
71913
|
}, []);
|
|
71741
71914
|
|
|
71742
|
-
// React to controlled value changes coming from outside.
|
|
71915
|
+
// React to controlled value changes coming from outside. The row is put in
|
|
71916
|
+
// place at once, unless the request asked for "smooth": the value is already
|
|
71917
|
+
// set for whoever reads it, and the wheel scrolls to it so the eye can follow
|
|
71918
|
+
// the change (a shortcut button under two wheels: which one moved, and by how
|
|
71919
|
+
// much). Never on the first centering (no target yet → nothing to glide from,
|
|
71920
|
+
// the displayed effect above handles it), nor under reduced motion.
|
|
71743
71921
|
useLayoutEffect(() => {
|
|
71922
|
+
const requestedBehavior = pendingBehaviorRef.current;
|
|
71923
|
+
pendingBehaviorRef.current = null;
|
|
71744
71924
|
const viewportEl = getViewport();
|
|
71745
71925
|
if (!viewportEl || viewportEl.offsetParent === null) {
|
|
71746
71926
|
return;
|
|
71747
71927
|
}
|
|
71748
|
-
|
|
71928
|
+
let behavior = "auto";
|
|
71929
|
+
if (requestedBehavior === "smooth" && centeredIndexRef.current !== null) {
|
|
71930
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
71931
|
+
if (!prefersReducedMotion) {
|
|
71932
|
+
behavior = "smooth";
|
|
71933
|
+
}
|
|
71934
|
+
}
|
|
71935
|
+
syncCenterToSelection(viewportEl, behavior);
|
|
71749
71936
|
});
|
|
71750
71937
|
useWheelInteractions({
|
|
71751
71938
|
ref,
|
|
@@ -71813,6 +72000,7 @@ function WheelUI(props) {
|
|
|
71813
72000
|
children: [jsx(Box, {
|
|
71814
72001
|
as: "input",
|
|
71815
72002
|
...controlHostProps,
|
|
72003
|
+
onnavi_ui_state_change: onUIStateChange,
|
|
71816
72004
|
tabindex: -1,
|
|
71817
72005
|
"aria-hidden": "true",
|
|
71818
72006
|
className: "navi_wheel_input"
|