@jsenv/navi 0.29.116 → 0.29.117
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 +106 -35
- package/dist/jsenv_navi.js.map +14 -5
- 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
|
@@ -29628,7 +29628,11 @@ registerNaviCommand("--navi-void", (source) => {
|
|
|
29628
29628
|
};
|
|
29629
29629
|
});
|
|
29630
29630
|
|
|
29631
|
-
|
|
29631
|
+
// "--navi-update:smooth" asks the control to be SEEN moving to the value — a
|
|
29632
|
+
// wheel scrolls to it rather than swapping its digits — so a shortcut under
|
|
29633
|
+
// two wheels shows which one it changed, and by how much. The value itself is
|
|
29634
|
+
// set at once either way; only the drawing is concerned.
|
|
29635
|
+
registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
29632
29636
|
const target =
|
|
29633
29637
|
resolveExplicitTarget(source) || resolveFirstParentControl(source);
|
|
29634
29638
|
if (!target) {
|
|
@@ -29645,6 +29649,7 @@ registerNaviCommand("--navi-update", (source, event) => {
|
|
|
29645
29649
|
const commandValue = resolveCommandValue(source, event);
|
|
29646
29650
|
dispatchRequestSetUIState(target, commandValue, {
|
|
29647
29651
|
event,
|
|
29652
|
+
behavior: argument,
|
|
29648
29653
|
});
|
|
29649
29654
|
},
|
|
29650
29655
|
});
|
|
@@ -50058,8 +50063,12 @@ const SlideContainer = ({
|
|
|
50058
50063
|
// after it — a step that has been answered is not a step to stay on.
|
|
50059
50064
|
const done = (area, event) => {
|
|
50060
50065
|
markAnswered(area);
|
|
50061
|
-
if (!moveNext(event
|
|
50062
|
-
|
|
50066
|
+
if (!moveNext(event, {
|
|
50067
|
+
released: true
|
|
50068
|
+
})) {
|
|
50069
|
+
movePrevious(event, {
|
|
50070
|
+
released: true
|
|
50071
|
+
});
|
|
50063
50072
|
}
|
|
50064
50073
|
};
|
|
50065
50074
|
|
|
@@ -50377,6 +50386,7 @@ const SlideContainer = ({
|
|
|
50377
50386
|
forward,
|
|
50378
50387
|
event,
|
|
50379
50388
|
value,
|
|
50389
|
+
released,
|
|
50380
50390
|
dx = 0,
|
|
50381
50391
|
dy = 0
|
|
50382
50392
|
} = {}) => {
|
|
@@ -50391,6 +50401,7 @@ const SlideContainer = ({
|
|
|
50391
50401
|
forward,
|
|
50392
50402
|
event,
|
|
50393
50403
|
value,
|
|
50404
|
+
released,
|
|
50394
50405
|
dx,
|
|
50395
50406
|
dy
|
|
50396
50407
|
});
|
|
@@ -50427,7 +50438,11 @@ const SlideContainer = ({
|
|
|
50427
50438
|
// event dispatched by hand: a slide that holds on to the user holds them
|
|
50428
50439
|
// whatever they press. Read off the slide being LEFT, because that is what
|
|
50429
50440
|
// has a reason to keep them (an answer still missing, a step not taken).
|
|
50430
|
-
|
|
50441
|
+
// `released` is the slide letting go itself (--navi-done, see Slide): the
|
|
50442
|
+
// one departure the forward hold does not apply to. Said here rather than
|
|
50443
|
+
// by dropping the attribute, which stays the render's to write — a hold
|
|
50444
|
+
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
50445
|
+
if (forward ? !released && currentElement?.hasAttribute("data-prevent-nav-next") : currentElement?.hasAttribute("data-prevent-nav-previous")) {
|
|
50431
50446
|
return false;
|
|
50432
50447
|
}
|
|
50433
50448
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
@@ -50576,15 +50591,20 @@ const SlideContainer = ({
|
|
|
50576
50591
|
dx,
|
|
50577
50592
|
dy,
|
|
50578
50593
|
event,
|
|
50579
|
-
value
|
|
50594
|
+
value,
|
|
50595
|
+
released
|
|
50580
50596
|
} = pendingRollsRef.current.shift();
|
|
50581
50597
|
if (dx || dy) {
|
|
50582
|
-
move(dx, dy, event,
|
|
50598
|
+
move(dx, dy, event, {
|
|
50599
|
+
value,
|
|
50600
|
+
released
|
|
50601
|
+
});
|
|
50583
50602
|
return;
|
|
50584
50603
|
}
|
|
50585
50604
|
goToArea(area, {
|
|
50586
50605
|
event,
|
|
50587
|
-
value
|
|
50606
|
+
value,
|
|
50607
|
+
released
|
|
50588
50608
|
});
|
|
50589
50609
|
}, [noTravel]);
|
|
50590
50610
|
|
|
@@ -50703,10 +50723,14 @@ const SlideContainer = ({
|
|
|
50703
50723
|
return area;
|
|
50704
50724
|
}
|
|
50705
50725
|
};
|
|
50706
|
-
const move = (dx, dy, event,
|
|
50726
|
+
const move = (dx, dy, event, {
|
|
50727
|
+
value,
|
|
50728
|
+
released
|
|
50729
|
+
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
50707
50730
|
forward: dx > 0 || dy > 0,
|
|
50708
50731
|
event,
|
|
50709
50732
|
value,
|
|
50733
|
+
released,
|
|
50710
50734
|
dx,
|
|
50711
50735
|
dy
|
|
50712
50736
|
});
|
|
@@ -50716,8 +50740,8 @@ const SlideContainer = ({
|
|
|
50716
50740
|
// slides are. On a map they mean the same thing, and fall back to the other
|
|
50717
50741
|
// axis when there is nothing that way — a step onwards, however the screens
|
|
50718
50742
|
// 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);
|
|
50743
|
+
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);
|
|
50744
|
+
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
50745
|
|
|
50722
50746
|
// Where the track is right now, as the gesture left it: the resting place of
|
|
50723
50747
|
// the slide being dragged, plus what the pointer has pulled since.
|
|
@@ -51364,7 +51388,9 @@ const SlideContainer = ({
|
|
|
51364
51388
|
// (a --navi-right command carries the click that ran it, that click its
|
|
51365
51389
|
// own mousedown), and the focus transfer reads the whole chain to know
|
|
51366
51390
|
// where the interaction started.
|
|
51367
|
-
move(dx, dy, e,
|
|
51391
|
+
move(dx, dy, e, {
|
|
51392
|
+
value
|
|
51393
|
+
});
|
|
51368
51394
|
}
|
|
51369
51395
|
// By name rather than by direction (--navi-go-to-slide): the caller says
|
|
51370
51396
|
// where, the map says nothing about it.
|
|
@@ -51524,11 +51550,6 @@ const Slide = ({
|
|
|
51524
51550
|
,
|
|
51525
51551
|
|
|
51526
51552
|
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
51553
|
container?.done(slideArea);
|
|
51533
51554
|
rest.onnavi_done?.(e);
|
|
51534
51555
|
},
|
|
@@ -57182,7 +57203,12 @@ installImportMetaCssBuild(import.meta);const css$B = /* css */`
|
|
|
57182
57203
|
--picker-popup-border-radius,
|
|
57183
57204
|
var(--picker-border-radius)
|
|
57184
57205
|
);
|
|
57185
|
-
|
|
57206
|
+
/* Explicit fallback, for the same reason as --popover-background-color
|
|
57207
|
+
above: the dialog paints border-width from this var with no fallback
|
|
57208
|
+
of its own, so an unset dialogBorderWidth would leave the var
|
|
57209
|
+
guaranteed-invalid and border-width at its initial "medium" (3px).
|
|
57210
|
+
0px is the dialog's own default. */
|
|
57211
|
+
--dialog-border-width: var(--picker-dialog-border-width, 0px);
|
|
57186
57212
|
--dialog-border-color: var(--x-picker-border-color);
|
|
57187
57213
|
/* The picker's own surface is not this one — see the popover branch,
|
|
57188
57214
|
including why the fallback is spelled out. */
|
|
@@ -70558,7 +70584,7 @@ const WheelGroupContext = createContext(null);
|
|
|
70558
70584
|
* @param {boolean} [props.glass] - Frost the neighbouring rows so the center reads as a clear "window" (iOS-picker style). Inherited from a WheelGroup.
|
|
70559
70585
|
* @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
70586
|
* @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.
|
|
70587
|
+
* @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
70588
|
* @param {string} [props.type] - Informative value kind (e.g. "integer", "day"). Used only for rendering hints, like tabular figures for "integer".
|
|
70563
70589
|
*/
|
|
70564
70590
|
const Wheel = props => {
|
|
@@ -71280,6 +71306,23 @@ function WheelUI(props) {
|
|
|
71280
71306
|
// faster, so a second press mid-glide reads as accelerating, not restarting.
|
|
71281
71307
|
const targetPosRef = useRef(null);
|
|
71282
71308
|
const glideRef = useRef(null);
|
|
71309
|
+
// Who aimed the running glide. A value set from outside (`--navi-update:smooth`)
|
|
71310
|
+
// is already committed by whoever set it: the wheel only catches up with it,
|
|
71311
|
+
// and its arrival must not be reported as a settle — no navi_wheel_settle, no
|
|
71312
|
+
// action. A user input is the opposite: its arrival IS the settle. Written at
|
|
71313
|
+
// every motion start (settle, glideTo).
|
|
71314
|
+
const glideFromOutsideRef = useRef(false);
|
|
71315
|
+
// How the value that just arrived asked to be shown. A set request can say
|
|
71316
|
+
// `behavior: "smooth"` (what `--navi-update:smooth` puts on it); the wheel
|
|
71317
|
+
// then scrolls to the value instead of swapping the digits. Read off the
|
|
71318
|
+
// navi_ui_state_change the controller dispatches — the request may have
|
|
71319
|
+
// reached this wheel through a group distributing to its children, so it is
|
|
71320
|
+
// looked up along the event chain. Consumed by the sync effect below.
|
|
71321
|
+
const pendingBehaviorRef = useRef(null);
|
|
71322
|
+
const onUIStateChange = e => {
|
|
71323
|
+
const setRequest = findEvent(e, "navi_set_ui_state");
|
|
71324
|
+
pendingBehaviorRef.current = setRequest ? setRequest.detail.behavior : null;
|
|
71325
|
+
};
|
|
71283
71326
|
// The glide loop is bound once (mount effect) but the speed can change live
|
|
71284
71327
|
// (e.g. a demo control) — read it through a ref so it uses the latest.
|
|
71285
71328
|
const glideSpeedRef = useRef(glideSpeed);
|
|
@@ -71470,11 +71513,11 @@ function WheelUI(props) {
|
|
|
71470
71513
|
posRef.current = index * size;
|
|
71471
71514
|
renderPos(vp);
|
|
71472
71515
|
}
|
|
71473
|
-
|
|
71516
|
+
centeredIndexRef.current = index;
|
|
71517
|
+
if (!interactive || glideFromOutsideRef.current) {
|
|
71474
71518
|
return;
|
|
71475
71519
|
}
|
|
71476
71520
|
const settleEvent = new CustomEvent("navi_wheel_settle");
|
|
71477
|
-
centeredIndexRef.current = index;
|
|
71478
71521
|
debugScroll(`settle: committed → ${trackedItemsRef.current[index].value}`);
|
|
71479
71522
|
requestSelectValue(trackedItemsRef.current[index].value, settleEvent);
|
|
71480
71523
|
// The value is now stable → commit the action. The wheel runs actionEvent
|
|
@@ -71557,13 +71600,21 @@ function WheelUI(props) {
|
|
|
71557
71600
|
// Spring stiffness (fraction of remaining distance per ~frame). Scales with the
|
|
71558
71601
|
// glide speed so slower = gentler chase; clamped so it never crawls or snaps.
|
|
71559
71602
|
const glideSpringFactor = () => clampNumber(glideSpeedRef.current * 1.4, 0.06, 0.45);
|
|
71560
|
-
const glideTo = (vp, target
|
|
71603
|
+
const glideTo = (vp, target, {
|
|
71604
|
+
fromOutside = false
|
|
71605
|
+
} = {}) => {
|
|
71561
71606
|
// A discrete glide overrides any fling momentum.
|
|
71562
71607
|
if (momentumRef.current !== null) {
|
|
71563
71608
|
cancelAnimationFrame(momentumRef.current);
|
|
71564
71609
|
momentumRef.current = null;
|
|
71565
71610
|
}
|
|
71566
71611
|
targetPosRef.current = target;
|
|
71612
|
+
// A user input anywhere in the movement makes its arrival a user settle;
|
|
71613
|
+
// an outside value re-aiming a glide the user started never takes that
|
|
71614
|
+
// away from them (their tap still has to be answered with a settle).
|
|
71615
|
+
if (glideRef.current === null || !fromOutside) {
|
|
71616
|
+
glideFromOutsideRef.current = fromOutside;
|
|
71617
|
+
}
|
|
71567
71618
|
if (glideRef.current === null) {
|
|
71568
71619
|
debugScroll("glide: start");
|
|
71569
71620
|
glideRef.current = requestAnimationFrame(() => glideStep(vp, performance.now()));
|
|
@@ -71579,6 +71630,7 @@ function WheelUI(props) {
|
|
|
71579
71630
|
// overshoots only a handful of rows (a picker isn't a free-scrolling list).
|
|
71580
71631
|
const settle = (vp, velocity) => {
|
|
71581
71632
|
cancelAnim();
|
|
71633
|
+
glideFromOutsideRef.current = false;
|
|
71582
71634
|
debugScroll(`settle: momentum start (v=${velocity}px/ms)`);
|
|
71583
71635
|
// A drag fling: allow the full swipe velocity (see WHEEL_FLING_MAX_VELOCITY)
|
|
71584
71636
|
// so a hard swipe carries across the list instead of being clipped to a few
|
|
@@ -71643,13 +71695,19 @@ function WheelUI(props) {
|
|
|
71643
71695
|
glideTo(vp, target);
|
|
71644
71696
|
};
|
|
71645
71697
|
|
|
71646
|
-
// Center value `index`
|
|
71647
|
-
//
|
|
71698
|
+
// Center value `index` for a value the wheel did not choose itself (first
|
|
71699
|
+
// display, a value set from outside). "smooth" glides to the nearest copy
|
|
71700
|
+
// (glideTargetFor) so a wrap goes the short way, and arrives silently (see
|
|
71701
|
+
// glideFromOutsideRef); "auto" puts the row in place at once, stopping
|
|
71702
|
+
// whatever was moving so no glide lands on top of it a frame later.
|
|
71648
71703
|
const centerOnIndex = (vp, index, behavior) => {
|
|
71649
71704
|
const target = glideTargetFor(vp, index);
|
|
71650
71705
|
if (behavior === "smooth") {
|
|
71651
|
-
glideTo(vp, target
|
|
71706
|
+
glideTo(vp, target, {
|
|
71707
|
+
fromOutside: true
|
|
71708
|
+
});
|
|
71652
71709
|
} else {
|
|
71710
|
+
cancelAnim();
|
|
71653
71711
|
setPos(vp, target);
|
|
71654
71712
|
}
|
|
71655
71713
|
};
|
|
@@ -71661,7 +71719,7 @@ function WheelUI(props) {
|
|
|
71661
71719
|
const glideToIndex = (vp, index, event) => {
|
|
71662
71720
|
centeredIndexRef.current = index;
|
|
71663
71721
|
requestSelectValue(trackedItemsRef.current[index].value, event);
|
|
71664
|
-
|
|
71722
|
+
glideTo(vp, glideTargetFor(vp, index));
|
|
71665
71723
|
};
|
|
71666
71724
|
// Index we are heading to (the target, not the mid-glide visual center).
|
|
71667
71725
|
const currentTargetIndex = vp => {
|
|
@@ -71690,14 +71748,12 @@ function WheelUI(props) {
|
|
|
71690
71748
|
// Sync the center with the current value — used on first display and whenever
|
|
71691
71749
|
// the controlled value changes from outside.
|
|
71692
71750
|
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) {
|
|
71751
|
+
// A fling in flight reports its own value at every row crossing and commits
|
|
71752
|
+
// on settle: the finger that threw it owns the wheel until then, and a value
|
|
71753
|
+
// arriving meanwhile is re-stated by the next crossing anyway. A glide is
|
|
71754
|
+
// different: its target is re-aimed below (glideTo never restarts the
|
|
71755
|
+
// loop), so a value set from outside mid-glide is where the wheel ends up.
|
|
71756
|
+
if (momentumRef.current !== null) {
|
|
71701
71757
|
return;
|
|
71702
71758
|
}
|
|
71703
71759
|
if (trackedItemsRef.current.length === 0) {
|
|
@@ -71739,13 +71795,27 @@ function WheelUI(props) {
|
|
|
71739
71795
|
};
|
|
71740
71796
|
}, []);
|
|
71741
71797
|
|
|
71742
|
-
// React to controlled value changes coming from outside.
|
|
71798
|
+
// React to controlled value changes coming from outside. The row is put in
|
|
71799
|
+
// place at once, unless the request asked for "smooth": the value is already
|
|
71800
|
+
// set for whoever reads it, and the wheel scrolls to it so the eye can follow
|
|
71801
|
+
// the change (a shortcut button under two wheels: which one moved, and by how
|
|
71802
|
+
// much). Never on the first centering (no target yet → nothing to glide from,
|
|
71803
|
+
// the displayed effect above handles it), nor under reduced motion.
|
|
71743
71804
|
useLayoutEffect(() => {
|
|
71805
|
+
const requestedBehavior = pendingBehaviorRef.current;
|
|
71806
|
+
pendingBehaviorRef.current = null;
|
|
71744
71807
|
const viewportEl = getViewport();
|
|
71745
71808
|
if (!viewportEl || viewportEl.offsetParent === null) {
|
|
71746
71809
|
return;
|
|
71747
71810
|
}
|
|
71748
|
-
|
|
71811
|
+
let behavior = "auto";
|
|
71812
|
+
if (requestedBehavior === "smooth" && centeredIndexRef.current !== null) {
|
|
71813
|
+
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
71814
|
+
if (!prefersReducedMotion) {
|
|
71815
|
+
behavior = "smooth";
|
|
71816
|
+
}
|
|
71817
|
+
}
|
|
71818
|
+
syncCenterToSelection(viewportEl, behavior);
|
|
71749
71819
|
});
|
|
71750
71820
|
useWheelInteractions({
|
|
71751
71821
|
ref,
|
|
@@ -71813,6 +71883,7 @@ function WheelUI(props) {
|
|
|
71813
71883
|
children: [jsx(Box, {
|
|
71814
71884
|
as: "input",
|
|
71815
71885
|
...controlHostProps,
|
|
71886
|
+
onnavi_ui_state_change: onUIStateChange,
|
|
71816
71887
|
tabindex: -1,
|
|
71817
71888
|
"aria-hidden": "true",
|
|
71818
71889
|
className: "navi_wheel_input"
|