@jsenv/navi 0.29.493 → 0.29.495
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/dev/jsenv_navi.js +186 -72
- package/dist/dev/jsenv_navi.js.map +24 -24
- package/dist/jsenv_navi.js +182 -68
- package/dist/jsenv_navi.js.map +24 -24
- package/docs/resource.md +2 -1
- package/docs/testid.md +1 -1
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -6,7 +6,7 @@ import { installImportMetaCssBuild, coarsePointerSignal, windowHeightSignal, win
|
|
|
6
6
|
import { createPubSub, createIterableWeakSet, elementIsFocusable, getScrollContainer, dispatchInternalCustomEvent, dispatchCustomEvent, createEventGroupLogger, findEvent, mergeOneStyle, findFocusable, getElementSignature, chainEvent, isTouchDrivenEvent, getKeyboardEventDefaultAction, isPressDrivenClick, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, createValueEffect, findFocusDelegateTarget, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, getScrollContainerSet, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, keepTouchRefusable, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, activeElementSignal, normalizeStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, isPressDisputedByDrag, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, isScrollable, resolveOklchLightness, contrastColor, overflowGoesToViewport, parsePositionArea, snapToPixel, trapFocusInside, getVirtualKeyboardOverlayHeight, onAncestorReopen, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange } from "@jsenv/dom";
|
|
7
7
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
8
8
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
9
|
-
import { useState, useLayoutEffect, useErrorBoundary, useContext, useMemo, useRef, useEffect, useReducer, useCallback
|
|
9
|
+
import { useState, useLayoutEffect, useErrorBoundary, useContext, useMemo, useRef, useEffect, useReducer, useCallback } from "preact/hooks";
|
|
10
10
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
11
11
|
import { humanizeI18n, prefixFirstAndIndentRemainingLines, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatMinuteDuration, formatTime, formatSecondDuration, formatHourDuration, formatTimeRelative, pluralCategory, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
12
12
|
export { createI18n, formatByteSize, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatList, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText, pluralCategory } from "@jsenv/humanize";
|
|
@@ -14058,13 +14058,13 @@ const writePressWritesHere = (pressWrites) => {
|
|
|
14058
14058
|
}
|
|
14059
14059
|
};
|
|
14060
14060
|
|
|
14061
|
-
//
|
|
14062
|
-
//
|
|
14063
|
-
//
|
|
14064
|
-
// same
|
|
14065
|
-
const
|
|
14066
|
-
const
|
|
14067
|
-
return
|
|
14061
|
+
// An id made up at render names one mount: navi's own ("navi_<n>", see
|
|
14062
|
+
// useGeneratedId) counts mounts in the document, and preact's useId(), which
|
|
14063
|
+
// an app may hand a component, gives "P<mask0>-<mask1>" from the render order.
|
|
14064
|
+
// Neither names the same component in two mounts or two documents.
|
|
14065
|
+
const GENERATED_ID_REGEX = /^(?:navi_\d+|P\d+-\d+)/;
|
|
14066
|
+
const isGeneratedId = (id) => {
|
|
14067
|
+
return GENERATED_ID_REGEX.test(id);
|
|
14068
14068
|
};
|
|
14069
14069
|
|
|
14070
14070
|
/**
|
|
@@ -14088,7 +14088,7 @@ const dropGeneratedIdKeys = (state) => {
|
|
|
14088
14088
|
}
|
|
14089
14089
|
let stateWithoutGeneratedIdKeys = state;
|
|
14090
14090
|
for (const key of Object.keys(state)) {
|
|
14091
|
-
if (!
|
|
14091
|
+
if (!isGeneratedId(key)) {
|
|
14092
14092
|
continue;
|
|
14093
14093
|
}
|
|
14094
14094
|
if (stateWithoutGeneratedIdKeys === state) {
|
|
@@ -15393,7 +15393,7 @@ Consider using unique IDs for each component instance.`,
|
|
|
15393
15393
|
return useNavStateBasic(id, options);
|
|
15394
15394
|
};
|
|
15395
15395
|
|
|
15396
|
-
// A generated id (
|
|
15396
|
+
// A generated id (see isGeneratedId) names one mount. State written under one
|
|
15397
15397
|
// outlives that mount in the history entry — a page left with a popup open —
|
|
15398
15398
|
// and the mount coming back to the entry generates another id: the state is
|
|
15399
15399
|
// there, and nothing reads it. Reported when a component mounting on the entry
|
|
@@ -15419,7 +15419,7 @@ const warnAboutOrphanGeneratedKeys = () => {
|
|
|
15419
15419
|
}
|
|
15420
15420
|
for (const key of Object.keys(state)) {
|
|
15421
15421
|
if (
|
|
15422
|
-
!
|
|
15422
|
+
!isGeneratedId(key) ||
|
|
15423
15423
|
idUsageMap.has(key) ||
|
|
15424
15424
|
orphanKeysReported.has(key)
|
|
15425
15425
|
) {
|
|
@@ -15427,7 +15427,7 @@ const warnAboutOrphanGeneratedKeys = () => {
|
|
|
15427
15427
|
}
|
|
15428
15428
|
orphanKeysReported.add(key);
|
|
15429
15429
|
console.warn(
|
|
15430
|
-
`useNavState: this history entry holds "${key}", written by a component whose id was generated
|
|
15430
|
+
`useNavState: this history entry holds "${key}", written by a component whose id was generated and that is not mounted anymore — a Picker without an id, a popup with navState and no id. A generated id names one mount, so nothing will read that state again: a popup open when this screen was left comes back closed. Give the component a stable id if it was meant to be found as it was.`,
|
|
15431
15431
|
);
|
|
15432
15432
|
}
|
|
15433
15433
|
};
|
|
@@ -15515,13 +15515,13 @@ const useNavStateBasic = (
|
|
|
15515
15515
|
|
|
15516
15516
|
let effectiveType = type;
|
|
15517
15517
|
// A push bakes the key into a history entry of its own, and an id naming a
|
|
15518
|
-
// render position (see
|
|
15518
|
+
// render position (see isGeneratedId) can be claimed by two
|
|
15519
15519
|
// mounts of this same page. A replace keeps the key on the entry the document
|
|
15520
15520
|
// is already on, where the id at least names one render.
|
|
15521
|
-
if (type === "push" &&
|
|
15521
|
+
if (type === "push" && isGeneratedId(id)) {
|
|
15522
15522
|
{
|
|
15523
15523
|
console.warn(
|
|
15524
|
-
`useNavState(${id}): this id looks auto-generated
|
|
15524
|
+
`useNavState(${id}): this id looks auto-generated and type is "push" — falling back to "replace". A push history entry keyed by an unstable id won't survive a reload correctly, and could even collide with a different component's own auto-generated id. Pass a stable, explicit id to use type: "push".`,
|
|
15525
15525
|
);
|
|
15526
15526
|
}
|
|
15527
15527
|
effectiveType = "replace";
|
|
@@ -15639,7 +15639,7 @@ const useNavStateBasic = (
|
|
|
15639
15639
|
* - "push": creates a new history entry — pressing the back button removes it and calls onLeave.
|
|
15640
15640
|
* - "replace": updates the current history entry — no extra history entry is created.
|
|
15641
15641
|
* Silently downgraded to "replace" (with a dev-only console.warn) when `id`
|
|
15642
|
-
* looks auto-generated (
|
|
15642
|
+
* looks auto-generated (navi's own, or preact's useId()) — an unstable id baked
|
|
15643
15643
|
* into a pushed history entry won't survive a reload correctly, and could
|
|
15644
15644
|
* even collide with a different component's own auto-generated id. Pass a
|
|
15645
15645
|
* stable, explicit id to actually get "push" behavior.
|
|
@@ -30792,7 +30792,8 @@ ${name}.many("${propertyName}") source location: ${declarationSite}`,
|
|
|
30792
30792
|
* - `DELETE: async ({ id }) => [id, null]`
|
|
30793
30793
|
*
|
|
30794
30794
|
* `ownerId` may also be `{ [uniqueKey]: value }` when the owner is known by an alternate key.
|
|
30795
|
-
* The property is `
|
|
30795
|
+
* The property is `undefined` until an answer speaks for the owner (its row, or a
|
|
30796
|
+
* callback), `null` when that answer holds none; setting it to `null` clears it.
|
|
30796
30797
|
* Mutations apply directly to the owner's signal, so the parent GET is never rerun; an
|
|
30797
30798
|
* owner the store does not hold yet is left alone, its child comes with its next read.
|
|
30798
30799
|
*
|
|
@@ -30851,7 +30852,10 @@ ${name}.many("${propertyName}") source location: ${declarationSite}`,
|
|
|
30851
30852
|
for (const childSetup of childSetupCallbackSet) {
|
|
30852
30853
|
childSetup(childItem);
|
|
30853
30854
|
}
|
|
30854
|
-
|
|
30855
|
+
// `undefined` until something answers for this owner, `null` once an
|
|
30856
|
+
// answer says it holds none: a screen tells « not read yet » from
|
|
30857
|
+
// « nothing » as it does with an action's data.
|
|
30858
|
+
const childSignal = signal(undefined);
|
|
30855
30859
|
// The child keeps its identity while its props change, so the owner's
|
|
30856
30860
|
// getter also reads this version, bumped by each change in place:
|
|
30857
30861
|
// whichever way the props came in — the owner's row, a write of this
|
|
@@ -30860,7 +30864,10 @@ ${name}.many("${propertyName}") source location: ${declarationSite}`,
|
|
|
30860
30864
|
// Returns whether the child changed — what the store asks a relation
|
|
30861
30865
|
// (see assign in array_signal_store.js).
|
|
30862
30866
|
const applyProps = (props) => {
|
|
30863
|
-
if (
|
|
30867
|
+
if (props === undefined) {
|
|
30868
|
+
return false;
|
|
30869
|
+
}
|
|
30870
|
+
if (props === null) {
|
|
30864
30871
|
if (childSignal.peek() === null) {
|
|
30865
30872
|
return false;
|
|
30866
30873
|
}
|
|
@@ -30882,7 +30889,7 @@ ${name}.many("${propertyName}") source location: ${declarationSite}`,
|
|
|
30882
30889
|
}
|
|
30883
30890
|
}
|
|
30884
30891
|
if (childSignal.peek() !== childItem) {
|
|
30885
|
-
childSignal.value = childItem; // first activation
|
|
30892
|
+
childSignal.value = childItem; // first activation
|
|
30886
30893
|
return true;
|
|
30887
30894
|
}
|
|
30888
30895
|
if (changed) {
|
|
@@ -34777,7 +34784,12 @@ const Box = props => {
|
|
|
34777
34784
|
mount,
|
|
34778
34785
|
...computeProps
|
|
34779
34786
|
} = props;
|
|
34780
|
-
|
|
34787
|
+
// The flow of the nearest Box above in the component tree, which lays this
|
|
34788
|
+
// box out unless a plain element sits between the two in the DOM: the box
|
|
34789
|
+
// then reads that element's flow once mounted (see readDomParentFlow).
|
|
34790
|
+
const contextBoxFlow = useContext(BoxFlowContext);
|
|
34791
|
+
const [domParentFlow, setDomParentFlow] = useState(null);
|
|
34792
|
+
const parentBoxFlow = domParentFlow && domParentFlow.contextBoxFlow === contextBoxFlow ? domParentFlow.flow : contextBoxFlow;
|
|
34781
34793
|
// Which interactions this box answers, and with what. Read here rather than
|
|
34782
34794
|
// on the control, so a swipe or a hold can be declared on anything — a row, a
|
|
34783
34795
|
// card, a block of text — and reach the control it belongs to (which is what
|
|
@@ -34824,7 +34836,17 @@ const Box = props => {
|
|
|
34824
34836
|
childForwardedProps,
|
|
34825
34837
|
styleDeps
|
|
34826
34838
|
} = computed;
|
|
34827
|
-
const
|
|
34839
|
+
const verifyParentFlow = parentBoxFlow === contextBoxFlow && FLOW_STYLING_ITS_CHILDREN_SET.has(contextBoxFlow) && readsParentFlow(computeProps);
|
|
34840
|
+
const syncBox = useCallback(verifyParentFlow ? boxEl => {
|
|
34841
|
+
const flow = readDomParentFlow(boxEl);
|
|
34842
|
+
if (flow !== contextBoxFlow) {
|
|
34843
|
+
setDomParentFlow({
|
|
34844
|
+
contextBoxFlow,
|
|
34845
|
+
flow
|
|
34846
|
+
});
|
|
34847
|
+
}
|
|
34848
|
+
return computed.syncBox(boxEl);
|
|
34849
|
+
} : computed.syncBox, styleDeps);
|
|
34828
34850
|
const finalRef = useComposeElementRef(syncBox, ref);
|
|
34829
34851
|
useInteractionsEffect(finalRef, interactionsRef);
|
|
34830
34852
|
let innerChildren = children;
|
|
@@ -35137,6 +35159,13 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
35137
35159
|
const mergedValue = prepareStyleValue(stylesTarget[name], value, name, styleContext);
|
|
35138
35160
|
const cssVar = readCSSVarName(styleContext.styleCSSVars[name]);
|
|
35139
35161
|
if (cssVar) {
|
|
35162
|
+
if (name === "color" && value === "inherit") {
|
|
35163
|
+
// A token set to `inherit` takes the token of the parent element
|
|
35164
|
+
// (a list's white for its selected items), never the parent's color.
|
|
35165
|
+
// `color: currentColor` IS `color: inherit`, and a token carries it.
|
|
35166
|
+
addCSSVar("currentColor", cssVar, stylesTarget);
|
|
35167
|
+
return true;
|
|
35168
|
+
}
|
|
35140
35169
|
addCSSVar(mergedValue, cssVar, stylesTarget);
|
|
35141
35170
|
if (name === "borderRadius" && value === "inherit") {
|
|
35142
35171
|
// "inherit" cannot be expressed via a CSS variable — a var() reference
|
|
@@ -35453,6 +35482,47 @@ const declaresSomething = value => {
|
|
|
35453
35482
|
}
|
|
35454
35483
|
return true;
|
|
35455
35484
|
};
|
|
35485
|
+
// The flows the styles of a child branch on (see expandX, selfAlignX… in
|
|
35486
|
+
// box_style_util.js): under any other the child writes the same styles
|
|
35487
|
+
// whatever its parent is.
|
|
35488
|
+
const FLOW_STYLING_ITS_CHILDREN_SET = new Set(["flex-x", "flex-y", "inline-flex-x", "inline-flex-y", "grid", "inline-grid"]);
|
|
35489
|
+
const PARENT_FLOW_PROP_NAMES = ["expand", "expandX", "expandY", "selfAlignX", "selfAlignY"];
|
|
35490
|
+
const readsParentFlow = props => {
|
|
35491
|
+
for (const name of PARENT_FLOW_PROP_NAMES) {
|
|
35492
|
+
if (props[name]) {
|
|
35493
|
+
return true;
|
|
35494
|
+
}
|
|
35495
|
+
}
|
|
35496
|
+
return false;
|
|
35497
|
+
};
|
|
35498
|
+
// A Box parent says its flow on its element; a plain element is read from its
|
|
35499
|
+
// computed style, which only a box mounted under one pays for.
|
|
35500
|
+
const readDomParentFlow = boxEl => {
|
|
35501
|
+
let parent = boxEl.parentElement;
|
|
35502
|
+
while (parent) {
|
|
35503
|
+
const flowAttribute = parent.getAttribute("navi-box-flow");
|
|
35504
|
+
if (flowAttribute) {
|
|
35505
|
+
return flowAttribute;
|
|
35506
|
+
}
|
|
35507
|
+
const {
|
|
35508
|
+
display,
|
|
35509
|
+
flexDirection
|
|
35510
|
+
} = window.getComputedStyle(parent);
|
|
35511
|
+
if (display === "contents") {
|
|
35512
|
+
parent = parent.parentElement;
|
|
35513
|
+
continue;
|
|
35514
|
+
}
|
|
35515
|
+
if (display === "flex" || display === "inline-flex") {
|
|
35516
|
+
const axis = flexDirection.startsWith("column") ? "y" : "x";
|
|
35517
|
+
return `${display}-${axis}`;
|
|
35518
|
+
}
|
|
35519
|
+
if (display === "grid" || display === "inline-grid") {
|
|
35520
|
+
return display;
|
|
35521
|
+
}
|
|
35522
|
+
return "block";
|
|
35523
|
+
}
|
|
35524
|
+
return undefined;
|
|
35525
|
+
};
|
|
35456
35526
|
const isScrollingOverflow = value => value === "auto" || value === "scroll";
|
|
35457
35527
|
const mayHoldComponent = children => {
|
|
35458
35528
|
if (children === null || children === undefined) {
|
|
@@ -46703,17 +46773,22 @@ document.body.addEventListener(
|
|
|
46703
46773
|
* on screen. A registry keyed by id then holds the newcomer, and loses the
|
|
46704
46774
|
* first control when the newcomer leaves. navi renders on the client only, so
|
|
46705
46775
|
* a counter is all the uniqueness needs.
|
|
46776
|
+
*
|
|
46777
|
+
* Held in a ref, not in useState: @preact/signals' shouldComponentUpdate
|
|
46778
|
+
* skips the render of a component holding hook state whose props did not
|
|
46779
|
+
* change, so an id kept as state would change when its caller renders.
|
|
46706
46780
|
*/
|
|
46707
46781
|
|
|
46708
46782
|
|
|
46709
46783
|
let generatedIdCount = 0;
|
|
46710
46784
|
|
|
46711
46785
|
const useGeneratedId = () => {
|
|
46712
|
-
const
|
|
46786
|
+
const idRef = useRef(null);
|
|
46787
|
+
if (idRef.current === null) {
|
|
46713
46788
|
generatedIdCount++;
|
|
46714
|
-
|
|
46715
|
-
}
|
|
46716
|
-
return
|
|
46789
|
+
idRef.current = `navi_${generatedIdCount}`;
|
|
46790
|
+
}
|
|
46791
|
+
return idRef.current;
|
|
46717
46792
|
};
|
|
46718
46793
|
|
|
46719
46794
|
/**
|
|
@@ -61334,7 +61409,7 @@ const Expandable = props => {
|
|
|
61334
61409
|
const rootRef = ref || defaultRef;
|
|
61335
61410
|
const uiRef = useRef();
|
|
61336
61411
|
const contentContainerRef = useRef();
|
|
61337
|
-
const contentId =
|
|
61412
|
+
const contentId = useGeneratedId();
|
|
61338
61413
|
const isColumn = layout === "column";
|
|
61339
61414
|
const closedContentSized = isColumn && mount === "always";
|
|
61340
61415
|
// Only the two directions the layout has room for: a column layout reveals
|
|
@@ -62974,7 +63049,7 @@ const ButtonConfirm = ({
|
|
|
62974
63049
|
// it is elsewhere by then, and pulling it back would take it from whatever
|
|
62975
63050
|
// the person moved to.
|
|
62976
63051
|
const restoreFocusRef = useRef(false);
|
|
62977
|
-
const questionId = `${
|
|
63052
|
+
const questionId = `${useGeneratedId()}_question`;
|
|
62978
63053
|
const cancel = restoreFocus => {
|
|
62979
63054
|
restoreFocusRef.current = restoreFocus;
|
|
62980
63055
|
setAsking(false);
|
|
@@ -63776,7 +63851,7 @@ const ControlSwap = props => {
|
|
|
63776
63851
|
const activeSide = sides[activeIndex];
|
|
63777
63852
|
const capRefs = [useRef(), useRef()];
|
|
63778
63853
|
const slotRefs = [useRef(), useRef()];
|
|
63779
|
-
const slotIdPrefix =
|
|
63854
|
+
const slotIdPrefix = useGeneratedId();
|
|
63780
63855
|
const activeNameRef = useRef(floor.name);
|
|
63781
63856
|
activeNameRef.current = activeSide.name;
|
|
63782
63857
|
const swapTo = (name, event, pressedCapIndex = null) => {
|
|
@@ -64637,7 +64712,7 @@ const InputCheckboxFieldInterface = props => {
|
|
|
64637
64712
|
// setting's name — which are its label and its accessible name: a mode one
|
|
64638
64713
|
// toggles is a checkbox, not a button writing a signal on each press.
|
|
64639
64714
|
const hasLabel = variant === "button" && children !== undefined && children !== null && children !== false;
|
|
64640
|
-
const labelId =
|
|
64715
|
+
const labelId = useGeneratedId();
|
|
64641
64716
|
const boxRef = useRef();
|
|
64642
64717
|
useAccentColorAttributes(boxRef, accentColor, {
|
|
64643
64718
|
elementSelector: ".navi_checkbox_accent_probe"
|
|
@@ -75878,7 +75953,7 @@ const PopupTitleContext = createContext(null);
|
|
|
75878
75953
|
const PopupTitle = props => {
|
|
75879
75954
|
import.meta.css = [css$E, "@jsenv/navi/src/layout/popup_title.jsx"];
|
|
75880
75955
|
const registerTitle = useContext(PopupTitleContext);
|
|
75881
|
-
const idDefault =
|
|
75956
|
+
const idDefault = useGeneratedId();
|
|
75882
75957
|
const id = props.id || `popup_title_${idDefault}`;
|
|
75883
75958
|
useLayoutEffect(() => {
|
|
75884
75959
|
if (!registerTitle) {
|
|
@@ -79274,7 +79349,7 @@ const usePopoverProps = props => {
|
|
|
79274
79349
|
// open, two separate invocations of openEffect that don't otherwise share
|
|
79275
79350
|
// any scope, hence the ref.
|
|
79276
79351
|
const disarmBackdropHideRef = useRef(null);
|
|
79277
|
-
const defaultId =
|
|
79352
|
+
const defaultId = useGeneratedId();
|
|
79278
79353
|
const id = rest.id || defaultId;
|
|
79279
79354
|
const backdropId = `${id}-backdrop`;
|
|
79280
79355
|
const debugPopup = useDebugPopup();
|
|
@@ -80877,10 +80952,10 @@ const PickerCustom = props => {
|
|
|
80877
80952
|
// before computing popupId below, so two Pickers without an explicit id never collide.
|
|
80878
80953
|
// Captured before the fallback chain below overwrites props.id — needed to
|
|
80879
80954
|
// know whether the id actually came from the caller (stable) or from
|
|
80880
|
-
//
|
|
80955
|
+
// useGeneratedId()/ControlIdContext (a generated id names one mount: a reload, or a
|
|
80881
80956
|
// return to this page, generates another), see pickerNavType below.
|
|
80882
80957
|
const hasExplicitId = Boolean(props.id);
|
|
80883
|
-
const idDefault =
|
|
80958
|
+
const idDefault = useGeneratedId();
|
|
80884
80959
|
const controlId = useContext(ControlIdContext);
|
|
80885
80960
|
props.id = props.id || controlId || idDefault;
|
|
80886
80961
|
// Given no value, the picker holds what the control in its popup holds (see
|
|
@@ -80982,7 +81057,7 @@ const PickerCustom = props => {
|
|
|
80982
81057
|
const debugPopup = useDebugPopup();
|
|
80983
81058
|
// In "dialog" mode with a stable, caller-provided id, enterExpanded()
|
|
80984
81059
|
// pushes a history entry so the back button closes it. Every other case
|
|
80985
|
-
// (popover mode, or a dialog whose id was auto-generated via
|
|
81060
|
+
// (popover mode, or a dialog whose id was auto-generated via useGeneratedId()/
|
|
80986
81061
|
// ControlIdContext) replaces the current history state instead — a
|
|
80987
81062
|
// generated id names one mount, so pushing it would either leave an entry
|
|
80988
81063
|
// nothing reads or, worse, collide with a different component's own
|
|
@@ -83875,7 +83950,7 @@ const ListSelectable = props => {
|
|
|
83875
83950
|
const Next = useNextResolver();
|
|
83876
83951
|
import.meta.css = [css$x, "@jsenv/navi/src/control/list/list_selectable.jsx"];
|
|
83877
83952
|
// we allow ourselves to auto-generate a name
|
|
83878
|
-
const defaultName =
|
|
83953
|
+
const defaultName = useGeneratedId();
|
|
83879
83954
|
props.name = props.name || `listbox_${defaultName}`;
|
|
83880
83955
|
const {
|
|
83881
83956
|
ref,
|
|
@@ -84224,7 +84299,7 @@ const ListItemSelectableResolver = props => {
|
|
|
84224
84299
|
};
|
|
84225
84300
|
const ListItemSelectable = props => {
|
|
84226
84301
|
const Next = useNextResolver();
|
|
84227
|
-
const defaultId =
|
|
84302
|
+
const defaultId = useGeneratedId();
|
|
84228
84303
|
// Whether the caller SAYS anything about this row's selection. Passing
|
|
84229
84304
|
// `checked: undefined` is not the same as not passing it: a control with the
|
|
84230
84305
|
// key present calls itself controlled, and a controlled row is one the list
|
|
@@ -85214,7 +85289,7 @@ const ListUI = props => {
|
|
|
85214
85289
|
} = props;
|
|
85215
85290
|
// Remembered by name, and a name made up at render (see ListFirstResolver)
|
|
85216
85291
|
// names no list a later mount would recognize.
|
|
85217
|
-
const rememberScroll = !scrollResetOnNavigation && !
|
|
85292
|
+
const rememberScroll = !scrollResetOnNavigation && !isGeneratedId(rest.id);
|
|
85218
85293
|
// Where the list was when its screen was left, when this is the way back
|
|
85219
85294
|
// (see scroll_restoration.js). Read once: `defaultScrolled` is held by
|
|
85220
85295
|
// reference, and a place read again each render would be a list moved each
|
|
@@ -85524,7 +85599,7 @@ const ListFirstResolver = props => {
|
|
|
85524
85599
|
const Next = useNextResolver();
|
|
85525
85600
|
const refDefault = useRef(null);
|
|
85526
85601
|
props.ref = props.ref || refDefault;
|
|
85527
|
-
const idDefault =
|
|
85602
|
+
const idDefault = useGeneratedId();
|
|
85528
85603
|
props.id = props.id || idDefault;
|
|
85529
85604
|
const listItemsRef = useRef(null);
|
|
85530
85605
|
if (!listItemsRef.current) {
|
|
@@ -89117,7 +89192,7 @@ const ListItemUI = props => {
|
|
|
89117
89192
|
if (identityMatters && props.id === undefined) {
|
|
89118
89193
|
console.warn("ListItem is missing an explicit id prop. Provide a stable id so pointed/selected state survives search reordering.");
|
|
89119
89194
|
}
|
|
89120
|
-
const idDefault =
|
|
89195
|
+
const idDefault = useGeneratedId();
|
|
89121
89196
|
props.id = props.id || idDefault;
|
|
89122
89197
|
const listItems = useContext(ListItemsContext);
|
|
89123
89198
|
const groupId = useContext(ListGroupContext);
|
|
@@ -89933,7 +90008,7 @@ const useRunItems = (store, {
|
|
|
89933
90008
|
renderSkeleton,
|
|
89934
90009
|
renderError
|
|
89935
90010
|
}) => {
|
|
89936
|
-
const ownerId =
|
|
90011
|
+
const ownerId = useGeneratedId();
|
|
89937
90012
|
// Dev only: the group `groupOfMissing` put an item not held in, by rank,
|
|
89938
90013
|
// checked against `groupBy` once the item lands.
|
|
89939
90014
|
const missingGroupCheckRef = useRef(null);
|
|
@@ -90502,7 +90577,7 @@ const ListRunSkeletonItem = ({
|
|
|
90502
90577
|
}) => {
|
|
90503
90578
|
const listItems = useContext(ListItemsContext);
|
|
90504
90579
|
const groupId = useContext(ListGroupContext);
|
|
90505
|
-
const entryId =
|
|
90580
|
+
const entryId = useGeneratedId();
|
|
90506
90581
|
listItems.draw(entryId, {
|
|
90507
90582
|
ownerId: runItem.ownerId,
|
|
90508
90583
|
place: runItem.index,
|
|
@@ -91243,7 +91318,7 @@ const ListItemGroup = ({
|
|
|
91243
91318
|
children,
|
|
91244
91319
|
...rest
|
|
91245
91320
|
}) => {
|
|
91246
|
-
const groupId =
|
|
91321
|
+
const groupId = useGeneratedId();
|
|
91247
91322
|
const listItems = useContext(ListItemsContext);
|
|
91248
91323
|
const group = listItems.group(groupId);
|
|
91249
91324
|
useLayoutEffect(() => {
|
|
@@ -95587,7 +95662,7 @@ const Spin = ({
|
|
|
95587
95662
|
} else {
|
|
95588
95663
|
compareValues ??= compareValuesDefault;
|
|
95589
95664
|
}
|
|
95590
|
-
const id =
|
|
95665
|
+
const id = useGeneratedId();
|
|
95591
95666
|
// What the group around it says, when there is one: how big the whole thing
|
|
95592
95667
|
// is written is said once, on the group, and every spin in it follows.
|
|
95593
95668
|
const group = useContext(SpinGroupContext);
|
|
@@ -96805,7 +96880,7 @@ const TimeRangeSpin = ({
|
|
|
96805
96880
|
endTimeProps,
|
|
96806
96881
|
...rest
|
|
96807
96882
|
}) => {
|
|
96808
|
-
const startId =
|
|
96883
|
+
const startId = useGeneratedId();
|
|
96809
96884
|
return jsxs(ControlGroup, {
|
|
96810
96885
|
flex: true,
|
|
96811
96886
|
alignY: "center",
|
|
@@ -96898,7 +96973,7 @@ const css$q = /* css */`.navi_checkbox_group {
|
|
|
96898
96973
|
const CheckboxGroup = props => {
|
|
96899
96974
|
const refDefault = useRef(null);
|
|
96900
96975
|
props.ref = props.ref || refDefault;
|
|
96901
|
-
const defaultName =
|
|
96976
|
+
const defaultName = useGeneratedId();
|
|
96902
96977
|
props.name = props.name || `checkbox_group_${defaultName}`;
|
|
96903
96978
|
const checkboxGroup = jsx(CheckboxGroupInterface, {
|
|
96904
96979
|
...props
|
|
@@ -97663,7 +97738,7 @@ const css$o = /* css */`.navi_radio_group {
|
|
|
97663
97738
|
const RadioGroup = props => {
|
|
97664
97739
|
const refDefault = useRef(null);
|
|
97665
97740
|
props.ref = props.ref || refDefault;
|
|
97666
|
-
const defaultName =
|
|
97741
|
+
const defaultName = useGeneratedId();
|
|
97667
97742
|
props.name = props.name || `radio_group_${defaultName}`;
|
|
97668
97743
|
const radioGroup = jsx(RadioGroupInterface, {
|
|
97669
97744
|
...props
|
|
@@ -98004,7 +98079,7 @@ const SplitButton = props => {
|
|
|
98004
98079
|
if (popupProps.positionArea === undefined && mode === "popover") {
|
|
98005
98080
|
popupProps.positionArea = "bottom-end";
|
|
98006
98081
|
}
|
|
98007
|
-
const idDefault =
|
|
98082
|
+
const idDefault = useGeneratedId();
|
|
98008
98083
|
const idResolved = id || idDefault;
|
|
98009
98084
|
const menuId = `${idResolved}_menu`;
|
|
98010
98085
|
const rootRef = useRef(null);
|
|
@@ -98759,12 +98834,18 @@ const buildMatchInfo = (searchText, items, matchFn) => {
|
|
|
98759
98834
|
* needed. `data.role === "presentation"` keeps it out entirely.
|
|
98760
98835
|
* Returns the visible rank (position among visible items), or -1 when hidden
|
|
98761
98836
|
* or filtered.
|
|
98837
|
+
* An item rendered without an index keeps the position it has, and a new one
|
|
98838
|
+
* goes last: for a host that cannot know the order at render and gives it
|
|
98839
|
+
* afterwards with setOrder.
|
|
98762
98840
|
* Signals and onChange are deferred to a microtask so multiple items updating
|
|
98763
98841
|
* in one commit cause only one notification.
|
|
98764
98842
|
*
|
|
98765
98843
|
* getTrackedItemByIndex(index): synchronous O(1) lookup of a visible item by
|
|
98766
98844
|
* its visible rank. Returns undefined when index is out of range.
|
|
98767
98845
|
*
|
|
98846
|
+
* setOrder(ids): gives each tracked item its position from its rank in
|
|
98847
|
+
* `ids`.
|
|
98848
|
+
*
|
|
98768
98849
|
* peekItems(): the items as they stand right now, without waiting for the
|
|
98769
98850
|
* deferred notification — what a sibling rendering after the items must read
|
|
98770
98851
|
* to paint them in the same commit.
|
|
@@ -99045,8 +99126,14 @@ const createItemTracker = (onChange) => {
|
|
|
99045
99126
|
// that determines this item's position among siblings.
|
|
99046
99127
|
// Returns the item's visible rank among non-hidden items, or -1 when hidden.
|
|
99047
99128
|
const useTrackItem = (data) => {
|
|
99048
|
-
const { id
|
|
99129
|
+
const { id } = data;
|
|
99049
99130
|
const key = keyForId(id);
|
|
99131
|
+
let { index } = data;
|
|
99132
|
+
if (index === undefined) {
|
|
99133
|
+
index = keyToExplicitOrder.has(key)
|
|
99134
|
+
? keyToExplicitOrder.get(key)
|
|
99135
|
+
: Infinity;
|
|
99136
|
+
}
|
|
99050
99137
|
|
|
99051
99138
|
syncItem(key, index, data);
|
|
99052
99139
|
notify();
|
|
@@ -99064,6 +99151,24 @@ const createItemTracker = (onChange) => {
|
|
|
99064
99151
|
return keyToOrderedIndex.get(key) ?? -1;
|
|
99065
99152
|
};
|
|
99066
99153
|
|
|
99154
|
+
const setOrder = (ids) => {
|
|
99155
|
+
let changed = false;
|
|
99156
|
+
for (let i = 0; i < ids.length; i++) {
|
|
99157
|
+
const key = idToKey.get(ids[i]);
|
|
99158
|
+
if (key === undefined || !allRegistrations.has(key)) {
|
|
99159
|
+
continue;
|
|
99160
|
+
}
|
|
99161
|
+
if (keyToExplicitOrder.get(key) === i) {
|
|
99162
|
+
continue;
|
|
99163
|
+
}
|
|
99164
|
+
syncItem(key, i, allRegistrations.get(key));
|
|
99165
|
+
changed = true;
|
|
99166
|
+
}
|
|
99167
|
+
if (changed) {
|
|
99168
|
+
notify();
|
|
99169
|
+
}
|
|
99170
|
+
};
|
|
99171
|
+
|
|
99067
99172
|
const getTrackedItemByIndex = (index) => {
|
|
99068
99173
|
const key = orderedKeys[index];
|
|
99069
99174
|
if (key === undefined) {
|
|
@@ -99097,6 +99202,7 @@ const createItemTracker = (onChange) => {
|
|
|
99097
99202
|
return {
|
|
99098
99203
|
useTrackItem,
|
|
99099
99204
|
getTrackedItemByIndex,
|
|
99205
|
+
setOrder,
|
|
99100
99206
|
peekItems,
|
|
99101
99207
|
itemsSignal,
|
|
99102
99208
|
visibleItemsSignal,
|
|
@@ -99494,8 +99600,8 @@ const getNumericRange = items => {
|
|
|
99494
99600
|
|
|
99495
99601
|
// Wheel.Item registers its {value, label} here so WheelUI knows the full ordered
|
|
99496
99602
|
// list of items regardless of how children are wrapped (providers, fragments…).
|
|
99497
|
-
//
|
|
99498
|
-
//
|
|
99603
|
+
// The position comes after the commit, from the marker each item leaves where it
|
|
99604
|
+
// stands (see WheelUI).
|
|
99499
99605
|
const WheelItemTrackerContext = createContext(null);
|
|
99500
99606
|
|
|
99501
99607
|
// Lets a WheelGroup push shared presentation (glass, orientation) down to every
|
|
@@ -100118,15 +100224,21 @@ function WheelUI(props) {
|
|
|
100118
100224
|
// (children may be wrapped). This ordered list is the source of truth for the
|
|
100119
100225
|
// whole wheel — the rendered rows are a small recycled window over it.
|
|
100120
100226
|
const tracker = useItemTracker();
|
|
100121
|
-
|
|
100122
|
-
|
|
100123
|
-
|
|
100124
|
-
|
|
100125
|
-
|
|
100126
|
-
|
|
100127
|
-
|
|
100128
|
-
|
|
100129
|
-
|
|
100227
|
+
// The order is read from the markers the items leave in the DOM, not from the
|
|
100228
|
+
// order they render in: an item whose props did not change may skip a render
|
|
100229
|
+
// (signals' shouldComponentUpdate), and a render count would then rank the
|
|
100230
|
+
// ones that did render first.
|
|
100231
|
+
useLayoutEffect(() => {
|
|
100232
|
+
const el = ref.current;
|
|
100233
|
+
if (!el) {
|
|
100234
|
+
return;
|
|
100235
|
+
}
|
|
100236
|
+
const ids = [];
|
|
100237
|
+
for (const marker of el.querySelectorAll("[data-navi-wheel-item]")) {
|
|
100238
|
+
ids.push(marker.getAttribute("data-navi-wheel-item"));
|
|
100239
|
+
}
|
|
100240
|
+
tracker.setOrder(ids);
|
|
100241
|
+
});
|
|
100130
100242
|
const trackedItems = tracker.itemsSignal.value;
|
|
100131
100243
|
const trackedItemsRef = useRef(trackedItems);
|
|
100132
100244
|
trackedItemsRef.current = trackedItems;
|
|
@@ -100933,7 +101045,7 @@ function WheelUI(props) {
|
|
|
100933
101045
|
className: "navi_wheel_pane",
|
|
100934
101046
|
"data-side": "start"
|
|
100935
101047
|
}), jsx(WheelItemTrackerContext.Provider, {
|
|
100936
|
-
value:
|
|
101048
|
+
value: tracker,
|
|
100937
101049
|
children: jsx(ControlFacadeChildrenWrapper, {
|
|
100938
101050
|
facadeController: facadeController,
|
|
100939
101051
|
children: children
|
|
@@ -101046,9 +101158,10 @@ const WheelWindow = ({
|
|
|
101046
101158
|
* Wheel.Item — a value in a Wheel. Must be used inside <Wheel>.
|
|
101047
101159
|
*
|
|
101048
101160
|
* Registration only: it records its {value, label, itemProps} with the wheel and
|
|
101049
|
-
* renders
|
|
101050
|
-
*
|
|
101051
|
-
*
|
|
101161
|
+
* renders no row, only a hidden marker the wheel reads its position from. The
|
|
101162
|
+
* wheel is virtualized — it renders a small recycled window of rows and fills
|
|
101163
|
+
* each from this tracked data — so the value list is the source of truth, not a
|
|
101164
|
+
* per-item DOM node. Selection lives on the wheel's value.
|
|
101052
101165
|
*
|
|
101053
101166
|
* @type {import("preact").FunctionComponent<{
|
|
101054
101167
|
* value: any,
|
|
@@ -101062,19 +101175,20 @@ const WheelItem = ({
|
|
|
101062
101175
|
children,
|
|
101063
101176
|
...rest
|
|
101064
101177
|
}) => {
|
|
101065
|
-
const idDefault =
|
|
101178
|
+
const idDefault = useGeneratedId();
|
|
101066
101179
|
const resolvedId = id || idDefault;
|
|
101067
101180
|
// Wheel.Item must live inside a Wheel, so the context is always present.
|
|
101068
|
-
const
|
|
101069
|
-
|
|
101070
|
-
trackerContext.tracker.useTrackItem({
|
|
101181
|
+
const tracker = useContext(WheelItemTrackerContext);
|
|
101182
|
+
tracker.useTrackItem({
|
|
101071
101183
|
id: resolvedId,
|
|
101072
|
-
index,
|
|
101073
101184
|
value,
|
|
101074
101185
|
label: children,
|
|
101075
101186
|
itemProps: rest
|
|
101076
101187
|
});
|
|
101077
|
-
return
|
|
101188
|
+
return jsx("span", {
|
|
101189
|
+
hidden: true,
|
|
101190
|
+
"data-navi-wheel-item": resolvedId
|
|
101191
|
+
});
|
|
101078
101192
|
};
|
|
101079
101193
|
Wheel.Item = WheelItem;
|
|
101080
101194
|
|
|
@@ -101660,7 +101774,7 @@ const TimeRangeWheel = ({
|
|
|
101660
101774
|
...rest
|
|
101661
101775
|
}) => {
|
|
101662
101776
|
import.meta.css = [css$j, "@jsenv/navi/src/control/wheel/wheel_time.jsx"];
|
|
101663
|
-
const startId =
|
|
101777
|
+
const startId = useGeneratedId();
|
|
101664
101778
|
const startRef = useRef(null);
|
|
101665
101779
|
const endRef = useRef(null);
|
|
101666
101780
|
const endHourList = useMemo(() => resolveHourList(hours), [hours ? hours.min : undefined, hours ? hours.max : undefined, hours]);
|
|
@@ -104648,7 +104762,7 @@ const useIsInTableHead = () => useContext(TableSectionContext) === "head";
|
|
|
104648
104762
|
const Table = props => {
|
|
104649
104763
|
installTableCss();
|
|
104650
104764
|
const tableDefaultRef = useRef();
|
|
104651
|
-
const tableDefaultId = `table-${
|
|
104765
|
+
const tableDefaultId = `table-${useGeneratedId()}`;
|
|
104652
104766
|
const {
|
|
104653
104767
|
id = tableDefaultId,
|
|
104654
104768
|
selection = [],
|