@jsenv/navi 0.29.322 → 0.29.324
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/README.md +2 -0
- package/dist/dev/jsenv_navi.js +366 -229
- package/dist/dev/jsenv_navi.js.map +21 -21
- package/dist/jsenv_navi.js +362 -229
- package/dist/jsenv_navi.js.map +21 -21
- package/docs/AI_INSTRUCTIONS.md +5 -3
- package/docs/popup_backdrop.md +31 -0
- package/docs/popup_open.md +45 -0
- package/docs/scroll.md +6 -4
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -7,7 +7,7 @@ export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
|
7
7
|
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, keepTouchRefusable, isPressDrivenClick, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, isTouchDrivenEvent, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
10
|
-
import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, Fragment as Fragment$1 } from "preact";
|
|
10
|
+
import { isValidElement, createContext, render, h, toChildArray, options, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
11
11
|
import { useErrorBoundary, useLayoutEffect, useContext, useCallback, useRef, useState, useEffect, useMemo, useId } from "preact/hooks";
|
|
12
12
|
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatTime, formatMinuteDuration, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
13
13
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
@@ -31648,8 +31648,26 @@ const resolveExplicitTarget = (element) => {
|
|
|
31648
31648
|
}
|
|
31649
31649
|
return undefined;
|
|
31650
31650
|
};
|
|
31651
|
+
// The control around the source that a command can be about: one holding a
|
|
31652
|
+
// value of its own. A popup is a control root too (it answers interactions,
|
|
31653
|
+
// carries a callout), but it has no host of its own — asked for one, it
|
|
31654
|
+
// answers with the first control INSIDE it, which for a field sending
|
|
31655
|
+
// --navi-update from a picker's popup is that very field: the command then
|
|
31656
|
+
// sets the field to what it already holds, which fires the command again,
|
|
31657
|
+
// without end. Such a surface is walked past, up to the picker or the form.
|
|
31651
31658
|
const resolveFirstParentControl = (el) => {
|
|
31652
|
-
|
|
31659
|
+
let parentControl = getParentControl(el);
|
|
31660
|
+
while (parentControl && !ownsControlHost(parentControl)) {
|
|
31661
|
+
parentControl = getParentControl(parentControl);
|
|
31662
|
+
}
|
|
31663
|
+
return parentControl;
|
|
31664
|
+
};
|
|
31665
|
+
const ownsControlHost = (controlRoot) => {
|
|
31666
|
+
const controlHost = findControlHost(controlRoot);
|
|
31667
|
+
return (
|
|
31668
|
+
Boolean(controlHost) &&
|
|
31669
|
+
controlHost.closest("[navi-control]") === controlRoot
|
|
31670
|
+
);
|
|
31653
31671
|
};
|
|
31654
31672
|
const resolveFirstChildControl = (el) => {
|
|
31655
31673
|
let startEl;
|
|
@@ -33840,19 +33858,27 @@ const writeInSignal = (signal, value, { history }) => {
|
|
|
33840
33858
|
// is open (`?seat=<gameId>` over a list of cards), and closed is the signal
|
|
33841
33859
|
// holding none of their values — `undefined`, which a state signal reads as
|
|
33842
33860
|
// its default.
|
|
33861
|
+
//
|
|
33862
|
+
// A popup without a `value` reads anything the signal holds — save `false`,
|
|
33863
|
+
// `null` and `undefined` — as open: a single sheet bound to `?open=<id>` is
|
|
33864
|
+
// open on whichever id the address names, and the id is the popup's content
|
|
33865
|
+
// to read, not its to write. Opening writes `true` only into a signal that
|
|
33866
|
+
// reads closed; closing writes `false` where the signal held `true`, so a
|
|
33867
|
+
// boolean signal keeps its boolean, and `undefined` for anything else.
|
|
33843
33868
|
const writeOpenedInSignal = (signal, opened, event, popupValue) => {
|
|
33844
|
-
|
|
33869
|
+
const signalValue = signal.peek();
|
|
33870
|
+
if (readOpened(signalValue, popupValue) === opened) {
|
|
33845
33871
|
// The signal already says so, meaning this open/close IS what it asked
|
|
33846
33872
|
// for: a back press that took the popup out of the url, the application
|
|
33847
33873
|
// writing it. Nothing to write back — and nothing to go back to either,
|
|
33848
33874
|
// since the navigation navBack would undo is the one that asked for this.
|
|
33849
33875
|
return;
|
|
33850
33876
|
}
|
|
33851
|
-
const closedValue = popupValue === undefined ? false : undefined;
|
|
33852
33877
|
if (opened) {
|
|
33853
33878
|
signal.value = popupValue === undefined ? true : popupValue;
|
|
33854
33879
|
return;
|
|
33855
33880
|
}
|
|
33881
|
+
const closedValue = readClosedValue(signalValue);
|
|
33856
33882
|
if (
|
|
33857
33883
|
signal.options?.getHistory?.() === "push" &&
|
|
33858
33884
|
// Nothing of this document behind: the popup was opened by the url itself
|
|
@@ -33873,10 +33899,15 @@ const writeOpenedInSignal = (signal, opened, event, popupValue) => {
|
|
|
33873
33899
|
};
|
|
33874
33900
|
const readOpened = (signalValue, popupValue) => {
|
|
33875
33901
|
if (popupValue === undefined) {
|
|
33876
|
-
return
|
|
33902
|
+
return (
|
|
33903
|
+
signalValue !== undefined && signalValue !== null && signalValue !== false
|
|
33904
|
+
);
|
|
33877
33905
|
}
|
|
33878
33906
|
return signalValue === popupValue;
|
|
33879
33907
|
};
|
|
33908
|
+
const readClosedValue = (openSignalValue) => {
|
|
33909
|
+
return openSignalValue === true ? false : undefined;
|
|
33910
|
+
};
|
|
33880
33911
|
|
|
33881
33912
|
/**
|
|
33882
33913
|
* Keeps an open controller in sync with where the caller says the popup should
|
|
@@ -33918,6 +33949,13 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
33918
33949
|
: signal
|
|
33919
33950
|
? readOpened(signal.value, value)
|
|
33920
33951
|
: props.open;
|
|
33952
|
+
// What the signal holds while it reads open — `true`, the popup's `value`,
|
|
33953
|
+
// or the id a single sheet is open on. Put back when a close is refused,
|
|
33954
|
+
// since by then the signal has been written closed.
|
|
33955
|
+
const openSignalValueRef = useRef(undefined);
|
|
33956
|
+
if (signal && open) {
|
|
33957
|
+
openSignalValueRef.current = signal.value;
|
|
33958
|
+
}
|
|
33921
33959
|
// Assigned on every render, like openEffect, so it always closes over the
|
|
33922
33960
|
// latest prop: a popup that opens or closes on its own (Escape, backdrop, a
|
|
33923
33961
|
// --navi-close command) writes what happened where the caller keeps it, so
|
|
@@ -33998,10 +34036,11 @@ const useOpenPropsEffectOnOpenController = (
|
|
|
33998
34036
|
// request already reads in the signal, and with a `value` what the
|
|
33999
34037
|
// signal holds may be another popup's, which the write would erase.
|
|
34000
34038
|
if (signal && openController.opened !== open) {
|
|
34001
|
-
const opened = openController.opened;
|
|
34002
34039
|
writeInSignal(
|
|
34003
34040
|
signal,
|
|
34004
|
-
|
|
34041
|
+
openController.opened
|
|
34042
|
+
? openSignalValueRef.current
|
|
34043
|
+
: readClosedValue(signal.peek()),
|
|
34005
34044
|
{ history: "replace" },
|
|
34006
34045
|
);
|
|
34007
34046
|
}
|
|
@@ -34379,6 +34418,21 @@ const resolveAutoAnimationKind = (anchor, parsedPositionArea) => {
|
|
|
34379
34418
|
*/
|
|
34380
34419
|
const OUTSIDE_REGION_ATTRIBUTE = "data-navi-popup-outside";
|
|
34381
34420
|
|
|
34421
|
+
/**
|
|
34422
|
+
* `data-navi-popup-inside` is the other direction: a box of the PAGE the
|
|
34423
|
+
* caller says belongs to a popup, so a press on it is not outside that popup
|
|
34424
|
+
* even though it lies beyond the border box — a card whose press is what
|
|
34425
|
+
* fills the one panel of a board. Its value names the popup by `id`, the way
|
|
34426
|
+
* `commandfor` does (several ids, space separated), because the exemption is
|
|
34427
|
+
* one popup's: a menu open beside the board still closes on the same press.
|
|
34428
|
+
*
|
|
34429
|
+
* Unlike the outside marker it answers for its whole subtree: the outside
|
|
34430
|
+
* marker carves free space out of a surface whose controls stay surface, this
|
|
34431
|
+
* one adds a whole thing — the card, its count, its buttons — to the popup's
|
|
34432
|
+
* own ground, and nothing in it is a dismissal.
|
|
34433
|
+
*/
|
|
34434
|
+
const INSIDE_ATTRIBUTE = "data-navi-popup-inside";
|
|
34435
|
+
|
|
34382
34436
|
/**
|
|
34383
34437
|
* What a press landing on a region the caller declared as not-its-surface
|
|
34384
34438
|
* does: exactly what the same press on the backdrop would do.
|
|
@@ -34503,6 +34557,14 @@ const armOutsidePressClose = (
|
|
|
34503
34557
|
) {
|
|
34504
34558
|
return;
|
|
34505
34559
|
}
|
|
34560
|
+
if (
|
|
34561
|
+
popupEl.id &&
|
|
34562
|
+
pointerDownEvent.target.closest?.(
|
|
34563
|
+
`[${INSIDE_ATTRIBUTE}~="${CSS.escape(popupEl.id)}"]`,
|
|
34564
|
+
)
|
|
34565
|
+
) {
|
|
34566
|
+
return;
|
|
34567
|
+
}
|
|
34506
34568
|
// Real DOM containment wins over the rectangle below — an element
|
|
34507
34569
|
// genuinely inside the popup (`overflow: visible`, a negative margin, an
|
|
34508
34570
|
// absolutely-positioned child) can be painted outside its border box, and
|
|
@@ -36486,6 +36548,46 @@ const acknowledgeOwnAction = (controller) => {
|
|
|
36486
36548
|
controller.acknowledgeUIState();
|
|
36487
36549
|
};
|
|
36488
36550
|
|
|
36551
|
+
// What a group of checkboxes or radios is worth is a claim about values, and
|
|
36552
|
+
// a value whose box is not there cannot be contradicted by that box: a row
|
|
36553
|
+
// scrolled out of a virtualized list, a filtered-out option, a whole page being
|
|
36554
|
+
// parked while a route action reruns — its children leave one at a time, and
|
|
36555
|
+
// each partial reading would otherwise be taken for the answer and written
|
|
36556
|
+
// into whatever the group is bound to (an array signal emptied down to `[]`
|
|
36557
|
+
// by its own unmount). `kept` is what the group holds as it is asked; a value
|
|
36558
|
+
// in it stays until a DRAWN child carrying that value says otherwise.
|
|
36559
|
+
//
|
|
36560
|
+
// Read off the group rather than remembered here on purpose: a value put ON
|
|
36561
|
+
// the group (a `value` prop, a signal, a reopened popup) replaces the whole
|
|
36562
|
+
// selection, and a private memory would go on holding the rows it could not
|
|
36563
|
+
// see being unselected.
|
|
36564
|
+
const keptAndCheckedChildUIStates = (children, fallbackState, kept) => {
|
|
36565
|
+
const drawnValues = new Set(children.map((child) => child.props.value));
|
|
36566
|
+
const values = Array.isArray(kept)
|
|
36567
|
+
? kept.filter((value) => !drawnValues.has(value))
|
|
36568
|
+
: [];
|
|
36569
|
+
for (const child of children) {
|
|
36570
|
+
if (child.uiState !== undefined) {
|
|
36571
|
+
values.push(child.uiState);
|
|
36572
|
+
}
|
|
36573
|
+
}
|
|
36574
|
+
return values.length === 0 ? undefined : values;
|
|
36575
|
+
};
|
|
36576
|
+
const keptOrFirstDefinedChildUIState = (children, fallbackState, kept) => {
|
|
36577
|
+
for (const child of children) {
|
|
36578
|
+
if (child.uiState !== undefined) {
|
|
36579
|
+
return child.uiState;
|
|
36580
|
+
}
|
|
36581
|
+
}
|
|
36582
|
+
// No drawn child claims it. If the child that held it IS drawn, it was really
|
|
36583
|
+
// deselected; if it is not, the group keeps what it holds.
|
|
36584
|
+
const keptIsDrawn = children.some((child) => child.props.value === kept);
|
|
36585
|
+
if (keptIsDrawn) {
|
|
36586
|
+
return undefined;
|
|
36587
|
+
}
|
|
36588
|
+
return kept;
|
|
36589
|
+
};
|
|
36590
|
+
|
|
36489
36591
|
// Default aggregate/distribute implementations keyed by controlType or stateType.
|
|
36490
36592
|
// Looked up in useUIGroupStateController to fill in omitted aggregateChildStates /
|
|
36491
36593
|
// distributeChildUIState. If neither a default nor an explicit impl is found for a
|
|
@@ -36494,7 +36596,7 @@ const GROUP_DEFAULTS = {
|
|
|
36494
36596
|
radio_group: {
|
|
36495
36597
|
childControlFilter: (child) =>
|
|
36496
36598
|
child.controlType === "input" && child.controlHostProps?.type === "radio",
|
|
36497
|
-
aggregateChildStates:
|
|
36599
|
+
aggregateChildStates: keptOrFirstDefinedChildUIState,
|
|
36498
36600
|
distributeChildUIState: (newUIState, childUIStateController) => {
|
|
36499
36601
|
const childSelected = childUIStateController.props.value === newUIState;
|
|
36500
36602
|
if (childSelected) {
|
|
@@ -36507,16 +36609,7 @@ const GROUP_DEFAULTS = {
|
|
|
36507
36609
|
childControlFilter: (child) =>
|
|
36508
36610
|
child.controlType === "input" &&
|
|
36509
36611
|
child.controlHostProps?.type === "checkbox",
|
|
36510
|
-
aggregateChildStates:
|
|
36511
|
-
const values = [];
|
|
36512
|
-
for (const child of children) {
|
|
36513
|
-
const childUIState = child.uiState;
|
|
36514
|
-
if (childUIState !== undefined) {
|
|
36515
|
-
values.push(childUIState);
|
|
36516
|
-
}
|
|
36517
|
-
}
|
|
36518
|
-
return values.length === 0 ? undefined : values;
|
|
36519
|
-
},
|
|
36612
|
+
aggregateChildStates: keptAndCheckedChildUIStates,
|
|
36520
36613
|
distributeChildUIState: (newUIState, childUIStateController) => {
|
|
36521
36614
|
const childSelected =
|
|
36522
36615
|
Array.isArray(newUIState) &&
|
|
@@ -47740,33 +47833,6 @@ const createToggleEvent = open => {
|
|
|
47740
47833
|
return toggleEvent;
|
|
47741
47834
|
};
|
|
47742
47835
|
|
|
47743
|
-
const NextResolverContext = createContext(null);
|
|
47744
|
-
const useNextResolver = () => useContext(NextResolverContext);
|
|
47745
|
-
|
|
47746
|
-
/**
|
|
47747
|
-
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
47748
|
-
* Each resolver is a Preact component rendered in sequence (hooks are allowed).
|
|
47749
|
-
* To pass through to the next resolver, call useNextResolver() and render the
|
|
47750
|
-
* returned Next component with the desired props.
|
|
47751
|
-
* To terminate the chain early (e.g. render a specialized component), render
|
|
47752
|
-
* directly without calling Next.
|
|
47753
|
-
*
|
|
47754
|
-
* The last entry in the array is the final/target component — it receives null
|
|
47755
|
-
* from useNextResolver() indicating it is terminal.
|
|
47756
|
-
*
|
|
47757
|
-
* Usage:
|
|
47758
|
-
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
47759
|
-
* // Then inside a component render:
|
|
47760
|
-
* renderButton(props)
|
|
47761
|
-
*
|
|
47762
|
-
* Each position of the chain has a runner of its own, defined once: it renders
|
|
47763
|
-
* its resolver under a NextResolverContext holding the runner after it. A
|
|
47764
|
-
* resolver that re-renders on its own and renders <Next> therefore resumes at
|
|
47765
|
-
* its own position without any bookkeeping — the Next it was given is the one
|
|
47766
|
-
* made for it. A chain rendered by the hundred (a list's rows) pays two
|
|
47767
|
-
* components per position, the runner and the resolver, so nothing here is a
|
|
47768
|
-
* component that merely forwards.
|
|
47769
|
-
*/
|
|
47770
47836
|
const createComponentResolver = (resolvers, {
|
|
47771
47837
|
pure
|
|
47772
47838
|
} = {}) => {
|
|
@@ -47775,13 +47841,10 @@ const createComponentResolver = (resolvers, {
|
|
|
47775
47841
|
for (let index = 0; index < resolvers.length; index++) {
|
|
47776
47842
|
const Resolver = resolvers[index];
|
|
47777
47843
|
const isLast = index === lastIndex;
|
|
47778
|
-
|
|
47779
|
-
|
|
47780
|
-
|
|
47781
|
-
|
|
47782
|
-
})
|
|
47783
|
-
});
|
|
47784
|
-
Runner.displayName = `${Resolver.displayName || Resolver.name}Runner`;
|
|
47844
|
+
function Runner(props) {
|
|
47845
|
+
return runResolverBody(this, Resolver, props, isLast ? null : runners[index + 1]);
|
|
47846
|
+
}
|
|
47847
|
+
Runner.displayName = Resolver.displayName || Resolver.name;
|
|
47785
47848
|
runners.push(Runner);
|
|
47786
47849
|
}
|
|
47787
47850
|
const FirstRunner = runners[0];
|
|
@@ -47812,6 +47875,54 @@ const createComponentResolver = (resolvers, {
|
|
|
47812
47875
|
}
|
|
47813
47876
|
return PureRenderComponent;
|
|
47814
47877
|
};
|
|
47878
|
+
const useNextResolver = () => {
|
|
47879
|
+
if (currentNext === OUTSIDE_RESOLVER_BODY) {
|
|
47880
|
+
throw new Error("useNextResolver() called outside a resolver body: a resolver rendered by another resolver is rendered with renderResolver(Resolver, props), not as a JSX child.");
|
|
47881
|
+
}
|
|
47882
|
+
return currentNext;
|
|
47883
|
+
};
|
|
47884
|
+
const renderResolver = (Resolver, props) => {
|
|
47885
|
+
if (currentNext === OUTSIDE_RESOLVER_BODY) {
|
|
47886
|
+
throw new Error(`renderResolver(${Resolver.displayName || Resolver.name}) called outside a resolver body: only a resolver can render another one.`);
|
|
47887
|
+
}
|
|
47888
|
+
let SubRunner = subRunnerByResolver.get(Resolver);
|
|
47889
|
+
if (!SubRunner) {
|
|
47890
|
+
SubRunner = function (props) {
|
|
47891
|
+
const {
|
|
47892
|
+
[NEXT_PROP]: next,
|
|
47893
|
+
...ownProps
|
|
47894
|
+
} = props;
|
|
47895
|
+
return runResolverBody(this, Resolver, ownProps, next);
|
|
47896
|
+
};
|
|
47897
|
+
SubRunner.displayName = Resolver.displayName || Resolver.name;
|
|
47898
|
+
subRunnerByResolver.set(Resolver, SubRunner);
|
|
47899
|
+
}
|
|
47900
|
+
return createElement(SubRunner, {
|
|
47901
|
+
...props,
|
|
47902
|
+
[NEXT_PROP]: currentNext
|
|
47903
|
+
});
|
|
47904
|
+
};
|
|
47905
|
+
|
|
47906
|
+
// What useNextResolver() answers: the runner after the one whose body is
|
|
47907
|
+
// running, null for the last — and, outside any body, a value that is neither.
|
|
47908
|
+
const OUTSIDE_RESOLVER_BODY = Symbol("outside_resolver_body");
|
|
47909
|
+
let currentNext = OUTSIDE_RESOLVER_BODY;
|
|
47910
|
+
const runResolverBody = (instance, Resolver, props, next) => {
|
|
47911
|
+
const nextBefore = currentNext;
|
|
47912
|
+
currentNext = next;
|
|
47913
|
+
try {
|
|
47914
|
+
return Resolver.call(instance, props);
|
|
47915
|
+
} finally {
|
|
47916
|
+
currentNext = nextBefore;
|
|
47917
|
+
}
|
|
47918
|
+
};
|
|
47919
|
+
|
|
47920
|
+
// One runner per resolver rendered through renderResolver, made on first use:
|
|
47921
|
+
// the same component type on every render, so a resolver picked again is
|
|
47922
|
+
// updated in place and another one picked is a remount, like any child.
|
|
47923
|
+
const subRunnerByResolver = new WeakMap();
|
|
47924
|
+
// Rides on the props of a sub-runner, stripped before the body sees them.
|
|
47925
|
+
const NEXT_PROP = "navi-resolver-next";
|
|
47815
47926
|
function pureShouldComponentUpdate(nextProps) {
|
|
47816
47927
|
return shallowDiffers(this.props, nextProps);
|
|
47817
47928
|
}
|
|
@@ -48637,9 +48748,7 @@ const ButtonConfirm = ({
|
|
|
48637
48748
|
const ButtonRouteResolver = props => {
|
|
48638
48749
|
const Next = useNextResolver();
|
|
48639
48750
|
if (props.route) {
|
|
48640
|
-
return
|
|
48641
|
-
...props
|
|
48642
|
-
});
|
|
48751
|
+
return renderResolver(ButtonWithRoute, props);
|
|
48643
48752
|
}
|
|
48644
48753
|
return jsx(Next, {
|
|
48645
48754
|
...props
|
|
@@ -51237,9 +51346,7 @@ const installInputCss = () => {
|
|
|
51237
51346
|
const InputModeResolver = props => {
|
|
51238
51347
|
const Next = useNextResolver();
|
|
51239
51348
|
if (props.inputMode === "numeric" || props.inputMode === "decimal") {
|
|
51240
|
-
return
|
|
51241
|
-
...props
|
|
51242
|
-
});
|
|
51349
|
+
return renderResolver(InputModeNumericOrDecimal, props);
|
|
51243
51350
|
}
|
|
51244
51351
|
return jsx(Next, {
|
|
51245
51352
|
...props
|
|
@@ -51467,39 +51574,25 @@ const InputTypeResolver = props => {
|
|
|
51467
51574
|
// very same "icon while empty, clear button once filled" affordance without
|
|
51468
51575
|
// pretending to be a search box.
|
|
51469
51576
|
if (props.clearable && props.type !== "search") {
|
|
51470
|
-
return
|
|
51471
|
-
...props
|
|
51472
|
-
});
|
|
51577
|
+
return renderResolver(InputClearable, props);
|
|
51473
51578
|
}
|
|
51474
51579
|
if (props.type === "search") {
|
|
51475
|
-
return
|
|
51476
|
-
...props
|
|
51477
|
-
});
|
|
51580
|
+
return renderResolver(InputSearch, props);
|
|
51478
51581
|
}
|
|
51479
51582
|
if (props.type === "email") {
|
|
51480
|
-
return
|
|
51481
|
-
...props
|
|
51482
|
-
});
|
|
51583
|
+
return renderResolver(InputEmail, props);
|
|
51483
51584
|
}
|
|
51484
51585
|
if (props.type === "tel") {
|
|
51485
|
-
return
|
|
51486
|
-
...props
|
|
51487
|
-
});
|
|
51586
|
+
return renderResolver(InputTel, props);
|
|
51488
51587
|
}
|
|
51489
51588
|
if (props.type === "number") {
|
|
51490
|
-
return
|
|
51491
|
-
...props
|
|
51492
|
-
});
|
|
51589
|
+
return renderResolver(InputNumber, props);
|
|
51493
51590
|
}
|
|
51494
51591
|
if (props.type === "color") {
|
|
51495
|
-
return
|
|
51496
|
-
...props
|
|
51497
|
-
});
|
|
51592
|
+
return renderResolver(InputColor, props);
|
|
51498
51593
|
}
|
|
51499
51594
|
if (props.type === "datetime-local") {
|
|
51500
|
-
return
|
|
51501
|
-
...props
|
|
51502
|
-
});
|
|
51595
|
+
return renderResolver(InputDatetimeLocal, props);
|
|
51503
51596
|
}
|
|
51504
51597
|
return jsx(Next, {
|
|
51505
51598
|
...props
|
|
@@ -51612,9 +51705,7 @@ const InputDatetimeLocal = props => {
|
|
|
51612
51705
|
const InputWithListResolver = props => {
|
|
51613
51706
|
const Next = useNextResolver();
|
|
51614
51707
|
if (props["navi-list"]) {
|
|
51615
|
-
return
|
|
51616
|
-
...props
|
|
51617
|
-
});
|
|
51708
|
+
return renderResolver(InputWithList, props);
|
|
51618
51709
|
}
|
|
51619
51710
|
return jsx(Next, {
|
|
51620
51711
|
...props
|
|
@@ -51721,9 +51812,7 @@ const ChevronDownSvg$1 = () => {
|
|
|
51721
51812
|
const InputWithSuggestionsResolver = props => {
|
|
51722
51813
|
const Next = useNextResolver();
|
|
51723
51814
|
if (props["navi-suggestions"]) {
|
|
51724
|
-
return
|
|
51725
|
-
...props
|
|
51726
|
-
});
|
|
51815
|
+
return renderResolver(InputTextualWithSuggestions, props);
|
|
51727
51816
|
}
|
|
51728
51817
|
return jsx(Next, {
|
|
51729
51818
|
...props
|
|
@@ -59418,14 +59507,15 @@ const css$E = /* css */`
|
|
|
59418
59507
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
59419
59508
|
* that element, which outranks whatever the dialog says).
|
|
59420
59509
|
* @param {boolean} [props.open] - Controlled open state.
|
|
59421
|
-
* @param {import("@preact/signals").Signal<
|
|
59510
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] - The open
|
|
59422
59511
|
* state said the way every navi control says it: the dialog opens and closes
|
|
59423
59512
|
* to match the signal, and writes into it whenever it opens or closes on its
|
|
59424
59513
|
* own (Escape, backdrop, a --navi-close command) — one binding to both drive
|
|
59425
59514
|
* the dialog and know where it is, and the state stays where the app put it.
|
|
59426
59515
|
* Excludes `open`; `onOpen`/`onClose` still fire. A signal holding `true` at
|
|
59427
59516
|
* mount behaves like `defaultOpen`: the dialog was already open, no entrance
|
|
59428
|
-
* plays.
|
|
59517
|
+
* plays. A signal holding an id (one dialog showing whichever card
|
|
59518
|
+
* the address names) reads as open, and the dialog leaves the id alone.
|
|
59429
59519
|
* @param {any} [props.value] - What `signal` holds while THIS dialog is
|
|
59430
59520
|
* open, for several of them sharing one signal that says which is open
|
|
59431
59521
|
* (`?seat=<gameId>` over a list of cards): open while `signal.value` is this
|
|
@@ -61033,14 +61123,15 @@ const css$D = /* css */`
|
|
|
61033
61123
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
61034
61124
|
* that element, which outranks whatever the popover says).
|
|
61035
61125
|
* @param {boolean} [props.open] - Controlled open state.
|
|
61036
|
-
* @param {import("@preact/signals").Signal<
|
|
61126
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] - The open
|
|
61037
61127
|
* state said the way every navi control says it: the popover opens and closes
|
|
61038
61128
|
* to match the signal, and writes into it whenever it opens or closes on its
|
|
61039
61129
|
* own (Escape, light dismiss, a --navi-close command) — one binding to both
|
|
61040
61130
|
* drive the popover and know where it is, and the state stays where the app
|
|
61041
61131
|
* put it. Excludes `open`; `onOpen`/`onClose` still fire. A signal holding
|
|
61042
61132
|
* `true` at mount behaves like `defaultOpen`: the popover was already open,
|
|
61043
|
-
* no entrance plays.
|
|
61133
|
+
* no entrance plays. A signal holding an id (one popover showing whichever card
|
|
61134
|
+
* the address names) reads as open, and the popover leaves the id alone.
|
|
61044
61135
|
* @param {any} [props.value] - What `signal` holds while THIS popover is
|
|
61045
61136
|
* open, for several of them sharing one signal that says which is open
|
|
61046
61137
|
* (`?seat=<gameId>` over a list of cards): open while `signal.value` is this
|
|
@@ -62473,9 +62564,7 @@ const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
|
62473
62564
|
const PickerCustomResolver = props => {
|
|
62474
62565
|
import.meta.css = [css$B, "@jsenv/navi/src/control/picker/picker_custom.jsx"];
|
|
62475
62566
|
if (props.children === undefined) {
|
|
62476
|
-
return
|
|
62477
|
-
...props
|
|
62478
|
-
});
|
|
62567
|
+
return renderResolver(PickerNative, props);
|
|
62479
62568
|
}
|
|
62480
62569
|
if (props.mode === "callout") {
|
|
62481
62570
|
// A tooltip is an icon one presses, unless told otherwise. Own-property
|
|
@@ -62537,14 +62626,12 @@ const PickerCustomResolver = props => {
|
|
|
62537
62626
|
// how a field says its value is a JS one, kept beside the DOM (see
|
|
62538
62627
|
// controller_registry.js) — the same thing type="array"/"object" already
|
|
62539
62628
|
// say for their shapes.
|
|
62540
|
-
return
|
|
62629
|
+
return renderResolver(PickerCustom, {
|
|
62541
62630
|
...props,
|
|
62542
62631
|
type: "navi_js"
|
|
62543
62632
|
});
|
|
62544
62633
|
}
|
|
62545
|
-
return
|
|
62546
|
-
...props
|
|
62547
|
-
});
|
|
62634
|
+
return renderResolver(PickerCustom, props);
|
|
62548
62635
|
};
|
|
62549
62636
|
const PickerNative = props => {
|
|
62550
62637
|
const Next = useNextResolver();
|
|
@@ -63156,9 +63243,9 @@ const PickerCustom = props => {
|
|
|
63156
63243
|
});
|
|
63157
63244
|
}
|
|
63158
63245
|
}
|
|
63159
|
-
return
|
|
63246
|
+
return renderResolver(PickerContentInsidePopup, {
|
|
63160
63247
|
...pickerProps,
|
|
63161
|
-
mode
|
|
63248
|
+
mode
|
|
63162
63249
|
});
|
|
63163
63250
|
};
|
|
63164
63251
|
|
|
@@ -64425,14 +64512,10 @@ const createItemTracker = (onChange) => {
|
|
|
64425
64512
|
const ListItemHeaderOrFooterResolver = props => {
|
|
64426
64513
|
const Next = useNextResolver();
|
|
64427
64514
|
if (props.header) {
|
|
64428
|
-
return
|
|
64429
|
-
...props
|
|
64430
|
-
});
|
|
64515
|
+
return renderResolver(ListItemHeader, props);
|
|
64431
64516
|
}
|
|
64432
64517
|
if (props.footer) {
|
|
64433
|
-
return
|
|
64434
|
-
...props
|
|
64435
|
-
});
|
|
64518
|
+
return renderResolver(ListItemFooter, props);
|
|
64436
64519
|
}
|
|
64437
64520
|
return jsx(Next, {
|
|
64438
64521
|
...props
|
|
@@ -64664,9 +64747,7 @@ const ListSelectableContext = createContext(false);
|
|
|
64664
64747
|
const ListSelectableResolver = props => {
|
|
64665
64748
|
const Next = useNextResolver();
|
|
64666
64749
|
if (props.selectable) {
|
|
64667
|
-
return
|
|
64668
|
-
...props
|
|
64669
|
-
});
|
|
64750
|
+
return renderResolver(ListSelectable, props);
|
|
64670
64751
|
}
|
|
64671
64752
|
return jsx(ListSelectableContext.Provider, {
|
|
64672
64753
|
value: false,
|
|
@@ -64688,45 +64769,6 @@ const ListSelectable = props => {
|
|
|
64688
64769
|
focusGroupDirection,
|
|
64689
64770
|
focusGroupWrap
|
|
64690
64771
|
} = props;
|
|
64691
|
-
// `kept` is what the list holds as it is asked, which is not the same as what
|
|
64692
|
-
// its rows say: a list draws the rows it needs and no more, so the selected
|
|
64693
|
-
// one may be scrolled out of the window or filtered out of the view, and a
|
|
64694
|
-
// row that is not there cannot say it is not selected. Reading it off the
|
|
64695
|
-
// group rather than remembering it here is what makes a value put ON the list
|
|
64696
|
-
// (a `value` prop, a signal, a reopened popup) replace the whole selection —
|
|
64697
|
-
// a private memory of its own would go on holding the rows it could not see
|
|
64698
|
-
// being unselected.
|
|
64699
|
-
//
|
|
64700
|
-
// `fallbackState` is the empty of the shape the list declared below
|
|
64701
|
-
// (`stateType`): `[]` for a multiple list, nothing for a single one. Taking
|
|
64702
|
-
// it is what lets an emptied list say "empty" — `undefined` is the word for
|
|
64703
|
-
// "unset", and a bound stateSignal reads that as "nothing decided here, go
|
|
64704
|
-
// back to the default" (see docs/control_value.md), which is how a list
|
|
64705
|
-
// emptied down to its last row puts that row back on reload.
|
|
64706
|
-
const aggregateChildStates = (children, fallbackState, kept) => {
|
|
64707
|
-
if (multiple) {
|
|
64708
|
-
const drawnValues = new Set(children.map(child => child.props.value));
|
|
64709
|
-
const stillSelected = Array.isArray(kept) ? kept.filter(value => !drawnValues.has(value)) : [];
|
|
64710
|
-
for (const child of children) {
|
|
64711
|
-
if (child.uiState !== undefined) {
|
|
64712
|
-
stillSelected.push(child.uiState);
|
|
64713
|
-
}
|
|
64714
|
-
}
|
|
64715
|
-
return stillSelected.length === 0 ? fallbackState : stillSelected;
|
|
64716
|
-
}
|
|
64717
|
-
for (const child of children) {
|
|
64718
|
-
if (child.uiState !== undefined) {
|
|
64719
|
-
return child.uiState;
|
|
64720
|
-
}
|
|
64721
|
-
}
|
|
64722
|
-
// No drawn row claims it. If the row that held it IS drawn, it was really
|
|
64723
|
-
// deselected; if it is not, the list keeps what it holds.
|
|
64724
|
-
const keptIsDrawn = children.some(child => child.props.value === kept);
|
|
64725
|
-
if (keptIsDrawn) {
|
|
64726
|
-
return undefined;
|
|
64727
|
-
}
|
|
64728
|
-
return kept;
|
|
64729
|
-
};
|
|
64730
64772
|
const [listControlRootProps, listControlProps, childrenWrapperProps] = useControlgroupProps({
|
|
64731
64773
|
// The defaults of the group this IS (see CheckboxGroup / RadioGroup):
|
|
64732
64774
|
// what a selectable list shows is a claim about what was accepted, so
|
|
@@ -64738,9 +64780,15 @@ const ListSelectable = props => {
|
|
|
64738
64780
|
resetOnError: true,
|
|
64739
64781
|
...props
|
|
64740
64782
|
}, {
|
|
64783
|
+
// A list draws the rows it needs and no more, so a selected row may be
|
|
64784
|
+
// scrolled out of the window or filtered out of the view: the default
|
|
64785
|
+
// aggregate of these two groups keeps a value whose row is not drawn
|
|
64786
|
+
// (see GROUP_DEFAULTS in ui_state_controller.js). `stateType` is what
|
|
64787
|
+
// an emptied multiple list says — `[]`, not `undefined`, which a bound
|
|
64788
|
+
// stateSignal reads as "nothing decided here, go back to the default"
|
|
64789
|
+
// (see docs/control_value.md).
|
|
64741
64790
|
stateType: multiple ? "array" : "",
|
|
64742
|
-
controlType: multiple ? "checkbox_group" : "radio_group"
|
|
64743
|
-
aggregateChildStates
|
|
64791
|
+
controlType: multiple ? "checkbox_group" : "radio_group"
|
|
64744
64792
|
});
|
|
64745
64793
|
const uiGroupStateController = getUIStateControllerById(listControlProps.id);
|
|
64746
64794
|
useFocusGroup(ref, {
|
|
@@ -64999,7 +65047,7 @@ const ListItemSelectableResolver = props => {
|
|
|
64999
65047
|
const isHeaderOrFooter = Boolean(props.header || props.footer);
|
|
65000
65048
|
const selectable = props.selectable ?? (isHeaderOrFooter || props.role === "presentation" ? false : listSelectable);
|
|
65001
65049
|
if (selectable) {
|
|
65002
|
-
return
|
|
65050
|
+
return renderResolver(ListItemSelectable, {
|
|
65003
65051
|
...props,
|
|
65004
65052
|
selectable: true
|
|
65005
65053
|
});
|
|
@@ -65942,6 +65990,7 @@ const ListUI = props => {
|
|
|
65942
65990
|
ref,
|
|
65943
65991
|
tracker,
|
|
65944
65992
|
renderBudget,
|
|
65993
|
+
renderBudgetSteady: renderBudgetAfterPaint,
|
|
65945
65994
|
virtualItemSize,
|
|
65946
65995
|
virtual,
|
|
65947
65996
|
scrolled,
|
|
@@ -66269,6 +66318,7 @@ const useListScrollSync = ({
|
|
|
66269
66318
|
ref,
|
|
66270
66319
|
tracker,
|
|
66271
66320
|
renderBudget,
|
|
66321
|
+
renderBudgetSteady,
|
|
66272
66322
|
virtualItemSize,
|
|
66273
66323
|
virtual,
|
|
66274
66324
|
scrolled,
|
|
@@ -67019,31 +67069,36 @@ const useListScrollSync = ({
|
|
|
67019
67069
|
}
|
|
67020
67070
|
});
|
|
67021
67071
|
|
|
67022
|
-
//
|
|
67023
|
-
|
|
67024
|
-
|
|
67025
|
-
|
|
67026
|
-
|
|
67072
|
+
// The window the visible band asks for, decided from geometry: the band's
|
|
67073
|
+
// two edges in row units, from where the scroller's viewport cuts the list
|
|
67074
|
+
// and the row size. What has to be drawn is everything between the edges,
|
|
67075
|
+
// plus what the scroll is about to bring — so the window is judged on the
|
|
67076
|
+
// rows it keeps AHEAD of the band, in the direction the user goes, and moves
|
|
67077
|
+
// once those fall under half a screen. Re-framed, it puts three quarters of
|
|
67078
|
+
// its spare rows ahead and one quarter behind: a window centred on the band
|
|
67079
|
+
// is due to move again as soon as the band has crossed the few rows it kept
|
|
67080
|
+
// ahead, and each move is rows drawn while the user waits for them.
|
|
67081
|
+
// Without a row size there is no geometry to reason on; the row under a
|
|
67082
|
+
// probe stands in for the band, framed the old way.
|
|
67083
|
+
const scrollDirectionRef = useRef(1);
|
|
67084
|
+
const windowSlidRef = useRef(false);
|
|
67085
|
+
useRef(false);
|
|
67086
|
+
const evaluateWindow = reason => {
|
|
67087
|
+
const total = virtual.totalSignal.peek();
|
|
67088
|
+
if (total <= renderBudget) {
|
|
67089
|
+
return;
|
|
67027
67090
|
}
|
|
67028
67091
|
const scrollerEl = getScroller();
|
|
67029
67092
|
const listEl = getListEl();
|
|
67030
|
-
|
|
67031
|
-
|
|
67032
|
-
|
|
67033
|
-
|
|
67034
|
-
|
|
67035
|
-
|
|
67036
|
-
|
|
67037
|
-
|
|
67038
|
-
|
|
67039
|
-
reportPosition();
|
|
67040
|
-
return;
|
|
67041
|
-
}
|
|
67042
|
-
reportPosition();
|
|
67043
|
-
const total = virtual.totalSignal.peek();
|
|
67044
|
-
if (total <= renderBudget) {
|
|
67045
|
-
return;
|
|
67046
|
-
}
|
|
67093
|
+
if (!scrollerEl || !listEl) {
|
|
67094
|
+
return;
|
|
67095
|
+
}
|
|
67096
|
+
const {
|
|
67097
|
+
start,
|
|
67098
|
+
end
|
|
67099
|
+
} = renderWindowRef.current;
|
|
67100
|
+
const virtualItemSize = virtualItemSizeSignal.peek();
|
|
67101
|
+
if (virtualItemSize === 0) {
|
|
67047
67102
|
const scrollInfo = getScrollInfo({
|
|
67048
67103
|
scrollValues: {
|
|
67049
67104
|
left: scrollerEl.scrollLeft,
|
|
@@ -67060,17 +67115,8 @@ const useListScrollSync = ({
|
|
|
67060
67115
|
return;
|
|
67061
67116
|
}
|
|
67062
67117
|
const {
|
|
67063
|
-
index
|
|
67064
|
-
reason
|
|
67118
|
+
index
|
|
67065
67119
|
} = scrollInfo;
|
|
67066
|
-
// Recentering on every row crossed would rebuild the whole window a few
|
|
67067
|
-
// times a second, and a window rebuilt is every row of it rendered
|
|
67068
|
-
// again. It only moves once what is on screen comes near one of its
|
|
67069
|
-
// edges — until then, it already holds what has to be drawn.
|
|
67070
|
-
const {
|
|
67071
|
-
start,
|
|
67072
|
-
end
|
|
67073
|
-
} = renderWindowRef.current;
|
|
67074
67120
|
const margin = Math.floor(renderBudget / 4);
|
|
67075
67121
|
const farFromStart = index - start >= margin || start === 0;
|
|
67076
67122
|
const farFromEnd = end - index > margin || end === total;
|
|
@@ -67078,12 +67124,113 @@ const useListScrollSync = ({
|
|
|
67078
67124
|
return;
|
|
67079
67125
|
}
|
|
67080
67126
|
const half = Math.floor(renderBudget / 2);
|
|
67081
|
-
let newStart =
|
|
67082
|
-
let newEnd =
|
|
67083
|
-
if (newEnd
|
|
67084
|
-
|
|
67127
|
+
let newStart = index - half < 0 ? 0 : index - half;
|
|
67128
|
+
let newEnd = newStart + renderBudget;
|
|
67129
|
+
if (newEnd > total) {
|
|
67130
|
+
newEnd = total;
|
|
67131
|
+
newStart = total - renderBudget < 0 ? 0 : total - renderBudget;
|
|
67132
|
+
}
|
|
67133
|
+
windowSlidRef.current = true;
|
|
67134
|
+
updateRenderWindow(newStart, newEnd, `${reason}: ${scrollInfo.reason}`);
|
|
67135
|
+
return;
|
|
67136
|
+
}
|
|
67137
|
+
const viewportRect = getScrollerViewportRect(scrollerEl);
|
|
67138
|
+
const listRect = listEl.getBoundingClientRect();
|
|
67139
|
+
const viewportFrom = horizontal ? viewportRect.left : viewportRect.top;
|
|
67140
|
+
const viewportTo = horizontal ? viewportRect.right : viewportRect.bottom;
|
|
67141
|
+
const listFrom = horizontal ? listRect.left : listRect.top;
|
|
67142
|
+
let bandStart = Math.floor((viewportFrom - listFrom) / virtualItemSize);
|
|
67143
|
+
// Exclusive, like the window's end.
|
|
67144
|
+
let bandEnd = Math.ceil((viewportTo - listFrom) / virtualItemSize);
|
|
67145
|
+
if (bandStart < 0) {
|
|
67146
|
+
bandStart = 0;
|
|
67147
|
+
}
|
|
67148
|
+
if (bandEnd > total) {
|
|
67149
|
+
bandEnd = total;
|
|
67150
|
+
}
|
|
67151
|
+
if (bandEnd <= bandStart) {
|
|
67152
|
+
return;
|
|
67153
|
+
}
|
|
67154
|
+
const visibleCount = bandEnd - bandStart;
|
|
67155
|
+
const spare = renderBudget - visibleCount;
|
|
67156
|
+
const behind = spare > 0 ? Math.floor(spare / 4) : 0;
|
|
67157
|
+
const ahead = spare > 0 ? spare - behind : 0;
|
|
67158
|
+
const halfScreen = Math.ceil(visibleCount / 2);
|
|
67159
|
+
const aheadNeeded = halfScreen < ahead / 2 ? halfScreen : Math.floor(ahead / 2);
|
|
67160
|
+
const behindNeeded = halfScreen < behind / 2 ? halfScreen : Math.floor(behind / 2);
|
|
67161
|
+
const forward = scrollDirectionRef.current > 0;
|
|
67162
|
+
const rowsAheadOfBand = forward ? end - bandEnd : bandStart - start;
|
|
67163
|
+
const rowsBehindBand = forward ? bandStart - start : end - bandEnd;
|
|
67164
|
+
const atEdgeAhead = forward ? end === total : start === 0;
|
|
67165
|
+
const atEdgeBehind = forward ? start === 0 : end === total;
|
|
67166
|
+
const coversBand = start <= bandStart && end >= bandEnd;
|
|
67167
|
+
const coversAhead = atEdgeAhead || rowsAheadOfBand >= aheadNeeded;
|
|
67168
|
+
const coversBehind = atEdgeBehind || rowsBehindBand >= behindNeeded;
|
|
67169
|
+
if (coversBand && coversAhead && coversBehind) {
|
|
67170
|
+
return;
|
|
67171
|
+
}
|
|
67172
|
+
let newStart = forward ? bandStart - behind : bandEnd + behind - renderBudget;
|
|
67173
|
+
if (newStart < 0) {
|
|
67174
|
+
newStart = 0;
|
|
67175
|
+
}
|
|
67176
|
+
let newEnd = newStart + renderBudget;
|
|
67177
|
+
if (newEnd > total) {
|
|
67178
|
+
newEnd = total;
|
|
67179
|
+
newStart = total - renderBudget < 0 ? 0 : total - renderBudget;
|
|
67180
|
+
}
|
|
67181
|
+
windowSlidRef.current = true;
|
|
67182
|
+
updateRenderWindow(newStart, newEnd);
|
|
67183
|
+
};
|
|
67184
|
+
// A slide is judged again once its rows are laid out: the scroll had moved
|
|
67185
|
+
// on while the rows were being drawn, or the anchoring of this very commit
|
|
67186
|
+
// moved it, and a window that stops short of the screen's edge with no
|
|
67187
|
+
// scroll event to come would stay short for good. On the next frame, after
|
|
67188
|
+
// the layout this commit's anchoring settles on.
|
|
67189
|
+
useLayoutEffect(() => {
|
|
67190
|
+
if (!windowSlidRef.current) {
|
|
67191
|
+
return undefined;
|
|
67192
|
+
}
|
|
67193
|
+
windowSlidRef.current = false;
|
|
67194
|
+
const frameId = requestAnimationFrame(() => {
|
|
67195
|
+
evaluateWindow("after slide");
|
|
67196
|
+
});
|
|
67197
|
+
return () => {
|
|
67198
|
+
cancelAnimationFrame(frameId);
|
|
67199
|
+
};
|
|
67200
|
+
}, [renderWindow]);
|
|
67201
|
+
|
|
67202
|
+
// Scroll listener — slides the window as the user scrolls.
|
|
67203
|
+
useLayoutEffect(() => {
|
|
67204
|
+
const listContainerEl = ref.current;
|
|
67205
|
+
if (!listContainerEl) {
|
|
67206
|
+
return undefined;
|
|
67207
|
+
}
|
|
67208
|
+
const scrollerEl = getScroller();
|
|
67209
|
+
const onScroll = () => {
|
|
67210
|
+
// Which way the user is going, read before the position is stored: what
|
|
67211
|
+
// the window keeps ahead is ahead of this.
|
|
67212
|
+
const previousScroll = currentScrollRef.current;
|
|
67213
|
+
if (previousScroll) {
|
|
67214
|
+
const delta = horizontal ? scrollerEl.scrollLeft - previousScroll.left : scrollerEl.scrollTop - previousScroll.top;
|
|
67215
|
+
if (delta > 0) {
|
|
67216
|
+
scrollDirectionRef.current = 1;
|
|
67217
|
+
} else if (delta < 0) {
|
|
67218
|
+
scrollDirectionRef.current = -1;
|
|
67219
|
+
}
|
|
67220
|
+
}
|
|
67221
|
+
updateCurrentScroll();
|
|
67222
|
+
// Where the user is now is where things must be held from now on.
|
|
67223
|
+
anchorRef.current = null;
|
|
67224
|
+
if (scrolledByListRef.current) {
|
|
67225
|
+
// The window stays where it is — the position it would be re-derived
|
|
67226
|
+
// from was chosen to keep the rows still — but where the list is has
|
|
67227
|
+
// genuinely changed, and whoever keeps that position must hear it.
|
|
67228
|
+
scrolledByListRef.current = false;
|
|
67229
|
+
reportPosition();
|
|
67230
|
+
return;
|
|
67085
67231
|
}
|
|
67086
|
-
|
|
67232
|
+
reportPosition();
|
|
67233
|
+
evaluateWindow("scroll");
|
|
67087
67234
|
};
|
|
67088
67235
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
67089
67236
|
// (document.scrollingElement); the document does.
|
|
@@ -70017,8 +70164,11 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
70017
70164
|
* window, which slides as the user scrolls while fillers hold the room of
|
|
70018
70165
|
* the rows outside it. Rows declared one by one as `<List.Item>` children
|
|
70019
70166
|
* are all drawn, whatever this says — a list with more than a few dozen rows
|
|
70020
|
-
* gives them to a run (see docs/scroll.md, "Many rows").
|
|
70021
|
-
*
|
|
70167
|
+
* gives them to a run (see docs/scroll.md, "Many rows"). The window keeps
|
|
70168
|
+
* most of its spare rows ahead of the scroll, and moves once those fall
|
|
70169
|
+
* under half a screen; it has to be larger than what the scroller shows at
|
|
70170
|
+
* once, with room for that lookahead — the list warns below 30, and when a
|
|
70171
|
+
* budget leaves fewer than two rows beyond the screen.
|
|
70022
70172
|
*
|
|
70023
70173
|
* `{ initial, after }` for a list drawn in the click that opens it (a popup):
|
|
70024
70174
|
* `initial` rows in the commit the browser paints first — about what a phone
|
|
@@ -70140,14 +70290,10 @@ const generateTimeSlots = (min, max, stepSeconds) => {
|
|
|
70140
70290
|
const PickerPresetResolver = props => {
|
|
70141
70291
|
const Next = useNextResolver();
|
|
70142
70292
|
if (props.type === "navi_time") {
|
|
70143
|
-
return
|
|
70144
|
-
...props
|
|
70145
|
-
});
|
|
70293
|
+
return renderResolver(PickerNaviTime, props);
|
|
70146
70294
|
}
|
|
70147
70295
|
if (props.type === "navi_minute") {
|
|
70148
|
-
return
|
|
70149
|
-
...props
|
|
70150
|
-
});
|
|
70296
|
+
return renderResolver(PickerNaviMinute, props);
|
|
70151
70297
|
}
|
|
70152
70298
|
return jsx(Next, {
|
|
70153
70299
|
...props
|
|
@@ -70945,59 +71091,37 @@ const PencilSvg = () => {
|
|
|
70945
71091
|
const PickerTypeResolver = props => {
|
|
70946
71092
|
const Next = useNextResolver();
|
|
70947
71093
|
if (props.type === "color") {
|
|
70948
|
-
return
|
|
70949
|
-
...props
|
|
70950
|
-
});
|
|
71094
|
+
return renderResolver(PickerColor, props);
|
|
70951
71095
|
}
|
|
70952
71096
|
if (props.type === "datetime") {
|
|
70953
|
-
return
|
|
70954
|
-
...props
|
|
70955
|
-
});
|
|
71097
|
+
return renderResolver(PickerDatetime, props);
|
|
70956
71098
|
}
|
|
70957
71099
|
if (props.type === "date") {
|
|
70958
|
-
return
|
|
70959
|
-
...props
|
|
70960
|
-
});
|
|
71100
|
+
return renderResolver(PickerDate, props);
|
|
70961
71101
|
}
|
|
70962
71102
|
if (props.type === "month") {
|
|
70963
|
-
return
|
|
70964
|
-
...props
|
|
70965
|
-
});
|
|
71103
|
+
return renderResolver(PickerMonth, props);
|
|
70966
71104
|
}
|
|
70967
71105
|
if (props.type === "week") {
|
|
70968
|
-
return
|
|
70969
|
-
...props
|
|
70970
|
-
});
|
|
71106
|
+
return renderResolver(PickerWeek, props);
|
|
70971
71107
|
}
|
|
70972
71108
|
if (props.type === "time") {
|
|
70973
|
-
return
|
|
70974
|
-
...props
|
|
70975
|
-
});
|
|
71109
|
+
return renderResolver(PickerTime, props);
|
|
70976
71110
|
}
|
|
70977
71111
|
if (props.type === "duration") {
|
|
70978
|
-
return
|
|
70979
|
-
...props
|
|
70980
|
-
});
|
|
71112
|
+
return renderResolver(PickerDuration, props);
|
|
70981
71113
|
}
|
|
70982
71114
|
if (props.type === "file") {
|
|
70983
|
-
return
|
|
70984
|
-
...props
|
|
70985
|
-
});
|
|
71115
|
+
return renderResolver(PickerFile, props);
|
|
70986
71116
|
}
|
|
70987
71117
|
if (props.type === "text") {
|
|
70988
|
-
return
|
|
70989
|
-
...props
|
|
70990
|
-
});
|
|
71118
|
+
return renderResolver(PickerText, props);
|
|
70991
71119
|
}
|
|
70992
71120
|
if (props.type === "array") {
|
|
70993
|
-
return
|
|
70994
|
-
...props
|
|
70995
|
-
});
|
|
71121
|
+
return renderResolver(PickerArray, props);
|
|
70996
71122
|
}
|
|
70997
71123
|
if (props.type === "object") {
|
|
70998
|
-
return
|
|
70999
|
-
...props
|
|
71000
|
-
});
|
|
71124
|
+
return renderResolver(PickerObject, props);
|
|
71001
71125
|
}
|
|
71002
71126
|
return jsx(Next, {
|
|
71003
71127
|
...props
|
|
@@ -84052,7 +84176,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
84052
84176
|
* @param {object} props
|
|
84053
84177
|
* @param {boolean} [props.open] - Controlled open state, forwarded as-is to
|
|
84054
84178
|
* `Popup`'s own `open`.
|
|
84055
|
-
* @param {import("@preact/signals").Signal<
|
|
84179
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] - The open
|
|
84056
84180
|
* state said the way every navi control says it: the panel opens and closes
|
|
84057
84181
|
* to match the signal, and writes into it whenever it opens or closes on its
|
|
84058
84182
|
* own (Escape, swipe, a --navi-close command) — one binding to both drive
|
|
@@ -84126,7 +84250,9 @@ const css = /* css */`.navi_side_panel {
|
|
|
84126
84250
|
* closes the panel on an outside click instead, and also enables trapping
|
|
84127
84251
|
* Tab navigation inside the panel (`focusCapture`) — closing on outside
|
|
84128
84252
|
* interaction only makes sense paired with not letting focus silently
|
|
84129
|
-
* leave the panel first.
|
|
84253
|
+
* leave the panel first. A box of the page whose press must not close the
|
|
84254
|
+
* panel (a card that fills it) names the panel:
|
|
84255
|
+
* `data-navi-popup-inside={id}` — see docs/popup_backdrop.md.
|
|
84130
84256
|
* @param {boolean} [props.swipeToClose=true] - Pushing the panel back
|
|
84131
84257
|
* towards the edge it is docked to closes it: the panel follows the
|
|
84132
84258
|
* pointer and finishes leaving (or comes back to rest) when it is
|
|
@@ -84261,6 +84387,7 @@ SidePanel.Foot = SidePanelFoot;
|
|
|
84261
84387
|
|
|
84262
84388
|
const createSlot = (SlotRenderer = Box) => {
|
|
84263
84389
|
const slotPropsSignal = signal();
|
|
84390
|
+
let filler = null;
|
|
84264
84391
|
const Slot = () => {
|
|
84265
84392
|
const props = slotPropsSignal.value;
|
|
84266
84393
|
return jsx(SlotRenderer, {
|
|
@@ -84269,9 +84396,15 @@ const createSlot = (SlotRenderer = Box) => {
|
|
|
84269
84396
|
});
|
|
84270
84397
|
};
|
|
84271
84398
|
const SlotFill = props => {
|
|
84399
|
+
const fillerRef = useRef();
|
|
84400
|
+
filler = fillerRef;
|
|
84272
84401
|
slotPropsSignal.value = props;
|
|
84273
84402
|
useLayoutEffect(() => {
|
|
84274
84403
|
return () => {
|
|
84404
|
+
if (filler !== fillerRef) {
|
|
84405
|
+
return;
|
|
84406
|
+
}
|
|
84407
|
+
filler = null;
|
|
84275
84408
|
slotPropsSignal.value = null;
|
|
84276
84409
|
};
|
|
84277
84410
|
}, []);
|