@jsenv/navi 0.29.37 → 0.29.39
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 +88 -53
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/AI_INSTRUCTIONS.md +5 -0
- package/docs/actions.md +3 -0
- package/docs/control_value.md +132 -0
- package/docs/popup_open.md +105 -1
- package/package.json +2 -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, 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";
|
|
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, markDragSource, 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";
|
|
@@ -12550,10 +12550,11 @@ defineInteractionDetector({
|
|
|
12550
12550
|
if (canReorder) {
|
|
12551
12551
|
element.setAttribute(REORDERABLE_ATTRIBUTE, "");
|
|
12552
12552
|
}
|
|
12553
|
-
// What @jsenv/dom puts on a drag source: no iOS callout,
|
|
12554
|
-
// the scroll until the press becomes a grab
|
|
12555
|
-
//
|
|
12556
|
-
|
|
12553
|
+
// What @jsenv/dom puts on a drag source: no iOS callout, the touch left to
|
|
12554
|
+
// the scroll until the press becomes a grab, and the listener that lets the
|
|
12555
|
+
// grab take it back. Its argument is the axis the SURROUNDINGS scroll on,
|
|
12556
|
+
// which for a list is the axis the list runs on.
|
|
12557
|
+
const unmarkDragSource = markDragSource(element, axes);
|
|
12557
12558
|
|
|
12558
12559
|
// What a release can mean, which is not all of what was declared: "grab" is a
|
|
12559
12560
|
// moment, not an outcome, and the gesture must not read it as one.
|
|
@@ -12630,7 +12631,7 @@ defineInteractionDetector({
|
|
|
12630
12631
|
|
|
12631
12632
|
return () => {
|
|
12632
12633
|
element.removeAttribute(REORDERABLE_ATTRIBUTE);
|
|
12633
|
-
|
|
12634
|
+
unmarkDragSource();
|
|
12634
12635
|
element.removeEventListener("pointerdown", onPointerDown);
|
|
12635
12636
|
};
|
|
12636
12637
|
},
|
|
@@ -23707,13 +23708,13 @@ const useUIGroupStateController = (
|
|
|
23707
23708
|
);
|
|
23708
23709
|
}
|
|
23709
23710
|
const parentUIStateController = useContext(ParentUIStateControllerContext);
|
|
23710
|
-
const hasValueProp = Object.hasOwn(props, "value");
|
|
23711
|
-
const hasOwnDefaultValueProp = Object.hasOwn(props, "defaultValue");
|
|
23712
23711
|
// A bound signal seeds the group the way `defaultValue` does — uncontrolled,
|
|
23713
23712
|
// with the signal's current value as what it starts on. Write-back is handled
|
|
23714
23713
|
// by applyState's own boundSignal; the read half is below: children are
|
|
23715
23714
|
// placed from it when they register, and again whenever it moves.
|
|
23716
|
-
const boundSignal =
|
|
23715
|
+
const boundSignal = props.signal;
|
|
23716
|
+
const hasValueProp = !boundSignal && Object.hasOwn(props, "value");
|
|
23717
|
+
const hasOwnDefaultValueProp = Object.hasOwn(props, "defaultValue");
|
|
23717
23718
|
const hasDefaultValueProp = hasOwnDefaultValueProp || Boolean(boundSignal);
|
|
23718
23719
|
const { id, name, value, uiAction } = props;
|
|
23719
23720
|
// A signal holding something wins over `defaultValue`: the default is a
|
|
@@ -24123,6 +24124,11 @@ const useUIGroupStateController = (
|
|
|
24123
24124
|
parentUIStateController,
|
|
24124
24125
|
uiAction,
|
|
24125
24126
|
uiActionInternal,
|
|
24127
|
+
// `props` is what writeBoundSignal reads to find the bound `signal`.
|
|
24128
|
+
// Missing here, a group whose component never re-renders between mount
|
|
24129
|
+
// and the first choice wrote nothing back into its signal — and said
|
|
24130
|
+
// nothing about it: the list showed the choice, the signal stayed empty.
|
|
24131
|
+
props,
|
|
24126
24132
|
};
|
|
24127
24133
|
},
|
|
24128
24134
|
// ── update: runs every render after the first ─────────────────────────
|
|
@@ -25321,22 +25327,25 @@ const createControlInfo = (props, {
|
|
|
25321
25327
|
defaultStatePropName = "defaultChecked";
|
|
25322
25328
|
value = props.value || "on";
|
|
25323
25329
|
signalHoldsChecked = true;
|
|
25324
|
-
if (
|
|
25330
|
+
if (signal) {
|
|
25331
|
+
if (props.defaultChecked) {
|
|
25332
|
+
// resolveInputProps may seed defaultChecked from a bound signal's
|
|
25333
|
+
// default: the control stays uncontrolled and follows the signal
|
|
25334
|
+
// through stateFromSignal.
|
|
25335
|
+
hasStateProp = false;
|
|
25336
|
+
stateInitial = value;
|
|
25337
|
+
stateFromSignal = signal.value ? value : undefined;
|
|
25338
|
+
} else {
|
|
25339
|
+
// A bound signal with no resolved default: its live value seeds state.
|
|
25340
|
+
hasStateProp = true;
|
|
25341
|
+
stateInitial = signal.value ? value : undefined;
|
|
25342
|
+
}
|
|
25343
|
+
} else if (Object.hasOwn(props, "checked")) {
|
|
25325
25344
|
hasStateProp = true;
|
|
25326
25345
|
stateInitial = props.checked ? value : undefined;
|
|
25327
25346
|
} else if (props.defaultChecked) {
|
|
25328
|
-
// resolveInputProps may seed defaultChecked from a bound signal's
|
|
25329
|
-
// default: the control stays uncontrolled and follows the signal
|
|
25330
|
-
// through stateFromSignal.
|
|
25331
25347
|
hasStateProp = false;
|
|
25332
25348
|
stateInitial = value;
|
|
25333
|
-
if (signal) {
|
|
25334
|
-
stateFromSignal = signal.value ? value : undefined;
|
|
25335
|
-
}
|
|
25336
|
-
} else if (signal) {
|
|
25337
|
-
// A bound signal with no resolved default: its live value seeds state.
|
|
25338
|
-
hasStateProp = true;
|
|
25339
|
-
stateInitial = signal.value ? value : undefined;
|
|
25340
25349
|
} else {
|
|
25341
25350
|
hasStateProp = false;
|
|
25342
25351
|
stateInitial = undefined;
|
|
@@ -25344,30 +25353,33 @@ const createControlInfo = (props, {
|
|
|
25344
25353
|
} else {
|
|
25345
25354
|
statePropName = "value";
|
|
25346
25355
|
defaultStatePropName = "defaultValue";
|
|
25347
|
-
if (
|
|
25356
|
+
if (signal) {
|
|
25357
|
+
if (Object.hasOwn(props, "defaultValue")) {
|
|
25358
|
+
// resolveInputProps seeds defaultValue from a bound signal's default,
|
|
25359
|
+
// so an input+signal is uncontrolled-with-default; the signal only
|
|
25360
|
+
// receives write-backs (onUIAction).
|
|
25361
|
+
hasStateProp = false;
|
|
25362
|
+
// A signal holding something wins over the default: `defaultValue` is
|
|
25363
|
+
// a suggestion of what to start from (and what a reset goes back to),
|
|
25364
|
+
// not an answer — while the signal's value IS the answer, restored
|
|
25365
|
+
// from the url or set by whoever owns it. Taking the default here
|
|
25366
|
+
// would show a suggestion in place of the value on every reload.
|
|
25367
|
+
stateInitial = signal.value !== undefined ? signal.value : props.defaultValue;
|
|
25368
|
+
stateFromSignal = stateInitial;
|
|
25369
|
+
} else {
|
|
25370
|
+
// A plain bound signal with no default (e.g. Wheel): its live value
|
|
25371
|
+
// seeds and controls the state.
|
|
25372
|
+
hasStateProp = true;
|
|
25373
|
+
value = signal.value;
|
|
25374
|
+
stateInitial = value;
|
|
25375
|
+
}
|
|
25376
|
+
} else if (Object.hasOwn(props, "value")) {
|
|
25348
25377
|
hasStateProp = true;
|
|
25349
25378
|
value = props.value;
|
|
25350
25379
|
stateInitial = value;
|
|
25351
25380
|
} else if (Object.hasOwn(props, "defaultValue")) {
|
|
25352
|
-
// resolveInputProps seeds defaultValue from a bound signal's default,
|
|
25353
|
-
// so an input+signal is uncontrolled-with-default; the signal only
|
|
25354
|
-
// receives write-backs (onUIAction).
|
|
25355
25381
|
hasStateProp = false;
|
|
25356
|
-
|
|
25357
|
-
// suggestion of what to start from (and what a reset goes back to), not
|
|
25358
|
-
// an answer — while the signal's value IS the answer, restored from the
|
|
25359
|
-
// url or set by whoever owns it. Taking the default here would show a
|
|
25360
|
-
// suggestion in place of the value on every reload.
|
|
25361
|
-
stateInitial = signal && signal.value !== undefined ? signal.value : props.defaultValue;
|
|
25362
|
-
if (signal) {
|
|
25363
|
-
stateFromSignal = stateInitial;
|
|
25364
|
-
}
|
|
25365
|
-
} else if (signal) {
|
|
25366
|
-
// A plain bound signal with no default (e.g. Wheel): its live value
|
|
25367
|
-
// seeds and controls the state.
|
|
25368
|
-
hasStateProp = true;
|
|
25369
|
-
value = signal.value;
|
|
25370
|
-
stateInitial = value;
|
|
25382
|
+
stateInitial = props.defaultValue;
|
|
25371
25383
|
} else {
|
|
25372
25384
|
hasStateProp = false;
|
|
25373
25385
|
stateInitial = undefined;
|
|
@@ -25387,20 +25399,23 @@ const createControlInfo = (props, {
|
|
|
25387
25399
|
} else if (controlType === "picker" || controlType === "select") {
|
|
25388
25400
|
statePropName = "value";
|
|
25389
25401
|
defaultStatePropName = "defaultValue";
|
|
25390
|
-
if (
|
|
25402
|
+
if (signal) {
|
|
25403
|
+
if (Object.hasOwn(props, "defaultValue")) {
|
|
25404
|
+
hasStateProp = false;
|
|
25405
|
+
// The signal's value is the answer, defaultValue only the suggestion to
|
|
25406
|
+
// start from.
|
|
25407
|
+
stateInitial = signal.value !== undefined ? signal.value : props.defaultValue;
|
|
25408
|
+
stateFromSignal = stateInitial;
|
|
25409
|
+
} else {
|
|
25410
|
+
hasStateProp = true;
|
|
25411
|
+
stateInitial = signal.value;
|
|
25412
|
+
}
|
|
25413
|
+
} else if (Object.hasOwn(props, "value")) {
|
|
25391
25414
|
hasStateProp = true;
|
|
25392
25415
|
stateInitial = props.value;
|
|
25393
25416
|
} else if (Object.hasOwn(props, "defaultValue")) {
|
|
25394
25417
|
hasStateProp = false;
|
|
25395
|
-
|
|
25396
|
-
// defaultValue only the suggestion to start from.
|
|
25397
|
-
stateInitial = signal && signal.value !== undefined ? signal.value : props.defaultValue;
|
|
25398
|
-
if (signal) {
|
|
25399
|
-
stateFromSignal = stateInitial;
|
|
25400
|
-
}
|
|
25401
|
-
} else if (signal) {
|
|
25402
|
-
hasStateProp = true;
|
|
25403
|
-
stateInitial = signal.value;
|
|
25418
|
+
stateInitial = props.defaultValue;
|
|
25404
25419
|
} else {
|
|
25405
25420
|
hasStateProp = false;
|
|
25406
25421
|
stateInitial = undefined;
|
|
@@ -26901,7 +26916,19 @@ const findFocusTarget = (containerEl) => {
|
|
|
26901
26916
|
// Neither is dropped, both are simply tried later — step 3 below for the
|
|
26902
26917
|
// first, and for the second the restore transferFocus does before ever
|
|
26903
26918
|
// calling here.
|
|
26904
|
-
|
|
26919
|
+
//
|
|
26920
|
+
// Skipped for good, unlike the two above: an element hidden from assistive
|
|
26921
|
+
// technology is not a place the focus can land at all. Something aria-hidden
|
|
26922
|
+
// and out of the tab order is a value holder standing behind what one
|
|
26923
|
+
// actually uses — a spin's headless picker behind its slides, say — and
|
|
26924
|
+
// landing there puts a ring on it, raises a phone's keyboard over the panel
|
|
26925
|
+
// that just opened, and has the browser complain about a focused aria-hidden
|
|
26926
|
+
// element. What one came to use is further down the same container.
|
|
26927
|
+
const isHiddenFromAssistiveTech = (element) =>
|
|
26928
|
+
Boolean(element.closest?.(`[aria-hidden="true"]`));
|
|
26929
|
+
|
|
26930
|
+
const skip = (element) =>
|
|
26931
|
+
isRestorableAutofocus(element) || isHiddenFromAssistiveTech(element);
|
|
26905
26932
|
|
|
26906
26933
|
// Every mark, not just the first: a mark is only worth stopping at if it
|
|
26907
26934
|
// leads somewhere focusable. One inside a screen waiting its turn (an inert
|
|
@@ -46865,7 +46892,10 @@ const resolveInputProps = (props) => {
|
|
|
46865
46892
|
if (Object.hasOwn(props, "defaultChecked")) ; else {
|
|
46866
46893
|
// If no explicit defaultChecked, derive it from the signal's default
|
|
46867
46894
|
// value so that resetUIState restores to the original default.
|
|
46868
|
-
|
|
46895
|
+
// Only a stateSignal carries a default of its own; a plain signal has
|
|
46896
|
+
// no `options` at all, and asking it for one used to throw on mount —
|
|
46897
|
+
// the same optional read every other branch here already does.
|
|
46898
|
+
const defaultVal = signalOptions?.getDefaultValue(false);
|
|
46869
46899
|
if (defaultVal === undefined) ; else if (props.type === "radio") {
|
|
46870
46900
|
if (defaultVal === true) {
|
|
46871
46901
|
props.defaultChecked = true;
|
|
@@ -58915,10 +58945,15 @@ const PickerFirstResolver = props => {
|
|
|
58915
58945
|
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
58916
58946
|
* picker also goes back in history — so anything written to the url while it
|
|
58917
58947
|
* was open (a route `stateSignal`, a search param) goes back with it. "close"
|
|
58918
|
-
* makes Escape say what clicking outside says: keep what was chosen, close
|
|
58948
|
+
* makes Escape say what clicking outside says: keep what was chosen, close —
|
|
58949
|
+
* a last resort, see docs/popup_open.md ("Escape cancels, the other gestures
|
|
58950
|
+
* keep") for why Escape should go on meaning cancel, and for what the value
|
|
58951
|
+
* at open is on the picker's very first open.
|
|
58919
58952
|
* @param {"close"|"cancel"|"capture"} [pointerInteractionOutsideEffect="close"]
|
|
58920
58953
|
* What a click outside the popup does: close and keep ("close"), close and
|
|
58921
|
-
* put back the value at open ("cancel"), or nothing at all ("capture").
|
|
58954
|
+
* put back the value at open ("cancel"), or nothing at all ("capture"). The
|
|
58955
|
+
* default is what gives a popup with no confirm button its way out that
|
|
58956
|
+
* keeps — see the same section.
|
|
58922
58957
|
* @param {number|string} [marginWithContainer] Minimum gap kept between the
|
|
58923
58958
|
* popup and the edges of what contains it (the viewport, or the picker's own
|
|
58924
58959
|
* positioned ancestor for `popupLayer="local"`). Caps the popup's size as
|