@jsenv/navi 0.29.477 → 0.29.479
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 +1810 -706
- package/dist/dev/jsenv_navi.js.map +40 -38
- package/dist/jsenv_navi.js +2239 -576
- package/dist/jsenv_navi.js.map +40 -38
- package/docs/AI_INSTRUCTIONS.md +3 -1
- package/docs/actions.md +23 -0
- package/docs/drag_to_travel.md +9 -3
- package/docs/form_changed.md +4 -3
- package/docs/interactions.md +8 -4
- package/docs/navigation.md +31 -2
- package/docs/pan_zoom.md +6 -4
- package/docs/popup_backdrop.md +5 -0
- package/docs/popup_lift.md +4 -3
- package/docs/route_transitions.md +7 -3
- package/docs/scroll.md +5 -5
- package/docs/testid.md +8 -5
- package/docs/view_transitions.md +3 -2
- package/package.json +3 -3
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, coarsePointerSignal, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
|
-
import { createPubSub, elementIsFocusable, createIterableWeakSet, 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, getScrollContainer, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, 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,
|
|
6
|
+
import { createPubSub, elementIsFocusable, createIterableWeakSet, 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, getScrollContainer, 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
9
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
10
10
|
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useEffect, useReducer, useCallback, useId } from "preact/hooks";
|
|
11
|
-
import { humanizeI18n, prefixFirstAndIndentRemainingLines, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay,
|
|
11
|
+
import { humanizeI18n, prefixFirstAndIndentRemainingLines, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatMinuteDuration, formatTime, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
12
12
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
13
13
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
14
14
|
import { CHAR_CLASS_PRESETS, createValidity, durationContainsNaN, compareTwoDurations, TYPE_RULE, durationToSeconds, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, parseDuration, durationToISOString } from "@jsenv/validity";
|
|
@@ -4021,6 +4021,11 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4021
4021
|
* asks nothing.
|
|
4022
4022
|
*/
|
|
4023
4023
|
const bindParams = (newParamsOrSignal, options = {}) => {
|
|
4024
|
+
if (newParamsOrSignal === null) {
|
|
4025
|
+
// A signal's `null` becomes `undefined` in mergeActionParams; a static
|
|
4026
|
+
// one never goes through it, so it is said the same way here.
|
|
4027
|
+
newParamsOrSignal = undefined;
|
|
4028
|
+
}
|
|
4024
4029
|
const { debounce, ...optionsWithoutDebounce } = options;
|
|
4025
4030
|
if (debounce) {
|
|
4026
4031
|
if (!isSignal(newParamsOrSignal)) {
|
|
@@ -4621,7 +4626,9 @@ const createActionProxyFromSignal = (
|
|
|
4621
4626
|
const inheritsAnswer =
|
|
4622
4627
|
inheritData && getActionPrivateProperties(action).inheritData;
|
|
4623
4628
|
const createTarget = (params) => {
|
|
4624
|
-
|
|
4629
|
+
// `undefined` is "nothing to ask": no answer is coming to replace a stand-in,
|
|
4630
|
+
// and that one instance is shared by every binding of the action.
|
|
4631
|
+
if (inheritsAnswer && params !== undefined) {
|
|
4625
4632
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
4626
4633
|
const previousTarget = previousActionTarget || action;
|
|
4627
4634
|
return action.bindParams(params, {
|
|
@@ -6495,9 +6502,11 @@ const CONTROL_ATTRIBUTE_SET = new Set([
|
|
|
6495
6502
|
"spellcheck",
|
|
6496
6503
|
"autoCorrect",
|
|
6497
6504
|
"aria-controls",
|
|
6498
|
-
// The name goes where the role is: the root box has none,
|
|
6499
|
-
// left on it names nothing at all, while the host is what
|
|
6500
|
-
// and what a screen reader announces (and what
|
|
6505
|
+
// The name goes where the role is: for most controls the root box has none,
|
|
6506
|
+
// so an aria-label left on it names nothing at all, while the host is what
|
|
6507
|
+
// the user focuses and what a screen reader announces (and what
|
|
6508
|
+
// getByRole({ name }) reads). A control whose role is on its root says so
|
|
6509
|
+
// with `nameOn: "root"` (see useControlProps).
|
|
6501
6510
|
"aria-label",
|
|
6502
6511
|
"aria-labelledby",
|
|
6503
6512
|
"tabIndex",
|
|
@@ -7155,7 +7164,7 @@ const useDebugUIState = naviDebug.useDebugUIState ;
|
|
|
7155
7164
|
* @param {boolean|Function} [props.debugPopup] - Popover/dialog open/close/positioning.
|
|
7156
7165
|
* @param {boolean|Function} [props.debugAction] - Action lifecycle.
|
|
7157
7166
|
* @param {boolean|Function} [props.debugUIState] - UI-state transitions and validation.
|
|
7158
|
-
* @param {import("
|
|
7167
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
7159
7168
|
*
|
|
7160
7169
|
* @example
|
|
7161
7170
|
* // Log everything under this subtree
|
|
@@ -7377,9 +7386,11 @@ const generateSignalId = () => {
|
|
|
7377
7386
|
* @param {Array} [options.oneOf] - Array of valid values for validation. Signal will be marked invalid if value is not in this array
|
|
7378
7387
|
* @param {boolean} [options.autoFix=false] - Hold the repaired value instead of the refused one: a number
|
|
7379
7388
|
* clamped to `min`/`max` and rounded to `step`, a value outside `oneOf` replaced by the signal's
|
|
7380
|
-
* default value
|
|
7381
|
-
*
|
|
7382
|
-
*
|
|
7389
|
+
* default value, or made absent when the signal has no default (an enumeration has no nearest
|
|
7390
|
+
* value, and its order says nothing). The default must therefore pass the rules itself: one of
|
|
7391
|
+
* `oneOf`. Without it the refused value is held as-is, marked invalid, and `validSignal` reads
|
|
7392
|
+
* `undefined`. When the value came from the address, the address is rewritten with the repaired
|
|
7393
|
+
* one (a replace).
|
|
7383
7394
|
* @param {boolean} [options.weak=false] - The param qualifies one visit, not the screen: it is written into a
|
|
7384
7395
|
* url only when explicitly named (`routeParams={{ edit: id }}`), never inherited from the signal's current
|
|
7385
7396
|
* value, and it goes back to the default value when the route stops matching. Use it for params like an
|
|
@@ -7521,7 +7532,7 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7521
7532
|
|
|
7522
7533
|
// Determine localStorage key: use id if persists=true, or legacy localStorage option
|
|
7523
7534
|
const localStorageKey = signalIdString;
|
|
7524
|
-
const [validity, updateValidity] = createValidity({
|
|
7535
|
+
const [validity, updateValidity, clearValidity] = createValidity({
|
|
7525
7536
|
type,
|
|
7526
7537
|
min,
|
|
7527
7538
|
max,
|
|
@@ -7534,6 +7545,21 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7534
7545
|
// processValue, below) so a dynamic default is read where it stands.
|
|
7535
7546
|
fallback: () => getDefaultValue(false),
|
|
7536
7547
|
});
|
|
7548
|
+
if (autoFix && staticDefaultValue !== undefined) {
|
|
7549
|
+
const [defaultValidity, validateDefault] = createValidity({
|
|
7550
|
+
type,
|
|
7551
|
+
min,
|
|
7552
|
+
max,
|
|
7553
|
+
step,
|
|
7554
|
+
oneOf,
|
|
7555
|
+
});
|
|
7556
|
+
validateDefault(staticDefaultValue);
|
|
7557
|
+
if (!defaultValidity.valid) {
|
|
7558
|
+
console.warn(
|
|
7559
|
+
`stateSignal "${signalIdString}": autoFix has nothing to repair to, the default ${JSON.stringify(staticDefaultValue)} is refused by the signal's own rules. Declare a default they accept (one of oneOf), or none.`,
|
|
7560
|
+
);
|
|
7561
|
+
}
|
|
7562
|
+
}
|
|
7537
7563
|
const readFromLocalStorage = persists
|
|
7538
7564
|
? () => {
|
|
7539
7565
|
const raw = window.localStorage.getItem(localStorageKey);
|
|
@@ -7637,9 +7663,19 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7637
7663
|
// Create signal with initial value: use stored value, or undefined to indicate no explicit value
|
|
7638
7664
|
const processValue = (value) => {
|
|
7639
7665
|
if (value === undefined) {
|
|
7666
|
+
// Absence is never validated, and the verdict on the previous value
|
|
7667
|
+
// (validSignal, validity.valid) must not outlive it.
|
|
7668
|
+
clearValidity();
|
|
7640
7669
|
return undefined;
|
|
7641
7670
|
}
|
|
7642
7671
|
updateValidity(value);
|
|
7672
|
+
// A state without a default is repaired to absent. validity cannot suggest
|
|
7673
|
+
// it: `undefined` is "no suggestion" there and its rules refuse it.
|
|
7674
|
+
if (autoFix && !validity.valid && getDefaultValue(false) === undefined) {
|
|
7675
|
+
clearValidity();
|
|
7676
|
+
validity.autoFixed = true;
|
|
7677
|
+
return undefined;
|
|
7678
|
+
}
|
|
7643
7679
|
// Always return the coerced value (type coercion applies), even if invalid.
|
|
7644
7680
|
// Invalid values are preserved as-is so the UI can display them and the URL
|
|
7645
7681
|
// can reflect the current input state without silently correcting it.
|
|
@@ -7938,6 +7974,18 @@ const readSignalForUrlBuild = (connection) => {
|
|
|
7938
7974
|
return connection.signal.value;
|
|
7939
7975
|
};
|
|
7940
7976
|
|
|
7977
|
+
/**
|
|
7978
|
+
* Whether a pattern catches the addresses below its own: "/map/", "/map/*" and
|
|
7979
|
+
* the root "/" do, "/places" does not. Only such a route lends its search
|
|
7980
|
+
* params to the routes below it: it is on screen wherever they are, so its
|
|
7981
|
+
* state holds there too. Declared on "/places", a search param belongs to
|
|
7982
|
+
* that address alone — "/places/:section" beside it also matches
|
|
7983
|
+
* "/places/clubs", and borrowing the list's search would hand it to the clubs.
|
|
7984
|
+
*/
|
|
7985
|
+
const coversAddressesBelow = (patternObj) => {
|
|
7986
|
+
return patternObj.pattern.trailingSlash || patternObj.pattern.wildcard;
|
|
7987
|
+
};
|
|
7988
|
+
|
|
7941
7989
|
/**
|
|
7942
7990
|
* Creates a custom route pattern matcher
|
|
7943
7991
|
*/
|
|
@@ -8060,11 +8108,13 @@ const createRoutePattern = (
|
|
|
8060
8108
|
}
|
|
8061
8109
|
}
|
|
8062
8110
|
|
|
8063
|
-
//
|
|
8064
|
-
// Search params are global and should be inherited from any matching ancestor
|
|
8065
|
-
// regardless of path segment relationships
|
|
8111
|
+
// Search params lent by the ancestors covering this route
|
|
8066
8112
|
let ancestorPatternObj = patternObject.parent;
|
|
8067
8113
|
while (ancestorPatternObj) {
|
|
8114
|
+
if (!coversAddressesBelow(ancestorPatternObj)) {
|
|
8115
|
+
ancestorPatternObj = ancestorPatternObj.parent;
|
|
8116
|
+
continue;
|
|
8117
|
+
}
|
|
8068
8118
|
for (const [
|
|
8069
8119
|
paramName,
|
|
8070
8120
|
ancestorConnection,
|
|
@@ -8149,7 +8199,12 @@ const createRoutePattern = (
|
|
|
8149
8199
|
childParam,
|
|
8150
8200
|
childConnection,
|
|
8151
8201
|
] of childPatternObj.queryConnectionMap) {
|
|
8152
|
-
|
|
8202
|
+
// a name this route declares is its own state's, whatever a child
|
|
8203
|
+
// declares under the same name
|
|
8204
|
+
if (
|
|
8205
|
+
childParam in resolvedParams ||
|
|
8206
|
+
queryConnectionMap.has(childParam)
|
|
8207
|
+
) {
|
|
8153
8208
|
continue;
|
|
8154
8209
|
}
|
|
8155
8210
|
const childSignalValue = readSignalForUrlBuild(childConnection);
|
|
@@ -8195,7 +8250,9 @@ const createRoutePattern = (
|
|
|
8195
8250
|
* - own connection signals (path + query, inherited query included)
|
|
8196
8251
|
* - ancestor path params pinned by this pattern's own literals: being on
|
|
8197
8252
|
* "/admin/settings" MEANS section=settings, whatever the signal says
|
|
8198
|
-
* -
|
|
8253
|
+
* - custom values of the search params lent by the ancestors covering this
|
|
8254
|
+
* route (coversAddressesBelow). Another ancestor's search params ride
|
|
8255
|
+
* only on that ancestor's own url (see buildMostPreciseUrl)
|
|
8199
8256
|
* - reachable descendants' connection signals. A descendant is reachable
|
|
8200
8257
|
* when its literals agree with the state collected so far (a tab value
|
|
8201
8258
|
* is unreachable while section conflicts with the tab route's literals)
|
|
@@ -8321,6 +8378,10 @@ const createRoutePattern = (
|
|
|
8321
8378
|
setEntry(seg.name, selfSeg.value, conn, seg.index, "ancestor", false);
|
|
8322
8379
|
}
|
|
8323
8380
|
}
|
|
8381
|
+
if (!coversAddressesBelow(ancestor)) {
|
|
8382
|
+
ancestor = ancestor.parent;
|
|
8383
|
+
continue;
|
|
8384
|
+
}
|
|
8324
8385
|
for (const [name, conn] of ancestor.queryConnectionMap) {
|
|
8325
8386
|
if (intended.has(name)) {
|
|
8326
8387
|
continue;
|
|
@@ -8467,6 +8528,10 @@ const createRoutePattern = (
|
|
|
8467
8528
|
if (!queryReachable || value === undefined) {
|
|
8468
8529
|
continue;
|
|
8469
8530
|
}
|
|
8531
|
+
// a name this route declares is its own state's (see resolveParams)
|
|
8532
|
+
if (queryConnectionMap.has(paramName)) {
|
|
8533
|
+
continue;
|
|
8534
|
+
}
|
|
8470
8535
|
if (conn.isCustomValue(value)) {
|
|
8471
8536
|
setEntry(paramName, value, conn, undefined, "descendant", false);
|
|
8472
8537
|
}
|
|
@@ -8726,20 +8791,50 @@ const createRoutePattern = (
|
|
|
8726
8791
|
);
|
|
8727
8792
|
};
|
|
8728
8793
|
|
|
8794
|
+
// An ancestor's url is that ancestor's address: it carries the search params
|
|
8795
|
+
// declared there, including those it lends to nobody (coversAddressesBelow).
|
|
8796
|
+
// Each ancestor climbed past adds its own, so a higher one must keep them too.
|
|
8797
|
+
const withOwnSearchParams = (ancestorPattern, intended) => {
|
|
8798
|
+
let intendedWithOwn = intended;
|
|
8799
|
+
for (const [name, conn] of ancestorPattern.queryConnectionMap) {
|
|
8800
|
+
if (intendedWithOwn.has(name)) {
|
|
8801
|
+
continue;
|
|
8802
|
+
}
|
|
8803
|
+
const value = readSignalForUrlBuild(conn);
|
|
8804
|
+
if (value === undefined || !conn.isCustomValue(value)) {
|
|
8805
|
+
continue;
|
|
8806
|
+
}
|
|
8807
|
+
if (intendedWithOwn === intended) {
|
|
8808
|
+
intendedWithOwn = new Map(intended);
|
|
8809
|
+
}
|
|
8810
|
+
intendedWithOwn.set(name, {
|
|
8811
|
+
value,
|
|
8812
|
+
connection: conn,
|
|
8813
|
+
segmentIndex: undefined,
|
|
8814
|
+
owner: "ancestor",
|
|
8815
|
+
explicit: false,
|
|
8816
|
+
pageNaming: false,
|
|
8817
|
+
});
|
|
8818
|
+
}
|
|
8819
|
+
return intendedWithOwn;
|
|
8820
|
+
};
|
|
8821
|
+
|
|
8729
8822
|
const buildMostPreciseUrl = (params = {}) => {
|
|
8730
8823
|
const { intended, reachableDescendants } = buildIntendedState(params);
|
|
8731
8824
|
// ancestors: start at the immediate parent, climb while urls keep round-
|
|
8732
8825
|
// tripping, keep the highest one ("/" is never a target)
|
|
8733
8826
|
let ancestorUrl = null;
|
|
8734
8827
|
let ancestor = patternObject.parent;
|
|
8828
|
+
let ancestorIntended = intended;
|
|
8735
8829
|
while (ancestor && ancestor.originalPattern !== "/") {
|
|
8736
8830
|
if (!isAncestorCollapseAllowed(ancestor, intended)) {
|
|
8737
8831
|
break;
|
|
8738
8832
|
}
|
|
8739
|
-
|
|
8833
|
+
ancestorIntended = withOwnSearchParams(ancestor, ancestorIntended);
|
|
8834
|
+
const url = buildCandidateUrl(ancestor, ancestorIntended, {
|
|
8740
8835
|
dropMissing: true,
|
|
8741
8836
|
});
|
|
8742
|
-
if (!url || !verifyUrl(url,
|
|
8837
|
+
if (!url || !verifyUrl(url, ancestorIntended)) {
|
|
8743
8838
|
break;
|
|
8744
8839
|
}
|
|
8745
8840
|
debug$3(
|
|
@@ -9922,11 +10017,15 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9922
10017
|
}
|
|
9923
10018
|
routePattern.familyRoot = root;
|
|
9924
10019
|
}
|
|
9925
|
-
// Phase 3: Inherit search
|
|
9926
|
-
//
|
|
10020
|
+
// Phase 3: Inherit the search param connections of the ancestors covering
|
|
10021
|
+
// the addresses below them (see coversAddressesBelow)
|
|
9927
10022
|
for (const routePattern of routePatternSet) {
|
|
9928
10023
|
let ancestorRoutePattern = routePattern.parent;
|
|
9929
10024
|
while (ancestorRoutePattern) {
|
|
10025
|
+
if (!coversAddressesBelow(ancestorRoutePattern)) {
|
|
10026
|
+
ancestorRoutePattern = ancestorRoutePattern.parent;
|
|
10027
|
+
continue;
|
|
10028
|
+
}
|
|
9930
10029
|
// For each ancestor's query connection, check if it should be inherited
|
|
9931
10030
|
for (const [
|
|
9932
10031
|
paramName,
|
|
@@ -10036,23 +10135,6 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
10036
10135
|
continue;
|
|
10037
10136
|
}
|
|
10038
10137
|
const { index } = paramSegment;
|
|
10039
|
-
const sharesPathUpTo = (otherSegments) => {
|
|
10040
|
-
for (let i = 0; i < index; i++) {
|
|
10041
|
-
const seg = routePattern.pattern.segments[i];
|
|
10042
|
-
const otherSeg = otherSegments[i];
|
|
10043
|
-
if (!otherSeg) {
|
|
10044
|
-
return false;
|
|
10045
|
-
}
|
|
10046
|
-
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
10047
|
-
if (seg.value !== otherSeg.value) {
|
|
10048
|
-
return false;
|
|
10049
|
-
}
|
|
10050
|
-
} else if (seg.type !== otherSeg.type) {
|
|
10051
|
-
return false;
|
|
10052
|
-
}
|
|
10053
|
-
}
|
|
10054
|
-
return true;
|
|
10055
|
-
};
|
|
10056
10138
|
for (const otherPattern of routePatternSet) {
|
|
10057
10139
|
if (otherPattern === routePattern) {
|
|
10058
10140
|
continue;
|
|
@@ -10062,7 +10144,9 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
10062
10144
|
if (!otherSegment || otherSegment.type !== "literal") {
|
|
10063
10145
|
continue;
|
|
10064
10146
|
}
|
|
10065
|
-
if (
|
|
10147
|
+
if (
|
|
10148
|
+
!sharesPathUpTo(routePattern.pattern.segments, otherSegments, index)
|
|
10149
|
+
) {
|
|
10066
10150
|
continue;
|
|
10067
10151
|
}
|
|
10068
10152
|
connection.namedByLiteralRoutes = true;
|
|
@@ -10070,12 +10154,91 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
10070
10154
|
}
|
|
10071
10155
|
}
|
|
10072
10156
|
}
|
|
10157
|
+
// Phase 4c: A default with an address of its own. "/me/:section" defaulting
|
|
10158
|
+
// to "players", beside "/me/players" and "/me": the default section has a
|
|
10159
|
+
// segment, and the bare address is another page. The param is then required,
|
|
10160
|
+
// and the address writes every value, the default included. Both routes must
|
|
10161
|
+
// be declared: with none at the bare address, that address is the default's
|
|
10162
|
+
// ("/admin" opens on "/admin/settings/:tab"); with no literal for the default,
|
|
10163
|
+
// the bare address IS the default section ("/games/me").
|
|
10164
|
+
// The state keeps its default (getDefaultValue): what the signal holds while
|
|
10165
|
+
// another page of the family says nothing about it (see route.js).
|
|
10166
|
+
for (const routePattern of routePatternSet) {
|
|
10167
|
+
const { segments } = routePattern.pattern;
|
|
10168
|
+
for (const connection of routePattern.pathConnectionMap.values()) {
|
|
10169
|
+
const defaultValue = connection.getDefaultValue();
|
|
10170
|
+
if (defaultValue === undefined) {
|
|
10171
|
+
continue;
|
|
10172
|
+
}
|
|
10173
|
+
const paramSegment = segments.find(
|
|
10174
|
+
(seg) => seg.type === "param" && seg.name === connection.paramName,
|
|
10175
|
+
);
|
|
10176
|
+
if (!paramSegment) {
|
|
10177
|
+
continue;
|
|
10178
|
+
}
|
|
10179
|
+
const { index } = paramSegment;
|
|
10180
|
+
let defaultHasLiteral = false;
|
|
10181
|
+
let bareAddressTaken = false;
|
|
10182
|
+
for (const otherPattern of routePatternSet) {
|
|
10183
|
+
if (otherPattern === routePattern) {
|
|
10184
|
+
continue;
|
|
10185
|
+
}
|
|
10186
|
+
const otherSegments = otherPattern.pattern.segments;
|
|
10187
|
+
if (!sharesPathUpTo(segments, otherSegments, index)) {
|
|
10188
|
+
continue;
|
|
10189
|
+
}
|
|
10190
|
+
const otherSegment = otherSegments[index];
|
|
10191
|
+
if (otherSegment) {
|
|
10192
|
+
if (
|
|
10193
|
+
otherSegment.type === "literal" &&
|
|
10194
|
+
otherSegment.value === String(defaultValue)
|
|
10195
|
+
) {
|
|
10196
|
+
defaultHasLiteral = true;
|
|
10197
|
+
}
|
|
10198
|
+
continue;
|
|
10199
|
+
}
|
|
10200
|
+
// a route covering the addresses below it is a frame around the
|
|
10201
|
+
// sections, not a page standing at the bare address
|
|
10202
|
+
if (!coversAddressesBelow(otherPattern)) {
|
|
10203
|
+
bareAddressTaken = true;
|
|
10204
|
+
}
|
|
10205
|
+
}
|
|
10206
|
+
if (!defaultHasLiteral || !bareAddressTaken) {
|
|
10207
|
+
continue;
|
|
10208
|
+
}
|
|
10209
|
+
paramSegment.optional = false;
|
|
10210
|
+
// Both answer for the address: the url build, the signal -> url sync,
|
|
10211
|
+
// and checkIfLiteralCanBeOptionalWithPatternObj ("/me" does not stand for
|
|
10212
|
+
// "/me/players" either)
|
|
10213
|
+
connection.isDefaultValue = (value) => value === undefined;
|
|
10214
|
+
connection.isCustomValue = (value) => value !== undefined;
|
|
10215
|
+
}
|
|
10216
|
+
}
|
|
10073
10217
|
// Phase 5: Calculate depths for all patterns
|
|
10074
10218
|
for (const routePattern of routePatternSet) {
|
|
10075
10219
|
calculatePatternDepth(routePattern);
|
|
10076
10220
|
}
|
|
10077
10221
|
debug$3("Pattern registry updated");
|
|
10078
10222
|
};
|
|
10223
|
+
// Whether two patterns spell the same path before `index`: the same literals,
|
|
10224
|
+
// and a param wherever the other has one
|
|
10225
|
+
const sharesPathUpTo = (segments, otherSegments, index) => {
|
|
10226
|
+
for (let i = 0; i < index; i++) {
|
|
10227
|
+
const seg = segments[i];
|
|
10228
|
+
const otherSeg = otherSegments[i];
|
|
10229
|
+
if (!otherSeg) {
|
|
10230
|
+
return false;
|
|
10231
|
+
}
|
|
10232
|
+
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
10233
|
+
if (seg.value !== otherSeg.value) {
|
|
10234
|
+
return false;
|
|
10235
|
+
}
|
|
10236
|
+
} else if (seg.type !== otherSeg.type) {
|
|
10237
|
+
return false;
|
|
10238
|
+
}
|
|
10239
|
+
}
|
|
10240
|
+
return true;
|
|
10241
|
+
};
|
|
10079
10242
|
// Store the most specific parent (closest parent in hierarchy)
|
|
10080
10243
|
const getPathSegmentCount = (pattern) => {
|
|
10081
10244
|
// Only count path segments, not query parameters
|
|
@@ -10137,6 +10300,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
10137
10300
|
* are the same state: writing the signal rewrites the address, an address
|
|
10138
10301
|
* arriving from outside writes the signal, and the param disappears from the
|
|
10139
10302
|
* url while the signal holds its default.
|
|
10303
|
+
* They exist on this route's address only, unless the pattern catches the
|
|
10304
|
+
* addresses below it (`/map/`, `/map/*`, the root): then they hold on every
|
|
10305
|
+
* route below as well. `/places` does not lend its `?q=` to `/places/clubs`.
|
|
10140
10306
|
* `{ signal, default }` says what that state is worth ON THIS ROUTE: the same
|
|
10141
10307
|
* step opens a creation wizard on its first question and an edition one on
|
|
10142
10308
|
* its summary, each address staying clean on the step its page opens on. One
|
|
@@ -10155,7 +10321,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
10155
10321
|
* whose param declines the segment does not match at all: no signal is
|
|
10156
10322
|
* written, `<Route fallback>` is reachable, and `/:gameId` can sit at the root
|
|
10157
10323
|
* without swallowing `/cgu`. A constrained param is also required — no segment
|
|
10158
|
-
* is not one of the values it accepts — so `/:gameId` does not match
|
|
10324
|
+
* is not one of the values it accepts — so `/:gameId` does not match `/`,
|
|
10325
|
+
* unless its default stands for the bare address (see docs/navigation.md,
|
|
10326
|
+
* "Declaring the sections is what makes them places").
|
|
10159
10327
|
*
|
|
10160
10328
|
* Constrain the shape, not the existence: whether that game exists is for the
|
|
10161
10329
|
* route action and the page to answer, on a route that did match.
|
|
@@ -11370,14 +11538,17 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
11370
11538
|
*
|
|
11371
11539
|
* - `isShown()` — whether the container would still show the page. False as
|
|
11372
11540
|
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
11373
|
-
* hidden until the route transition leaving it is over,
|
|
11374
|
-
*
|
|
11375
|
-
*
|
|
11376
|
-
*
|
|
11541
|
+
* hidden until the route transition leaving it is over, nothing it reads is
|
|
11542
|
+
* worth a render any more, and nothing it holds speaks for the page shown (a
|
|
11543
|
+
* list's hold on the document, see holdDocumentScroll). False for good once
|
|
11544
|
+
* it is kept while leaving: a return to its route mounts the page anew. Asked
|
|
11545
|
+
* outside any render — by a subscription deciding whether to re-render, by
|
|
11546
|
+
* the scroll restoration when a position is due — and it reads the routes
|
|
11377
11547
|
* untracked, so asking subscribes nobody to them.
|
|
11378
11548
|
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
11379
|
-
* plays: still mounted, so what it fills outside its own nodes (a
|
|
11380
|
-
* bar) steps out rather than showing
|
|
11549
|
+
* plays: still mounted, so what it fills or moves outside its own nodes (a
|
|
11550
|
+
* slot in a bar, the document a list scrolls) steps out rather than showing
|
|
11551
|
+
* with, or moving, the page arriving. Every box of it measures 0×0 by then.
|
|
11381
11552
|
*/
|
|
11382
11553
|
const RoutePageContext = createContext(null);
|
|
11383
11554
|
|
|
@@ -12300,7 +12471,7 @@ const putBack = (restoration) => {
|
|
|
12300
12471
|
// The page is not whole yet: the last release puts it back.
|
|
12301
12472
|
return;
|
|
12302
12473
|
}
|
|
12303
|
-
if (
|
|
12474
|
+
if (isDocumentHeld()) {
|
|
12304
12475
|
pendingRestoration = null;
|
|
12305
12476
|
return;
|
|
12306
12477
|
}
|
|
@@ -12316,20 +12487,33 @@ const putBack = (restoration) => {
|
|
|
12316
12487
|
// height, so the same pixels now fall on other rows. Put back after the list
|
|
12317
12488
|
// has placed itself, they would undo it, in the very picture a route transition
|
|
12318
12489
|
// takes. So the list holds the document while it is placing itself, and the
|
|
12319
|
-
// url's offset is not put back meanwhile.
|
|
12320
|
-
// document.
|
|
12321
|
-
|
|
12322
|
-
|
|
12323
|
-
|
|
12324
|
-
|
|
12490
|
+
// url's offset is not put back meanwhile. One hold per list: two lists can
|
|
12491
|
+
// share one document.
|
|
12492
|
+
//
|
|
12493
|
+
// A hold is for the page its list is on, and mounted is not shown: a page kept
|
|
12494
|
+
// while its route transition plays still has its list when the page returned to
|
|
12495
|
+
// renders, and that list says nothing about where the page arriving goes. So a
|
|
12496
|
+
// hold is taken with the list's page (RoutePageContext), asked whether it is
|
|
12497
|
+
// shown when a position is due — `null` outside any route container: that list
|
|
12498
|
+
// is on whatever page is shown. Not a url: which page an address shows is the
|
|
12499
|
+
// routes' to say, and one page can be shown under several (its params moving
|
|
12500
|
+
// under it).
|
|
12501
|
+
const documentHolds = new Set();
|
|
12502
|
+
const holdDocumentScroll = (routePage) => {
|
|
12503
|
+
const hold = { routePage };
|
|
12504
|
+
documentHolds.add(hold);
|
|
12325
12505
|
return () => {
|
|
12326
|
-
|
|
12327
|
-
return;
|
|
12328
|
-
}
|
|
12329
|
-
released = true;
|
|
12330
|
-
documentHoldCount--;
|
|
12506
|
+
documentHolds.delete(hold);
|
|
12331
12507
|
};
|
|
12332
12508
|
};
|
|
12509
|
+
const isDocumentHeld = () => {
|
|
12510
|
+
for (const { routePage } of documentHolds) {
|
|
12511
|
+
if (routePage === null || routePage.isShown()) {
|
|
12512
|
+
return true;
|
|
12513
|
+
}
|
|
12514
|
+
}
|
|
12515
|
+
return false;
|
|
12516
|
+
};
|
|
12333
12517
|
|
|
12334
12518
|
// What a page holds instead: not the place, which stays the url's, but the
|
|
12335
12519
|
// moment it is put back (see useHoldScrollRestoration). Counted: several parts
|
|
@@ -12587,9 +12771,9 @@ const restoreScrollPosition = (url) => {
|
|
|
12587
12771
|
if (!position) {
|
|
12588
12772
|
return false;
|
|
12589
12773
|
}
|
|
12590
|
-
if (
|
|
12591
|
-
// There is a place to go back to, and the list holding the document
|
|
12592
|
-
// putting it back (see holdDocumentScroll).
|
|
12774
|
+
if (isDocumentHeld()) {
|
|
12775
|
+
// There is a place to go back to, and the list holding the document on
|
|
12776
|
+
// that page is putting it back (see holdDocumentScroll).
|
|
12593
12777
|
return true;
|
|
12594
12778
|
}
|
|
12595
12779
|
scrollTo(position);
|
|
@@ -12858,6 +13042,25 @@ const getHrefTargetInfo = (
|
|
|
12858
13042
|
};
|
|
12859
13043
|
};
|
|
12860
13044
|
|
|
13045
|
+
// The target of a link whose caller names none: a page of another site opens
|
|
13046
|
+
// in a new tab. Only a page: a `tel:`, `mailto:` or `sms:` address is handed to
|
|
13047
|
+
// another app, a `blob:` or `data:` one is this page's own, and a tab opened
|
|
13048
|
+
// for them would be left behind empty.
|
|
13049
|
+
const getDefaultHrefTarget = (
|
|
13050
|
+
href,
|
|
13051
|
+
currentUrlString = window.location.href,
|
|
13052
|
+
) => {
|
|
13053
|
+
const { isEmpty, isSameSite } = getHrefTargetInfo(href, currentUrlString);
|
|
13054
|
+
if (isEmpty || isSameSite) {
|
|
13055
|
+
return undefined;
|
|
13056
|
+
}
|
|
13057
|
+
const { protocol } = new URL(href, currentUrlString);
|
|
13058
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
13059
|
+
return undefined;
|
|
13060
|
+
}
|
|
13061
|
+
return "_blank";
|
|
13062
|
+
};
|
|
13063
|
+
|
|
12861
13064
|
// A field of getHrefTargetInfo that depends on the page one is on (isCurrent,
|
|
12862
13065
|
// isAnchor), read through a computed of that one boolean: the caller re-renders
|
|
12863
13066
|
// when it flips, not at every address write — a search param bound to a wheel
|
|
@@ -12898,6 +13101,27 @@ const linkAsksForDocument = (linkElement) => {
|
|
|
12898
13101
|
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
12899
13102
|
};
|
|
12900
13103
|
|
|
13104
|
+
/*
|
|
13105
|
+
* A link does not always move the tab it is in. A `target` naming another
|
|
13106
|
+
* browsing context (a new tab, a named window) loads the address there, in a
|
|
13107
|
+
* document of its own; a `download` saves it. The browser answers both
|
|
13108
|
+
* without touching this page, and that is the point of writing them: reading
|
|
13109
|
+
* the terms beside a half-filled form costs nothing. Routing such a link
|
|
13110
|
+
* would take this tab instead, and give no tab and no file.
|
|
13111
|
+
*
|
|
13112
|
+
* `_parent` and `_top` count as elsewhere too. Inside a frame they are; at the
|
|
13113
|
+
* top level the browser treats them as `_self` and loads a document, which
|
|
13114
|
+
* costs a load and nothing else.
|
|
13115
|
+
*/
|
|
13116
|
+
|
|
13117
|
+
const linkOpensElsewhere = (linkElement) => {
|
|
13118
|
+
const target = linkElement.getAttribute("target");
|
|
13119
|
+
if (target && target !== "_self") {
|
|
13120
|
+
return true;
|
|
13121
|
+
}
|
|
13122
|
+
return linkElement.hasAttribute("download");
|
|
13123
|
+
};
|
|
13124
|
+
|
|
12901
13125
|
/*
|
|
12902
13126
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
12903
13127
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -13227,8 +13451,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
13227
13451
|
// Ignore non-left clicks
|
|
13228
13452
|
return;
|
|
13229
13453
|
}
|
|
13230
|
-
if (e.metaKey) {
|
|
13231
|
-
//
|
|
13454
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
13455
|
+
// A modifier asks the browser for something other than this tab: a new
|
|
13456
|
+
// tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
|
|
13457
|
+
// (alt).
|
|
13232
13458
|
return;
|
|
13233
13459
|
}
|
|
13234
13460
|
if (e.defaultPrevented) {
|
|
@@ -13239,6 +13465,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
13239
13465
|
if (linkElement.hasAttribute("data-readonly")) {
|
|
13240
13466
|
return;
|
|
13241
13467
|
}
|
|
13468
|
+
if (linkOpensElsewhere(linkElement)) {
|
|
13469
|
+
// Another tab or window, or a download (see link_elsewhere.js).
|
|
13470
|
+
return;
|
|
13471
|
+
}
|
|
13242
13472
|
const href = linkElement.href;
|
|
13243
13473
|
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
13244
13474
|
getHrefTargetInfo(href);
|
|
@@ -17525,6 +17755,9 @@ const surfaceTextCss = /* css */ `
|
|
|
17525
17755
|
}
|
|
17526
17756
|
`;
|
|
17527
17757
|
|
|
17758
|
+
// What a callout's own JSX may know about the callout around it: how to close
|
|
17759
|
+
// it, and the element — whose id is what `commandFor` needs when the button
|
|
17760
|
+
// closing it is not inside it (inside, `--navi-close` finds it on its own).
|
|
17528
17761
|
const CalloutContext = createContext();
|
|
17529
17762
|
const useCalloutRequestClose = () => {
|
|
17530
17763
|
return useContext(CalloutContext)?.requestClose;
|
|
@@ -17658,7 +17891,7 @@ const CALLOUT_STATUS_GLYPH_VIEWBOX = "0 0 540 540";
|
|
|
17658
17891
|
const CALLOUT_STATUS_GLYPH_PATH = "m232.5,121 8,196h59l8-196zm36.5,224a37,37 0 1,0 2,0z";
|
|
17659
17892
|
|
|
17660
17893
|
/**
|
|
17661
|
-
* @type {import("
|
|
17894
|
+
* @type {import("preact").FunctionComponent<{
|
|
17662
17895
|
* status?: "info" | "warning" | "error" | "success" | "none",
|
|
17663
17896
|
* shape?: "square" | "circle",
|
|
17664
17897
|
* }>}
|
|
@@ -22604,23 +22837,12 @@ defineInteractionDetector({
|
|
|
22604
22837
|
// A link and an image are draggable without anyone asking, and a native
|
|
22605
22838
|
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
22606
22839
|
// events with it and paints a ghost of the row the hand is trying to swipe.
|
|
22607
|
-
//
|
|
22608
|
-
//
|
|
22609
|
-
//
|
|
22610
|
-
//
|
|
22611
|
-
//
|
|
22612
|
-
// browser honours (`-webkit-user-drag` is one engine's).
|
|
22613
|
-
// The cost is stated rather than worked around: an element that takes a
|
|
22614
|
-
// swipe cannot also be dragged out of the page, because there is one
|
|
22615
|
-
// gesture and it cannot mean both.
|
|
22840
|
+
// `draggable` refuses it on the element itself, before it starts; the drag
|
|
22841
|
+
// of a link or a thumbnail INSIDE it is refused by the travel reading the
|
|
22842
|
+
// press (startDragToTravel). The cost is stated rather than worked around:
|
|
22843
|
+
// an element that takes a swipe cannot also be dragged out of the page,
|
|
22844
|
+
// because there is one gesture and it cannot mean both.
|
|
22616
22845
|
mark("draggable", "false");
|
|
22617
|
-
const onDragStart = (dragStartEvent) => {
|
|
22618
|
-
dragStartEvent.preventDefault();
|
|
22619
|
-
};
|
|
22620
|
-
element.addEventListener("dragstart", onDragStart);
|
|
22621
|
-
undo.push(() => {
|
|
22622
|
-
element.removeEventListener("dragstart", onDragStart);
|
|
22623
|
-
});
|
|
22624
22846
|
|
|
22625
22847
|
// A touch this element may take has to be refusable before the finger
|
|
22626
22848
|
// lands, or the browser can cancel an established swipe mid-gesture by
|
|
@@ -23767,9 +23989,10 @@ const isMac = detectMac();
|
|
|
23767
23989
|
* place rather than about this box.
|
|
23768
23990
|
*
|
|
23769
23991
|
* `data-pan-after-hold="kept"` asks for that wait once rather than before every
|
|
23770
|
-
* pan: the surface given the hand keeps it — panning on contact,
|
|
23771
|
-
* plan opened full screen — and asks again after a press has
|
|
23772
|
-
* it, which is the hand saying it has moved on.
|
|
23992
|
+
* pan: the surface a finger has given the hand keeps it — panning on contact,
|
|
23993
|
+
* like the same plan opened full screen — and asks again after a press has
|
|
23994
|
+
* landed away from it, which is the hand saying it has moved on. A mouse never
|
|
23995
|
+
* owed the wait, so it neither spends it nor marks it. What answers "away" is the same
|
|
23773
23996
|
* thing that closes a popup, and the surface is the one that watches for it, so
|
|
23774
23997
|
* an application never keeps a `pointerdown` listener on the window to know.
|
|
23775
23998
|
* `[data-hand-kept]` is on the element for as long as it holds the hand that
|
|
@@ -24898,7 +25121,8 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
24898
25121
|
* and that field is the control the declaration speaks for: an ArrowUp declared
|
|
24899
25122
|
* on a text field is the field saying what ArrowUp does there, not a container
|
|
24900
25123
|
* taking the caret from a field inside it. A field inside a box that declares
|
|
24901
|
-
* keys for something else (a row, a list) keeps every key it has a use for
|
|
25124
|
+
* keys for something else (a row, a list) keeps every key it has a use for,
|
|
25125
|
+
* even when it is the only control in that box.
|
|
24902
25126
|
*
|
|
24903
25127
|
* A declared key that matches takes the key: its default does not happen as
|
|
24904
25128
|
* well, whether the gate lets the interaction through or refuses it and says
|
|
@@ -24924,7 +25148,10 @@ defineInteractionDetector({
|
|
|
24924
25148
|
};
|
|
24925
25149
|
}
|
|
24926
25150
|
const onKeyDown = createOnKeyDownForShortcuts(shortcuts, {
|
|
24927
|
-
|
|
25151
|
+
// Only the control's own root speaks for its host: a box that wraps a
|
|
25152
|
+
// single control is reached by that control's gate, not handed its keys.
|
|
25153
|
+
ownsTarget: (target) =>
|
|
25154
|
+
isControlRoot(element) && target === findNearestControlHost(element),
|
|
24928
25155
|
});
|
|
24929
25156
|
element.addEventListener("keydown", onKeyDown);
|
|
24930
25157
|
return () => {
|
|
@@ -28992,6 +29219,127 @@ const useForceRender = () => {
|
|
|
28992
29219
|
|
|
28993
29220
|
// https://github.com/preactjs/preact/issues/4756
|
|
28994
29221
|
|
|
29222
|
+
/**
|
|
29223
|
+
* Reads the current state of an action and returns `[data, loading, error, dismissError]`.
|
|
29224
|
+
*
|
|
29225
|
+
* By default (`loading` and `error` not set) the component suspends on load
|
|
29226
|
+
* and throws on failure, delegating both states to the nearest `<Loading>` and
|
|
29227
|
+
* `<ErrorBoundary>` ancestors. Pass `loading: true` or `error: true` to handle
|
|
29228
|
+
* either state directly inside the component instead.
|
|
29229
|
+
*
|
|
29230
|
+
* Return value: `[data, loading, error, dismissError]`
|
|
29231
|
+
* - `data` — the action's last successful data, or `undefined` if it has never completed
|
|
29232
|
+
* - `loading` — `true` while the action is running (only when `loading: true` is passed)
|
|
29233
|
+
* - `error` — the Error thrown by the action (only when `error: true` is passed)
|
|
29234
|
+
* - `dismissError` — takes that error out of what this hook returns, nothing else (only when `error: true` is passed)
|
|
29235
|
+
*
|
|
29236
|
+
* Stale data is always returned: the previous `data` remains available while
|
|
29237
|
+
* the action re-runs and after a run has failed, so a screen that had something
|
|
29238
|
+
* to show never has it taken away. What is drawn over it — a loading indicator,
|
|
29239
|
+
* the error, nothing — is entirely up to the component.
|
|
29240
|
+
*
|
|
29241
|
+
* `loading` covers the delay of a debounced binding
|
|
29242
|
+
* (`bindParams(paramsSignal, { debounce })`, `actionRunEffect({ debounce })`)
|
|
29243
|
+
* as well as the run itself: from the moment the params move, what is on screen
|
|
29244
|
+
* is the previous answer, and a screen showing "searching…" while someone types
|
|
29245
|
+
* needs to know it. Suspending has nothing to wait on during that delay, so
|
|
29246
|
+
* only `loading: true` reads it — the default keeps showing the previous answer
|
|
29247
|
+
* until the run really starts.
|
|
29248
|
+
*
|
|
29249
|
+
* When `loading` is not set (default), the component suspends until data is
|
|
29250
|
+
* ready, so `data` is always defined when the component renders and `loading`
|
|
29251
|
+
* is always `false`. With `loading: true` the component never suspends: every
|
|
29252
|
+
* state — running, settling, params nobody has started yet, a failure whose
|
|
29253
|
+
* message was dismissed — comes back as a value, so the subtree it draws is
|
|
29254
|
+
* never swapped for the boundary above and remounted.
|
|
29255
|
+
*
|
|
29256
|
+
* When `error` is not set (default), any action failure causes the component
|
|
29257
|
+
* to throw, so `error` is always `undefined` when the component renders. With
|
|
29258
|
+
* `error: true` the failure arrives beside the data the action already had — a
|
|
29259
|
+
* message on the content, not one in place of it. `dismissError()` removes the
|
|
29260
|
+
* message and nothing else: nothing is asked again, and what comes back is the
|
|
29261
|
+
* same state minus the error — the same data, or none at all when nothing ever
|
|
29262
|
+
* came back.
|
|
29263
|
+
*
|
|
29264
|
+
* `data === undefined` says there is nothing to show and `loading` says whether
|
|
29265
|
+
* anything is on its way: a skeleton reads both, or it claims a load nobody
|
|
29266
|
+
* started (see docs/data_states.md).
|
|
29267
|
+
*
|
|
29268
|
+
* @param {object | (() => Promise<any>) | Promise<any>} promiseOrAction -
|
|
29269
|
+
* an action, or the request this component owns written as a function:
|
|
29270
|
+
* made into an action once, on the first render, and kept for the life of
|
|
29271
|
+
* the instance — so it may be written inline — with what it resolves to as
|
|
29272
|
+
* the data and `run` implied. Code loaded on demand reads this way
|
|
29273
|
+
* (`() => import("./plan.jsx").then((m) => m.Plan)`, see
|
|
29274
|
+
* docs/dynamic_import.md), as does anything else asked for once per mount.
|
|
29275
|
+
* @param {{ loading?: true, error?: true, run?: true, prerun?: true, onLoad?: (data: any, context: {params: any}) => void }} [options]
|
|
29276
|
+
* @param {true} [options.run] - The fallback for data nothing else can ask for:
|
|
29277
|
+
* this component owns the request and starts it, from the render that reads
|
|
29278
|
+
* it. `loading` and `error` keep their meaning — delegated by default,
|
|
29279
|
+
* handled inline when asked for — so a run started here suspends into
|
|
29280
|
+
* `<Loading>` like any other.
|
|
29281
|
+
*
|
|
29282
|
+
* A failure is shown, not asked again: a render that retried it would retry
|
|
29283
|
+
* it in a loop while the network is down — that is `rerun()`'s job. Except a
|
|
29284
|
+
* failure a prerun left (a `prerun: true` head start, a press that preran
|
|
29285
|
+
* it): nothing on screen asked for that run, so this asks again, once.
|
|
29286
|
+
*
|
|
29287
|
+
* Prefer a `routeAction` wherever the parameter is one the address holds (a
|
|
29288
|
+
* path param, a search param bound to a `stateSignal`), popup included: it is
|
|
29289
|
+
* asked for when the address changes, with everything else that address
|
|
29290
|
+
* needs, whereas this one cannot start before the component that draws it
|
|
29291
|
+
* exists — one render late, and behind whatever gesture mounted it. What is
|
|
29292
|
+
* left for `run` is the parameter chosen inside the component and dying with
|
|
29293
|
+
* it — and any run that WRITES, whatever its parameter: an address asks again
|
|
29294
|
+
* on every arrival, which is right for a read and wrong for a write (see
|
|
29295
|
+
* docs/actions.md and docs/popup_open.md).
|
|
29296
|
+
* @param {true} [options.prerun] - A head start: this component starts a
|
|
29297
|
+
* read that another one, mounted later, owns with `run: true` — the read
|
|
29298
|
+
* leaves as soon as its params are known instead of once the owner exists.
|
|
29299
|
+
* Started from the render like `run`, as a prerun: it draws none of it, so
|
|
29300
|
+
* nothing suspends (no `<Loading>` needed) and a failure is neither thrown
|
|
29301
|
+
* nor reported — the owner asks again. A head start still running when the
|
|
29302
|
+
* owner reads it is the owner's wait.
|
|
29303
|
+
* @param {(data: any, context: {params: any}) => void} [options.onLoad] - what
|
|
29304
|
+
* this screen does with the data ONCE, when it becomes known: seed the fields
|
|
29305
|
+
* someone is about to edit, remember where a list was, focus something.
|
|
29306
|
+
*
|
|
29307
|
+
* Fired once per set of params — never again for a rerun that brings the same
|
|
29308
|
+
* thing back. That is the whole point of it: a save, a refresh, a poll all
|
|
29309
|
+
* hand the data over again, and copying it a second time would overwrite what
|
|
29310
|
+
* the person is in the middle of writing. The action knows what it ran with,
|
|
29311
|
+
* so nobody has to guess the dependency.
|
|
29312
|
+
*
|
|
29313
|
+
* Called from a layout effect, so what it writes belongs to the same tick as
|
|
29314
|
+
* the render that got the data — a `<Form pristineKey>` taking its reference
|
|
29315
|
+
* then sees the filled fields rather than the empty ones.
|
|
29316
|
+
* @returns {[data: unknown, loading: boolean, error: Error | undefined, dismissError: (() => void) | undefined]}
|
|
29317
|
+
*
|
|
29318
|
+
* @example <caption>Default — delegate both states (least code)</caption>
|
|
29319
|
+
* const [user] = useAsyncData(userAction);
|
|
29320
|
+
* // user is always defined here — never loading, never error
|
|
29321
|
+
*
|
|
29322
|
+
* @example <caption>Handle loading inline while keeping stale data</caption>
|
|
29323
|
+
* const [user, loading] = useAsyncData(userAction, { loading: true });
|
|
29324
|
+
* // First load: user === undefined, loading === true → show skeleton
|
|
29325
|
+
* // While refreshing: user === <stale>, loading === true → show stale + spinner
|
|
29326
|
+
* // Done: user === <fresh>, loading === false
|
|
29327
|
+
*
|
|
29328
|
+
* @example <caption>Handle error inline</caption>
|
|
29329
|
+
* const [user, , error] = useAsyncData(userAction, { error: true });
|
|
29330
|
+
* if (error) return <ErrorCard message={error.message} />;
|
|
29331
|
+
*
|
|
29332
|
+
* @example <caption>Say the failure over the rows already on screen</caption>
|
|
29333
|
+
* const [items, , error, dismissError] = useAsyncData(listAction, { error: true });
|
|
29334
|
+
* // First failure: items === undefined → only the message
|
|
29335
|
+
* // Failed refresh: items === <stale> → the message above the rows
|
|
29336
|
+
* return (
|
|
29337
|
+
* <>
|
|
29338
|
+
* {error && <ErrorStrip error={error} onClose={dismissError} />}
|
|
29339
|
+
* {items && <List items={items} />}
|
|
29340
|
+
* </>
|
|
29341
|
+
* );
|
|
29342
|
+
*/
|
|
28995
29343
|
const useAsyncData = (promiseOrAction, {
|
|
28996
29344
|
loading = "delegate",
|
|
28997
29345
|
error = "delegate",
|
|
@@ -29441,7 +29789,7 @@ const LoadingFallback = ({
|
|
|
29441
29789
|
* ones that would render fine.
|
|
29442
29790
|
*
|
|
29443
29791
|
* @param {object} props
|
|
29444
|
-
* @param {Function|import("
|
|
29792
|
+
* @param {Function|import("preact").VNode} [props.fallback] - what is displayed
|
|
29445
29793
|
* instead of the children: an element, or a component receiving
|
|
29446
29794
|
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
29447
29795
|
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
@@ -29800,6 +30148,12 @@ const hasWidthHeight = (context) => {
|
|
|
29800
30148
|
(context.styles.height || context.remainingProps.height)
|
|
29801
30149
|
);
|
|
29802
30150
|
};
|
|
30151
|
+
// The `stretch` sizing keyword: the containing block's width minus the box's
|
|
30152
|
+
// own margins, so a box with a margin fills its row without leaving it (100%
|
|
30153
|
+
// adds the margins on top). Spelled with the prefix because an inline style
|
|
30154
|
+
// holds one value, and this is the one name Chromium, WebKit and Firefox all
|
|
30155
|
+
// read (Firefox 155 has no unprefixed `stretch`).
|
|
30156
|
+
const FILL_AVAILABLE_WIDTH = "-webkit-fill-available";
|
|
29803
30157
|
const DIMENSION_PROPS = {
|
|
29804
30158
|
boxSizing: PASS_THROUGH,
|
|
29805
30159
|
width: PASS_THROUGH,
|
|
@@ -29852,16 +30206,16 @@ const DIMENSION_PROPS = {
|
|
|
29852
30206
|
return { flexGrow: expandWeight(value), flexBasis: "0%" };
|
|
29853
30207
|
}
|
|
29854
30208
|
if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
|
|
29855
|
-
// width
|
|
30209
|
+
// A width is what fills the cross axis; align-self stretch would do
|
|
29856
30210
|
// the same but takes the parent's alignX away — a maxWidth-capped item
|
|
29857
30211
|
// in an alignX="center" parent would then sit at the start instead of
|
|
29858
30212
|
// in the middle.
|
|
29859
30213
|
return {
|
|
29860
|
-
width:
|
|
30214
|
+
width: FILL_AVAILABLE_WIDTH,
|
|
29861
30215
|
};
|
|
29862
30216
|
}
|
|
29863
30217
|
// Can't use flexGrow — parent is not flex-x
|
|
29864
|
-
return { width:
|
|
30218
|
+
return { width: FILL_AVAILABLE_WIDTH };
|
|
29865
30219
|
},
|
|
29866
30220
|
expandY: (value, { parentBoxFlow }) => {
|
|
29867
30221
|
if (!value) {
|
|
@@ -30422,6 +30776,7 @@ const getVisualChildStylePropStrategy = (name) => {
|
|
|
30422
30776
|
};
|
|
30423
30777
|
|
|
30424
30778
|
const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
|
|
30779
|
+
const isTypoProp = (name) => TYPO_PROP_NAME_SET.has(name);
|
|
30425
30780
|
|
|
30426
30781
|
const getStringifier = (key) => {
|
|
30427
30782
|
if (SPACING_PROP_SET.has(key)) {
|
|
@@ -31063,10 +31418,10 @@ const readCSSVarName = entry => Array.isArray(entry) ? entry[0] : entry;
|
|
|
31063
31418
|
const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
31064
31419
|
|
|
31065
31420
|
/**
|
|
31066
|
-
* @type {import("
|
|
31421
|
+
* @type {import("preact").FunctionComponent<{
|
|
31067
31422
|
* as?: string,
|
|
31068
31423
|
* className?: string,
|
|
31069
|
-
* style?: import("
|
|
31424
|
+
* style?: import("preact").JSX.CSSProperties & { [pseudo: string]: import("preact").JSX.CSSProperties },
|
|
31070
31425
|
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
31071
31426
|
* inline?: boolean,
|
|
31072
31427
|
* block?: boolean,
|
|
@@ -31081,7 +31436,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
31081
31436
|
* hasChildUsingForwardedProps?: boolean,
|
|
31082
31437
|
* childPropSet?: Set<string>,
|
|
31083
31438
|
* preventInitialTransition?: boolean,
|
|
31084
|
-
* separator?: import("
|
|
31439
|
+
* separator?: import("preact").ComponentChildren | ((index: number) => import("preact").ComponentChildren),
|
|
31085
31440
|
* selfInteractions?: string,
|
|
31086
31441
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
31087
31442
|
* alignX?: string,
|
|
@@ -31093,7 +31448,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
31093
31448
|
* body?: boolean,
|
|
31094
31449
|
* sticky?: boolean,
|
|
31095
31450
|
* mount?: "always" | "after-paint",
|
|
31096
|
-
* children?: import("
|
|
31451
|
+
* children?: import("preact").ComponentChildren,
|
|
31097
31452
|
* [key: string]: any,
|
|
31098
31453
|
* }>}
|
|
31099
31454
|
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
@@ -33700,7 +34055,7 @@ const debug$1 = (...args) => {
|
|
|
33700
34055
|
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
33701
34056
|
* movement be written about it and its `matchingSignal` be read from outside
|
|
33702
34057
|
* the tree (see route_fallback.js).
|
|
33703
|
-
* @param {Function|import("
|
|
34058
|
+
* @param {Function|import("preact").VNode} [props.element] - what the branch renders
|
|
33704
34059
|
* @param {object} [props.elementProps] - props given to `element`
|
|
33705
34060
|
*
|
|
33706
34061
|
* A branch says what it renders, not what happens when it cannot: loading and
|
|
@@ -36089,7 +36444,7 @@ const css$13 = /* css */`@property --navi-route-transition-leave {
|
|
|
36089
36444
|
* that. An application that already has an element holding its pages can mark
|
|
36090
36445
|
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
36091
36446
|
*
|
|
36092
|
-
* @type {import("
|
|
36447
|
+
* @type {import("preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
36093
36448
|
*/
|
|
36094
36449
|
const RouteTransitionArea = ({
|
|
36095
36450
|
children,
|
|
@@ -36879,7 +37234,7 @@ const beginTransition = ({
|
|
|
36879
37234
|
warnAboutNamesEscapingArea(areaElement, capturedNames);
|
|
36880
37235
|
return;
|
|
36881
37236
|
}
|
|
36882
|
-
for (const name of capturedNames) {
|
|
37237
|
+
for (const name of capturedNames.keys()) {
|
|
36883
37238
|
if (name === "root") {
|
|
36884
37239
|
continue;
|
|
36885
37240
|
}
|
|
@@ -37082,9 +37437,10 @@ const ignoreSkipped$1 = () => {};
|
|
|
37082
37437
|
|
|
37083
37438
|
// The names the browser captured, read off the pictures themselves: what was
|
|
37084
37439
|
// asked for in CSS and what was taken are not the same question (see
|
|
37085
|
-
// viewTransitionReady).
|
|
37440
|
+
// viewTransitionReady). Each with the states it was taken in: a name taken in
|
|
37441
|
+
// only one of them pairs with nothing.
|
|
37086
37442
|
const capturedViewTransitionNames = () => {
|
|
37087
|
-
const names = new
|
|
37443
|
+
const names = new Map();
|
|
37088
37444
|
for (const animation of document.getAnimations()) {
|
|
37089
37445
|
const pseudoElement = animation.effect?.pseudoElement;
|
|
37090
37446
|
if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
|
|
@@ -37094,43 +37450,59 @@ const capturedViewTransitionNames = () => {
|
|
|
37094
37450
|
if (nameStart === -1) {
|
|
37095
37451
|
continue;
|
|
37096
37452
|
}
|
|
37097
|
-
|
|
37453
|
+
const name = pseudoElement.slice(nameStart + 1, -1);
|
|
37454
|
+
let states = names.get(name);
|
|
37455
|
+
if (!states) {
|
|
37456
|
+
states = {
|
|
37457
|
+
old: false,
|
|
37458
|
+
new: false
|
|
37459
|
+
};
|
|
37460
|
+
names.set(name, states);
|
|
37461
|
+
}
|
|
37462
|
+
if (pseudoElement.startsWith("::view-transition-old(")) {
|
|
37463
|
+
states.old = true;
|
|
37464
|
+
} else if (pseudoElement.startsWith("::view-transition-new(")) {
|
|
37465
|
+
states.new = true;
|
|
37466
|
+
}
|
|
37098
37467
|
}
|
|
37099
37468
|
return names;
|
|
37100
37469
|
};
|
|
37101
37470
|
|
|
37102
|
-
//
|
|
37103
|
-
//
|
|
37104
|
-
//
|
|
37105
|
-
//
|
|
37471
|
+
// A name written inside the area is a group of its own: it stands where it was
|
|
37472
|
+
// captured while the area's two pictures carry the movement, in every browser.
|
|
37473
|
+
// Nested groups only keep it cut at the pages' edge (see the @supports block in
|
|
37474
|
+
// the CSS above). Paired with the same name on the other page it is a morph;
|
|
37475
|
+
// taken in one state only it pairs with nothing, and is a piece of its page
|
|
37476
|
+
// left behind, fading, while that page moves.
|
|
37106
37477
|
const NESTED_GROUPS_SUPPORTED = window.CSS.supports("view-transition-group", "contain");
|
|
37107
37478
|
const warnAboutNamesEscapingArea = (areaElement, capturedNames) => {
|
|
37108
|
-
|
|
37109
|
-
|
|
37110
|
-
}
|
|
37111
|
-
let escapedName = null;
|
|
37112
|
-
for (const name of capturedNames) {
|
|
37479
|
+
const unpairedNames = new Set();
|
|
37480
|
+
for (const [name, states] of capturedNames) {
|
|
37113
37481
|
if (name === "root" || name === AREA_NAME || name.startsWith(FURNITURE_NAME_PREFIX)) {
|
|
37114
37482
|
continue;
|
|
37115
37483
|
}
|
|
37116
|
-
|
|
37117
|
-
|
|
37484
|
+
if (states.old && states.new) {
|
|
37485
|
+
continue;
|
|
37486
|
+
}
|
|
37487
|
+
unpairedNames.add(name);
|
|
37118
37488
|
}
|
|
37119
37489
|
// A name captured next to the area is not necessarily inside it — a bar the
|
|
37120
37490
|
// application animates on the same clock is named on purpose. The subtree is
|
|
37121
37491
|
// walked only once something is there to find, so the common case reads
|
|
37122
|
-
// nothing.
|
|
37123
|
-
|
|
37492
|
+
// nothing. The page being left is still in it, hidden (keepLeavingPages in
|
|
37493
|
+
// route.jsx), so a name taken only in the old state is found too.
|
|
37494
|
+
if (unpairedNames.size === 0) {
|
|
37124
37495
|
return;
|
|
37125
37496
|
}
|
|
37126
37497
|
for (const descendant of areaElement.querySelectorAll("*")) {
|
|
37127
37498
|
const {
|
|
37128
37499
|
viewTransitionName
|
|
37129
37500
|
} = getComputedStyle(descendant);
|
|
37130
|
-
if (!viewTransitionName
|
|
37501
|
+
if (!unpairedNames.has(viewTransitionName)) {
|
|
37131
37502
|
continue;
|
|
37132
37503
|
}
|
|
37133
|
-
|
|
37504
|
+
const unclipped = NESTED_GROUPS_SUPPORTED ? "" : " This browser has no nested groups (view-transition-group: contain), so it is not even cut at the pages' edge.";
|
|
37505
|
+
warnOnce("names-escaping-area", `"${viewTransitionName}" is a view-transition-name written inside the element marked ${TRANSITION_AREA_ATTRIBUTE}, and the page on the other side of the navigation has nothing of that name. A name lifts its element out of the pages' pictures, in every browser: with nothing to morph into, it stands where it was captured and fades on its own while the pages move.${unclipped} Give that name only for the length of the gesture it serves, or drop it while the pages move: :root:is([${TRANSITION_ATTRIBUTE}], [${ROUTE_TRAVEL_ATTRIBUTE}]) <that element> { view-transition-name: none } (with !important over an inline name).`);
|
|
37134
37506
|
return;
|
|
37135
37507
|
}
|
|
37136
37508
|
};
|
|
@@ -37275,6 +37647,8 @@ const installRouteTransitionCss = () => {
|
|
|
37275
37647
|
};
|
|
37276
37648
|
|
|
37277
37649
|
installImportMetaCssBuild(import.meta);
|
|
37650
|
+
// The pictures carrying a travel, among everything else the movement takes
|
|
37651
|
+
// along (see findLeadAnimation).
|
|
37278
37652
|
const TRAVEL_LEAD_NAMES = ["navi-route-travel"];
|
|
37279
37653
|
|
|
37280
37654
|
// A browser with no view transitions of its own has no picture of the page
|
|
@@ -37542,7 +37916,7 @@ const css$12 = /* css */`.navi_route_travel {
|
|
|
37542
37916
|
`;
|
|
37543
37917
|
|
|
37544
37918
|
/**
|
|
37545
|
-
* @type {import("
|
|
37919
|
+
* @type {import("preact").FunctionComponent<{
|
|
37546
37920
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
37547
37921
|
* axis?: "x"|"y",
|
|
37548
37922
|
* travelByDrag?: boolean,
|
|
@@ -40370,7 +40744,7 @@ const resetCommand = (source, event) => {
|
|
|
40370
40744
|
// answers: "save" stays, "delete" goes back to the list. Same shape as the
|
|
40371
40745
|
// browser's own formaction/formmethod, and the same shape the action already
|
|
40372
40746
|
// has (it is told which button requested it).
|
|
40373
|
-
const resolveAfterSend = (target, requester) => {
|
|
40747
|
+
const resolveAfterSend = (target, requester, surface) => {
|
|
40374
40748
|
const askedForByRequester = requester?.getAttribute?.("data-after-send");
|
|
40375
40749
|
if (askedForByRequester) {
|
|
40376
40750
|
return askedForByRequester;
|
|
@@ -40379,11 +40753,6 @@ const resolveAfterSend = (target, requester) => {
|
|
|
40379
40753
|
if (askedFor) {
|
|
40380
40754
|
return askedFor;
|
|
40381
40755
|
}
|
|
40382
|
-
// From above the target: a popup that IS the send target is handled on its
|
|
40383
|
-
// own (see the aria-expanded branch below), and must not answer twice.
|
|
40384
|
-
const surface = target.parentElement?.closest(
|
|
40385
|
-
`[data-slide], [aria-expanded]`,
|
|
40386
|
-
);
|
|
40387
40756
|
if (!surface) {
|
|
40388
40757
|
return undefined;
|
|
40389
40758
|
}
|
|
@@ -40395,6 +40764,11 @@ const resolveAfterSend = (target, requester) => {
|
|
|
40395
40764
|
}
|
|
40396
40765
|
return undefined;
|
|
40397
40766
|
};
|
|
40767
|
+
// The surface a send answers when the control says nothing. Found from above
|
|
40768
|
+
// the target: a popup that IS the send target is handled on its own (see the
|
|
40769
|
+
// aria-expanded branch in sendCommand), and must not answer twice.
|
|
40770
|
+
const resolveSendSurface = (target) =>
|
|
40771
|
+
target.parentElement?.closest(`[data-slide], [aria-expanded]`);
|
|
40398
40772
|
// What follows a send is caused by the send, not by the gesture: the send
|
|
40399
40773
|
// answered the gesture and may have prevented it (an Enter must not also click
|
|
40400
40774
|
// the submit button, nor activate the trigger of the popup it closed), and a
|
|
@@ -40476,20 +40850,30 @@ const sendCommand = (source, event, { requester }) => {
|
|
|
40476
40850
|
// and the popup must stay open — with the form still in front of the
|
|
40477
40851
|
// user, showing what it is waiting for.
|
|
40478
40852
|
let invalid = false;
|
|
40479
|
-
// What follows the send is
|
|
40480
|
-
//
|
|
40481
|
-
//
|
|
40482
|
-
//
|
|
40483
|
-
//
|
|
40484
|
-
//
|
|
40853
|
+
// What follows the send is aimed at the press, like a button's command
|
|
40854
|
+
// waiting for its action (see resolveNaviCommand): the send may take the
|
|
40855
|
+
// form out of the document (a form keyed on what it edits, a row that
|
|
40856
|
+
// moves once saved), and resolved from there it finds no popup to close.
|
|
40857
|
+
// Which command it is gets read again once the send went through: an
|
|
40858
|
+
// action that learned where to go from the response writes it on the
|
|
40859
|
+
// form while it runs (data-after-send). That one can only be aimed when
|
|
40860
|
+
// it is read.
|
|
40861
|
+
const surface = resolveSendSurface(target);
|
|
40862
|
+
const sentEvent = createSentEvent(event);
|
|
40863
|
+
const afterSendAtPress = resolveAfterSend(target, sendRequester, surface);
|
|
40864
|
+
const runAimedAtPress = afterSendAtPress
|
|
40865
|
+
? resolveNaviCommand(source, afterSendAtPress, sentEvent)
|
|
40866
|
+
: undefined;
|
|
40485
40867
|
const runAfterSend = () => {
|
|
40486
|
-
const afterSend = resolveAfterSend(target, sendRequester);
|
|
40868
|
+
const afterSend = resolveAfterSend(target, sendRequester, surface);
|
|
40487
40869
|
if (!afterSend) {
|
|
40488
40870
|
return;
|
|
40489
40871
|
}
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
|
|
40872
|
+
if (afterSend === afterSendAtPress) {
|
|
40873
|
+
runAimedAtPress?.();
|
|
40874
|
+
return;
|
|
40875
|
+
}
|
|
40876
|
+
triggerNaviCommand(source, afterSend, sentEvent);
|
|
40493
40877
|
};
|
|
40494
40878
|
const {
|
|
40495
40879
|
result: sent,
|
|
@@ -41444,6 +41828,12 @@ const isDeclaredInsidePopup = (target, popupId) =>
|
|
|
41444
41828
|
* Lives on the popup's own content element rather than in the backdrop's
|
|
41445
41829
|
* handler, for both renderers: the backdrop is a sibling behind the popup
|
|
41446
41830
|
* (never an ancestor), so a press inside the popup's box never reaches it.
|
|
41831
|
+
*
|
|
41832
|
+
* Inside a box that travels by drag — a row of slides walked by a swipe, a
|
|
41833
|
+
* sheet pushed back towards its edge — the region is also where that travel
|
|
41834
|
+
* starts, and at the mousedown nobody knows yet which of the two the press is.
|
|
41835
|
+
* The dismissal then waits for the click, which only comes if the press stayed
|
|
41836
|
+
* a press: a travel swallows the one it leaves behind.
|
|
41447
41837
|
*/
|
|
41448
41838
|
const handlePressOnOutsideRegion = (
|
|
41449
41839
|
mouseDownEvent,
|
|
@@ -41474,11 +41864,36 @@ const handlePressOnOutsideRegion = (
|
|
|
41474
41864
|
mouseDownEvent.preventDefault();
|
|
41475
41865
|
return;
|
|
41476
41866
|
}
|
|
41477
|
-
if (pressOutside
|
|
41478
|
-
|
|
41479
|
-
|
|
41867
|
+
if (pressOutside !== "close" && pressOutside !== "cancel") {
|
|
41868
|
+
return;
|
|
41869
|
+
}
|
|
41870
|
+
const isCancel = pressOutside === "cancel";
|
|
41871
|
+
if (isPressDisputedByDrag(target)) {
|
|
41872
|
+
onClickOfThisPress(target, (clickEvent) => {
|
|
41873
|
+
openController.requestClose(clickEvent, { isCancel });
|
|
41480
41874
|
});
|
|
41875
|
+
return;
|
|
41481
41876
|
}
|
|
41877
|
+
openController.requestClose(mouseDownEvent, { isCancel });
|
|
41878
|
+
};
|
|
41879
|
+
|
|
41880
|
+
// The click ending the press whose mousedown landed on `element`. A click goes
|
|
41881
|
+
// to what the press and the release have in common, so it reaches `element`
|
|
41882
|
+
// only when the release stayed on it or inside it. A press that never clicks
|
|
41883
|
+
// leaves it armed until the next one, and no longer — a click always comes
|
|
41884
|
+
// after a press, so it cannot reach the click of another (the rule
|
|
41885
|
+
// click_suppression.js in @jsenv/dom goes by).
|
|
41886
|
+
const onClickOfThisPress = (element, callback) => {
|
|
41887
|
+
const onClick = (clickEvent) => {
|
|
41888
|
+
disarm();
|
|
41889
|
+
callback(clickEvent);
|
|
41890
|
+
};
|
|
41891
|
+
const disarm = () => {
|
|
41892
|
+
element.removeEventListener("click", onClick);
|
|
41893
|
+
window.removeEventListener("pointerdown", disarm, { capture: true });
|
|
41894
|
+
};
|
|
41895
|
+
element.addEventListener("click", onClick);
|
|
41896
|
+
window.addEventListener("pointerdown", disarm, { capture: true });
|
|
41482
41897
|
};
|
|
41483
41898
|
|
|
41484
41899
|
/**
|
|
@@ -46825,6 +47240,9 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
46825
47240
|
* handles the timing centrally rather than each component having to manage its
|
|
46826
47241
|
* own debounce logic.
|
|
46827
47242
|
*/
|
|
47243
|
+
// Sentinel used as the initial value of lastActionValueRef.
|
|
47244
|
+
// Distinct from undefined so that undefined (e.g. unchecked radio) can itself
|
|
47245
|
+
// be stored as a valid "last action value" and trigger the dedup logic.
|
|
46828
47246
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
46829
47247
|
|
|
46830
47248
|
// The control types rendered as an element `<label for>` can name: HTML's
|
|
@@ -46924,13 +47342,18 @@ const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyD
|
|
|
46924
47342
|
* the control opens, so that a listener hears the control as a whole.
|
|
46925
47343
|
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
46926
47344
|
* host's either way: the control's own reactions to them call them.
|
|
47345
|
+
* @param {"host"|"root"} [options.nameOn] Where `aria-label` and
|
|
47346
|
+
* `aria-labelledby` land: where the role is. `"host"`, the default, for every
|
|
47347
|
+
* control whose host is what is announced; `"root"` for a control whose role
|
|
47348
|
+
* is on its root and whose host is an `aria-hidden` carrier (a wheel).
|
|
46927
47349
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
46928
47350
|
*/
|
|
46929
47351
|
const useControlProps = (props, {
|
|
46930
47352
|
controlType,
|
|
46931
47353
|
persists,
|
|
46932
47354
|
uiActionInternal,
|
|
46933
|
-
listenersOn = "host"
|
|
47355
|
+
listenersOn = "host",
|
|
47356
|
+
nameOn = "host"
|
|
46934
47357
|
}) => {
|
|
46935
47358
|
const debugUIState = useDebugUIState();
|
|
46936
47359
|
const debugAction = useDebugAction();
|
|
@@ -47019,6 +47442,7 @@ const useControlProps = (props, {
|
|
|
47019
47442
|
boundAction,
|
|
47020
47443
|
controlInfo,
|
|
47021
47444
|
listenersOn,
|
|
47445
|
+
nameOn,
|
|
47022
47446
|
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
47023
47447
|
});
|
|
47024
47448
|
{
|
|
@@ -48270,6 +48694,7 @@ const useInteractiveProps = (props, {
|
|
|
48270
48694
|
boundAction,
|
|
48271
48695
|
controlInfo,
|
|
48272
48696
|
listenersOn,
|
|
48697
|
+
nameOn,
|
|
48273
48698
|
listenersCalledByReactions
|
|
48274
48699
|
}) => {
|
|
48275
48700
|
const {
|
|
@@ -48277,6 +48702,7 @@ const useInteractiveProps = (props, {
|
|
|
48277
48702
|
} = props;
|
|
48278
48703
|
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
48279
48704
|
listenersOn,
|
|
48705
|
+
nameOn,
|
|
48280
48706
|
listenersCalledByReactions
|
|
48281
48707
|
});
|
|
48282
48708
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
@@ -48890,8 +49316,10 @@ const useInteractiveProps = (props, {
|
|
|
48890
49316
|
}
|
|
48891
49317
|
return [controlRootProps, controlHostProps];
|
|
48892
49318
|
};
|
|
49319
|
+
const NAME_ATTRIBUTE_SET = new Set(["aria-label", "aria-labelledby"]);
|
|
48893
49320
|
const splitControlProps = (props, {
|
|
48894
49321
|
listenersOn,
|
|
49322
|
+
nameOn,
|
|
48895
49323
|
listenersCalledByReactions
|
|
48896
49324
|
}) => {
|
|
48897
49325
|
const {
|
|
@@ -48902,6 +49330,10 @@ const splitControlProps = (props, {
|
|
|
48902
49330
|
};
|
|
48903
49331
|
const controlRootProps = {};
|
|
48904
49332
|
for (const key of Object.keys(props)) {
|
|
49333
|
+
if (nameOn === "root" && NAME_ATTRIBUTE_SET.has(key)) {
|
|
49334
|
+
controlRootProps[key] = props[key];
|
|
49335
|
+
continue;
|
|
49336
|
+
}
|
|
48905
49337
|
const attributeName = controlAttributeFromProp(key);
|
|
48906
49338
|
if (attributeName) {
|
|
48907
49339
|
controlHostProps[attributeName] = props[key];
|
|
@@ -51031,7 +51463,7 @@ const css$$ = /* css */`.navi_text_anchor {
|
|
|
51031
51463
|
* @param {{ size?: number, verticalAlign?: string }} [lineLayout]
|
|
51032
51464
|
* Describes the surrounding line context. Used as layout-effect dependencies so the correction
|
|
51033
51465
|
* reruns when the surrounding text's font-size or vertical-align changes.
|
|
51034
|
-
* @param {import("
|
|
51466
|
+
* @param {import("preact").RefObject} childRef — ref on the child element to reposition
|
|
51035
51467
|
*/
|
|
51036
51468
|
const TextAnchor = ({
|
|
51037
51469
|
childRef,
|
|
@@ -51693,25 +52125,25 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51693
52125
|
* See `docs/typography.md` for the decisions behind it — truncating, rows made
|
|
51694
52126
|
* of an icon, a text and an icon, and where a line may break.
|
|
51695
52127
|
*
|
|
51696
|
-
* @type {import("
|
|
51697
|
-
* children?: import("
|
|
52128
|
+
* @type {import("preact").FunctionComponent<{
|
|
52129
|
+
* children?: import("preact").ComponentChildren,
|
|
51698
52130
|
* as?: string,
|
|
51699
52131
|
* className?: string,
|
|
51700
|
-
* style?: import("
|
|
52132
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
51701
52133
|
* bold?: boolean,
|
|
51702
52134
|
* noWrap?: boolean,
|
|
51703
52135
|
* maxLines?: number,
|
|
51704
|
-
* spacing?: string | number | import("
|
|
52136
|
+
* spacing?: string | number | import("preact").ComponentChildren,
|
|
51705
52137
|
* loading?: boolean,
|
|
51706
52138
|
* skeleton?: boolean,
|
|
51707
52139
|
* attachLastChild?: boolean,
|
|
51708
52140
|
* preventSpaceUnderlines?: boolean,
|
|
51709
|
-
* holdSpaceForStyle?: import("
|
|
52141
|
+
* holdSpaceForStyle?: import("preact").JSX.CSSProperties,
|
|
51710
52142
|
* boldStable?: boolean,
|
|
51711
52143
|
* shrinkWrap?: boolean,
|
|
51712
52144
|
* capitalize?: boolean,
|
|
51713
52145
|
* selectRange?: string | [number, number] | [string, string],
|
|
51714
|
-
* childrenOutsideFlow?: import("
|
|
52146
|
+
* childrenOutsideFlow?: import("preact").ComponentChildren,
|
|
51715
52147
|
* [key: string]: any,
|
|
51716
52148
|
* }>}
|
|
51717
52149
|
*
|
|
@@ -51752,7 +52184,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51752
52184
|
* Replaces real space characters between children with padding-based spaces.
|
|
51753
52185
|
* Useful inside `<a>` elements where browsers draw an underline under spaces.
|
|
51754
52186
|
*
|
|
51755
|
-
* @param {import("
|
|
52187
|
+
* @param {import("preact").JSX.CSSProperties} [holdSpaceForStyle]
|
|
51756
52188
|
* Prevents layout shifts when text styles change (e.g. font-weight, font-size).
|
|
51757
52189
|
* Pass the CSS properties representing the "largest" visual state of the text.
|
|
51758
52190
|
* An invisible placeholder rendered with those styles reserves the space; the
|
|
@@ -51785,7 +52217,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51785
52217
|
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
51786
52218
|
* substrings selecting from the first to the end of the second.
|
|
51787
52219
|
*
|
|
51788
|
-
* @param {import("
|
|
52220
|
+
* @param {import("preact").ComponentChildren} [childrenOutsideFlow]
|
|
51789
52221
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
51790
52222
|
* internally for overlays such as the skeleton container.
|
|
51791
52223
|
*/
|
|
@@ -52073,7 +52505,7 @@ const TextWithSelectRange = ({
|
|
|
52073
52505
|
* @param {object} props
|
|
52074
52506
|
* @param {string} [props.href] - URL/id of an external SVG symbol, rendered via
|
|
52075
52507
|
* `<svg><use href></svg>`. Mutually exclusive with meaningful `children`.
|
|
52076
|
-
* @param {import("
|
|
52508
|
+
* @param {import("preact").ComponentChildren} [props.children] - Inline icon
|
|
52077
52509
|
* content: an `<svg>` element, or a string (renders as a text/emoji icon).
|
|
52078
52510
|
* @param {boolean} [props.decorative] - Marks the icon `aria-hidden`. Defaults
|
|
52079
52511
|
* to `true` for an unsized, non-interactive icon; pass `false` for a
|
|
@@ -52248,6 +52680,11 @@ const usePreloadOnIntent = (ref, href, prefetch = true) => {
|
|
|
52248
52680
|
return undefined;
|
|
52249
52681
|
}
|
|
52250
52682
|
const preload = () => {
|
|
52683
|
+
// The tab or the window it opens loads its own code: what would be
|
|
52684
|
+
// fetched here is not what runs there.
|
|
52685
|
+
if (linkOpensElsewhere(element)) {
|
|
52686
|
+
return;
|
|
52687
|
+
}
|
|
52251
52688
|
preloadUrl(href);
|
|
52252
52689
|
};
|
|
52253
52690
|
element.addEventListener("pointerenter", preload);
|
|
@@ -52456,6 +52893,15 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
52456
52893
|
};
|
|
52457
52894
|
|
|
52458
52895
|
installImportMetaCssBuild(import.meta);
|
|
52896
|
+
/*
|
|
52897
|
+
* Apply opacity to child content, not the link element itself.
|
|
52898
|
+
*
|
|
52899
|
+
* Why not apply opacity directly to .navi_link?
|
|
52900
|
+
* - Would make focus outlines semi-transparent too (accessibility issue)
|
|
52901
|
+
* - We want dimmed text but full-opacity focus indicators for visibility
|
|
52902
|
+
*
|
|
52903
|
+
* This approach dims the content while preserving focus outline visibility.
|
|
52904
|
+
*/
|
|
52459
52905
|
const css$Z = /* css */`@layer navi {
|
|
52460
52906
|
.navi_link {
|
|
52461
52907
|
--link-border-radius: unset;
|
|
@@ -52476,6 +52922,19 @@ const css$Z = /* css */`@layer navi {
|
|
|
52476
52922
|
--link-current-indicator-spacing: 0;
|
|
52477
52923
|
--link-current-indicator-color: var(--navi-link-current-indicator-color);
|
|
52478
52924
|
}
|
|
52925
|
+
|
|
52926
|
+
.navi_link[data-variant="text"], .navi_link[data-variant="icon"] {
|
|
52927
|
+
--link-color: unset;
|
|
52928
|
+
--link-text-decoration: none;
|
|
52929
|
+
}
|
|
52930
|
+
|
|
52931
|
+
.navi_link[data-variant="tab"] {
|
|
52932
|
+
--link-background-hover: color-mix(in srgb,
|
|
52933
|
+
var(--link-background, transparent),
|
|
52934
|
+
var(--x-link-contrasting-color) 15%);
|
|
52935
|
+
--link-color: unset;
|
|
52936
|
+
--link-text-decoration: none;
|
|
52937
|
+
}
|
|
52479
52938
|
}
|
|
52480
52939
|
|
|
52481
52940
|
.navi_link {
|
|
@@ -52496,7 +52955,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
52496
52955
|
--x-link-padding-right: var(--link-padding-right, var(--link-padding-x, var(--link-padding, 0px)));
|
|
52497
52956
|
--x-link-padding-bottom: var(--link-padding-bottom, var(--link-padding-y, var(--link-padding, 0px)));
|
|
52498
52957
|
--x-link-padding-left: var(--link-padding-left, var(--link-padding-x, var(--link-padding, 0px)));
|
|
52499
|
-
aspect-ratio: inherit;
|
|
52500
52958
|
padding-top: var(--x-link-padding-top);
|
|
52501
52959
|
padding-right: var(--x-link-padding-right);
|
|
52502
52960
|
padding-bottom: var(--x-link-padding-bottom);
|
|
@@ -52685,17 +53143,7 @@ const css$Z = /* css */`@layer navi {
|
|
|
52685
53143
|
margin-left: -.1em;
|
|
52686
53144
|
}
|
|
52687
53145
|
|
|
52688
|
-
&[data-variant="text"], &[data-variant="icon"] {
|
|
52689
|
-
--link-color: unset;
|
|
52690
|
-
--link-text-decoration: none;
|
|
52691
|
-
}
|
|
52692
|
-
|
|
52693
53146
|
&[data-variant="tab"] {
|
|
52694
|
-
--link-background-hover: color-mix(in srgb,
|
|
52695
|
-
var(--link-background, transparent),
|
|
52696
|
-
var(--x-link-contrasting-color) 15%);
|
|
52697
|
-
--link-color: unset;
|
|
52698
|
-
--link-text-decoration: none;
|
|
52699
53147
|
white-space: nowrap;
|
|
52700
53148
|
user-select: none;
|
|
52701
53149
|
|
|
@@ -52782,8 +53230,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52782
53230
|
*
|
|
52783
53231
|
* Href-derived behavior (all computed from `href`, recomputed on navigation):
|
|
52784
53232
|
* - internal vs external target (`data-href-internal`/`data-href-external`),
|
|
52785
|
-
* with `target`/`rel` auto-defaulted
|
|
52786
|
-
* `noopener noreferrer`
|
|
53233
|
+
* with `target`/`rel` auto-defaulted unless explicitly set: `_blank` for a
|
|
53234
|
+
* page of another site (`http:`/`https:`), `noopener noreferrer` for any
|
|
53235
|
+
* external address, nothing otherwise.
|
|
52787
53236
|
* - "current page" detection (`data-href-current`, `aria-current="page"`).
|
|
52788
53237
|
* - anchor links (`href` starting with `#`) get `data-href-anchor`.
|
|
52789
53238
|
* - an automatic end icon for `tel:`/`sms:`/`mailto:`/`github.com`, external
|
|
@@ -52845,14 +53294,14 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52845
53294
|
* text reads (see `holdSpaceForStyle` in `text.jsx`).
|
|
52846
53295
|
* @param {boolean} [props.currentEffectShadow] - Inset-shadow effect while
|
|
52847
53296
|
* current (used with `variant="tab"`).
|
|
52848
|
-
* @param {boolean|import("
|
|
53297
|
+
* @param {boolean|import("preact").ComponentChild} [props.startIcon] - Icon
|
|
52849
53298
|
* placed before the text.
|
|
52850
|
-
* @param {boolean|import("
|
|
53299
|
+
* @param {boolean|import("preact").ComponentChild} [props.endIcon] - Icon
|
|
52851
53300
|
* placed after the text; when omitted, an icon may be auto-chosen from the
|
|
52852
53301
|
* href (see above).
|
|
52853
|
-
* @param {boolean|import("
|
|
53302
|
+
* @param {boolean|import("preact").ComponentChild} [props.blankTargetIcon] -
|
|
52854
53303
|
* Override/suppress the auto external-link icon.
|
|
52855
|
-
* @param {boolean|import("
|
|
53304
|
+
* @param {boolean|import("preact").ComponentChild} [props.anchorIcon] -
|
|
52856
53305
|
* Controls the auto anchor icon shown for a childless anchor link:
|
|
52857
53306
|
* `true`/`undefined` uses the default chain SVG, `false` suppresses it, any
|
|
52858
53307
|
* other node replaces it. To render a plain `#` instead, prefer writing it as
|
|
@@ -52923,7 +53372,7 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52923
53372
|
* @param {boolean} [props.preventDefault] - Call `event.preventDefault()` on
|
|
52924
53373
|
* click (navigation suppressed; `onClick` still runs).
|
|
52925
53374
|
* @param {(event: MouseEvent) => void} [props.onClick]
|
|
52926
|
-
* @param {import("
|
|
53375
|
+
* @param {import("preact").ComponentChildren} [props.children] - Link content;
|
|
52927
53376
|
* falls back to the route's relative URL / `href` per the rules above.
|
|
52928
53377
|
* @param {string} [props.name] - Control facade name (links may be nameless).
|
|
52929
53378
|
* @param {boolean} [props.disabled]
|
|
@@ -53068,7 +53517,7 @@ const LinkPlain = props => {
|
|
|
53068
53517
|
":-navi-href-current": innerCurrent,
|
|
53069
53518
|
":-navi-selected": selected
|
|
53070
53519
|
};
|
|
53071
|
-
const innerTarget = target === undefined ?
|
|
53520
|
+
const innerTarget = target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target;
|
|
53072
53521
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
53073
53522
|
let innerEndIcon;
|
|
53074
53523
|
if (endIcon === undefined) {
|
|
@@ -53235,7 +53684,7 @@ const LinkPlain = props => {
|
|
|
53235
53684
|
},
|
|
53236
53685
|
href: href,
|
|
53237
53686
|
rel: innerRel,
|
|
53238
|
-
target: innerTarget === "_self" ? undefined :
|
|
53687
|
+
target: innerTarget === "_self" ? undefined : innerTarget
|
|
53239
53688
|
// Which slide this tab is, and which box to say it to: read by the <Nav>
|
|
53240
53689
|
// around it to place the row's own bar, and by the command above to find
|
|
53241
53690
|
// the container across the document.
|
|
@@ -53487,6 +53936,9 @@ const useSlideContainer = (target) => {
|
|
|
53487
53936
|
};
|
|
53488
53937
|
|
|
53489
53938
|
installImportMetaCssBuild(import.meta);
|
|
53939
|
+
// A name of its own for the bar under the current tab, one per nav on the page:
|
|
53940
|
+
// two bars answering to the same name are two elements claiming one picture,
|
|
53941
|
+
// and the browser refuses the whole transition rather than pick.
|
|
53490
53942
|
let navCount = 0;
|
|
53491
53943
|
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
53492
53944
|
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
@@ -53511,6 +53963,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
53511
53963
|
};
|
|
53512
53964
|
const css$Y = /* css */`@layer navi {
|
|
53513
53965
|
.navi_nav {
|
|
53966
|
+
--nav-width: fit-content;
|
|
53514
53967
|
--nav-border: none;
|
|
53515
53968
|
--nav-padding: 0px;
|
|
53516
53969
|
--nav-border-radius: 0px;
|
|
@@ -53534,7 +53987,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
53534
53987
|
var(--nav-indicator-length-delta));
|
|
53535
53988
|
--x-nav-indicator-color: var(--nav-indicator-color, var(--nav-current-indicator-color));
|
|
53536
53989
|
--x-nav-indicator-color-toward: var(--nav-indicator-color-toward, var(--x-nav-indicator-color));
|
|
53537
|
-
z-index:
|
|
53990
|
+
z-index: 2;
|
|
53538
53991
|
background: color-mix(in srgb,
|
|
53539
53992
|
var(--x-nav-indicator-color-toward)
|
|
53540
53993
|
calc(var(--slide-travel-progress) * var(--nav-indicator-toward-sign, 0) *
|
|
@@ -53591,7 +54044,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
53591
54044
|
}
|
|
53592
54045
|
|
|
53593
54046
|
.navi_nav {
|
|
53594
|
-
width:
|
|
54047
|
+
width: var(--nav-width);
|
|
53595
54048
|
padding-top: var(--nav-padding-top, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
53596
54049
|
padding-right: var(--nav-padding-right, var(--nav-padding-x, var(--nav-padding, unset)));
|
|
53597
54050
|
padding-bottom: var(--nav-padding-bottom, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
@@ -53620,12 +54073,12 @@ const css$Y = /* css */`@layer navi {
|
|
|
53620
54073
|
--x-nav-child-border-radius: calc(var(--nav-border-radius) - var(--nav-padding));
|
|
53621
54074
|
--x-nav-link-border-radius: var(--link-border-radius, var(--x-nav-child-border-radius));
|
|
53622
54075
|
|
|
53623
|
-
&:
|
|
54076
|
+
&:nth-child(1 of .navi_link) {
|
|
53624
54077
|
border-top-left-radius: var(--x-nav-link-border-radius);
|
|
53625
54078
|
border-bottom-left-radius: var(--x-nav-link-border-radius);
|
|
53626
54079
|
}
|
|
53627
54080
|
|
|
53628
|
-
&:last-child {
|
|
54081
|
+
&:nth-last-child(1 of .navi_link) {
|
|
53629
54082
|
border-top-right-radius: var(--x-nav-link-border-radius);
|
|
53630
54083
|
border-bottom-right-radius: var(--x-nav-link-border-radius);
|
|
53631
54084
|
}
|
|
@@ -53728,7 +54181,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
53728
54181
|
};
|
|
53729
54182
|
|
|
53730
54183
|
/**
|
|
53731
|
-
* @type {import("
|
|
54184
|
+
* @type {import("preact").FunctionComponent<{
|
|
53732
54185
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
53733
54186
|
* currentIndicatorSlides?: boolean,
|
|
53734
54187
|
* slideContainer?: string,
|
|
@@ -54878,7 +55331,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
54878
55331
|
};
|
|
54879
55332
|
|
|
54880
55333
|
/**
|
|
54881
|
-
* @type {import("
|
|
55334
|
+
* @type {import("preact").FunctionComponent<{
|
|
54882
55335
|
* value?: any,
|
|
54883
55336
|
* defaultValue?: any,
|
|
54884
55337
|
* onChange?: (value: any) => void,
|
|
@@ -54922,7 +55375,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
54922
55375
|
* grows the binder instead of closing in on the text.
|
|
54923
55376
|
* @param {string} [props.tabBorderColor] - The inactive tabs alone; the rest of
|
|
54924
55377
|
* the outline follows `borderColor`.
|
|
54925
|
-
* @param {import("
|
|
55378
|
+
* @param {import("preact").ComponentChildren} props.children - `Binder.Item`s.
|
|
54926
55379
|
*/
|
|
54927
55380
|
const Binder = ({
|
|
54928
55381
|
children,
|
|
@@ -55063,13 +55516,13 @@ const Binder = ({
|
|
|
55063
55516
|
/**
|
|
55064
55517
|
* @param {any} [props.value] - What identifies this item; defaults to its
|
|
55065
55518
|
* position among its siblings.
|
|
55066
|
-
* @param {import("
|
|
55519
|
+
* @param {import("preact").ComponentChildren} [props.label] - What the tab
|
|
55067
55520
|
* shows.
|
|
55068
55521
|
* @param {object} [props.route] - Makes the tab a `Link`
|
|
55069
55522
|
* to that route, and the binder follows the url: the open item is the one
|
|
55070
55523
|
* whose route matches, no `value`/`onChange` needed.
|
|
55071
55524
|
* @param {string} [props.href] - Same, for a plain url.
|
|
55072
|
-
* @param {import("
|
|
55525
|
+
* @param {import("preact").ComponentChildren} [props.children] - The page.
|
|
55073
55526
|
*
|
|
55074
55527
|
* Every other prop goes to the tab (a `Box`, or a `Link` when routed): an item
|
|
55075
55528
|
* styles and configures the control that opens it.
|
|
@@ -55479,7 +55932,7 @@ const FixedBarStyleCSSVars = {
|
|
|
55479
55932
|
};
|
|
55480
55933
|
|
|
55481
55934
|
/**
|
|
55482
|
-
* @type {import("
|
|
55935
|
+
* @type {import("preact").FunctionComponent<{
|
|
55483
55936
|
* area?: "top"|"bottom"|"left"|"right",
|
|
55484
55937
|
* width?: string|number,
|
|
55485
55938
|
* height?: string|number,
|
|
@@ -55870,7 +56323,7 @@ const css$U = /* css */`.navi_summary_marker {
|
|
|
55870
56323
|
`;
|
|
55871
56324
|
|
|
55872
56325
|
/**
|
|
55873
|
-
* @type {import("
|
|
56326
|
+
* @type {import("preact").FunctionComponent<{
|
|
55874
56327
|
* open?: boolean,
|
|
55875
56328
|
* loading?: boolean,
|
|
55876
56329
|
* openDirection?: "down" | "up" | "left",
|
|
@@ -56290,8 +56743,8 @@ const useExpandableContext = partName => {
|
|
|
56290
56743
|
};
|
|
56291
56744
|
|
|
56292
56745
|
/**
|
|
56293
|
-
* @type {import("
|
|
56294
|
-
* ui?: import("
|
|
56746
|
+
* @type {import("preact").FunctionComponent<{
|
|
56747
|
+
* ui?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
56295
56748
|
* open?: boolean,
|
|
56296
56749
|
* defaultOpen?: boolean,
|
|
56297
56750
|
* signal?: import("@preact/signals").Signal,
|
|
@@ -56931,9 +57384,9 @@ const Expandable = props => {
|
|
|
56931
57384
|
* Controls inside it keep their own behavior and do not toggle. A Box, so it
|
|
56932
57385
|
* takes the layout and style props every Box takes.
|
|
56933
57386
|
*
|
|
56934
|
-
* @type {import("
|
|
56935
|
-
* marker?: false | import("
|
|
56936
|
-
* children?: import("
|
|
57387
|
+
* @type {import("preact").FunctionComponent<{
|
|
57388
|
+
* marker?: false | import("preact").ComponentChildren,
|
|
57389
|
+
* children?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
56937
57390
|
* }>}
|
|
56938
57391
|
* @param marker - The chevron drawn before the label. `false` removes it (a UI
|
|
56939
57392
|
* part that is already an icon has no room for one); any node replaces it.
|
|
@@ -57008,7 +57461,7 @@ const ExpandableUI = ({
|
|
|
57008
57461
|
* Expandable, children may be a function `(data) => ui` or a branches object —
|
|
57009
57462
|
* see ActionRenderer.
|
|
57010
57463
|
*
|
|
57011
|
-
* @type {import("
|
|
57464
|
+
* @type {import("preact").FunctionComponent<{}>}
|
|
57012
57465
|
*/
|
|
57013
57466
|
const ExpandableContent = ({
|
|
57014
57467
|
children,
|
|
@@ -57164,6 +57617,40 @@ const createToggleEvent = open => {
|
|
|
57164
57617
|
return toggleEvent;
|
|
57165
57618
|
};
|
|
57166
57619
|
|
|
57620
|
+
/**
|
|
57621
|
+
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
57622
|
+
* Each resolver is a component function rendered in sequence (hooks are allowed).
|
|
57623
|
+
* To pass through to the next resolver, call useNextResolver() and render the
|
|
57624
|
+
* returned Next component with the desired props.
|
|
57625
|
+
* To terminate the chain early (e.g. render a specialized component), render
|
|
57626
|
+
* directly without calling Next.
|
|
57627
|
+
*
|
|
57628
|
+
* The last entry in the array is the final/target component — it receives null
|
|
57629
|
+
* from useNextResolver() indicating it is terminal.
|
|
57630
|
+
*
|
|
57631
|
+
* Usage:
|
|
57632
|
+
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
57633
|
+
* // Then inside a component render:
|
|
57634
|
+
* renderButton(props)
|
|
57635
|
+
*
|
|
57636
|
+
* A chain step is one component: a runner, defined once per position, whose
|
|
57637
|
+
* render CALLS its resolver's body rather than rendering it as a child. The
|
|
57638
|
+
* body runs synchronously inside the runner's render — its hooks are the
|
|
57639
|
+
* runner's, at the same tree position on every render — and for exactly that
|
|
57640
|
+
* long, `useNextResolver()` answers with the runner after this one (null for
|
|
57641
|
+
* the last). Nothing is handed down through a context: a chain rendered by the
|
|
57642
|
+
* hundred (a list's rows) pays one component per step, not a provider and a
|
|
57643
|
+
* wrapper around each.
|
|
57644
|
+
*
|
|
57645
|
+
* A resolver that renders ANOTHER resolver — a type resolver picking the
|
|
57646
|
+
* component for `type="date"` — cannot render it as a JSX child: that child
|
|
57647
|
+
* would render later, outside any body, with no Next to read. It renders it
|
|
57648
|
+
* with `renderResolver(Resolver, props)`, which captures the Next of the body
|
|
57649
|
+
* it is called from and hands it to a runner made for that resolver — the
|
|
57650
|
+
* child continues the chain where its parent stood. Called anywhere else, or
|
|
57651
|
+
* a `useNextResolver()` reached outside a body, throws: the chain has no
|
|
57652
|
+
* silent answer to give there.
|
|
57653
|
+
*/
|
|
57167
57654
|
const createComponentResolver = (resolvers, {
|
|
57168
57655
|
pure
|
|
57169
57656
|
} = {}) => {
|
|
@@ -57373,10 +57860,21 @@ const useAccentColorAttributes = (
|
|
|
57373
57860
|
};
|
|
57374
57861
|
|
|
57375
57862
|
installImportMetaCssBuild(import.meta);
|
|
57863
|
+
/**
|
|
57864
|
+
* We need the content to visually shrink (scale down) but the button interactive area MUST remain intact
|
|
57865
|
+
* Otherwise a click on the edges of the button cannot not trigger the click event (mouseup occurs outside the button)
|
|
57866
|
+
**/
|
|
57867
|
+
|
|
57868
|
+
/**
|
|
57869
|
+
* We have to re-define the CSS of button because getComputedStyle(button).borderColor returns
|
|
57870
|
+
* rgb(0, 0, 0) while being visually grey in chrome
|
|
57871
|
+
* So we redefine chrome styles so that loader can keep up with the actual color visible to the user
|
|
57872
|
+
*/
|
|
57376
57873
|
const css$R = /* css */`@layer navi {
|
|
57377
57874
|
.navi_button {
|
|
57378
57875
|
--button-border-radius: var(--navi-control-border-radius);
|
|
57379
57876
|
--button-border-width: var(--navi-control-border-width);
|
|
57877
|
+
--border-width: var(--button-border-width);
|
|
57380
57878
|
--button-cta-background-color: var(--navi-accent-color);
|
|
57381
57879
|
--button-outline-width: var(--navi-focus-outline-width);
|
|
57382
57880
|
--button-outline-offset: calc(-.5 * var(--button-outline-width));
|
|
@@ -57385,7 +57883,7 @@ const css$R = /* css */`@layer navi {
|
|
|
57385
57883
|
--button-padding-y-default: var(--navi-button-padding-y-default);
|
|
57386
57884
|
--button-loader-color: var(--navi-loader-color);
|
|
57387
57885
|
--button-border-color: var(--navi-control-border-color);
|
|
57388
|
-
--button-background-color: var(--
|
|
57886
|
+
--button-background-color: var(--navi-button-background-color);
|
|
57389
57887
|
--button-color: currentColor;
|
|
57390
57888
|
--button-cursor: pointer;
|
|
57391
57889
|
--button-font-size: var(--navi-control-font-size);
|
|
@@ -57418,6 +57916,92 @@ const css$R = /* css */`@layer navi {
|
|
|
57418
57916
|
line-height: var(--navi-control-line-height);
|
|
57419
57917
|
display: inline-block;
|
|
57420
57918
|
}
|
|
57919
|
+
|
|
57920
|
+
.navi_button[data-variant="discrete"] {
|
|
57921
|
+
--button-border-width: 0;
|
|
57922
|
+
--button-border-color: transparent;
|
|
57923
|
+
--button-border-color-hover: var(--button-border-color);
|
|
57924
|
+
--button-border-color-current: var(--button-border-color);
|
|
57925
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
57926
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
57927
|
+
--button-background-color: transparent;
|
|
57928
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57929
|
+
currentColor 8%,
|
|
57930
|
+
var(--button-background-color));
|
|
57931
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57932
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57933
|
+
}
|
|
57934
|
+
|
|
57935
|
+
.navi_button[data-variant="bare"] {
|
|
57936
|
+
--button-padding-x-default: 0;
|
|
57937
|
+
--button-padding-y-default: 0;
|
|
57938
|
+
--button-border-width: 0;
|
|
57939
|
+
--button-border-color: transparent;
|
|
57940
|
+
--button-border-color-hover: var(--button-border-color);
|
|
57941
|
+
--button-border-color-current: var(--button-border-color);
|
|
57942
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
57943
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
57944
|
+
--button-background-color: transparent;
|
|
57945
|
+
--button-background-color-hover: var(--button-background-color);
|
|
57946
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57947
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57948
|
+
font-size: inherit;
|
|
57949
|
+
font-family: inherit;
|
|
57950
|
+
line-height: inherit;
|
|
57951
|
+
}
|
|
57952
|
+
|
|
57953
|
+
.navi_button[data-variant="discrete-border"] {
|
|
57954
|
+
--button-background-color: transparent;
|
|
57955
|
+
--button-background-color-hover: var(--button-background-color);
|
|
57956
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57957
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57958
|
+
}
|
|
57959
|
+
|
|
57960
|
+
.navi_button[data-variant="border"] {
|
|
57961
|
+
--button-background-color: transparent;
|
|
57962
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57963
|
+
currentColor 8%,
|
|
57964
|
+
var(--button-background-color));
|
|
57965
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57966
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57967
|
+
}
|
|
57968
|
+
|
|
57969
|
+
.navi_button[data-icon] {
|
|
57970
|
+
--button-padding: 0;
|
|
57971
|
+
}
|
|
57972
|
+
|
|
57973
|
+
.navi_button[data-cta] {
|
|
57974
|
+
--button-background-color: var(--button-cta-background-color);
|
|
57975
|
+
--button-border-color: var(--button-cta-background-color);
|
|
57976
|
+
--button-color: white;
|
|
57977
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57978
|
+
var(--button-background-color) 85%,
|
|
57979
|
+
white);
|
|
57980
|
+
--button-border-color-hover: color-mix(in srgb,
|
|
57981
|
+
var(--button-border-color) 85%,
|
|
57982
|
+
white);
|
|
57983
|
+
--button-background-color-readonly: color-mix(in srgb,
|
|
57984
|
+
var(--button-background-color) 50%,
|
|
57985
|
+
white);
|
|
57986
|
+
--button-border-color-readonly: color-mix(in srgb,
|
|
57987
|
+
var(--button-border-color) 50%,
|
|
57988
|
+
white);
|
|
57989
|
+
--button-background-color-disabled: color-mix(in srgb,
|
|
57990
|
+
var(--button-background-color) 40%,
|
|
57991
|
+
white);
|
|
57992
|
+
--button-border-color-disabled: color-mix(in srgb,
|
|
57993
|
+
var(--button-border-color) 40%,
|
|
57994
|
+
white);
|
|
57995
|
+
--button-color-disabled: color-mix(in srgb,
|
|
57996
|
+
var(--button-color) 60%,
|
|
57997
|
+
transparent);
|
|
57998
|
+
}
|
|
57999
|
+
|
|
58000
|
+
.navi_button[data-content-display="contents"] {
|
|
58001
|
+
background: none;
|
|
58002
|
+
border: none;
|
|
58003
|
+
padding: 0;
|
|
58004
|
+
}
|
|
57421
58005
|
}
|
|
57422
58006
|
|
|
57423
58007
|
a.navi_button {
|
|
@@ -57434,7 +58018,6 @@ a.navi_button {
|
|
|
57434
58018
|
--x-button-color: var(--button-color);
|
|
57435
58019
|
--x-button-cursor: var(--button-cursor);
|
|
57436
58020
|
box-sizing: border-box;
|
|
57437
|
-
aspect-ratio: inherit;
|
|
57438
58021
|
color: var(--x-button-color);
|
|
57439
58022
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--button-border-radius));
|
|
57440
58023
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--button-border-radius));
|
|
@@ -57443,12 +58026,15 @@ a.navi_button {
|
|
|
57443
58026
|
cursor: var(--x-button-cursor);
|
|
57444
58027
|
touch-action: manipulation;
|
|
57445
58028
|
user-select: none;
|
|
57446
|
-
background: none;
|
|
57447
|
-
border: none;
|
|
57448
58029
|
outline: none;
|
|
57449
|
-
padding: 0;
|
|
57450
58030
|
position: relative;
|
|
57451
58031
|
|
|
58032
|
+
&:where(:not([data-content-display="contents"])) {
|
|
58033
|
+
background: none;
|
|
58034
|
+
border: none;
|
|
58035
|
+
padding: 0;
|
|
58036
|
+
}
|
|
58037
|
+
|
|
57452
58038
|
& .navi_button_content {
|
|
57453
58039
|
--x-corner-top-left-radius: initial;
|
|
57454
58040
|
--x-corner-top-right-radius: initial;
|
|
@@ -57470,8 +58056,7 @@ a.navi_button {
|
|
|
57470
58056
|
gap: inherit;
|
|
57471
58057
|
color: inherit;
|
|
57472
58058
|
vertical-align: inherit;
|
|
57473
|
-
background: var(--x-button-background);
|
|
57474
|
-
background-color: var(--x-button-background-color, var(--x-button-background));
|
|
58059
|
+
background: var(--x-button-background, var(--x-button-background-color));
|
|
57475
58060
|
border: var(--x-button-border);
|
|
57476
58061
|
border-radius: inherit;
|
|
57477
58062
|
outline-width: var(--button-outline-width);
|
|
@@ -57494,6 +58079,10 @@ a.navi_button {
|
|
|
57494
58079
|
}
|
|
57495
58080
|
}
|
|
57496
58081
|
|
|
58082
|
+
&[data-background-color] .navi_button_content {
|
|
58083
|
+
background-color: var(--x-button-background-color);
|
|
58084
|
+
}
|
|
58085
|
+
|
|
57497
58086
|
&[data-href-current] {
|
|
57498
58087
|
--x-button-border-color: var(--button-border-color-current);
|
|
57499
58088
|
--x-button-background-color: var(--button-background-color-current);
|
|
@@ -57538,7 +58127,7 @@ a.navi_button {
|
|
|
57538
58127
|
}
|
|
57539
58128
|
|
|
57540
58129
|
&[data-content-display="contents"] {
|
|
57541
|
-
&
|
|
58130
|
+
&[data-focus-visible] {
|
|
57542
58131
|
outline: var(--button-outline-width) solid var(--button-outline-color);
|
|
57543
58132
|
outline-offset: var(--button-outline-offset);
|
|
57544
58133
|
}
|
|
@@ -57564,19 +58153,6 @@ a.navi_button {
|
|
|
57564
58153
|
}
|
|
57565
58154
|
|
|
57566
58155
|
&[data-variant="discrete"] {
|
|
57567
|
-
--button-border-width: 0;
|
|
57568
|
-
--button-border-color: transparent;
|
|
57569
|
-
--button-border-color-hover: var(--button-border-color);
|
|
57570
|
-
--button-border-color-current: var(--button-border-color);
|
|
57571
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
57572
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
57573
|
-
--button-background-color: var(--button-background, transparent);
|
|
57574
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57575
|
-
currentColor 8%,
|
|
57576
|
-
var(--button-background-color));
|
|
57577
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57578
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57579
|
-
|
|
57580
58156
|
@media (hover: hover) {
|
|
57581
58157
|
&[data-pressed] {
|
|
57582
58158
|
& .navi_button_content {
|
|
@@ -57587,17 +58163,6 @@ a.navi_button {
|
|
|
57587
58163
|
}
|
|
57588
58164
|
|
|
57589
58165
|
&[data-variant="bare"] {
|
|
57590
|
-
--button-border-width: 0;
|
|
57591
|
-
--button-border-color: transparent;
|
|
57592
|
-
--button-border-color-hover: var(--button-border-color);
|
|
57593
|
-
--button-border-color-current: var(--button-border-color);
|
|
57594
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
57595
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
57596
|
-
--button-background-color: var(--button-background, transparent);
|
|
57597
|
-
--button-background-color-hover: var(--button-background-color);
|
|
57598
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57599
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57600
|
-
|
|
57601
58166
|
&[data-pressed] {
|
|
57602
58167
|
& .navi_button_content {
|
|
57603
58168
|
transform: none;
|
|
@@ -57606,10 +58171,6 @@ a.navi_button {
|
|
|
57606
58171
|
}
|
|
57607
58172
|
|
|
57608
58173
|
&[data-variant="discrete-border"] {
|
|
57609
|
-
--button-background-color: var(--button-background, transparent);
|
|
57610
|
-
--button-background-color-hover: var(--button-background-color);
|
|
57611
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57612
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57613
58174
|
--x-button-border-color: transparent;
|
|
57614
58175
|
|
|
57615
58176
|
&[data-hover] {
|
|
@@ -57621,15 +58182,6 @@ a.navi_button {
|
|
|
57621
58182
|
}
|
|
57622
58183
|
}
|
|
57623
58184
|
|
|
57624
|
-
&[data-variant="border"] {
|
|
57625
|
-
--button-background-color: var(--button-background, transparent);
|
|
57626
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57627
|
-
currentColor 8%,
|
|
57628
|
-
var(--button-background-color));
|
|
57629
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57630
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57631
|
-
}
|
|
57632
|
-
|
|
57633
58185
|
&[data-press-effect="scale"][data-pressed] {
|
|
57634
58186
|
& .navi_button_content {
|
|
57635
58187
|
transform: scale(.9);
|
|
@@ -57643,37 +58195,9 @@ a.navi_button {
|
|
|
57643
58195
|
}
|
|
57644
58196
|
|
|
57645
58197
|
&[data-icon] {
|
|
57646
|
-
--button-padding: 0;
|
|
57647
58198
|
justify-content: center;
|
|
57648
58199
|
align-items: center;
|
|
57649
58200
|
}
|
|
57650
|
-
|
|
57651
|
-
&[data-cta] {
|
|
57652
|
-
--button-background-color: var(--button-background, var(--button-cta-background-color));
|
|
57653
|
-
--button-border-color: var(--button-cta-background-color);
|
|
57654
|
-
--button-color: white;
|
|
57655
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57656
|
-
var(--button-background-color) 85%,
|
|
57657
|
-
white);
|
|
57658
|
-
--button-border-color-hover: color-mix(in srgb,
|
|
57659
|
-
var(--button-border-color) 85%,
|
|
57660
|
-
white);
|
|
57661
|
-
--button-background-color-readonly: color-mix(in srgb,
|
|
57662
|
-
var(--button-background-color) 50%,
|
|
57663
|
-
white);
|
|
57664
|
-
--button-border-color-readonly: color-mix(in srgb,
|
|
57665
|
-
var(--button-border-color) 50%,
|
|
57666
|
-
white);
|
|
57667
|
-
--button-background-color-disabled: color-mix(in srgb,
|
|
57668
|
-
var(--button-background-color) 40%,
|
|
57669
|
-
white);
|
|
57670
|
-
--button-border-color-disabled: color-mix(in srgb,
|
|
57671
|
-
var(--button-border-color) 40%,
|
|
57672
|
-
white);
|
|
57673
|
-
--button-color-disabled: color-mix(in srgb,
|
|
57674
|
-
var(--button-color) 60%,
|
|
57675
|
-
transparent);
|
|
57676
|
-
}
|
|
57677
58201
|
}
|
|
57678
58202
|
`;
|
|
57679
58203
|
const ButtonUI = props => {
|
|
@@ -57802,6 +58326,7 @@ const ButtonUI = props => {
|
|
|
57802
58326
|
"data-press-effect": pressEffect,
|
|
57803
58327
|
"data-icon": icon ? "" : undefined,
|
|
57804
58328
|
"data-cta": cta ? "" : undefined,
|
|
58329
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
57805
58330
|
"data-callout-arrow-x": "center"
|
|
57806
58331
|
// style management
|
|
57807
58332
|
,
|
|
@@ -58114,7 +58639,7 @@ const ButtonWithHref = props => {
|
|
|
58114
58639
|
} = getHrefTargetInfo(href, documentUrlSignal.peek());
|
|
58115
58640
|
return jsx(Next, {
|
|
58116
58641
|
...props,
|
|
58117
|
-
target: target === undefined ?
|
|
58642
|
+
target: target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target,
|
|
58118
58643
|
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
58119
58644
|
// Under whatever the caller already says: a route knows better whether
|
|
58120
58645
|
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
@@ -58274,7 +58799,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58274
58799
|
};
|
|
58275
58800
|
|
|
58276
58801
|
/**
|
|
58277
|
-
* @type {import("
|
|
58802
|
+
* @type {import("preact").FunctionComponent<{
|
|
58278
58803
|
* selfInteractions?: string,
|
|
58279
58804
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
58280
58805
|
* replace?: boolean,
|
|
@@ -58356,7 +58881,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58356
58881
|
* more: the work may already have happened on the other side (see
|
|
58357
58882
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
58358
58883
|
* where the screen can be re-opened on what is actually there.
|
|
58359
|
-
* @param {boolean|import("
|
|
58884
|
+
* @param {boolean|import("preact").ComponentChildren} [confirm] Ask before
|
|
58360
58885
|
* doing it, in the place the button stands: the first press replaces the
|
|
58361
58886
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
58362
58887
|
* does what the button was for. `true` asks navi's default question. The
|
|
@@ -58367,9 +58892,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58367
58892
|
* question up, its message on the button that asked. Use `<Picker
|
|
58368
58893
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
58369
58894
|
* or when the row has no space for it.
|
|
58370
|
-
* @param {import("
|
|
58895
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] Label of the
|
|
58371
58896
|
* second press.
|
|
58372
|
-
* @param {import("
|
|
58897
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Label of the way
|
|
58373
58898
|
* out of the question.
|
|
58374
58899
|
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
58375
58900
|
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
@@ -58468,13 +58993,13 @@ const css$P = /* css */`.navi_control_swap {
|
|
|
58468
58993
|
`;
|
|
58469
58994
|
|
|
58470
58995
|
/**
|
|
58471
|
-
* @type {import("
|
|
58996
|
+
* @type {import("preact").FunctionComponent<{
|
|
58472
58997
|
* value?: string,
|
|
58473
58998
|
* defaultValue?: string,
|
|
58474
58999
|
* signal?: import("@preact/signals").Signal<string>,
|
|
58475
59000
|
* onChange?: (name: string, event: Event) => void,
|
|
58476
59001
|
* animation?: boolean,
|
|
58477
|
-
* children?: import("
|
|
59002
|
+
* children?: import("preact").ComponentChildren,
|
|
58478
59003
|
* }>}
|
|
58479
59004
|
* @param value - The name of the side holding the floor, driven from outside:
|
|
58480
59005
|
* the row swaps to match every change of it, and a press on a cap can still
|
|
@@ -58621,13 +59146,13 @@ const ControlSwap = props => {
|
|
|
58621
59146
|
* reads these and draws the caps at its ends, the controls between them. The
|
|
58622
59147
|
* props below say what the side IS; every other prop describes its cap.
|
|
58623
59148
|
*
|
|
58624
|
-
* @type {import("
|
|
59149
|
+
* @type {import("preact").FunctionComponent<{
|
|
58625
59150
|
* name?: string,
|
|
58626
|
-
* icon: import("
|
|
59151
|
+
* icon: import("preact").ComponentChildren,
|
|
58627
59152
|
* label: string,
|
|
58628
|
-
* badge?: boolean | import("
|
|
59153
|
+
* badge?: boolean | import("preact").ComponentChildren,
|
|
58629
59154
|
* autoFocus?: boolean,
|
|
58630
|
-
* children?: import("
|
|
59155
|
+
* children?: import("preact").ComponentChildren,
|
|
58631
59156
|
* [key: string]: any,
|
|
58632
59157
|
* }>}
|
|
58633
59158
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
@@ -58835,6 +59360,51 @@ const createAvailableConstraint = (
|
|
|
58835
59360
|
};
|
|
58836
59361
|
};
|
|
58837
59362
|
|
|
59363
|
+
/**
|
|
59364
|
+
* ControlGroup — a named sub-group of form controls whose combined state is
|
|
59365
|
+
* aggregated into a single key/value object, exactly like a Form but without
|
|
59366
|
+
* the <form> element semantics.
|
|
59367
|
+
*
|
|
59368
|
+
* Use it when you need a self-contained cluster of inputs that:
|
|
59369
|
+
* - produce one composite value (an object) as their joint state
|
|
59370
|
+
* - can carry their own action / uiAction / command
|
|
59371
|
+
* - may live inside or outside a <Form>
|
|
59372
|
+
*
|
|
59373
|
+
* Props:
|
|
59374
|
+
* name — the key under which the composite value is registered in a
|
|
59375
|
+
* parent Form (optional when used standalone)
|
|
59376
|
+
* action — called with the aggregated object value when any child changes
|
|
59377
|
+
* command — navi command string (e.g. "--navi-send")
|
|
59378
|
+
* disabled — propagates to all children
|
|
59379
|
+
* readOnly — propagates to all children
|
|
59380
|
+
* as — HTML element to render (default "div")
|
|
59381
|
+
* children — the controls that belong to this group
|
|
59382
|
+
*
|
|
59383
|
+
* aggregateChildStates / distributeChildUIState
|
|
59384
|
+
* — what the group is worth, in both directions, when that is not
|
|
59385
|
+
* simply "one key per child": two wheels that add up to a number
|
|
59386
|
+
* of minutes, three fields that make one date. With them the
|
|
59387
|
+
* group takes and hands back a single value, so it can be driven
|
|
59388
|
+
* by one `value`/`signal` like any other control. Same mechanism
|
|
59389
|
+
* InputDuration uses for its own hour/minute/second fields (see
|
|
59390
|
+
* input_duration.jsx). `name` is unaffected: it still says under
|
|
59391
|
+
* which key the group's value — whatever shape it takes —
|
|
59392
|
+
* lands in the form around it.
|
|
59393
|
+
*
|
|
59394
|
+
* distributeChildStates
|
|
59395
|
+
* — the same way down, asked ONCE for all the children at a time:
|
|
59396
|
+
* `(groupValue, children) => Map<child, state>`. The plural of
|
|
59397
|
+
* `distributeChildUIState`, and the mirror of
|
|
59398
|
+
* `aggregateChildStates`, which already sees every child. For a
|
|
59399
|
+
* group whose children cannot be placed one at a time — four
|
|
59400
|
+
* seats where who sits down decides who moves, a row where the
|
|
59401
|
+
* first answer changes what the others may show. A child the Map
|
|
59402
|
+
* does not name is left where it is. Given both, this one wins.
|
|
59403
|
+
* It also closes the loop: the group holds ONE answer its
|
|
59404
|
+
* children are views of, so when one of them speaks the others
|
|
59405
|
+
* are placed again (the one that acted is left where the user
|
|
59406
|
+
* put it).
|
|
59407
|
+
*/
|
|
58838
59408
|
const ControlGroup = props => {
|
|
58839
59409
|
const defaultRef = useRef();
|
|
58840
59410
|
props.ref = props.ref || defaultRef;
|
|
@@ -59027,6 +59597,41 @@ const css$N = /* css */`@layer navi {
|
|
|
59027
59597
|
var(--button-background-color) 95%,
|
|
59028
59598
|
black);
|
|
59029
59599
|
}
|
|
59600
|
+
|
|
59601
|
+
.navi_checkbox[data-variant="switch"] {
|
|
59602
|
+
--margin: var(--switch-margin);
|
|
59603
|
+
--width: var(--switch-outer-width);
|
|
59604
|
+
--height: unset;
|
|
59605
|
+
--border-radius: var(--switch-border-radius);
|
|
59606
|
+
--background-color: var(--switch-background-color);
|
|
59607
|
+
--background-color-hover: var(--switch-background-color-hover);
|
|
59608
|
+
--background-color-readonly: var(--switch-background-color-readonly);
|
|
59609
|
+
--background-color-disabled: var(--switch-background-color-disabled);
|
|
59610
|
+
--background-color-checked: var(--switch-background-color-checked);
|
|
59611
|
+
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
59612
|
+
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
59613
|
+
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
59614
|
+
}
|
|
59615
|
+
|
|
59616
|
+
.navi_checkbox[data-variant="icon"] {
|
|
59617
|
+
--margin: 0;
|
|
59618
|
+
--width: auto;
|
|
59619
|
+
--height: auto;
|
|
59620
|
+
}
|
|
59621
|
+
|
|
59622
|
+
.navi_checkbox[data-variant="button"] {
|
|
59623
|
+
--margin: 0;
|
|
59624
|
+
--width: auto;
|
|
59625
|
+
--height: auto;
|
|
59626
|
+
--border-color: var(--button-border-color);
|
|
59627
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
59628
|
+
--background-color: var(--button-background-color);
|
|
59629
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
59630
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
59631
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
59632
|
+
--border-color-checked: var(--button-border-color);
|
|
59633
|
+
--background-color-checked: var(--button-background-color);
|
|
59634
|
+
}
|
|
59030
59635
|
}
|
|
59031
59636
|
|
|
59032
59637
|
.navi_checkbox {
|
|
@@ -59167,18 +59772,6 @@ const css$N = /* css */`@layer navi {
|
|
|
59167
59772
|
|
|
59168
59773
|
&[data-variant="switch"] {
|
|
59169
59774
|
--switch-outer-width: calc(var(--switch-width) + var(--switch-padding));
|
|
59170
|
-
--margin: var(--switch-margin);
|
|
59171
|
-
--width: var(--switch-outer-width);
|
|
59172
|
-
--height: unset;
|
|
59173
|
-
--border-radius: var(--switch-border-radius);
|
|
59174
|
-
--background-color: var(--switch-background-color);
|
|
59175
|
-
--background-color-hover: var(--switch-background-color-hover);
|
|
59176
|
-
--background-color-readonly: var(--switch-background-color-readonly);
|
|
59177
|
-
--background-color-disabled: var(--switch-background-color-disabled);
|
|
59178
|
-
--background-color-checked: var(--switch-background-color-checked);
|
|
59179
|
-
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
59180
|
-
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
59181
|
-
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
59182
59775
|
min-width: var(--switch-outer-width);
|
|
59183
59776
|
padding: var(--switch-padding);
|
|
59184
59777
|
background-color: var(--x-background-color);
|
|
@@ -59188,25 +59781,11 @@ const css$N = /* css */`@layer navi {
|
|
|
59188
59781
|
}
|
|
59189
59782
|
|
|
59190
59783
|
&[data-variant="icon"] {
|
|
59191
|
-
--margin: 0;
|
|
59192
|
-
--width: auto;
|
|
59193
|
-
--height: auto;
|
|
59194
59784
|
background: none;
|
|
59195
59785
|
border: none;
|
|
59196
59786
|
}
|
|
59197
59787
|
|
|
59198
59788
|
&[data-variant="button"] {
|
|
59199
|
-
--margin: 0;
|
|
59200
|
-
--width: auto;
|
|
59201
|
-
--height: auto;
|
|
59202
|
-
--border-color: var(--button-border-color);
|
|
59203
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
59204
|
-
--background-color: var(--button-background-color);
|
|
59205
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
59206
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
59207
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
59208
|
-
--border-color-checked: var(--button-border-color);
|
|
59209
|
-
--background-color-checked: var(--button-background-color);
|
|
59210
59789
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
59211
59790
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
59212
59791
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -59403,7 +59982,7 @@ const css$M = /* css */`@layer navi {
|
|
|
59403
59982
|
[navi-field] {
|
|
59404
59983
|
--spacing-with-control: var(--navi-xs);
|
|
59405
59984
|
|
|
59406
|
-
& .navi_checkbox, & .navi_radio {
|
|
59985
|
+
& .navi_checkbox:not([data-variant="switch"]), & .navi_radio {
|
|
59407
59986
|
--margin: 0;
|
|
59408
59987
|
}
|
|
59409
59988
|
}
|
|
@@ -59758,6 +60337,28 @@ const css$L = /* css */`@layer navi {
|
|
|
59758
60337
|
--button-border-color-disabled: var(--border-color-readonly);
|
|
59759
60338
|
--button-background-color-disabled: var(--background-color-readonly);
|
|
59760
60339
|
}
|
|
60340
|
+
|
|
60341
|
+
.navi_radio[data-variant="icon"] {
|
|
60342
|
+
--width: auto;
|
|
60343
|
+
--height: auto;
|
|
60344
|
+
--outline-offset: 2px;
|
|
60345
|
+
--outline-width: 2px;
|
|
60346
|
+
}
|
|
60347
|
+
|
|
60348
|
+
.navi_radio[data-variant="button"] {
|
|
60349
|
+
--margin: 0;
|
|
60350
|
+
--outline-offset: 0px;
|
|
60351
|
+
--width: auto;
|
|
60352
|
+
--height: auto;
|
|
60353
|
+
--border-color: var(--button-border-color);
|
|
60354
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
60355
|
+
--background-color: var(--button-background-color);
|
|
60356
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
60357
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
60358
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
60359
|
+
--border-color-checked: var(--button-border-color);
|
|
60360
|
+
--background-color-checked: var(--button-background-color);
|
|
60361
|
+
}
|
|
59761
60362
|
}
|
|
59762
60363
|
|
|
59763
60364
|
.navi_radio {
|
|
@@ -59913,26 +60514,7 @@ const css$L = /* css */`@layer navi {
|
|
|
59913
60514
|
}
|
|
59914
60515
|
}
|
|
59915
60516
|
|
|
59916
|
-
&[data-variant="icon"] {
|
|
59917
|
-
--width: auto;
|
|
59918
|
-
--height: auto;
|
|
59919
|
-
--outline-offset: 2px;
|
|
59920
|
-
--outline-width: 2px;
|
|
59921
|
-
}
|
|
59922
|
-
|
|
59923
60517
|
&[data-variant="button"] {
|
|
59924
|
-
--margin: 0;
|
|
59925
|
-
--outline-offset: 0px;
|
|
59926
|
-
--width: auto;
|
|
59927
|
-
--height: auto;
|
|
59928
|
-
--border-color: var(--button-border-color);
|
|
59929
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
59930
|
-
--background-color: var(--button-background-color);
|
|
59931
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
59932
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
59933
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
59934
|
-
--border-color-checked: var(--button-border-color);
|
|
59935
|
-
--background-color-checked: var(--button-background-color);
|
|
59936
60518
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
59937
60519
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
59938
60520
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -60156,6 +60738,7 @@ const css$K = /* css */`@layer navi {
|
|
|
60156
60738
|
--outline-width: var(--navi-focus-outline-width);
|
|
60157
60739
|
--outline-offset: var(--outline-width);
|
|
60158
60740
|
--outline-color: var(--navi-focus-outline-color);
|
|
60741
|
+
--margin: 2px;
|
|
60159
60742
|
--height: 8px;
|
|
60160
60743
|
--thumb-size: 16px;
|
|
60161
60744
|
--thumb-width: var(--thumb-size);
|
|
@@ -60231,6 +60814,7 @@ const css$K = /* css */`@layer navi {
|
|
|
60231
60814
|
box-sizing: border-box;
|
|
60232
60815
|
width: fit-content;
|
|
60233
60816
|
height: var(--height);
|
|
60817
|
+
margin: var(--margin);
|
|
60234
60818
|
font-size: var(--font-size);
|
|
60235
60819
|
font-family: var(--font-family);
|
|
60236
60820
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--outline-border-radius));
|
|
@@ -60243,7 +60827,6 @@ const css$K = /* css */`@layer navi {
|
|
|
60243
60827
|
outline-offset: var(--outline-offset);
|
|
60244
60828
|
flex-direction: row;
|
|
60245
60829
|
align-items: center;
|
|
60246
|
-
margin: 2px;
|
|
60247
60830
|
position: relative;
|
|
60248
60831
|
|
|
60249
60832
|
& .navi_control_input {
|
|
@@ -60540,6 +61123,45 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60540
61123
|
grey);
|
|
60541
61124
|
--color-disabled: var(--color-dimmed);
|
|
60542
61125
|
}
|
|
61126
|
+
|
|
61127
|
+
.navi_input[data-background-transparent] {
|
|
61128
|
+
--background-color-hover: var(--background-color);
|
|
61129
|
+
--background-color-focus: var(--navi-surface-color);
|
|
61130
|
+
}
|
|
61131
|
+
|
|
61132
|
+
.navi_input[data-variant="discrete"], .navi_input[data-variant="discrete-border"] {
|
|
61133
|
+
--background-color: transparent;
|
|
61134
|
+
--background-color-hover: var(--background-color);
|
|
61135
|
+
--background-color-focus: var(--navi-surface-color);
|
|
61136
|
+
|
|
61137
|
+
&[data-background] {
|
|
61138
|
+
--background-color-focus: transparent;
|
|
61139
|
+
}
|
|
61140
|
+
}
|
|
61141
|
+
|
|
61142
|
+
.navi_input[data-variant="discrete"] {
|
|
61143
|
+
--border-color: transparent;
|
|
61144
|
+
}
|
|
61145
|
+
|
|
61146
|
+
.navi_input[data-variant="text"] {
|
|
61147
|
+
--background-color: transparent;
|
|
61148
|
+
--background-color-hover: var(--background-color);
|
|
61149
|
+
--background-color-focus: var(--background-color);
|
|
61150
|
+
--background-color-readonly: var(--background-color);
|
|
61151
|
+
--background-color-disabled: var(--background-color);
|
|
61152
|
+
--border-color: transparent;
|
|
61153
|
+
--border-color-hover: var(--border-color);
|
|
61154
|
+
--border-color-readonly: var(--border-color);
|
|
61155
|
+
--border-color-disabled: var(--border-color);
|
|
61156
|
+
}
|
|
61157
|
+
|
|
61158
|
+
.navi_input[data-variant="underline"] {
|
|
61159
|
+
--background-color: transparent;
|
|
61160
|
+
--background-color-hover: var(--background-color);
|
|
61161
|
+
--background-color-focus: var(--background-color);
|
|
61162
|
+
--background-color-readonly: var(--background-color);
|
|
61163
|
+
--background-color-disabled: var(--background-color);
|
|
61164
|
+
}
|
|
60543
61165
|
}
|
|
60544
61166
|
|
|
60545
61167
|
.navi_input {
|
|
@@ -60691,19 +61313,12 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60691
61313
|
}
|
|
60692
61314
|
|
|
60693
61315
|
&[data-background-transparent] {
|
|
60694
|
-
--background-color-hover: var(--background-color);
|
|
60695
|
-
--background-color-focus: var(--navi-surface-color);
|
|
60696
|
-
|
|
60697
61316
|
&[data-focus] {
|
|
60698
61317
|
--x-background-color: var(--background-color-focus);
|
|
60699
61318
|
}
|
|
60700
61319
|
}
|
|
60701
61320
|
|
|
60702
61321
|
&[data-variant="discrete"], &[data-variant="discrete-border"] {
|
|
60703
|
-
--background-color: transparent;
|
|
60704
|
-
--background-color-hover: var(--background-color);
|
|
60705
|
-
--background-color-focus: var(--navi-surface-color);
|
|
60706
|
-
|
|
60707
61322
|
&[data-focus] {
|
|
60708
61323
|
--x-background-color: var(--background-color-focus);
|
|
60709
61324
|
}
|
|
@@ -60711,15 +61326,9 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60711
61326
|
&[data-readonly], &[data-disabled] {
|
|
60712
61327
|
--x-background-color: var(--background-color);
|
|
60713
61328
|
}
|
|
60714
|
-
|
|
60715
|
-
&[data-background] {
|
|
60716
|
-
--background-color-focus: transparent;
|
|
60717
|
-
}
|
|
60718
61329
|
}
|
|
60719
61330
|
|
|
60720
61331
|
&[data-variant="discrete"] {
|
|
60721
|
-
--border-color: transparent;
|
|
60722
|
-
|
|
60723
61332
|
&[data-focus] {
|
|
60724
61333
|
--x-border-color: color-mix(in srgb,
|
|
60725
61334
|
var(--border-color) 55%,
|
|
@@ -60728,24 +61337,10 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60728
61337
|
}
|
|
60729
61338
|
|
|
60730
61339
|
&[data-variant="text"] {
|
|
60731
|
-
--background-color: transparent;
|
|
60732
|
-
--background-color-hover: var(--background-color);
|
|
60733
|
-
--background-color-focus: var(--background-color);
|
|
60734
|
-
--background-color-readonly: var(--background-color);
|
|
60735
|
-
--background-color-disabled: var(--background-color);
|
|
60736
|
-
--border-color: transparent;
|
|
60737
|
-
--border-color-hover: var(--border-color);
|
|
60738
|
-
--border-color-readonly: var(--border-color);
|
|
60739
|
-
--border-color-disabled: var(--border-color);
|
|
60740
61340
|
cursor: inherit;
|
|
60741
61341
|
}
|
|
60742
61342
|
|
|
60743
61343
|
&[data-variant="underline"] {
|
|
60744
|
-
--background-color: transparent;
|
|
60745
|
-
--background-color-hover: var(--background-color);
|
|
60746
|
-
--background-color-focus: var(--background-color);
|
|
60747
|
-
--background-color-readonly: var(--background-color);
|
|
60748
|
-
--background-color-disabled: var(--background-color);
|
|
60749
61344
|
border: none;
|
|
60750
61345
|
border-radius: 0;
|
|
60751
61346
|
padding-left: 0;
|
|
@@ -61941,6 +62536,38 @@ const InputStyleCSSVars = {
|
|
|
61941
62536
|
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value"];
|
|
61942
62537
|
const InputPseudoElements = ["::-navi-loader"];
|
|
61943
62538
|
|
|
62539
|
+
/**
|
|
62540
|
+
* @type {import("preact").FunctionComponent<{
|
|
62541
|
+
* type?: string,
|
|
62542
|
+
* icon?: import("preact").ComponentChildren | null,
|
|
62543
|
+
* clearable?: boolean,
|
|
62544
|
+
* value?: any,
|
|
62545
|
+
* boolean?: boolean,
|
|
62546
|
+
* selectOnPress?: boolean,
|
|
62547
|
+
* [key: string]: any,
|
|
62548
|
+
* }>}
|
|
62549
|
+
* @param {any} [value] On a checkbox, what it is worth checked (`"on"` when
|
|
62550
|
+
* left out); unchecked it is `undefined`. See control_value.md, "What a
|
|
62551
|
+
* checkbox is worth".
|
|
62552
|
+
* @param {boolean} [boolean] Makes a checkbox a yes/no: `true` checked, `false`
|
|
62553
|
+
* unchecked, everywhere it hands its value. Takes no `value`.
|
|
62554
|
+
* @param {import("preact").ComponentChildren|null} [icon] What the textual
|
|
62555
|
+
* types that draw an icon (`search`, `email`, `tel`) put in their slot: left
|
|
62556
|
+
* out, the type's own glyph; `null`, nothing — for a field an application
|
|
62557
|
+
* draws its own magnifier beside, where a second one two centimetres away
|
|
62558
|
+
* would be one too many; anything else is drawn instead. A `search` field
|
|
62559
|
+
* swaps that slot for the clear cross as soon as it holds a value, whatever
|
|
62560
|
+
* this says.
|
|
62561
|
+
* @param {boolean} [clearable] Gives any other type the same slot: the icon
|
|
62562
|
+
* while empty (none, unless `icon` says otherwise), the clear cross once
|
|
62563
|
+
* filled.
|
|
62564
|
+
* @param {boolean} [selectOnPress] For a textual field whose value is usually
|
|
62565
|
+
* replaced rather than edited, like a browser's address bar: the first press
|
|
62566
|
+
* after the field took the focus selects its whole text once released, with a
|
|
62567
|
+
* mouse or a finger, and a second press places the caret. A focus that came
|
|
62568
|
+
* without a press (a dialog opening, Tab, code) selects nothing; the first key
|
|
62569
|
+
* that types selects first, so it replaces the text.
|
|
62570
|
+
*/
|
|
61944
62571
|
const Input = props => {
|
|
61945
62572
|
resolveInputProps(props);
|
|
61946
62573
|
const {
|
|
@@ -62030,7 +62657,7 @@ const TextareaStyleCSSVars = {
|
|
|
62030
62657
|
};
|
|
62031
62658
|
|
|
62032
62659
|
/**
|
|
62033
|
-
* @type {import("
|
|
62660
|
+
* @type {import("preact").FunctionComponent<{
|
|
62034
62661
|
* value?: string,
|
|
62035
62662
|
* defaultValue?: string,
|
|
62036
62663
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -62137,7 +62764,7 @@ const Textarea = ({
|
|
|
62137
62764
|
* draws: put it under the box, in a form footer, next to a label, and feed it
|
|
62138
62765
|
* the same value or signal as the textarea.
|
|
62139
62766
|
*
|
|
62140
|
-
* @type {import("
|
|
62767
|
+
* @type {import("preact").FunctionComponent<{
|
|
62141
62768
|
* value?: string,
|
|
62142
62769
|
* signal?: import("@preact/signals").Signal<string>,
|
|
62143
62770
|
* maxLength?: number,
|
|
@@ -62370,8 +62997,8 @@ const useEditionController = () => {
|
|
|
62370
62997
|
* it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
|
|
62371
62998
|
* the demo `control/demos/action/8_editable_demo.html` does it with props.
|
|
62372
62999
|
*
|
|
62373
|
-
* @type {import("
|
|
62374
|
-
* children?: import("
|
|
63000
|
+
* @type {import("preact").FunctionComponent<{
|
|
63001
|
+
* children?: import("preact").ComponentChildren,
|
|
62375
63002
|
* action: Function | object,
|
|
62376
63003
|
* editing?: { event?: Event } | null,
|
|
62377
63004
|
* onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
|
|
@@ -62734,6 +63361,77 @@ const commitSubtree = (controller, e) => {
|
|
|
62734
63361
|
* <form> element (FormNested).
|
|
62735
63362
|
*/
|
|
62736
63363
|
|
|
63364
|
+
/**
|
|
63365
|
+
* @param {object} props
|
|
63366
|
+
* @param {boolean} [props.standalone] - Its value is its own: the form does not
|
|
63367
|
+
* register with the control group around it (a Picker, a ControlGroup), so
|
|
63368
|
+
* what is typed in it never becomes part of that group's value, and nothing
|
|
63369
|
+
* that group does — distributing a value, resetting, cascading validation —
|
|
63370
|
+
* reaches it. For a form that lives INSIDE something else while answering a
|
|
63371
|
+
* different question — "create the thing I am about to pick" inside a picker,
|
|
63372
|
+
* say. Every control takes the same prop, for the same reason. A form inside
|
|
63373
|
+
* another form is that by construction and does not need the prop: the group
|
|
63374
|
+
* it would register into is a form, which never owns another form's fields.
|
|
63375
|
+
* @param {boolean} [props.canSendWhileUnchanged] - Send even when nothing changed. By
|
|
63376
|
+
* default a form only acts on an answer that is actually new: submitting a
|
|
63377
|
+
* form nobody touched — one just rendered, one reopened and left alone — runs
|
|
63378
|
+
* no action (a field still showing its `defaultValue` is an answer: confirming
|
|
63379
|
+
* a suggestion), and after the first submission it is that value the next
|
|
63380
|
+
* one is compared against. Everything around the action still happens either
|
|
63381
|
+
* way: the constraints are checked, and what follows the send still follows
|
|
63382
|
+
* it (the slide moves on, the popup closes) — the user is done regardless of
|
|
63383
|
+
* whether there was anything to send. Set this for a form where sending the
|
|
63384
|
+
* same thing twice is the point — a single button that fires off a
|
|
63385
|
+
* notification, an action whose duplicates are fine. See also
|
|
63386
|
+
* `readOnlyWhileFormUnchanged` on `Button`, for a submit that should say it
|
|
63387
|
+
* is waiting rather than accept a press that sends nothing.
|
|
63388
|
+
* @param {boolean} [props.actionAbortable] - The person waiting may give up on
|
|
63389
|
+
* the send: closing the popup this form holds calls the run off and goes
|
|
63390
|
+
* through, instead of being refused. For a send whose answer may never come —
|
|
63391
|
+
* a request over a network that stops answering — where a sheet with no way
|
|
63392
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
63393
|
+
* more: the write may already have landed on the other side (see
|
|
63394
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
63395
|
+
* where the screen can be re-opened on what is actually there. Every control
|
|
63396
|
+
* inside the form inherits it along with the wait itself.
|
|
63397
|
+
* @param {boolean} [props.optimistic] - The send is taken as done on the form's
|
|
63398
|
+
* own word: no busy state, and what follows it (`command="--navi-close"`, a
|
|
63399
|
+
* slide moving on) runs at once, the run going on behind. A failure then has
|
|
63400
|
+
* no sheet to be read in: `resetOnError` puts the form back and the error
|
|
63401
|
+
* callout is drawn on what surrounds the closed sheet. For a write not worth being
|
|
63402
|
+
* waited for — a score, a note — never for one whose refusal changes what the
|
|
63403
|
+
* person does next (see
|
|
63404
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
63405
|
+
* @param {any} [props.pristineKey] - What the form is measured against, taken
|
|
63406
|
+
* again every time this changes. A form knows what it holds as soon as its
|
|
63407
|
+
* fields have registered, which is the right moment for a form whose values
|
|
63408
|
+
* are there on the first render — and never the right one for a screen that
|
|
63409
|
+
* modifies something: the resource arrives a request later and fills the
|
|
63410
|
+
* fields, and a form comparing against what it held BEFORE that opens already
|
|
63411
|
+
* changed. Pass whatever says the filling is done (a boolean, the resource
|
|
63412
|
+
* itself, a count of what has loaded) and what the fields carry at that
|
|
63413
|
+
* moment becomes the reference — including the fields that settle in a render
|
|
63414
|
+
* of their own, so there is no tick to wait for on the caller's side. Change
|
|
63415
|
+
* it once, when the screen is ready: taken again after someone started
|
|
63416
|
+
* typing, it would call what they wrote the reference.
|
|
63417
|
+
* @param {string} [props.command] - What follows a submission that went
|
|
63418
|
+
* through: the form has answered its question, and this says what the screen
|
|
63419
|
+
* does about it. Nothing runs when the submission is refused — the form then
|
|
63420
|
+
* stays in front of the user, showing what it is waiting for.
|
|
63421
|
+
*
|
|
63422
|
+
* `"--navi-close"` dismisses the popup the form is in,
|
|
63423
|
+
* `"--navi-left"`/`"--navi-right"`/`"--navi-up"`/`"--navi-down"` move on the
|
|
63424
|
+
* slide map it is in, `"--navi-nav-to:/the/url"` takes the user to a page,
|
|
63425
|
+
* `"--navi-void"` stays put — the one for a form inside a popup that must
|
|
63426
|
+
* stay open once it is sent (a side panel one keeps editing in).
|
|
63427
|
+
* Any navi command, really: it is triggered from the form, so it finds its
|
|
63428
|
+
* target the way that command always does.
|
|
63429
|
+
*
|
|
63430
|
+
* Left out, the surface the form sits in decides (see resolveAfterSend in
|
|
63431
|
+
* commands.js): a popup closes, a slide goes on to the next one — or back to
|
|
63432
|
+
* the one it came from when there is no next — and a form on a page does
|
|
63433
|
+
* nothing.
|
|
63434
|
+
*/
|
|
62737
63435
|
const Form = props => {
|
|
62738
63436
|
const defaultRef = useRef();
|
|
62739
63437
|
props.ref = props.ref || defaultRef;
|
|
@@ -63070,6 +63768,7 @@ const useRequestSubmitThroughGates = ref => {
|
|
|
63070
63768
|
installImportMetaCssBuild(import.meta);
|
|
63071
63769
|
const css$H = /* css */`.navi_group {
|
|
63072
63770
|
--group-border-width: var(--navi-control-border-width);
|
|
63771
|
+
--border-width: initial;
|
|
63073
63772
|
|
|
63074
63773
|
& > :not([navi-out-of-flow]) {
|
|
63075
63774
|
&:hover, &[data-hover] {
|
|
@@ -66329,6 +67028,122 @@ SlideContainer.Down = SlideDown;
|
|
|
66329
67028
|
SlideContainer.First = SlideFirst;
|
|
66330
67029
|
SlideContainer.Last = SlideLast;
|
|
66331
67030
|
|
|
67031
|
+
/**
|
|
67032
|
+
* Displays a date/time value in a human-readable format using the current locale.
|
|
67033
|
+
*
|
|
67034
|
+
* @param {Date|number|string} children
|
|
67035
|
+
* The date to display. Accepts:
|
|
67036
|
+
* - a `Date` instance
|
|
67037
|
+
* - a Unix timestamp (number, in ms)
|
|
67038
|
+
* - a date string `"YYYY-MM-DD"` or any string parseable by `Date`
|
|
67039
|
+
* If the value cannot be parsed, it is rendered as-is.
|
|
67040
|
+
* If undefined/null, renders a placeholder shaped like the type: `"–"` for
|
|
67041
|
+
* `"relative"`, `"--:--"` for `"time"`, `"--"` for the durations, the
|
|
67042
|
+
* locale's field pattern for `"date"`/`"month"`/`"week"`/`"datetime"`.
|
|
67043
|
+
*
|
|
67044
|
+
* @param {"date"|"month"|"week"|"datetime"|"time"|"hour"|"minute"|"second"|"duration"|"relative"} [type="relative"]
|
|
67045
|
+
* Controls the display format:
|
|
67046
|
+
* - `"date"` → "lundi 11 mai" (long by default), "samedi 1 janvier 2050" outside the current year (see `year`);
|
|
67047
|
+
* `format="short"` → "lun. 11 mai"; `format="numeric"` → "11/05/2026";
|
|
67048
|
+
* `format={{ weekday: "long", month: "short" }}` spells the two apart → "mercredi 2 sept.";
|
|
67049
|
+
* a part set to `false` is dropped — `format={{ day: false, month: false }}` → "mercredi"
|
|
67050
|
+
* - `"month"` → "juin 2026"
|
|
67051
|
+
* - `"week"` → the ISO week as given ("2026-W38"), in a `<time datetime>`
|
|
67052
|
+
* - `"datetime"` → "lun. 11 mai, 14:30" (long); `format="short"` → "11 mai, 14:30"; `format="narrow"` → "11/05, 14:30"
|
|
67053
|
+
* - `"time"` → time-of-day as duration by default (e.g. "14:30" → "14 heures 30");
|
|
67054
|
+
* `format="timestring"` → clock "14 h 30". Midnight (00:xx) is
|
|
67055
|
+
* special-cased regardless of `format` — "0 heures 5 minutes"
|
|
67056
|
+
* would otherwise collapse to "5 minutes", indistinguishable
|
|
67057
|
+
* from an actual 5-minute duration: `format="long"` (default)
|
|
67058
|
+
* says "minuit et 5 minutes" (`format="short"/"narrow"/"compact"`
|
|
67059
|
+
* keep the zero hour instead — "0 h et 5 min"/"0h 5min"/"00h05").
|
|
67060
|
+
* `format="compact"` also zero-pads any single-digit hour, not
|
|
67061
|
+
* just midnight (e.g. "5h30" → "05h30"), closer to "05:30";
|
|
67062
|
+
* `pad={false}` writes it the way one says it instead ("8h30",
|
|
67063
|
+
* "8h"). See `<TimeRange>` for a span between two of them.
|
|
67064
|
+
* - `"hour"` → hours as duration (e.g. 1.5 → "1 heure 30 minutes")
|
|
67065
|
+
* - `"minute"` → minutes as duration (e.g. 90 → "1 heure 30 minutes")
|
|
67066
|
+
* - `"second"` → seconds as duration (e.g. 90 → "1 minute 30 secondes")
|
|
67067
|
+
* These three promote to the largest fitting unit, days included (36 hours
|
|
67068
|
+
* → "1 jour et 12 heures"); see `forceUnit` to stay in the given unit.
|
|
67069
|
+
* - `"duration"` → duration string/object/number (seconds); `format="iso"` → ISO 8601
|
|
67070
|
+
* - `"relative"` → "dans 1 heure 30" / "En cours" / "il y a 2 heures"
|
|
67071
|
+
* Handles past, present, and future.
|
|
67072
|
+
* `format="short"` → "dans 1 h et 30 min"; `format="narrow"`
|
|
67073
|
+
* → "+3 h"; `format="compact"` → "dans 1h30".
|
|
67074
|
+
* `eventDuration` defaults to 0 (instantaneous: no "En cours" window).
|
|
67075
|
+
*
|
|
67076
|
+
* @param {number|string} [eventDuration=0]
|
|
67077
|
+
* Duration of the event in milliseconds, or a duration string (`"PT1H30M"`,
|
|
67078
|
+
* `"1hour30minute"`). Only used with `type="relative"`.
|
|
67079
|
+
* When omitted, the event is instantaneous (point in time, no "En cours" window).
|
|
67080
|
+
* @param {boolean} [bare]
|
|
67081
|
+
* When true, strips the past-tense literal ("il y a", "ago") and returns only integer + unit.
|
|
67082
|
+
* Only applies to the past state of `type="relative"`.
|
|
67083
|
+
* @param {"long"|"short"|"narrow"|"compact"|"numeric"|"timestring"|"iso"|{ weekday?: "long"|"short"|"narrow"|false, day?: boolean, month?: "long"|"short"|"narrow"|"numeric"|false }} [format="long"]
|
|
67084
|
+
* Controls the verbosity of the output. Defaults to `"long"` for all types.
|
|
67085
|
+
* `"long"`, `"short"`, `"narrow"` and `"compact"` are verbosities every `type`
|
|
67086
|
+
* writes. The three under them name a shape only some types have; a `type`
|
|
67087
|
+
* without that shape writes its `"long"` form and names the dropped word in
|
|
67088
|
+
* the console.
|
|
67089
|
+
* - `"short"` → Intl short (e.g. "2 h et 15 min", short month for dates/datetimes, no weekday for datetime)
|
|
67090
|
+
* - `"narrow"` → Intl narrow (e.g. "2h 15min", numeric month for datetime)
|
|
67091
|
+
* - `"compact"` → custom compact notation (e.g. "2h15", no minute symbol
|
|
67092
|
+
* when hours present). A date has nothing tighter than its
|
|
67093
|
+
* digits, so `type="date"`/`"month"`/`"datetime"` write the
|
|
67094
|
+
* all-digit spelling ("17/09/2026", "17/09 14:30")
|
|
67095
|
+
* - `"numeric"` → numeric date, for `type="date"` and `type="month"` (e.g. "11/09/2026", "09/2026")
|
|
67096
|
+
* - `"timestring"` → clock display for `type="time"`, `type="minute"`, `type="hour"`, and `type="second"` (e.g. "14:30"; `type="second"` always writes the hours, "00:01:30" for 90)
|
|
67097
|
+
* - `"iso"` → ISO 8601 string, only for `type="duration"` (e.g. "PT2H15M")
|
|
67098
|
+
* - an object → `type="date"` only, one verbosity per part
|
|
67099
|
+
* (`{ weekday: "long", month: "short" }`): a narrow card
|
|
67100
|
+
* keeps the weekday whole, since that is what the eye
|
|
67101
|
+
* lands on, and abbreviates the month, since that is
|
|
67102
|
+
* where the characters are. `false` drops a part:
|
|
67103
|
+
* `{ day: false, month: false }` is the weekday alone
|
|
67104
|
+
* ("mardi"), `{ month: false }` weekday + day-of-month
|
|
67105
|
+
* ("mardi 18").
|
|
67106
|
+
* @param {boolean} [forceUnit=false]
|
|
67107
|
+
* Keeps the value in the unit named by `type` however big it gets, for
|
|
67108
|
+
* `type="hour"`, `"minute"` and `"second"`: 36 hours reads "36 heures"
|
|
67109
|
+
* instead of the default "1 jour et 12 heures". The default promotes to the
|
|
67110
|
+
* largest fitting unit because it reads better; force the unit when the unit
|
|
67111
|
+
* itself is the information (a quota, a counter).
|
|
67112
|
+
* @param {number} [decimals]
|
|
67113
|
+
* `type="duration"` and `type="second"` only — how many decimals a fraction
|
|
67114
|
+
* of a second keeps, rounded: with `decimals={1}`, 1.234 reads "1,2 seconde"
|
|
67115
|
+
* and 1.96 "2 secondes"; a whole value is never padded ("12 secondes").
|
|
67116
|
+
* Without it the fraction is written to the millisecond ("1,234 seconde"). A
|
|
67117
|
+
* duration measured in milliseconds rarely comes out whole, and its last
|
|
67118
|
+
* digits are usually noise.
|
|
67119
|
+
* @param {boolean} [pad=true]
|
|
67120
|
+
* `type="time"` + `format="compact"` only — whether the clock is written at a
|
|
67121
|
+
* fixed width. `true` (default) zero-pads the hour and always writes the
|
|
67122
|
+
* minutes ("08h00", "08h30"), so a column of times lines up. `false` writes
|
|
67123
|
+
* the shape one says out loud instead: bare hour, minutes only when there are
|
|
67124
|
+
* any ("8h", "8h30"). The spelled-out formats put their units in words and
|
|
67125
|
+
* need neither, so they ignore it.
|
|
67126
|
+
* @param {"hour"|"minute"} [precision]
|
|
67127
|
+
* `type="time"` + `format="compact"` only, and rarely set by hand — whether a
|
|
67128
|
+
* zero minute is written ("8h00") or dropped ("8h"). Defaults to whatever
|
|
67129
|
+
* `pad` implies; the one reason to force `"minute"` on an unpadded clock is
|
|
67130
|
+
* to agree with a partner that has minutes of its own, which is what
|
|
67131
|
+
* `<TimeRange>` does for you.
|
|
67132
|
+
* @param {boolean|"auto"} [year]
|
|
67133
|
+
* `type="date"` only — whether the year is written. `"auto"` writes it only
|
|
67134
|
+
* when the date is not in the current year, the way a calendar does
|
|
67135
|
+
* ("lundi 11 mai", but "samedi 1 janvier 2050"); `false` drops it ("30/07",
|
|
67136
|
+
* the day/month order still following the locale). Defaults to `"auto"`
|
|
67137
|
+
* for the spelled formats and to `true` for `format="numeric"`. A format
|
|
67138
|
+
* without the month (`{ month: false }`) never writes it.
|
|
67139
|
+
* @param {boolean} [dayLabel]
|
|
67140
|
+
* When true and `type="date"`, appends the locale-aware relative label
|
|
67141
|
+
* ("hier", "aujourd'hui", "demain") when the date is yesterday, today, or tomorrow.
|
|
67142
|
+
* @param {string} [lang]
|
|
67143
|
+
* BCP 47 locale tag (e.g. `"fr"`, `"en-US"`).
|
|
67144
|
+
* Defaults to `languagesSignal.value`: the browser's languages, as
|
|
67145
|
+
* `setPreferredLanguage`/`setSupportedLanguages` narrow them.
|
|
67146
|
+
*/
|
|
66332
67147
|
const Time = props => {
|
|
66333
67148
|
const {
|
|
66334
67149
|
type
|
|
@@ -66593,6 +67408,20 @@ const TimeMinute = ({
|
|
|
66593
67408
|
children: String(children)
|
|
66594
67409
|
});
|
|
66595
67410
|
}
|
|
67411
|
+
if (!Number.isInteger(minutes)) {
|
|
67412
|
+
// a fraction of a minute is read in seconds, the way formatMinuteDuration
|
|
67413
|
+
// writes it ("1 minute et 30 secondes")
|
|
67414
|
+
const clock = formatClock(Math.round(minutes * 60));
|
|
67415
|
+
return jsx(TimeText, {
|
|
67416
|
+
dateTime: clock,
|
|
67417
|
+
...props,
|
|
67418
|
+
children: format === "timestring" ? clock : formatMinuteDuration(minutes, {
|
|
67419
|
+
lang,
|
|
67420
|
+
format,
|
|
67421
|
+
forceUnit
|
|
67422
|
+
})
|
|
67423
|
+
});
|
|
67424
|
+
}
|
|
66596
67425
|
const totalHours = Math.floor(minutes / 60);
|
|
66597
67426
|
const remainingMinutes = minutes % 60;
|
|
66598
67427
|
const hh = String(totalHours).padStart(2, "0");
|
|
@@ -66622,6 +67451,7 @@ const TimeSecond = ({
|
|
|
66622
67451
|
lang = languagesSignal.value,
|
|
66623
67452
|
format = "long",
|
|
66624
67453
|
forceUnit = false,
|
|
67454
|
+
decimals,
|
|
66625
67455
|
...props
|
|
66626
67456
|
}) => {
|
|
66627
67457
|
if (children === undefined || children === null) {
|
|
@@ -66637,19 +67467,17 @@ const TimeSecond = ({
|
|
|
66637
67467
|
children: String(children)
|
|
66638
67468
|
});
|
|
66639
67469
|
}
|
|
66640
|
-
const
|
|
66641
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
66642
|
-
const s = seconds % 60;
|
|
66643
|
-
const dateTime = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
67470
|
+
const dateTime = formatClock(seconds);
|
|
66644
67471
|
let text;
|
|
66645
67472
|
if (format === "timestring") {
|
|
66646
67473
|
// Always HH:MM:SS to avoid ambiguity with HH:MM time-of-day format
|
|
66647
|
-
text =
|
|
67474
|
+
text = formatClock(seconds, decimals);
|
|
66648
67475
|
} else {
|
|
66649
67476
|
text = formatSecondDuration(seconds, {
|
|
66650
67477
|
lang,
|
|
66651
67478
|
format,
|
|
66652
|
-
forceUnit
|
|
67479
|
+
forceUnit,
|
|
67480
|
+
decimals
|
|
66653
67481
|
});
|
|
66654
67482
|
}
|
|
66655
67483
|
return jsx(TimeText, {
|
|
@@ -66702,6 +67530,7 @@ const TimeDuration = ({
|
|
|
66702
67530
|
children,
|
|
66703
67531
|
lang = languagesSignal.value,
|
|
66704
67532
|
format = "long",
|
|
67533
|
+
decimals,
|
|
66705
67534
|
...props
|
|
66706
67535
|
}) => {
|
|
66707
67536
|
if (children === undefined || children === null) {
|
|
@@ -66749,7 +67578,8 @@ const TimeDuration = ({
|
|
|
66749
67578
|
...props,
|
|
66750
67579
|
children: formatDuration(duration, {
|
|
66751
67580
|
lang,
|
|
66752
|
-
format
|
|
67581
|
+
format,
|
|
67582
|
+
decimals
|
|
66753
67583
|
})
|
|
66754
67584
|
});
|
|
66755
67585
|
}
|
|
@@ -66761,7 +67591,8 @@ const TimeDuration = ({
|
|
|
66761
67591
|
}
|
|
66762
67592
|
const text = formatDuration(duration, {
|
|
66763
67593
|
lang,
|
|
66764
|
-
format
|
|
67594
|
+
format,
|
|
67595
|
+
decimals
|
|
66765
67596
|
});
|
|
66766
67597
|
return jsx(TimeText, {
|
|
66767
67598
|
dateTime: isoString,
|
|
@@ -66901,6 +67732,24 @@ const TimeRange = ({
|
|
|
66901
67732
|
});
|
|
66902
67733
|
};
|
|
66903
67734
|
|
|
67735
|
+
// "HH:MM:SS", the fraction of a second after a period ("00:00:01.234"): the
|
|
67736
|
+
// shape a `datetime` attribute reads, which takes up to 3 decimals.
|
|
67737
|
+
const formatClock = (seconds, decimals = 3) => {
|
|
67738
|
+
const factor = 10 ** decimals;
|
|
67739
|
+
const total = Math.round(seconds * factor);
|
|
67740
|
+
const fraction = total % factor;
|
|
67741
|
+
const totalSeconds = (total - fraction) / factor;
|
|
67742
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
67743
|
+
const m = Math.floor(totalSeconds % 3600 / 60);
|
|
67744
|
+
const s = totalSeconds % 60;
|
|
67745
|
+
const clock = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
67746
|
+
if (fraction === 0) {
|
|
67747
|
+
return clock;
|
|
67748
|
+
}
|
|
67749
|
+
const digits = String(fraction).padStart(decimals, "0").replace(/0+$/, "");
|
|
67750
|
+
return `${clock}.${digits}`;
|
|
67751
|
+
};
|
|
67752
|
+
|
|
66904
67753
|
/**
|
|
66905
67754
|
* Wraps multiple inputs together and handles keyboard navigation and paste
|
|
66906
67755
|
* distribution between them.
|
|
@@ -67154,6 +68003,11 @@ const renderSafe = (value) => {
|
|
|
67154
68003
|
};
|
|
67155
68004
|
|
|
67156
68005
|
installImportMetaCssBuild(import.meta);
|
|
68006
|
+
/**
|
|
68007
|
+
* The group a popup opens in, or null: what a Popover or a Dialog reads to know
|
|
68008
|
+
* that its wall is the group's. Every popup provides null again to what it
|
|
68009
|
+
* holds, so a popup opened from inside a popup is in front of it, not a peer.
|
|
68010
|
+
*/
|
|
67157
68011
|
const PopupGroupContext = createContext(null);
|
|
67158
68012
|
|
|
67159
68013
|
/**
|
|
@@ -67320,13 +68174,13 @@ const css$F = /* css */`@property --x-popup-group-top {
|
|
|
67320
68174
|
* `navi-out-of-flow` like everything navi adds to a tree. See
|
|
67321
68175
|
* docs/popup_backdrop.md.
|
|
67322
68176
|
*
|
|
67323
|
-
* @type {import("
|
|
68177
|
+
* @type {import("preact").FunctionComponent<{
|
|
67324
68178
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
67325
68179
|
* backdropColor?: string,
|
|
67326
68180
|
* backdropFilter?: string,
|
|
67327
68181
|
* themeColor?: string | false,
|
|
67328
68182
|
* role?: string,
|
|
67329
|
-
* children?: import("
|
|
68183
|
+
* children?: import("preact").ComponentChildren,
|
|
67330
68184
|
* [key: string]: any,
|
|
67331
68185
|
* }>}
|
|
67332
68186
|
* @param {"auto"|"lift"|"discrete"|"invisible"} [props.backdropVariant="auto"] -
|
|
@@ -69373,6 +70227,35 @@ const clipOf = (side, distance) => {
|
|
|
69373
70227
|
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
69374
70228
|
};
|
|
69375
70229
|
|
|
70230
|
+
/**
|
|
70231
|
+
* The cross that closes the popup it sits in — reached as `<Popup.Close />`,
|
|
70232
|
+
* `<Dialog.Close />` or `<Popover.Close />`. A discrete icon button sending
|
|
70233
|
+
* `--navi-close`, which the nearest popup above it answers; the caller only
|
|
70234
|
+
* decides where it goes (the right end of a header, most of the time).
|
|
70235
|
+
*
|
|
70236
|
+
* It is the way out of a popup, so it is the one affordance in there that the
|
|
70237
|
+
* surrounding control's read-only and disabled do not reach: a read-only picker
|
|
70238
|
+
* still opens (reading changes nothing) and hands its popup that read-only, and
|
|
70239
|
+
* a cross wearing it would trap the user in a popup they were invited to read.
|
|
70240
|
+
* A close button written by hand inherits it and refuses the press — which is
|
|
70241
|
+
* the reason to use this one rather than a `<Button command="--navi-close" />`.
|
|
70242
|
+
*
|
|
70243
|
+
* Exempt from the state around it, never from the popup's own answer: the cross
|
|
70244
|
+
* ASKS, and the popup decides — a dialog holding a form mid-send keeps the
|
|
70245
|
+
* close, a Picker holding a value that does not validate keeps it too.
|
|
70246
|
+
*
|
|
70247
|
+
* @type {import("preact").FunctionComponent<{
|
|
70248
|
+
* label?: string,
|
|
70249
|
+
* iconSize?: number | string,
|
|
70250
|
+
* } & Record<string, any>>}
|
|
70251
|
+
* @param {string} [props.label] - What assistive tech reads for the cross
|
|
70252
|
+
* (navi's own "Close" text by default, in the active language).
|
|
70253
|
+
* @param {number|string} [props.iconSize] - How big the cross is drawn, apart
|
|
70254
|
+
* from the box around it. Unset, the glyph follows the button's font size
|
|
70255
|
+
* like a character, and `size` moves glyph and padding together. Set it when
|
|
70256
|
+
* the cross stands in for an affordance the app draws elsewhere and has to
|
|
70257
|
+
* land on the same pixels: `padding="s" iconSize="m"`.
|
|
70258
|
+
*/
|
|
69376
70259
|
const PopupClose = ({
|
|
69377
70260
|
label,
|
|
69378
70261
|
iconSize,
|
|
@@ -69621,6 +70504,9 @@ const TitlePseudoClasses = [":hover"];
|
|
|
69621
70504
|
* step of a form — so only the title knows when it is there.
|
|
69622
70505
|
*/
|
|
69623
70506
|
|
|
70507
|
+
// The popup a title names: the nearest one around it. A surface that is no
|
|
70508
|
+
// popup of this kind but holds content of its own (a picker's callout) sets it
|
|
70509
|
+
// back to null, so a title in there names nothing rather than the popup around.
|
|
69624
70510
|
const PopupTitleContext = createContext(null);
|
|
69625
70511
|
|
|
69626
70512
|
/**
|
|
@@ -69630,10 +70516,10 @@ const PopupTitleContext = createContext(null);
|
|
|
69630
70516
|
* announces the popup by it, and `getByRole("dialog", { name })` finds it.
|
|
69631
70517
|
* An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
|
|
69632
70518
|
*
|
|
69633
|
-
* @type {import("
|
|
70519
|
+
* @type {import("preact").FunctionComponent<{
|
|
69634
70520
|
* as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
69635
70521
|
* id?: string,
|
|
69636
|
-
* children?: import("
|
|
70522
|
+
* children?: import("preact").ComponentChildren,
|
|
69637
70523
|
* } & Record<string, any>>}
|
|
69638
70524
|
*/
|
|
69639
70525
|
const PopupTitle = props => {
|
|
@@ -69694,6 +70580,12 @@ const usePopupName = ({
|
|
|
69694
70580
|
const NO_TITLE = [];
|
|
69695
70581
|
|
|
69696
70582
|
installImportMetaCssBuild(import.meta);
|
|
70583
|
+
// Same need popover.jsx has for its own local renderer (see its
|
|
70584
|
+
// openLocalPopoverCount): a layer="local" dialog is a plain positioned element
|
|
70585
|
+
// in normal flow, so it gets none of the top layer's free "last shown wins" and
|
|
70586
|
+
// would paint under anything declared after it. A live count, not an
|
|
70587
|
+
// ever-growing counter, so the order stays small and resets once nothing is
|
|
70588
|
+
// open.
|
|
69697
70589
|
let openLocalDialogCount = 0;
|
|
69698
70590
|
const css$C = /* css */`
|
|
69699
70591
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -70768,7 +71660,7 @@ const css$C = /* css */`
|
|
|
70768
71660
|
* the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
|
|
70769
71661
|
* nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
|
|
70770
71662
|
* @param {string} [props.openerAriaLabel] - The same, as a string.
|
|
70771
|
-
* @param {import("
|
|
71663
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
70772
71664
|
*/
|
|
70773
71665
|
const Dialog = props => {
|
|
70774
71666
|
import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
@@ -71631,8 +72523,10 @@ const useDialogProps = props => {
|
|
|
71631
72523
|
// gap — the same thing its CSS width/height say (see [data-expand-x],
|
|
71632
72524
|
// [data-expand-y]) — so the placement derives it instead of measuring
|
|
71633
72525
|
// it: a measurement here lays out the dialog's whole content, rows and
|
|
71634
|
-
// all, for a number already known.
|
|
71635
|
-
|
|
72526
|
+
// all, for a number already known. A cap (maxWidth/maxHeight, a Picker's
|
|
72527
|
+
// anchor ceiling) stretches it to less than that, and only a measured
|
|
72528
|
+
// box can be centred.
|
|
72529
|
+
const fill = expandX && expandY && !isSizeCapped(dialogEl);
|
|
71636
72530
|
const pickOptions = {
|
|
71637
72531
|
positionArea,
|
|
71638
72532
|
container: positionedAncestor,
|
|
@@ -72208,6 +73102,14 @@ const switchTransitionsOn = dialogEl => {
|
|
|
72208
73102
|
dialogEl.style.transitionProperty = "";
|
|
72209
73103
|
dialogEl.removeAttribute("navi-transitions-off");
|
|
72210
73104
|
};
|
|
73105
|
+
|
|
73106
|
+
// Reads style, not layout: an unset cap is the guaranteed-invalid value its
|
|
73107
|
+
// @property gives it, and reads back as "". Catches a cap written inline by
|
|
73108
|
+
// the prop as well as one set from a stylesheet.
|
|
73109
|
+
const isSizeCapped = dialogEl => {
|
|
73110
|
+
const computedStyle = getComputedStyle(dialogEl);
|
|
73111
|
+
return computedStyle.getPropertyValue("--dialog-max-width") !== "" || computedStyle.getPropertyValue("--dialog-max-height") !== "";
|
|
73112
|
+
};
|
|
72211
73113
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
72212
73114
|
|
|
72213
73115
|
// Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
|
|
@@ -72233,6 +73135,11 @@ Dialog.Close = PopupClose;
|
|
|
72233
73135
|
Dialog.Title = PopupTitle;
|
|
72234
73136
|
|
|
72235
73137
|
installImportMetaCssBuild(import.meta);
|
|
73138
|
+
// Custom renderer only (layer="local") — count of currently-open instances,
|
|
73139
|
+
// see --popover-stack-order's own CSS comment for why a plain "last shown"
|
|
73140
|
+
// default doesn't happen for free the way it does for the top-layer
|
|
73141
|
+
// renderer. A live count (not an ever-growing counter) so the assigned
|
|
73142
|
+
// order stays small and resets to 0 once nothing is open.
|
|
72236
73143
|
let openLocalPopoverCount = 0;
|
|
72237
73144
|
const css$B = /* css */`
|
|
72238
73145
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -72840,7 +73747,7 @@ const css$B = /* css */`
|
|
|
72840
73747
|
* something names — these, `aria-label`/`aria-labelledby`, a
|
|
72841
73748
|
* `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
|
|
72842
73749
|
* an element with no role is read by nobody.
|
|
72843
|
-
* @param {import("
|
|
73750
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
72844
73751
|
*/
|
|
72845
73752
|
const Popover = props => {
|
|
72846
73753
|
import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
|
|
@@ -74210,7 +75117,7 @@ const css$A = /* css */`@layer navi {
|
|
|
74210
75117
|
* content whose fresh state is its initial state: an edit left unsent at
|
|
74211
75118
|
* the close does not come back at the next opening (an untouched field
|
|
74212
75119
|
* needs none of this to show a new `defaultValue` — it follows one).
|
|
74213
|
-
* @param {import("
|
|
75120
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
74214
75121
|
*/
|
|
74215
75122
|
const Popup = props => {
|
|
74216
75123
|
import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
|
|
@@ -75901,6 +76808,18 @@ const PickerConfirmBody = ({
|
|
|
75901
76808
|
});
|
|
75902
76809
|
};
|
|
75903
76810
|
|
|
76811
|
+
/**
|
|
76812
|
+
* PickerNaviMinute — a custom minute picker that opens a number input.
|
|
76813
|
+
*
|
|
76814
|
+
* Renders a popover containing an `<input type="number">` pre-filled with the
|
|
76815
|
+
* current value, plus Confirm / Clear / Cancel actions. The picker holds a
|
|
76816
|
+
* number of minutes: its own field is a text one told
|
|
76817
|
+
* `navi-input-type="minute"`, so the value is read back as a number and a
|
|
76818
|
+
* min/max/step message speaks of minutes.
|
|
76819
|
+
*
|
|
76820
|
+
* Usage:
|
|
76821
|
+
* <Picker type="navi_minute" min={15} max={300} step={15} value={90} />
|
|
76822
|
+
*/
|
|
75904
76823
|
const PickerNaviMinute = props => {
|
|
75905
76824
|
const Next = useNextResolver();
|
|
75906
76825
|
const {
|
|
@@ -76284,6 +77203,16 @@ const LoadingDotsSvg = () => {
|
|
|
76284
77203
|
});
|
|
76285
77204
|
};
|
|
76286
77205
|
|
|
77206
|
+
/**
|
|
77207
|
+
* A loading indicator sized to match the surrounding text (1em × 1em).
|
|
77208
|
+
* Inherits color from the current text color by default.
|
|
77209
|
+
*
|
|
77210
|
+
* @param {"circle"|"dots"} [props.variant="circle"] - Visual style of the indicator
|
|
77211
|
+
* @param {string} [props.color="currentColor"] - Color of the indicator; inherits from CSS `color` by default
|
|
77212
|
+
*
|
|
77213
|
+
* Every other prop goes to the `Icon` it is drawn in: `size` is a font size
|
|
77214
|
+
* (the indicator being 1em), `width`/`height` size it as a block.
|
|
77215
|
+
*/
|
|
76287
77216
|
const LoadingIndicator = ({
|
|
76288
77217
|
variant = "circle",
|
|
76289
77218
|
...props
|
|
@@ -76346,7 +77275,7 @@ const css$x = /* css */`@layer navi {
|
|
|
76346
77275
|
* Everything about how it looks goes through a CSS var, so it can be set per
|
|
76347
77276
|
* separator as a prop or once for a whole surface in CSS.
|
|
76348
77277
|
*
|
|
76349
|
-
* @type {import("
|
|
77278
|
+
* @type {import("preact").FunctionComponent<{
|
|
76350
77279
|
* vertical?: boolean,
|
|
76351
77280
|
* size?: string|number,
|
|
76352
77281
|
* color?: string,
|
|
@@ -77949,6 +78878,9 @@ const applySearchHighlight = (el, highlight) => {
|
|
|
77949
78878
|
};
|
|
77950
78879
|
|
|
77951
78880
|
installImportMetaCssBuild(import.meta);
|
|
78881
|
+
// Everything the list knows about its items — see list_items.js. Filled in by
|
|
78882
|
+
// the children as they render, read by everything that must reserve room for
|
|
78883
|
+
// what is not rendered, count what is, or know what stands above it.
|
|
77952
78884
|
const ListItemsContext = createContext(null);
|
|
77953
78885
|
// The group an item is declared in, by id (see ListItemGroup): what its count
|
|
77954
78886
|
// and its separator are scoped to.
|
|
@@ -79515,6 +80447,19 @@ const useListScrollSync = ({
|
|
|
79515
80447
|
// eslint-disable-next-line no-unused-expressions
|
|
79516
80448
|
virtualItemSizeSignal.value;
|
|
79517
80449
|
}
|
|
80450
|
+
// The hold ending — the user reaching for the list, something else moving
|
|
80451
|
+
// it, the item it opens on never coming — ends its hold on the document with
|
|
80452
|
+
// it (see holdDocumentScroll), rather than at the next render, which nothing
|
|
80453
|
+
// owes: an offset put back on this page meanwhile is the reader's.
|
|
80454
|
+
const releaseDocumentHoldRef = useRef(null);
|
|
80455
|
+
const letGo = () => {
|
|
80456
|
+
startPlaceRef.current.userTookOver = true;
|
|
80457
|
+
const releaseDocumentHold = releaseDocumentHoldRef.current;
|
|
80458
|
+
if (releaseDocumentHold) {
|
|
80459
|
+
releaseDocumentHoldRef.current = null;
|
|
80460
|
+
releaseDocumentHold();
|
|
80461
|
+
}
|
|
80462
|
+
};
|
|
79518
80463
|
// Set around a scroll the list performs itself. What it protects against is
|
|
79519
80464
|
// not the scroll event as such, but what the listener would conclude from it:
|
|
79520
80465
|
// the position it is about to read was chosen to keep the items where they
|
|
@@ -79749,7 +80694,7 @@ const useListScrollSync = ({
|
|
|
79749
80694
|
}
|
|
79750
80695
|
openAt = scrolledFallback;
|
|
79751
80696
|
if (openAt === "start" || openAt === undefined) {
|
|
79752
|
-
|
|
80697
|
+
letGo();
|
|
79753
80698
|
return;
|
|
79754
80699
|
}
|
|
79755
80700
|
} else {
|
|
@@ -79901,13 +80846,39 @@ const useListScrollSync = ({
|
|
|
79901
80846
|
}, []);
|
|
79902
80847
|
// Held on an item of a list scrolling the document: where the document goes is
|
|
79903
80848
|
// this list's to say, measured on the item, not the url's offset in pixels
|
|
79904
|
-
// (see holdDocumentScroll).
|
|
80849
|
+
// (see holdDocumentScroll). The hold read again: a layout effect of this
|
|
80850
|
+
// commit may have let go already (see placeWhereHeld).
|
|
80851
|
+
const routePage = useContext(RoutePageContext);
|
|
79905
80852
|
useLayoutEffect(() => {
|
|
79906
|
-
if (!heldSomewhere || getScroller() !== document.scrollingElement) {
|
|
80853
|
+
if (!heldSomewhere || startPlaceRef.current.userTookOver || getScroller() !== document.scrollingElement) {
|
|
79907
80854
|
return undefined;
|
|
79908
80855
|
}
|
|
79909
|
-
|
|
79910
|
-
|
|
80856
|
+
const releaseDocumentHold = holdDocumentScroll(routePage);
|
|
80857
|
+
releaseDocumentHoldRef.current = releaseDocumentHold;
|
|
80858
|
+
return () => {
|
|
80859
|
+
releaseDocumentHoldRef.current = null;
|
|
80860
|
+
releaseDocumentHold();
|
|
80861
|
+
};
|
|
80862
|
+
}, [heldSomewhere, scrolledWanted, scrollerElResolved]);
|
|
80863
|
+
// A page kept while its route transition plays is hidden until it is taken
|
|
80864
|
+
// down: nothing of the list can be measured there any more, and its scroller
|
|
80865
|
+
// shows the page arriving (see route_page_context.js). So the list lets go of
|
|
80866
|
+
// it, and stops answering its resizes and scrolls: measured hidden, every
|
|
80867
|
+
// item stands at 0×0, and the top item put back where it was would scroll
|
|
80868
|
+
// the page arriving by that item's offset. Kept mounted on purpose, so the
|
|
80869
|
+
// listeners stay; what they would do is what ends.
|
|
80870
|
+
const pageLeftRef = useRef(false);
|
|
80871
|
+
useLayoutEffect(() => {
|
|
80872
|
+
if (!routePage) {
|
|
80873
|
+
return undefined;
|
|
80874
|
+
}
|
|
80875
|
+
return routePage.leavingSignal.subscribe(leaving => {
|
|
80876
|
+
if (leaving) {
|
|
80877
|
+
pageLeftRef.current = true;
|
|
80878
|
+
letGo();
|
|
80879
|
+
}
|
|
80880
|
+
});
|
|
80881
|
+
}, []);
|
|
79911
80882
|
// What to do when the list's own geometry moves under it, kept fresh for the
|
|
79912
80883
|
// observer below (which is installed once).
|
|
79913
80884
|
const onGeometryChangeRef = useRef(null);
|
|
@@ -79941,18 +80912,15 @@ const useListScrollSync = ({
|
|
|
79941
80912
|
return undefined;
|
|
79942
80913
|
}
|
|
79943
80914
|
const scrollerEl = getScroller();
|
|
79944
|
-
const takeOver = () => {
|
|
79945
|
-
startPlaceRef.current.userTookOver = true;
|
|
79946
|
-
};
|
|
79947
80915
|
const takeOverEvents = ["wheel", "touchstart", "pointerdown", "keydown"];
|
|
79948
80916
|
for (const type of takeOverEvents) {
|
|
79949
|
-
scrollerEl.addEventListener(type,
|
|
80917
|
+
scrollerEl.addEventListener(type, letGo, {
|
|
79950
80918
|
passive: true
|
|
79951
80919
|
});
|
|
79952
80920
|
}
|
|
79953
80921
|
return () => {
|
|
79954
80922
|
for (const type of takeOverEvents) {
|
|
79955
|
-
scrollerEl.removeEventListener(type,
|
|
80923
|
+
scrollerEl.removeEventListener(type, letGo);
|
|
79956
80924
|
}
|
|
79957
80925
|
};
|
|
79958
80926
|
}, [scrolledWanted, scrollerElResolved]);
|
|
@@ -80110,6 +81078,9 @@ const useListScrollSync = ({
|
|
|
80110
81078
|
}
|
|
80111
81079
|
};
|
|
80112
81080
|
const observer = new ResizeObserver(entries => {
|
|
81081
|
+
if (pageLeftRef.current) {
|
|
81082
|
+
return;
|
|
81083
|
+
}
|
|
80113
81084
|
// A box that grows may be a box that starts to scroll — the scroller is
|
|
80114
81085
|
// resolved again before anything is done about the resize, so that a
|
|
80115
81086
|
// list which has outgrown a bounded ancestor stops holding on to the
|
|
@@ -80523,6 +81494,9 @@ const useListScrollSync = ({
|
|
|
80523
81494
|
}
|
|
80524
81495
|
const scrollerEl = getScroller();
|
|
80525
81496
|
const onScroll = () => {
|
|
81497
|
+
if (pageLeftRef.current) {
|
|
81498
|
+
return;
|
|
81499
|
+
}
|
|
80526
81500
|
// Which way the user is going, read before the position is stored: what
|
|
80527
81501
|
// the window keeps ahead is ahead of this.
|
|
80528
81502
|
const previousScroll = currentScrollRef.current;
|
|
@@ -80580,7 +81554,7 @@ const useListScrollSync = ({
|
|
|
80580
81554
|
const low = way.from < way.to ? way.from : way.to;
|
|
80581
81555
|
const high = way.from < way.to ? way.to : way.from;
|
|
80582
81556
|
if (position < low - 1 || position > high + 1) {
|
|
80583
|
-
|
|
81557
|
+
letGo();
|
|
80584
81558
|
}
|
|
80585
81559
|
};
|
|
80586
81560
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
@@ -82661,7 +83635,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82661
83635
|
* of it. Props not listed here are forwarded to the rendered <li> (Box layout
|
|
82662
83636
|
* props included).
|
|
82663
83637
|
*
|
|
82664
|
-
* @type {import("
|
|
83638
|
+
* @type {import("preact").FunctionComponent<{
|
|
82665
83639
|
* id?: string,
|
|
82666
83640
|
* selectable?: boolean,
|
|
82667
83641
|
* value?: any,
|
|
@@ -82670,14 +83644,14 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82670
83644
|
* pointed?: boolean,
|
|
82671
83645
|
* selectableArea?: "all" | "manual",
|
|
82672
83646
|
* skeleton?: boolean,
|
|
82673
|
-
* error?: boolean | import("
|
|
83647
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
82674
83648
|
* onErrorDismiss?: (event: Event) => void,
|
|
82675
83649
|
* loading?: boolean | "adding" | "removing" | "updating",
|
|
82676
83650
|
* readOnly?: boolean,
|
|
82677
83651
|
* filtered?: boolean,
|
|
82678
83652
|
* hidden?: boolean,
|
|
82679
83653
|
* matchInfo?: {match: boolean, matchScore?: number, matchRanges?: Array<[number, number]>},
|
|
82680
|
-
* children?: import("
|
|
83654
|
+
* children?: import("preact").ComponentChildren,
|
|
82681
83655
|
* [key: string]: any,
|
|
82682
83656
|
* }>}
|
|
82683
83657
|
* @param {string} [props.id]
|
|
@@ -82712,7 +83686,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82712
83686
|
* real item. This is what a `renderSkeleton` returns for an item on its way;
|
|
82713
83687
|
* Box layout props (padding…) pass through so the placeholder can match the
|
|
82714
83688
|
* real items' metrics.
|
|
82715
|
-
* @param {boolean|import("
|
|
83689
|
+
* @param {boolean|import("preact").ComponentChildren} [props.error]
|
|
82716
83690
|
* What this item stood for failed: the message replaces its content, styled
|
|
82717
83691
|
* like the list's own error. `true` shows a generic sentence. When
|
|
82718
83692
|
* `onErrorDismiss` is given, a dismiss button is drawn next to the message
|
|
@@ -82852,21 +83826,21 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
82852
83826
|
* Several runs can live in one list, next to plain `<List.Item>` children and
|
|
82853
83827
|
* inside `<List.Group>`s; each takes its place in declaration order.
|
|
82854
83828
|
*
|
|
82855
|
-
* @type {import("
|
|
82856
|
-
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("
|
|
83829
|
+
* @type {import("preact").FunctionComponent<{
|
|
83830
|
+
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("preact").ComponentChildren,
|
|
82857
83831
|
* findText?: (item: any, index: number) => string,
|
|
82858
83832
|
* items?: any[],
|
|
82859
83833
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
82860
83834
|
* debounce?: number,
|
|
82861
83835
|
* count?: number,
|
|
82862
83836
|
* groupBy?: (item: any, index: number) => any,
|
|
82863
|
-
* renderGroupLabel?: (item: any, index: number) => import("
|
|
83837
|
+
* renderGroupLabel?: (item: any, index: number) => import("preact").ComponentChildren,
|
|
82864
83838
|
* groupLabelProps?: (item: any, index: number) => object,
|
|
82865
83839
|
* pageSize?: number,
|
|
82866
83840
|
* memoryBudget?: number,
|
|
82867
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
83841
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
82868
83842
|
* itemSize?: (index: number) => number,
|
|
82869
|
-
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("
|
|
83843
|
+
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("preact").ComponentChildren,
|
|
82870
83844
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
82871
83845
|
* onCountChange?: (count: number|undefined) => void,
|
|
82872
83846
|
* }>}
|
|
@@ -84470,7 +85444,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84470
85444
|
* List — generic virtualized scroll container.
|
|
84471
85445
|
* Items must use <List.Item> to participate in tracking.
|
|
84472
85446
|
*
|
|
84473
|
-
* @type {import("
|
|
85447
|
+
* @type {import("preact").FunctionComponent<{
|
|
84474
85448
|
* selectable?: boolean,
|
|
84475
85449
|
* multiple?: boolean,
|
|
84476
85450
|
* deselectable?: boolean,
|
|
@@ -84491,16 +85465,16 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84491
85465
|
* scroller?: "self" | "parent" | "document" | Element | {current: Element},
|
|
84492
85466
|
* hoverWhileScrolling?: boolean,
|
|
84493
85467
|
* scrollResetOnNavigation?: boolean,
|
|
84494
|
-
* fallback?: import("
|
|
84495
|
-
* searchFallback?: import("
|
|
85468
|
+
* fallback?: import("preact").ComponentChildren,
|
|
85469
|
+
* searchFallback?: import("preact").ComponentChildren,
|
|
84496
85470
|
* searchText?: string,
|
|
84497
85471
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
|
|
84498
85472
|
* loading?: boolean,
|
|
84499
|
-
* loadingFallback?: "skeleton" | "loader" | import("
|
|
85473
|
+
* loadingFallback?: "skeleton" | "loader" | import("preact").ComponentChildren,
|
|
84500
85474
|
* loadingSkeletonCount?: number,
|
|
84501
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
84502
|
-
* error?: boolean | import("
|
|
84503
|
-
* separator?: boolean | import("
|
|
85475
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
85476
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
85477
|
+
* separator?: boolean | import("preact").ComponentChildren,
|
|
84504
85478
|
* itemTransition?: boolean,
|
|
84505
85479
|
* lockSize?: boolean,
|
|
84506
85480
|
* horizontal?: boolean,
|
|
@@ -84514,7 +85488,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84514
85488
|
* expandX?: boolean,
|
|
84515
85489
|
* expandY?: boolean,
|
|
84516
85490
|
* expand?: boolean,
|
|
84517
|
-
* children?: import("
|
|
85491
|
+
* children?: import("preact").ComponentChildren,
|
|
84518
85492
|
* [key: string]: any,
|
|
84519
85493
|
* }>}
|
|
84520
85494
|
* @param {string} [props.columns]
|
|
@@ -84574,7 +85548,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84574
85548
|
* have, so nothing moves when they arrive. Used for the items a `<List.Items>`
|
|
84575
85549
|
* stands for and does not hold yet, and for the placeholder items drawn while
|
|
84576
85550
|
* the whole list is `loading`. Defaults to a bare `<List.Item skeleton>`.
|
|
84577
|
-
* @param {"skeleton"|"loader"|import("
|
|
85551
|
+
* @param {"skeleton"|"loader"|import("preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
84578
85552
|
* What to display in place of the items while `loading` — that is, while
|
|
84579
85553
|
* there is nothing to show at all: `"skeleton"` renders
|
|
84580
85554
|
* `loadingSkeletonCount` placeholder items (look:
|
|
@@ -84758,6 +85732,17 @@ const List = /*#__PURE__*/Object.assign(ListResolved, {
|
|
|
84758
85732
|
Group: ListItemGroup
|
|
84759
85733
|
});
|
|
84760
85734
|
|
|
85735
|
+
/**
|
|
85736
|
+
* PickerNaviTime — a fully custom time picker driven by min/max/step.
|
|
85737
|
+
*
|
|
85738
|
+
* Unlike the native `<input type="time">`, this picker only exposes the exact
|
|
85739
|
+
* slots generated from min/max/step, so the user can never pick an out-of-range
|
|
85740
|
+
* or out-of-step value. The popover content is a selectable `List` of the
|
|
85741
|
+
* available slots; picking one closes the popover and submits the value.
|
|
85742
|
+
*
|
|
85743
|
+
* Usage:
|
|
85744
|
+
* <Picker type="navi_time" min="08:00" max="18:00" step="00:30" />
|
|
85745
|
+
*/
|
|
84761
85746
|
const PickerNaviTime = props => {
|
|
84762
85747
|
const Next = useNextResolver();
|
|
84763
85748
|
const {
|
|
@@ -84865,18 +85850,20 @@ const createBadgeRegistry = () => {
|
|
|
84865
85850
|
};
|
|
84866
85851
|
|
|
84867
85852
|
installImportMetaCssBuild(import.meta);
|
|
84868
|
-
const css$u = /* css */`@layer navi
|
|
85853
|
+
const css$u = /* css */`@layer navi {
|
|
85854
|
+
.navi_badge {
|
|
85855
|
+
--font-size: .7em;
|
|
85856
|
+
--badge-padding-x-default: .8em;
|
|
85857
|
+
--badge-padding-y-default: .4em;
|
|
85858
|
+
}
|
|
85859
|
+
}
|
|
84869
85860
|
|
|
84870
85861
|
.navi_badge {
|
|
84871
|
-
--font-size: .7em;
|
|
84872
|
-
--badge-padding-x-default: .8em;
|
|
84873
|
-
--badge-padding-y-default: .4em;
|
|
84874
85862
|
--x-badge-padding-top: var(--badge-padding-top, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
84875
85863
|
--x-badge-padding-right: var(--badge-padding-right, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
84876
85864
|
--x-badge-padding-bottom: var(--badge-padding-bottom, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
84877
85865
|
--x-badge-padding-left: var(--badge-padding-left, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
84878
85866
|
--x-background: var(--background, light-dark(#e0e0e0, #3a3a3a));
|
|
84879
|
-
--x-background-color: var(--background-color, var(--x-background));
|
|
84880
85867
|
--x-color: var(--color, white);
|
|
84881
85868
|
max-width: 200px;
|
|
84882
85869
|
padding-top: var(--x-badge-padding-top);
|
|
@@ -84887,7 +85874,6 @@ const css$u = /* css */`@layer navi;
|
|
|
84887
85874
|
font-size: var(--font-size);
|
|
84888
85875
|
text-box: trim-both cap alphabetic;
|
|
84889
85876
|
background: var(--x-background);
|
|
84890
|
-
background-color: var(--x-background-color);
|
|
84891
85877
|
border-radius: 1em;
|
|
84892
85878
|
display: inline;
|
|
84893
85879
|
position: relative;
|
|
@@ -85013,7 +85999,6 @@ const BadgeStyleCSSVars = {
|
|
|
85013
85999
|
paddingRight: "--badge-padding-right",
|
|
85014
86000
|
paddingBottom: "--badge-padding-bottom",
|
|
85015
86001
|
paddingLeft: "--badge-padding-left",
|
|
85016
|
-
backgroundColor: "--background-color",
|
|
85017
86002
|
background: "--background",
|
|
85018
86003
|
borderColor: "--border-color",
|
|
85019
86004
|
color: "--color",
|
|
@@ -85187,7 +86172,7 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
85187
86172
|
* shrinkWrap on its own builds the measurement ghost: under maxLines the list
|
|
85188
86173
|
* is measured in place, once the rows are known.
|
|
85189
86174
|
*
|
|
85190
|
-
* @param {import("
|
|
86175
|
+
* @param {import("preact").ComponentChildren} [fallback]
|
|
85191
86176
|
* Rendered in place of the badges when there is none. Without it an empty
|
|
85192
86177
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
85193
86178
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
@@ -85821,10 +86806,10 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
85821
86806
|
* — a row being read still says what was picked, it just no longer offers to
|
|
85822
86807
|
* unpick it.
|
|
85823
86808
|
*
|
|
85824
|
-
* @type {import("
|
|
86809
|
+
* @type {import("preact").FunctionComponent<{
|
|
85825
86810
|
* value: any,
|
|
85826
86811
|
* commandFor?: string,
|
|
85827
|
-
* children?: import("
|
|
86812
|
+
* children?: import("preact").ComponentChildren,
|
|
85828
86813
|
* [key: string]: any,
|
|
85829
86814
|
* }>}
|
|
85830
86815
|
* @param {any} value The value this chip stands for — one entry of what the
|
|
@@ -86090,6 +87075,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86090
87075
|
.navi_picker {
|
|
86091
87076
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
86092
87077
|
--picker-border-width: var(--navi-control-border-width);
|
|
87078
|
+
--border-width: var(--picker-border-width);
|
|
86093
87079
|
--picker-outline-width: var(--navi-focus-outline-width);
|
|
86094
87080
|
--picker-outline-offset: calc(-.5 * var(--picker-outline-width));
|
|
86095
87081
|
--picker-outline-color: var(--navi-focus-outline-color);
|
|
@@ -86129,6 +87115,111 @@ const css$r = /* css */`@layer navi {
|
|
|
86129
87115
|
--picker-align-x-default: flex-start;
|
|
86130
87116
|
--picker-align-y-default: center;
|
|
86131
87117
|
--picker-text-align-default: initial;
|
|
87118
|
+
font-size: var(--picker-font-size);
|
|
87119
|
+
font-family: var(--picker-font-family);
|
|
87120
|
+
line-height: var(--navi-control-line-height);
|
|
87121
|
+
}
|
|
87122
|
+
|
|
87123
|
+
.navi_picker[data-variant="icon"] {
|
|
87124
|
+
--picker-border-width: 0px;
|
|
87125
|
+
--picker-border-color: transparent;
|
|
87126
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87127
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87128
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87129
|
+
--picker-background-color: transparent;
|
|
87130
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87131
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87132
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87133
|
+
--picker-icon-color: currentColor;
|
|
87134
|
+
}
|
|
87135
|
+
|
|
87136
|
+
.navi_picker[data-variant="discrete"] {
|
|
87137
|
+
--picker-border-width: 0px;
|
|
87138
|
+
--picker-border-color: transparent;
|
|
87139
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87140
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87141
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87142
|
+
--picker-background-color: transparent;
|
|
87143
|
+
--picker-background-color-hover: color-mix(in srgb,
|
|
87144
|
+
currentColor 8%,
|
|
87145
|
+
var(--picker-background-color));
|
|
87146
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87147
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87148
|
+
}
|
|
87149
|
+
|
|
87150
|
+
.navi_picker[data-variant="headless"] {
|
|
87151
|
+
--picker-padding-x-default: 0;
|
|
87152
|
+
--picker-padding-y-default: 0;
|
|
87153
|
+
--picker-border-width: 0px;
|
|
87154
|
+
--picker-border-color: transparent;
|
|
87155
|
+
--picker-outline-width: 0px;
|
|
87156
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87157
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87158
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87159
|
+
--picker-background-color: transparent;
|
|
87160
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87161
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87162
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87163
|
+
--picker-icon-color: currentColor;
|
|
87164
|
+
}
|
|
87165
|
+
|
|
87166
|
+
.navi_picker[data-variant="bare"] {
|
|
87167
|
+
--picker-padding-x-default: 0;
|
|
87168
|
+
--picker-padding-y-default: 0;
|
|
87169
|
+
--picker-align-y-default: stretch;
|
|
87170
|
+
--picker-border-width: 0px;
|
|
87171
|
+
--picker-border-color: transparent;
|
|
87172
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87173
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87174
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87175
|
+
--picker-background-color: transparent;
|
|
87176
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87177
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87178
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87179
|
+
--picker-icon-color: currentColor;
|
|
87180
|
+
font-size: inherit;
|
|
87181
|
+
font-family: inherit;
|
|
87182
|
+
line-height: inherit;
|
|
87183
|
+
}
|
|
87184
|
+
|
|
87185
|
+
.navi_picker[data-variant="button"] {
|
|
87186
|
+
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
87187
|
+
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
87188
|
+
--picker-align-x-default: center;
|
|
87189
|
+
--picker-background-color: var(--navi-button-background-color);
|
|
87190
|
+
--picker-border-color-readonly: color-mix(in srgb,
|
|
87191
|
+
var(--picker-border-color) 30%,
|
|
87192
|
+
var(--navi-surface-color));
|
|
87193
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87194
|
+
--picker-color-readonly: color-mix(in srgb,
|
|
87195
|
+
var(--picker-color) 30%,
|
|
87196
|
+
transparent);
|
|
87197
|
+
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
87198
|
+
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
87199
|
+
--picker-color-disabled: var(--picker-color-readonly);
|
|
87200
|
+
--picker-text-align-default: center;
|
|
87201
|
+
}
|
|
87202
|
+
|
|
87203
|
+
.navi_picker[data-variant="text"] {
|
|
87204
|
+
--picker-padding-x-default: 0;
|
|
87205
|
+
--picker-padding-y-default: 0;
|
|
87206
|
+
--picker-border-width: 0px;
|
|
87207
|
+
--picker-border-color: transparent;
|
|
87208
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87209
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87210
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87211
|
+
--picker-background-color: transparent;
|
|
87212
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87213
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87214
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87215
|
+
font-size: inherit;
|
|
87216
|
+
font-family: inherit;
|
|
87217
|
+
}
|
|
87218
|
+
|
|
87219
|
+
.navi_picker[data-icon] {
|
|
87220
|
+
--picker-padding-x-default: 0;
|
|
87221
|
+
--picker-padding-y-default: 0;
|
|
87222
|
+
--picker-align-x-default: center;
|
|
86132
87223
|
}
|
|
86133
87224
|
}
|
|
86134
87225
|
|
|
@@ -86151,10 +87242,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86151
87242
|
box-sizing: border-box;
|
|
86152
87243
|
max-width: 100%;
|
|
86153
87244
|
color: var(--x-picker-color);
|
|
86154
|
-
font-size: var(--picker-font-size);
|
|
86155
|
-
font-family: var(--picker-font-family);
|
|
86156
87245
|
text-align: inherit;
|
|
86157
|
-
line-height: var(--navi-control-line-height);
|
|
86158
87246
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--picker-border-radius));
|
|
86159
87247
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--picker-border-radius));
|
|
86160
87248
|
border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--picker-border-radius));
|
|
@@ -86396,48 +87484,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86396
87484
|
--x-picker-border-color: var(--callout-color);
|
|
86397
87485
|
}
|
|
86398
87486
|
|
|
86399
|
-
&[data-variant="icon"] {
|
|
86400
|
-
--picker-border-width: 0px;
|
|
86401
|
-
--picker-border-color: transparent;
|
|
86402
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86403
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86404
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86405
|
-
--picker-background-color: transparent;
|
|
86406
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86407
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86408
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86409
|
-
--picker-icon-color: currentColor;
|
|
86410
|
-
}
|
|
86411
|
-
|
|
86412
|
-
&[data-variant="discrete"] {
|
|
86413
|
-
--picker-border-width: 0px;
|
|
86414
|
-
--picker-border-color: transparent;
|
|
86415
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86416
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86417
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86418
|
-
--picker-background-color: transparent;
|
|
86419
|
-
--picker-background-color-hover: color-mix(in srgb,
|
|
86420
|
-
currentColor 8%,
|
|
86421
|
-
var(--picker-background-color));
|
|
86422
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86423
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86424
|
-
}
|
|
86425
|
-
|
|
86426
87487
|
&[data-variant="headless"] {
|
|
86427
|
-
--picker-padding-x-default: 0;
|
|
86428
|
-
--picker-padding-y-default: 0;
|
|
86429
|
-
--picker-border-width: 0px;
|
|
86430
|
-
--picker-border-color: transparent;
|
|
86431
|
-
--picker-outline-width: 0px;
|
|
86432
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86433
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86434
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86435
|
-
--picker-background-color: transparent;
|
|
86436
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86437
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86438
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86439
|
-
--picker-icon-color: currentColor;
|
|
86440
|
-
|
|
86441
87488
|
& > .navi_picker_box {
|
|
86442
87489
|
z-index: -1;
|
|
86443
87490
|
position: absolute;
|
|
@@ -86446,23 +87493,6 @@ const css$r = /* css */`@layer navi {
|
|
|
86446
87493
|
}
|
|
86447
87494
|
|
|
86448
87495
|
&[data-variant="bare"] {
|
|
86449
|
-
--picker-padding-x-default: 0;
|
|
86450
|
-
--picker-padding-y-default: 0;
|
|
86451
|
-
--picker-align-y-default: stretch;
|
|
86452
|
-
--picker-border-width: 0px;
|
|
86453
|
-
--picker-border-color: transparent;
|
|
86454
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86455
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86456
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86457
|
-
--picker-background-color: transparent;
|
|
86458
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86459
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86460
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86461
|
-
--picker-icon-color: currentColor;
|
|
86462
|
-
font-size: inherit;
|
|
86463
|
-
font-family: inherit;
|
|
86464
|
-
line-height: inherit;
|
|
86465
|
-
|
|
86466
87496
|
& > .navi_picker_box {
|
|
86467
87497
|
min-height: 0;
|
|
86468
87498
|
}
|
|
@@ -86497,39 +87527,8 @@ const css$r = /* css */`@layer navi {
|
|
|
86497
87527
|
}
|
|
86498
87528
|
}
|
|
86499
87529
|
|
|
86500
|
-
&[data-variant="button"] {
|
|
86501
|
-
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
86502
|
-
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
86503
|
-
--picker-align-x-default: center;
|
|
86504
|
-
--picker-background-color: var(--navi-button-background-color);
|
|
86505
|
-
--picker-border-color-readonly: color-mix(in srgb,
|
|
86506
|
-
var(--picker-border-color) 30%,
|
|
86507
|
-
var(--navi-surface-color));
|
|
86508
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86509
|
-
--picker-color-readonly: color-mix(in srgb,
|
|
86510
|
-
var(--picker-color) 30%,
|
|
86511
|
-
transparent);
|
|
86512
|
-
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
86513
|
-
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
86514
|
-
--picker-color-disabled: var(--picker-color-readonly);
|
|
86515
|
-
--picker-text-align-default: center;
|
|
86516
|
-
}
|
|
86517
|
-
|
|
86518
87530
|
&[data-variant="text"] {
|
|
86519
|
-
--picker-padding-x-default: 0;
|
|
86520
|
-
--picker-padding-y-default: 0;
|
|
86521
|
-
--picker-border-width: 0px;
|
|
86522
|
-
--picker-border-color: transparent;
|
|
86523
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86524
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86525
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86526
|
-
--picker-background-color: transparent;
|
|
86527
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86528
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86529
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86530
|
-
font-size: inherit;
|
|
86531
87531
|
text-underline-offset: .2em;
|
|
86532
|
-
font-family: inherit;
|
|
86533
87532
|
text-decoration: underline dotted;
|
|
86534
87533
|
|
|
86535
87534
|
&[data-hover] {
|
|
@@ -86538,10 +87537,6 @@ const css$r = /* css */`@layer navi {
|
|
|
86538
87537
|
}
|
|
86539
87538
|
|
|
86540
87539
|
&[data-icon] {
|
|
86541
|
-
--picker-padding-x-default: 0;
|
|
86542
|
-
--picker-padding-y-default: 0;
|
|
86543
|
-
--picker-align-x-default: center;
|
|
86544
|
-
|
|
86545
87540
|
& > .navi_picker_box {
|
|
86546
87541
|
min-height: 0;
|
|
86547
87542
|
}
|
|
@@ -86980,13 +87975,13 @@ const PickerButton = props => {
|
|
|
86980
87975
|
* It goes when there is nothing to take out and when nothing can be changed, so
|
|
86981
87976
|
* a caller places it once and never asks whether to draw it.
|
|
86982
87977
|
*
|
|
86983
|
-
* @type {import("
|
|
87978
|
+
* @type {import("preact").FunctionComponent<{
|
|
86984
87979
|
* size?: number | string,
|
|
86985
|
-
* children?: import("
|
|
87980
|
+
* children?: import("preact").ComponentChildren,
|
|
86986
87981
|
* } & Record<string, any>>}
|
|
86987
87982
|
* @param {number|string} [size="inherit"] How big the cross is drawn, as
|
|
86988
87983
|
* `<Icon size>` reads it — "inherit" takes the size of the text around it.
|
|
86989
|
-
* @param {import("
|
|
87984
|
+
* @param {import("preact").ComponentChildren} [children] The icon, in place of
|
|
86990
87985
|
* the cross. Anything else goes to the `<Button>` underneath (`variant`,
|
|
86991
87986
|
* `paddingX`, `aria-label`, …).
|
|
86992
87987
|
*/
|
|
@@ -87306,6 +88301,115 @@ const PickerFirstResolver = props => {
|
|
|
87306
88301
|
});
|
|
87307
88302
|
};
|
|
87308
88303
|
|
|
88304
|
+
// Every picker type is resolved into one of these before it gets here (see
|
|
88305
|
+
// PickerTypeResolver and resolveInputProps): a native input type, or navi_js
|
|
88306
|
+
// for the ones whose value is a whole object. Anything else is a type nobody
|
|
88307
|
+
// implements — the picker then behaves as a plain text one, silently, and an
|
|
88308
|
+
// object value reaches the DOM as "[object Object]".
|
|
88309
|
+
const PICKER_RESOLVED_TYPE_SET = new Set(["text", "search", "tel", "url", "email", "password", "number", "range", "date", "month", "week", "time", "datetime-local", "color", "file", "checkbox", "radio", "hidden", "navi_js"]);
|
|
88310
|
+
const pickerTypeWarnedSet = new Set();
|
|
88311
|
+
// A headless picker draws nothing at all — no value, no slot — so anything a
|
|
88312
|
+
// caller wrote for either is silently dropped. Said out loud, because a prop
|
|
88313
|
+
// that renders nowhere looks exactly like a prop that does not work.
|
|
88314
|
+
let uiDrawnByNobodyWarned = false;
|
|
88315
|
+
const warnOnUIDrawnByNobody = props => {
|
|
88316
|
+
if (uiDrawnByNobodyWarned) {
|
|
88317
|
+
return;
|
|
88318
|
+
}
|
|
88319
|
+
const {
|
|
88320
|
+
ui,
|
|
88321
|
+
variant
|
|
88322
|
+
} = props;
|
|
88323
|
+
if (variant !== "headless" || ui === undefined || ui === "default") {
|
|
88324
|
+
return;
|
|
88325
|
+
}
|
|
88326
|
+
if (pickerUIIsNaviOwn(ui)) {
|
|
88327
|
+
// What a `type` resolver puts there for a picker that did not name a
|
|
88328
|
+
// drawing (see picker_types.jsx). Nobody wrote it, so there is nobody to
|
|
88329
|
+
// tell that it is not drawn.
|
|
88330
|
+
return;
|
|
88331
|
+
}
|
|
88332
|
+
uiDrawnByNobodyWarned = true;
|
|
88333
|
+
console.warn(`[navi] <Picker variant="headless" ui={…}> — a headless picker draws nothing, so this "ui" is never rendered. ` + `For a trigger that is one icon of your own: <Picker variant="icon" ui={<YourSvg />}>.`);
|
|
88334
|
+
};
|
|
88335
|
+
|
|
88336
|
+
// `clearable` is "put the cross in the right slot", and a picker without one is
|
|
88337
|
+
// handed the prop and drops it. Said out loud, because a caller asking for a
|
|
88338
|
+
// cross and getting none has no way to tell "ignored here" from "broken".
|
|
88339
|
+
const clearableWithoutSlotWarnedSet = new Set();
|
|
88340
|
+
const warnOnClearableWithoutSlot = (props, hasRightSlot) => {
|
|
88341
|
+
const {
|
|
88342
|
+
clearable,
|
|
88343
|
+
variant,
|
|
88344
|
+
icon,
|
|
88345
|
+
rightSlot,
|
|
88346
|
+
picksNothing
|
|
88347
|
+
} = props;
|
|
88348
|
+
if (!clearable || hasRightSlot) {
|
|
88349
|
+
return;
|
|
88350
|
+
}
|
|
88351
|
+
// What took the slot away: what the message names, and what makes two
|
|
88352
|
+
// pickers the same case.
|
|
88353
|
+
const cause = picksNothing ? "picks-nothing" : rightSlot === null ? "rightSlot={null}" : icon ? "icon" : `variant="${variant}"`;
|
|
88354
|
+
if (clearableWithoutSlotWarnedSet.has(cause)) {
|
|
88355
|
+
return;
|
|
88356
|
+
}
|
|
88357
|
+
clearableWithoutSlotWarnedSet.add(cause);
|
|
88358
|
+
if (picksNothing) {
|
|
88359
|
+
console.warn(`[navi] <Picker type="confirm" clearable> — a picker that picks nothing holds nothing to clear, so no cross is drawn.`);
|
|
88360
|
+
return;
|
|
88361
|
+
}
|
|
88362
|
+
console.warn(`[navi] <Picker ${cause} clearable> — that leaves no right slot, so no cross is drawn. ` + `Draw it where it belongs instead: <Picker ${cause} ui={<YourDrawing>… <Picker.Clear /></YourDrawing>}>. ` + `It is the same cross, and it takes itself away when there is nothing left to clear.`);
|
|
88363
|
+
};
|
|
88364
|
+
let chevronOffBareWarned = false;
|
|
88365
|
+
const warnOnChevronOffBare = (props, isBare) => {
|
|
88366
|
+
const {
|
|
88367
|
+
chevron,
|
|
88368
|
+
variant
|
|
88369
|
+
} = props;
|
|
88370
|
+
if (chevron === undefined || isBare || chevronOffBareWarned) {
|
|
88371
|
+
return;
|
|
88372
|
+
}
|
|
88373
|
+
chevronOffBareWarned = true;
|
|
88374
|
+
console.warn(`[navi] <Picker variant="${variant}" chevron="${chevron}"> — \`chevron\` places the chevron of a bare picker, whose drawing has no slot beside it. ` + `This variant draws its chevron in its right slot already (rightSlotIcon, rightSlot to change it); \`chevron\` is ignored.`);
|
|
88375
|
+
};
|
|
88376
|
+
const warnOnUnknownPickerType = props => {
|
|
88377
|
+
const {
|
|
88378
|
+
type
|
|
88379
|
+
} = props;
|
|
88380
|
+
if (type === undefined || PICKER_RESOLVED_TYPE_SET.has(type)) {
|
|
88381
|
+
return;
|
|
88382
|
+
}
|
|
88383
|
+
if (pickerTypeWarnedSet.has(type)) {
|
|
88384
|
+
return;
|
|
88385
|
+
}
|
|
88386
|
+
pickerTypeWarnedSet.add(type);
|
|
88387
|
+
console.warn(`[navi] <Picker type="${type}"> — "${type}" is not a picker type. ` + `The picker holds a single text value instead, so an object given to it is written as "[object Object]". ` + `Types are: date, month, week, time, datetime, duration, color, file, text, array, object, confirm — ` + `"object" being the one for a popup holding several named controls.`);
|
|
88388
|
+
};
|
|
88389
|
+
|
|
88390
|
+
// PickerFirstResolver comes before the presets so a preset reads the
|
|
88391
|
+
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
88392
|
+
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
88393
|
+
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
88394
|
+
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
88395
|
+
Date: PickerDateUI,
|
|
88396
|
+
Time: PickerTimeUI,
|
|
88397
|
+
Duration: PickerDurationUI,
|
|
88398
|
+
Week: PickerWeekUI,
|
|
88399
|
+
Datetime: PickerDatetimeUI,
|
|
88400
|
+
File: PickerFileUI,
|
|
88401
|
+
Color: PickerColorUI,
|
|
88402
|
+
Object: PickerObjectUI,
|
|
88403
|
+
Multiple: PickerArrayUI,
|
|
88404
|
+
PencilSvg,
|
|
88405
|
+
ChevronDownSvg: ChevronDownSvg$1,
|
|
88406
|
+
ClockSvg,
|
|
88407
|
+
DurationSvg,
|
|
88408
|
+
CalendarSvg,
|
|
88409
|
+
FileSvg,
|
|
88410
|
+
ColorSvg
|
|
88411
|
+
});
|
|
88412
|
+
|
|
87309
88413
|
/**
|
|
87310
88414
|
* Button-like trigger that opens a picker (native or custom popup) when clicked.
|
|
87311
88415
|
*
|
|
@@ -87324,13 +88428,13 @@ const PickerFirstResolver = props => {
|
|
|
87324
88428
|
* picker_confirm.jsx, and `message`, `confirmLabel`, `cancelLabel`,
|
|
87325
88429
|
* `focusOnOpen` below.
|
|
87326
88430
|
*
|
|
87327
|
-
* @type {import("
|
|
88431
|
+
* @type {import("preact").FunctionComponent<{
|
|
87328
88432
|
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
87329
88433
|
* value?: any,
|
|
87330
88434
|
* defaultValue?: any,
|
|
87331
88435
|
* name?: string,
|
|
87332
|
-
* placeholder?: import("
|
|
87333
|
-
* ui?: import("
|
|
88436
|
+
* placeholder?: import("preact").ComponentChildren,
|
|
88437
|
+
* ui?: import("preact").ComponentChildren | "default" | import("preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
87334
88438
|
* required?: boolean,
|
|
87335
88439
|
* min?: Date | string | number,
|
|
87336
88440
|
* max?: Date | string | number,
|
|
@@ -87340,8 +88444,8 @@ const PickerFirstResolver = props => {
|
|
|
87340
88444
|
* openWhileReadOnly?: boolean,
|
|
87341
88445
|
* error?: boolean | string,
|
|
87342
88446
|
* uiAction?: (value: any, event: Event) => void,
|
|
87343
|
-
* action?: (value: any, event: Event) => void,
|
|
87344
|
-
* children?: import("
|
|
88447
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void,
|
|
88448
|
+
* children?: import("preact").ComponentChildren,
|
|
87345
88449
|
* mode?: "popover" | "dialog" | "callout",
|
|
87346
88450
|
* openOn?: "press" | "longpress" | "contextmenu" | string | string[],
|
|
87347
88451
|
* animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
|
|
@@ -87361,14 +88465,14 @@ const PickerFirstResolver = props => {
|
|
|
87361
88465
|
* icon?: boolean,
|
|
87362
88466
|
* alignX?: "start" | "center" | "end",
|
|
87363
88467
|
* alignY?: "start" | "center" | "end" | "stretch",
|
|
87364
|
-
* rightSlotIcon?: import("
|
|
88468
|
+
* rightSlotIcon?: import("preact").ComponentChildren,
|
|
87365
88469
|
* rightSlotIconSize?: number | string,
|
|
87366
|
-
* rightSlot?: import("
|
|
88470
|
+
* rightSlot?: import("preact").ComponentChildren,
|
|
87367
88471
|
* chevron?: "end" | "start" | "hang-end",
|
|
87368
|
-
* clearConfirm?: string | import("
|
|
87369
|
-
* message?: import("
|
|
87370
|
-
* confirmLabel?: import("
|
|
87371
|
-
* cancelLabel?: import("
|
|
88472
|
+
* clearConfirm?: string | import("preact").ComponentChildren,
|
|
88473
|
+
* message?: import("preact").ComponentChildren,
|
|
88474
|
+
* confirmLabel?: import("preact").ComponentChildren,
|
|
88475
|
+
* cancelLabel?: import("preact").ComponentChildren,
|
|
87372
88476
|
* focusOnOpen?: "confirm" | "cancel" | "none",
|
|
87373
88477
|
* onConfirm?: (confirmEvent: CustomEvent) => void,
|
|
87374
88478
|
* onOpen?: (openEvent: CustomEvent) => void,
|
|
@@ -87401,14 +88505,14 @@ const PickerFirstResolver = props => {
|
|
|
87401
88505
|
* dialogSizeFromAnchor?: boolean,
|
|
87402
88506
|
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
87403
88507
|
* marginWithContainer?: number | string,
|
|
87404
|
-
* anchor?: import("
|
|
88508
|
+
* anchor?: import("preact").RefObject<HTMLElement> | HTMLElement,
|
|
87405
88509
|
* escapeEffect?: "cancel" | "close",
|
|
87406
88510
|
* pressOutside?: "close" | "cancel" | "capture",
|
|
87407
88511
|
* backdrop?: boolean,
|
|
87408
88512
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
87409
88513
|
* backdropColor?: string,
|
|
87410
88514
|
* backdropFilter?: string,
|
|
87411
|
-
* ref?: import("
|
|
88515
|
+
* ref?: import("preact").RefObject<HTMLElement>,
|
|
87412
88516
|
* [key: string]: any,
|
|
87413
88517
|
* }>}
|
|
87414
88518
|
* @param {string} [popupBackgroundColor] The popup's surface. Left out it is
|
|
@@ -87447,7 +88551,7 @@ const PickerFirstResolver = props => {
|
|
|
87447
88551
|
* max_lines_context.js). `variant="text"` and `variant="bare"` are not
|
|
87448
88552
|
* clamped: their drawing is the caller's own, a `Text` in the `ui` cuts
|
|
87449
88553
|
* itself with its own `maxLines`. See docs/typography.md.
|
|
87450
|
-
* @param {import("
|
|
88554
|
+
* @param {import("preact").ComponentChildren | "default" | import("preact").ComponentType} [ui] What the
|
|
87451
88555
|
* trigger draws in place of the value's default rendering (a date, a list
|
|
87452
88556
|
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
87453
88557
|
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
@@ -87564,7 +88668,7 @@ const PickerFirstResolver = props => {
|
|
|
87564
88668
|
* popover narrower than the trigger stays narrow. Popover mode only — a
|
|
87565
88669
|
* dialog never takes the trigger's width to begin with (see
|
|
87566
88670
|
* `dialogMinWidth`).
|
|
87567
|
-
* @param {import("
|
|
88671
|
+
* @param {import("preact").ComponentChildren} [rightSlotIcon] What the right
|
|
87568
88672
|
* slot draws in place of the chevron. It is the whole slot, not an addition
|
|
87569
88673
|
* to it: the picker then no longer says on its own that it opens, so pass
|
|
87570
88674
|
* something that does. Decoration only — it is wrapped in an `<Icon>`, which
|
|
@@ -87596,7 +88700,7 @@ const PickerFirstResolver = props => {
|
|
|
87596
88700
|
* any `pressPaddingRight`. The chevron is drawn in `--picker-icon-color`
|
|
87597
88701
|
* (`currentColor` here) and `--picker-icon-color-hover`; a dimmed one is
|
|
87598
88702
|
* `--picker-icon-color: color-mix(in srgb, currentColor 55%, transparent)`.
|
|
87599
|
-
* @param {import("
|
|
88703
|
+
* @param {import("preact").ComponentChildren} [rightSlot] Same place, rendered
|
|
87600
88704
|
* as-is: no `<Icon>` around it, nothing `aria-hidden`. This is where an
|
|
87601
88705
|
* interactive right slot goes. `null` takes the slot away entirely — the
|
|
87602
88706
|
* gutter it carries with it, so the trigger measures its drawing and centers
|
|
@@ -87614,7 +88718,7 @@ const PickerFirstResolver = props => {
|
|
|
87614
88718
|
* opening it, the anchor being itself, and one popup per instance without
|
|
87615
88719
|
* an id to wire. See "The trigger wears the wait" in docs/popup_open.md for
|
|
87616
88720
|
* when that beats a `Button` opening a shared `Dialog`.
|
|
87617
|
-
* @param {string|import("
|
|
88721
|
+
* @param {string|import("preact").ComponentChildren} [clearConfirm] The
|
|
87618
88722
|
* question asked before the clear cross clears anything — the `message` of
|
|
87619
88723
|
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
87620
88724
|
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
@@ -87671,15 +88775,15 @@ const PickerFirstResolver = props => {
|
|
|
87671
88775
|
* buttons. A shape, not a paint: the variant still decides what is drawn
|
|
87672
88776
|
* around it, so `variant="discrete" icon` is the discrete hover wash on an
|
|
87673
88777
|
* icon-sized square. `variant="icon"` is this shape with no surface at all.
|
|
87674
|
-
* @param {import("
|
|
88778
|
+
* @param {import("preact").ComponentChildren} [message] `type="confirm"`: the
|
|
87675
88779
|
* question, in the popup's default body — text, or JSX when it needs an
|
|
87676
88780
|
* emphasis, a link. Left out, a generic "are you sure?" in the current
|
|
87677
88781
|
* language. To draw the whole popup instead, pass `children`: yes is then
|
|
87678
88782
|
* whatever carries `--navi-confirm`, no whatever closes (`--navi-cancel`).
|
|
87679
|
-
* @param {import("
|
|
88783
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] `type="confirm"`,
|
|
87680
88784
|
* default body: what the yes button reads. Defaults to the translated
|
|
87681
88785
|
* "Confirm".
|
|
87682
|
-
* @param {import("
|
|
88786
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Same, for the no
|
|
87683
88787
|
* button. Defaults to the translated "Cancel".
|
|
87684
88788
|
* @param {"confirm"|"cancel"|"none"} [focusOnOpen="confirm"] `type="confirm"`,
|
|
87685
88789
|
* default body: which button the keyboard lands on when the popup opens.
|
|
@@ -87796,7 +88900,7 @@ const PickerFirstResolver = props => {
|
|
|
87796
88900
|
* should go on meaning cancel, and
|
|
87797
88901
|
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
87798
88902
|
* back.
|
|
87799
|
-
* @param {import("
|
|
88903
|
+
* @param {import("preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
87800
88904
|
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
87801
88905
|
* trigger is one piece of a bigger control (the chevron half of a split
|
|
87802
88906
|
* button) points at the whole control, so the popup lines up with it and is
|
|
@@ -87880,115 +88984,6 @@ const PickerFirstResolver = props => {
|
|
|
87880
88984
|
* `type="confirm"`) never builds them early: its popup holds no value, there
|
|
87881
88985
|
* is nothing to read.
|
|
87882
88986
|
*/
|
|
87883
|
-
// Every picker type is resolved into one of these before it gets here (see
|
|
87884
|
-
// PickerTypeResolver and resolveInputProps): a native input type, or navi_js
|
|
87885
|
-
// for the ones whose value is a whole object. Anything else is a type nobody
|
|
87886
|
-
// implements — the picker then behaves as a plain text one, silently, and an
|
|
87887
|
-
// object value reaches the DOM as "[object Object]".
|
|
87888
|
-
const PICKER_RESOLVED_TYPE_SET = new Set(["text", "search", "tel", "url", "email", "password", "number", "range", "date", "month", "week", "time", "datetime-local", "color", "file", "checkbox", "radio", "hidden", "navi_js"]);
|
|
87889
|
-
const pickerTypeWarnedSet = new Set();
|
|
87890
|
-
// A headless picker draws nothing at all — no value, no slot — so anything a
|
|
87891
|
-
// caller wrote for either is silently dropped. Said out loud, because a prop
|
|
87892
|
-
// that renders nowhere looks exactly like a prop that does not work.
|
|
87893
|
-
let uiDrawnByNobodyWarned = false;
|
|
87894
|
-
const warnOnUIDrawnByNobody = props => {
|
|
87895
|
-
if (uiDrawnByNobodyWarned) {
|
|
87896
|
-
return;
|
|
87897
|
-
}
|
|
87898
|
-
const {
|
|
87899
|
-
ui,
|
|
87900
|
-
variant
|
|
87901
|
-
} = props;
|
|
87902
|
-
if (variant !== "headless" || ui === undefined || ui === "default") {
|
|
87903
|
-
return;
|
|
87904
|
-
}
|
|
87905
|
-
if (pickerUIIsNaviOwn(ui)) {
|
|
87906
|
-
// What a `type` resolver puts there for a picker that did not name a
|
|
87907
|
-
// drawing (see picker_types.jsx). Nobody wrote it, so there is nobody to
|
|
87908
|
-
// tell that it is not drawn.
|
|
87909
|
-
return;
|
|
87910
|
-
}
|
|
87911
|
-
uiDrawnByNobodyWarned = true;
|
|
87912
|
-
console.warn(`[navi] <Picker variant="headless" ui={…}> — a headless picker draws nothing, so this "ui" is never rendered. ` + `For a trigger that is one icon of your own: <Picker variant="icon" ui={<YourSvg />}>.`);
|
|
87913
|
-
};
|
|
87914
|
-
|
|
87915
|
-
// `clearable` is "put the cross in the right slot", and a picker without one is
|
|
87916
|
-
// handed the prop and drops it. Said out loud, because a caller asking for a
|
|
87917
|
-
// cross and getting none has no way to tell "ignored here" from "broken".
|
|
87918
|
-
const clearableWithoutSlotWarnedSet = new Set();
|
|
87919
|
-
const warnOnClearableWithoutSlot = (props, hasRightSlot) => {
|
|
87920
|
-
const {
|
|
87921
|
-
clearable,
|
|
87922
|
-
variant,
|
|
87923
|
-
icon,
|
|
87924
|
-
rightSlot,
|
|
87925
|
-
picksNothing
|
|
87926
|
-
} = props;
|
|
87927
|
-
if (!clearable || hasRightSlot) {
|
|
87928
|
-
return;
|
|
87929
|
-
}
|
|
87930
|
-
// What took the slot away: what the message names, and what makes two
|
|
87931
|
-
// pickers the same case.
|
|
87932
|
-
const cause = picksNothing ? "picks-nothing" : rightSlot === null ? "rightSlot={null}" : icon ? "icon" : `variant="${variant}"`;
|
|
87933
|
-
if (clearableWithoutSlotWarnedSet.has(cause)) {
|
|
87934
|
-
return;
|
|
87935
|
-
}
|
|
87936
|
-
clearableWithoutSlotWarnedSet.add(cause);
|
|
87937
|
-
if (picksNothing) {
|
|
87938
|
-
console.warn(`[navi] <Picker type="confirm" clearable> — a picker that picks nothing holds nothing to clear, so no cross is drawn.`);
|
|
87939
|
-
return;
|
|
87940
|
-
}
|
|
87941
|
-
console.warn(`[navi] <Picker ${cause} clearable> — that leaves no right slot, so no cross is drawn. ` + `Draw it where it belongs instead: <Picker ${cause} ui={<YourDrawing>… <Picker.Clear /></YourDrawing>}>. ` + `It is the same cross, and it takes itself away when there is nothing left to clear.`);
|
|
87942
|
-
};
|
|
87943
|
-
let chevronOffBareWarned = false;
|
|
87944
|
-
const warnOnChevronOffBare = (props, isBare) => {
|
|
87945
|
-
const {
|
|
87946
|
-
chevron,
|
|
87947
|
-
variant
|
|
87948
|
-
} = props;
|
|
87949
|
-
if (chevron === undefined || isBare || chevronOffBareWarned) {
|
|
87950
|
-
return;
|
|
87951
|
-
}
|
|
87952
|
-
chevronOffBareWarned = true;
|
|
87953
|
-
console.warn(`[navi] <Picker variant="${variant}" chevron="${chevron}"> — \`chevron\` places the chevron of a bare picker, whose drawing has no slot beside it. ` + `This variant draws its chevron in its right slot already (rightSlotIcon, rightSlot to change it); \`chevron\` is ignored.`);
|
|
87954
|
-
};
|
|
87955
|
-
const warnOnUnknownPickerType = props => {
|
|
87956
|
-
const {
|
|
87957
|
-
type
|
|
87958
|
-
} = props;
|
|
87959
|
-
if (type === undefined || PICKER_RESOLVED_TYPE_SET.has(type)) {
|
|
87960
|
-
return;
|
|
87961
|
-
}
|
|
87962
|
-
if (pickerTypeWarnedSet.has(type)) {
|
|
87963
|
-
return;
|
|
87964
|
-
}
|
|
87965
|
-
pickerTypeWarnedSet.add(type);
|
|
87966
|
-
console.warn(`[navi] <Picker type="${type}"> — "${type}" is not a picker type. ` + `The picker holds a single text value instead, so an object given to it is written as "[object Object]". ` + `Types are: date, month, week, time, datetime, duration, color, file, text, array, object, confirm — ` + `"object" being the one for a popup holding several named controls.`);
|
|
87967
|
-
};
|
|
87968
|
-
|
|
87969
|
-
// PickerFirstResolver comes before the presets so a preset reads the
|
|
87970
|
-
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
87971
|
-
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
87972
|
-
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
87973
|
-
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
87974
|
-
Date: PickerDateUI,
|
|
87975
|
-
Time: PickerTimeUI,
|
|
87976
|
-
Duration: PickerDurationUI,
|
|
87977
|
-
Week: PickerWeekUI,
|
|
87978
|
-
Datetime: PickerDatetimeUI,
|
|
87979
|
-
File: PickerFileUI,
|
|
87980
|
-
Color: PickerColorUI,
|
|
87981
|
-
Object: PickerObjectUI,
|
|
87982
|
-
Multiple: PickerArrayUI,
|
|
87983
|
-
PencilSvg,
|
|
87984
|
-
ChevronDownSvg: ChevronDownSvg$1,
|
|
87985
|
-
ClockSvg,
|
|
87986
|
-
DurationSvg,
|
|
87987
|
-
CalendarSvg,
|
|
87988
|
-
FileSvg,
|
|
87989
|
-
ColorSvg
|
|
87990
|
-
});
|
|
87991
|
-
|
|
87992
88987
|
// Assembled in pure expressions rather than written onto Picker afterwards
|
|
87993
88988
|
// (`Picker.Chip = PickerChip`): what createComponentResolver returns is an object
|
|
87994
88989
|
// a bundler cannot see through, so a property assigned to it later is a side
|
|
@@ -88245,7 +89240,7 @@ const css$q = /* css */`@layer navi {
|
|
|
88245
89240
|
const TRAVELS_NONE = [];
|
|
88246
89241
|
|
|
88247
89242
|
/**
|
|
88248
|
-
* @type {import("
|
|
89243
|
+
* @type {import("preact").FunctionComponent<{
|
|
88249
89244
|
* value?: any,
|
|
88250
89245
|
* defaultValue?: any,
|
|
88251
89246
|
* signal?: import("@preact/signals").Signal<any>,
|
|
@@ -88261,7 +89256,7 @@ const TRAVELS_NONE = [];
|
|
|
88261
89256
|
* fallbackValue?: any,
|
|
88262
89257
|
* valueAtStep: (value: any, count: number) => any,
|
|
88263
89258
|
* compareValues?: (a: any, b: any) => number,
|
|
88264
|
-
* renderValue?: (value: any, context: { maxLines?: number }) => import("
|
|
89259
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("preact").ComponentChildren,
|
|
88265
89260
|
* controlProps?: object,
|
|
88266
89261
|
* duration?: number,
|
|
88267
89262
|
* vertical?: boolean,
|
|
@@ -89048,14 +90043,14 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
89048
90043
|
* single `value`/`signal` like any other control. `TimeSpin` is that, for a
|
|
89049
90044
|
* time of day.
|
|
89050
90045
|
*
|
|
89051
|
-
* @type {import("
|
|
90046
|
+
* @type {import("preact").FunctionComponent<{
|
|
89052
90047
|
* name?: string,
|
|
89053
90048
|
* value?: any,
|
|
89054
90049
|
* defaultValue?: any,
|
|
89055
90050
|
* signal?: import("@preact/signals").Signal<any>,
|
|
89056
90051
|
* aggregateChildStates?: (childUIStateControllers: any[]) => any,
|
|
89057
90052
|
* distributeChildUIState?: (groupState: any, childUIStateController: any) => any,
|
|
89058
|
-
* children?: import("
|
|
90053
|
+
* children?: import("preact").ComponentChildren,
|
|
89059
90054
|
* [key: string]: any,
|
|
89060
90055
|
* }>}
|
|
89061
90056
|
* Everything a box takes is taken here too — `width`, `borderWidth`,
|
|
@@ -89158,7 +90153,7 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
89158
90153
|
* above and below it by default — sideways they would be where the caret
|
|
89159
90154
|
* moves — with the bigger number up top.
|
|
89160
90155
|
*
|
|
89161
|
-
* @type {import("
|
|
90156
|
+
* @type {import("preact").FunctionComponent<{
|
|
89162
90157
|
* value?: number|string,
|
|
89163
90158
|
* defaultValue?: number|string,
|
|
89164
90159
|
* min?: number,
|
|
@@ -89263,7 +90258,7 @@ const numberAtStep = (value, count, {
|
|
|
89263
90258
|
* A day one steps through: the date plus what it is to today when there is a
|
|
89264
90259
|
* word for it, and a calendar behind it for the day that is far from here.
|
|
89265
90260
|
*
|
|
89266
|
-
* @type {import("
|
|
90261
|
+
* @type {import("preact").FunctionComponent<{
|
|
89267
90262
|
* value?: string,
|
|
89268
90263
|
* defaultValue?: string,
|
|
89269
90264
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -89274,7 +90269,7 @@ const numberAtStep = (value, count, {
|
|
|
89274
90269
|
* maxMessage?: any,
|
|
89275
90270
|
* step?: number,
|
|
89276
90271
|
* lang?: string,
|
|
89277
|
-
* renderDay?: (day: string) => import("
|
|
90272
|
+
* renderDay?: (day: string) => import("preact").ComponentChildren,
|
|
89278
90273
|
* [key: string]: any,
|
|
89279
90274
|
* }>}
|
|
89280
90275
|
* @param {string} [value] The day shown, as "YYYY-MM-DD".
|
|
@@ -89285,7 +90280,7 @@ const numberAtStep = (value, count, {
|
|
|
89285
90280
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
89286
90281
|
* Long by default, since this is a control one reads rather than a column
|
|
89287
90282
|
* one scans; `short` where the room is not there.
|
|
89288
|
-
* @param {(day: string) => import("
|
|
90283
|
+
* @param {(day: string) => import("preact").ComponentChildren} [renderDay] What
|
|
89289
90284
|
* to write for a day. Defaults to the date plus what it is to today when
|
|
89290
90285
|
* there is a word for it ("samedi 8 août (demain)"), since a day near now is
|
|
89291
90286
|
* read as a distance from now before it is read as a date.
|
|
@@ -89402,7 +90397,7 @@ const HOUR_MAX = 23;
|
|
|
89402
90397
|
const MINUTE_MAX = 59;
|
|
89403
90398
|
|
|
89404
90399
|
/**
|
|
89405
|
-
* @type {import("
|
|
90400
|
+
* @type {import("preact").FunctionComponent<{
|
|
89406
90401
|
* name?: string,
|
|
89407
90402
|
* value?: string,
|
|
89408
90403
|
* defaultValue?: string,
|
|
@@ -89410,7 +90405,7 @@ const MINUTE_MAX = 59;
|
|
|
89410
90405
|
* minuteStep?: number,
|
|
89411
90406
|
* pad?: number,
|
|
89412
90407
|
* loop?: boolean,
|
|
89413
|
-
* separator?: import("
|
|
90408
|
+
* separator?: import("preact").ComponentChildren,
|
|
89414
90409
|
* hourLabel?: string,
|
|
89415
90410
|
* minuteLabel?: string,
|
|
89416
90411
|
* [key: string]: any,
|
|
@@ -89423,7 +90418,7 @@ const MINUTE_MAX = 59;
|
|
|
89423
90418
|
* of a day to stop at. Say `loop={false}` for two ends one cannot step past.
|
|
89424
90419
|
* @param {number} [pad=2] How many digits an hour and a minute are written on:
|
|
89425
90420
|
* a clock says "07:00", never "7:0". Say `pad={0}` for the bare numbers.
|
|
89426
|
-
* @param {import("
|
|
90421
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
89427
90422
|
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
89428
90423
|
* Everything a box takes is taken here too — `width`, `borderRadius`, `size`.
|
|
89429
90424
|
*/
|
|
@@ -89489,7 +90484,7 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
89489
90484
|
};
|
|
89490
90485
|
|
|
89491
90486
|
/**
|
|
89492
|
-
* @type {import("
|
|
90487
|
+
* @type {import("preact").FunctionComponent<{
|
|
89493
90488
|
* name?: string,
|
|
89494
90489
|
* value?: { start?: string, end?: string },
|
|
89495
90490
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -89499,13 +90494,13 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
89499
90494
|
* timeProps?: object,
|
|
89500
90495
|
* loop?: boolean,
|
|
89501
90496
|
* size?: string,
|
|
89502
|
-
* startLabel?: import("
|
|
89503
|
-
* endLabel?: import("
|
|
90497
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
90498
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
89504
90499
|
* [key: string]: any,
|
|
89505
90500
|
* }>}
|
|
89506
90501
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
89507
90502
|
* "HH:MM".
|
|
89508
|
-
* @param {import("
|
|
90503
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
89509
90504
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
89510
90505
|
* `null` for neither.
|
|
89511
90506
|
* @param {number} [minuteStep=1] How many minutes a press on a minute chevron
|
|
@@ -89602,7 +90597,7 @@ const css$p = /* css */`.navi_checkbox_group {
|
|
|
89602
90597
|
*
|
|
89603
90598
|
* See `<RadioGroup>` for the same shape holding a single value.
|
|
89604
90599
|
*
|
|
89605
|
-
* @type {import("
|
|
90600
|
+
* @type {import("preact").FunctionComponent<{
|
|
89606
90601
|
* maxLength?: number,
|
|
89607
90602
|
* maxLengthGuard?: number,
|
|
89608
90603
|
* [key: string]: any,
|
|
@@ -89663,6 +90658,19 @@ const CheckboxGroupInterface = props => {
|
|
|
89663
90658
|
});
|
|
89664
90659
|
};
|
|
89665
90660
|
|
|
90661
|
+
/**
|
|
90662
|
+
* @type {import("preact").FunctionComponent<{
|
|
90663
|
+
* unit: string,
|
|
90664
|
+
* plural?: boolean,
|
|
90665
|
+
* format?: "long" | "short" | "narrow",
|
|
90666
|
+
* lang?: string,
|
|
90667
|
+
* label?: string,
|
|
90668
|
+
* size?: string,
|
|
90669
|
+
* sizeRatio?: number,
|
|
90670
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
90671
|
+
* [key: string]: any,
|
|
90672
|
+
* }>}
|
|
90673
|
+
*/
|
|
89666
90674
|
const Unit = ({
|
|
89667
90675
|
unit,
|
|
89668
90676
|
plural,
|
|
@@ -90420,7 +91428,7 @@ const css$m = /* css */`.navi_input.navi_select {
|
|
|
90420
91428
|
`;
|
|
90421
91429
|
|
|
90422
91430
|
/**
|
|
90423
|
-
* @type {import("
|
|
91431
|
+
* @type {import("preact").FunctionComponent<{
|
|
90424
91432
|
* value?: string,
|
|
90425
91433
|
* defaultValue?: string,
|
|
90426
91434
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -90502,30 +91510,40 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90502
91510
|
|
|
90503
91511
|
& > .navi_group {
|
|
90504
91512
|
flex: auto;
|
|
91513
|
+
|
|
91514
|
+
& > .navi_button {
|
|
91515
|
+
flex: auto;
|
|
91516
|
+
}
|
|
90505
91517
|
}
|
|
90506
91518
|
}
|
|
90507
91519
|
|
|
90508
91520
|
.navi_split_button_menu {
|
|
91521
|
+
--border-width: 0px;
|
|
90509
91522
|
display: flex;
|
|
90510
91523
|
position: relative;
|
|
91524
|
+
|
|
91525
|
+
& > .navi_button {
|
|
91526
|
+
margin-left: calc(var(--border-width) * -1);
|
|
91527
|
+
}
|
|
90511
91528
|
}
|
|
90512
91529
|
`;
|
|
90513
91530
|
|
|
90514
91531
|
/**
|
|
90515
|
-
* @type {import("
|
|
90516
|
-
* options: Array<{value: any, label: import("
|
|
91532
|
+
* @type {import("preact").FunctionComponent<{
|
|
91533
|
+
* options: Array<{value: any, label: import("preact").ComponentChildren, [key: string]: any}>,
|
|
90517
91534
|
* value?: any,
|
|
90518
91535
|
* defaultValue?: any,
|
|
90519
|
-
* label?: import("
|
|
90520
|
-
* action?: (value: any, event: Event) => void | Promise<void>,
|
|
91536
|
+
* label?: import("preact").ComponentChildren,
|
|
91537
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void | Promise<void>,
|
|
90521
91538
|
* command?: string,
|
|
90522
91539
|
* commandFor?: string,
|
|
90523
91540
|
* prefetch?: boolean,
|
|
90524
|
-
* onValueChange?: (value: any, event: Event) => void,
|
|
91541
|
+
* onValueChange?: (value: any, { event }: { event: Event }) => void,
|
|
90525
91542
|
* chooseEffect?: "select" | "run",
|
|
90526
91543
|
* menuLabel?: string,
|
|
90527
|
-
* menuIcon?: import("
|
|
91544
|
+
* menuIcon?: import("preact").ComponentChildren,
|
|
90528
91545
|
* menuIconSize?: number | string,
|
|
91546
|
+
* menuTestId?: string,
|
|
90529
91547
|
* loading?: boolean,
|
|
90530
91548
|
* readOnly?: boolean,
|
|
90531
91549
|
* disabled?: boolean,
|
|
@@ -90561,19 +91579,21 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90561
91579
|
* popupTestId?: string,
|
|
90562
91580
|
* [key: string]: any,
|
|
90563
91581
|
* }>}
|
|
90564
|
-
* @param {Array<{value: any, label: import("
|
|
91582
|
+
* @param {Array<{value: any, label: import("preact").ComponentChildren}>} options
|
|
90565
91583
|
* The ways of doing the thing, in the order the menu lists them. Anything
|
|
90566
91584
|
* else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
|
|
90567
91585
|
* @param {any} [value] Which entry the button stands for — the one it runs, and
|
|
90568
91586
|
* the one it is named after. `defaultValue` is the uncontrolled form; without
|
|
90569
91587
|
* either, the first entry.
|
|
90570
|
-
* @param {import("
|
|
91588
|
+
* @param {import("preact").ComponentChildren} [label] What the button says, when
|
|
90571
91589
|
* that is not the name of the entry it stands for — a button that keeps one
|
|
90572
91590
|
* name whatever the menu does.
|
|
90573
|
-
* @param {(value: any, event
|
|
90574
|
-
* entry given. Pressed on the button it is the entry the button stands
|
|
90575
|
-
* with `chooseEffect="run"` it is also the entry just chosen.
|
|
90576
|
-
*
|
|
91591
|
+
* @param {(value: any, { event, signal }) => void} [action] Do the thing, for
|
|
91592
|
+
* the entry given. Pressed on the button it is the entry the button stands
|
|
91593
|
+
* for; with `chooseEffect="run"` it is also the entry just chosen. Run as a
|
|
91594
|
+
* navi action: the second argument is the run's (see docs/actions.md), the
|
|
91595
|
+
* press being its `event`. Awaited: the whole split button is busy until it
|
|
91596
|
+
* settles.
|
|
90577
91597
|
* @param {string} [command] What a press on the button does once its `action`
|
|
90578
91598
|
* is done, as on a `<Button>` (with `commandFor`, and `command-value` when
|
|
90579
91599
|
* what the command is about is not the entry the button stands for). A
|
|
@@ -90587,12 +91607,14 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90587
91607
|
* menu of one-offs around a primary action.
|
|
90588
91608
|
* @param {string} [menuLabel] What the chevron half is called, for a screen
|
|
90589
91609
|
* reader and for the tooltip a long press raises.
|
|
90590
|
-
* @param {import("
|
|
91610
|
+
* @param {import("preact").ComponentChildren} [menuIcon] What the chevron half
|
|
90591
91611
|
* draws, in place of the chevron. It is the whole of what that half says, so
|
|
90592
91612
|
* pass something that still reads as "there is more here" — and say what,
|
|
90593
91613
|
* through `menuLabel`.
|
|
90594
91614
|
* @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
|
|
90595
91615
|
* the button's own font size.
|
|
91616
|
+
* @param {string} [menuTestId] The `data-testid` of the chevron half, which
|
|
91617
|
+
* navi builds and the caller has no other way to name.
|
|
90596
91618
|
* @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
|
|
90597
91619
|
* picker left to itself turns into a dialog on a small screen; a split
|
|
90598
91620
|
* button's menu stays hung off the button there, so this says popover unless
|
|
@@ -90613,8 +91635,12 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90613
91635
|
* `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
|
|
90614
91636
|
* An `aria-*` attribute describes the button and reaches the main half —
|
|
90615
91637
|
* `aria-pressed` for a split button whose main half toggles a mode;
|
|
90616
|
-
* `aria-hidden` excepted, which hides the whole control.
|
|
90617
|
-
*
|
|
91638
|
+
* `aria-hidden` excepted, which hides the whole control. How the words are
|
|
91639
|
+
* written (`size`, `bold`, `lineHeight`, `noWrap`…) reaches both halves, and so
|
|
91640
|
+
* does vertical padding (`paddingY`, `paddingTop`, `paddingBottom`); the rest
|
|
91641
|
+
* of the padding is the main half's — the chevron half keeps its own, which
|
|
91642
|
+
* gives it its width. Anything else (`className`, `width`, margins, `data-*`)
|
|
91643
|
+
* lands on the split button's own box.
|
|
90618
91644
|
*/
|
|
90619
91645
|
const SplitButton = props => {
|
|
90620
91646
|
import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
@@ -90633,6 +91659,7 @@ const SplitButton = props => {
|
|
|
90633
91659
|
menuLabel = buttonI18n("button.more_actions"),
|
|
90634
91660
|
menuIcon = jsx(ChevronDownSvg$1, {}),
|
|
90635
91661
|
menuIconSize,
|
|
91662
|
+
menuTestId,
|
|
90636
91663
|
loading,
|
|
90637
91664
|
readOnly,
|
|
90638
91665
|
disabled,
|
|
@@ -90652,11 +91679,13 @@ const SplitButton = props => {
|
|
|
90652
91679
|
...rest
|
|
90653
91680
|
} = props;
|
|
90654
91681
|
// Everything the popup answers to travels to the Picker; an aria-* describes
|
|
90655
|
-
// the button a screen reader meets, which is the main half;
|
|
90656
|
-
//
|
|
90657
|
-
//
|
|
90658
|
-
//
|
|
90659
|
-
|
|
91682
|
+
// the button a screen reader meets, which is the main half; how the words are
|
|
91683
|
+
// written and how far they sit from the edge belong to the halves, since a
|
|
91684
|
+
// <button> inherits neither from the box around it; everything else is the
|
|
91685
|
+
// split button's own box (margins, width, data-*). Sorted by name rather
|
|
91686
|
+
// than named one by one so a Picker popup prop is forwarded by adding it to
|
|
91687
|
+
// that list, not by threading it through here.
|
|
91688
|
+
const [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps] = sortRestProps(rest);
|
|
90660
91689
|
// A split button is a control on the page, not a place one goes: its menu
|
|
90661
91690
|
// hangs off it even on a phone, where a picker left to itself would decide a
|
|
90662
91691
|
// small screen means a dialog. Passing mode="dialog" asks for that back.
|
|
@@ -90701,6 +91730,7 @@ const SplitButton = props => {
|
|
|
90701
91730
|
borderColor,
|
|
90702
91731
|
borderWidth,
|
|
90703
91732
|
borderRadius,
|
|
91733
|
+
...halfStyleProps,
|
|
90704
91734
|
pressEffect,
|
|
90705
91735
|
loading: loadingResolved,
|
|
90706
91736
|
readOnly,
|
|
@@ -90710,7 +91740,7 @@ const SplitButton = props => {
|
|
|
90710
91740
|
};
|
|
90711
91741
|
return jsxs(Box, {
|
|
90712
91742
|
ref: rootRef,
|
|
90713
|
-
|
|
91743
|
+
baseClassName: "navi_split_button",
|
|
90714
91744
|
borderRadius: borderRadius,
|
|
90715
91745
|
...boxProps,
|
|
90716
91746
|
children: [jsx(LoadingOutline, {
|
|
@@ -90727,6 +91757,7 @@ const SplitButton = props => {
|
|
|
90727
91757
|
"command-value": commandValue,
|
|
90728
91758
|
prefetch: prefetch,
|
|
90729
91759
|
...buttonAriaProps,
|
|
91760
|
+
...mainPaddingProps,
|
|
90730
91761
|
...halfProps,
|
|
90731
91762
|
children: label === undefined ? optionShown?.label : label
|
|
90732
91763
|
}), jsxs("div", {
|
|
@@ -90736,6 +91767,7 @@ const SplitButton = props => {
|
|
|
90736
91767
|
paddingX: "s",
|
|
90737
91768
|
expandY: true,
|
|
90738
91769
|
"aria-label": menuLabel,
|
|
91770
|
+
"data-testid": menuTestId,
|
|
90739
91771
|
command: "--navi-open",
|
|
90740
91772
|
commandFor: menuId,
|
|
90741
91773
|
...halfProps,
|
|
@@ -90815,9 +91847,16 @@ const SplitButton = props => {
|
|
|
90815
91847
|
// a caller reaches all of them through the split button (see picker.jsx's JSDoc
|
|
90816
91848
|
// for what each one says).
|
|
90817
91849
|
const POPUP_PROP_SET = new Set(["mode", "popupLayer", "popupTestId", "positionArea", "popoverMode", "popoverSpacing", "popupWidthFitContent", "popoverMaxHeight", "dialogMinWidth", "dialogMinHeight", "dialogMaxWidth", "dialogMaxHeight", "dialogExpand", "dialogExpandX", "dialogExpandY", "dockedOnSmallTouchScreen", "marginWithContainer", "backdrop", "backdropVariant", "backdropColor", "backdropFilter", "pressOutside", "escapeEffect", "closeOnFocusOut", "scrollCapture", "focusCapture", "popupBackgroundColor", "popupBorderRadius", "animation"]);
|
|
91850
|
+
// Vertical padding is said about both halves so they stay the same height; the
|
|
91851
|
+
// horizontal one is the main half's alone, the chevron half's own (paddingX="s")
|
|
91852
|
+
// being what gives it its width.
|
|
91853
|
+
const HALF_PADDING_PROP_SET = new Set(["paddingY", "paddingTop", "paddingBottom"]);
|
|
91854
|
+
const MAIN_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingLeft", "paddingRight"]);
|
|
90818
91855
|
const sortRestProps = props => {
|
|
90819
91856
|
const popupProps = {};
|
|
90820
91857
|
const buttonAriaProps = {};
|
|
91858
|
+
const halfStyleProps = {};
|
|
91859
|
+
const mainPaddingProps = {};
|
|
90821
91860
|
const boxProps = {};
|
|
90822
91861
|
for (const key of Object.keys(props)) {
|
|
90823
91862
|
if (POPUP_PROP_SET.has(key)) {
|
|
@@ -90826,11 +91865,15 @@ const sortRestProps = props => {
|
|
|
90826
91865
|
// aria-hidden hides a subtree rather than describing a button: on the
|
|
90827
91866
|
// main half alone it would leave the chevron half exposed.
|
|
90828
91867
|
buttonAriaProps[key] = props[key];
|
|
91868
|
+
} else if (isTypoProp(key) || HALF_PADDING_PROP_SET.has(key)) {
|
|
91869
|
+
halfStyleProps[key] = props[key];
|
|
91870
|
+
} else if (MAIN_PADDING_PROP_SET.has(key)) {
|
|
91871
|
+
mainPaddingProps[key] = props[key];
|
|
90829
91872
|
} else {
|
|
90830
91873
|
boxProps[key] = props[key];
|
|
90831
91874
|
}
|
|
90832
91875
|
}
|
|
90833
|
-
return [popupProps, buttonAriaProps, boxProps];
|
|
91876
|
+
return [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps];
|
|
90834
91877
|
};
|
|
90835
91878
|
|
|
90836
91879
|
/**
|
|
@@ -92032,7 +93075,7 @@ const WheelGroupContext = createContext(null);
|
|
|
92032
93075
|
* value, the value lives in one hidden input for the form. The visible rows are
|
|
92033
93076
|
* presentational.
|
|
92034
93077
|
*
|
|
92035
|
-
* @type {import("
|
|
93078
|
+
* @type {import("preact").FunctionComponent<{
|
|
92036
93079
|
* value?: any,
|
|
92037
93080
|
* defaultValue?: any,
|
|
92038
93081
|
* action?: (value: any) => void,
|
|
@@ -92050,7 +93093,7 @@ const WheelGroupContext = createContext(null);
|
|
|
92050
93093
|
* readOnly?: boolean,
|
|
92051
93094
|
* disabled?: boolean,
|
|
92052
93095
|
* loading?: boolean,
|
|
92053
|
-
* children?: import("
|
|
93096
|
+
* children?: import("preact").ComponentChildren,
|
|
92054
93097
|
* [key: string]: any,
|
|
92055
93098
|
* }>}
|
|
92056
93099
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
@@ -92679,8 +93722,9 @@ function WheelUI(props) {
|
|
|
92679
93722
|
}, {
|
|
92680
93723
|
controlType: "input",
|
|
92681
93724
|
// The host is the hidden input carrying the value: the container is
|
|
92682
|
-
// what is focused and spun.
|
|
92683
|
-
listenersOn: "root"
|
|
93725
|
+
// what is focused and spun, and the spinbutton its name has to name.
|
|
93726
|
+
listenersOn: "root",
|
|
93727
|
+
nameOn: "root"
|
|
92684
93728
|
});
|
|
92685
93729
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
92686
93730
|
const {
|
|
@@ -93537,9 +94581,9 @@ const WheelWindow = ({
|
|
|
93537
94581
|
* of rows and fills each from this tracked data — so the value list is the source
|
|
93538
94582
|
* of truth, not a per-item DOM node. Selection lives on the wheel's value.
|
|
93539
94583
|
*
|
|
93540
|
-
* @type {import("
|
|
94584
|
+
* @type {import("preact").FunctionComponent<{
|
|
93541
94585
|
* value: any,
|
|
93542
|
-
* children?: import("
|
|
94586
|
+
* children?: import("preact").ComponentChildren,
|
|
93543
94587
|
* [key: string]: any,
|
|
93544
94588
|
* }>}
|
|
93545
94589
|
*/
|
|
@@ -93574,9 +94618,9 @@ Wheel.Item = WheelItem;
|
|
|
93574
94618
|
* each Wheel.Item (it's a Box: paddingX="0.5ch") so the gap stays scrollable.
|
|
93575
94619
|
* `spacing` is just the Box flex gap between children (a layout gap, not padding).
|
|
93576
94620
|
*
|
|
93577
|
-
* @type {import("
|
|
94621
|
+
* @type {import("preact").FunctionComponent<{
|
|
93578
94622
|
* horizontal?: boolean,
|
|
93579
|
-
* children?: import("
|
|
94623
|
+
* children?: import("preact").ComponentChildren,
|
|
93580
94624
|
* [key: string]: any,
|
|
93581
94625
|
* }>}
|
|
93582
94626
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
@@ -93773,14 +94817,14 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
93773
94817
|
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
93774
94818
|
|
|
93775
94819
|
/**
|
|
93776
|
-
* @type {import("
|
|
94820
|
+
* @type {import("preact").FunctionComponent<{
|
|
93777
94821
|
* name?: string,
|
|
93778
94822
|
* value?: string,
|
|
93779
94823
|
* defaultValue?: string,
|
|
93780
94824
|
* signal?: import("@preact/signals").Signal<string>,
|
|
93781
94825
|
* minuteStep?: number,
|
|
93782
94826
|
* loop?: boolean,
|
|
93783
|
-
* separator?: import("
|
|
94827
|
+
* separator?: import("preact").ComponentChildren,
|
|
93784
94828
|
* hourLabel?: string,
|
|
93785
94829
|
* minuteLabel?: string,
|
|
93786
94830
|
* size?: string,
|
|
@@ -93801,7 +94845,7 @@ const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
|
93801
94845
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
93802
94846
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
93803
94847
|
* past.
|
|
93804
|
-
* @param {import("
|
|
94848
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
93805
94849
|
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
93806
94850
|
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
93807
94851
|
* is written by default outside French.
|
|
@@ -93920,7 +94964,7 @@ const TimeWheel = ({
|
|
|
93920
94964
|
};
|
|
93921
94965
|
|
|
93922
94966
|
/**
|
|
93923
|
-
* @type {import("
|
|
94967
|
+
* @type {import("preact").FunctionComponent<{
|
|
93924
94968
|
* name?: string,
|
|
93925
94969
|
* value?: { start?: string, end?: string },
|
|
93926
94970
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -93929,15 +94973,15 @@ const TimeWheel = ({
|
|
|
93929
94973
|
* minDuration?: number,
|
|
93930
94974
|
* loop?: boolean,
|
|
93931
94975
|
* size?: string,
|
|
93932
|
-
* startLabel?: import("
|
|
93933
|
-
* endLabel?: import("
|
|
94976
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
94977
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
93934
94978
|
* labelPosition?: "before" | "above",
|
|
93935
94979
|
* timeProps?: object,
|
|
93936
94980
|
* [key: string]: any,
|
|
93937
94981
|
* }>}
|
|
93938
94982
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
93939
94983
|
* "HH:MM".
|
|
93940
|
-
* @param {import("
|
|
94984
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
93941
94985
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
93942
94986
|
* `null` for neither.
|
|
93943
94987
|
* @param {"before"|"above"} [labelPosition="before"] Where each label stands.
|
|
@@ -97922,17 +98966,25 @@ const Address = ({
|
|
|
97922
98966
|
};
|
|
97923
98967
|
|
|
97924
98968
|
installImportMetaCssBuild(import.meta);
|
|
97925
|
-
const css$a = /* css */`@layer navi
|
|
98969
|
+
const css$a = /* css */`@layer navi {
|
|
98970
|
+
.navi_text.navi_badge_count {
|
|
98971
|
+
--font-size: .7em;
|
|
98972
|
+
--badge-count-padding-x-default: .5em;
|
|
98973
|
+
--badge-count-padding-y-default: .3em;
|
|
98974
|
+
|
|
98975
|
+
&[data-ellipse][data-single-char], &[data-circle] {
|
|
98976
|
+
--badge-count-padding-x-default: 0;
|
|
98977
|
+
--badge-count-padding-y-default: 0;
|
|
98978
|
+
}
|
|
98979
|
+
}
|
|
98980
|
+
}
|
|
97926
98981
|
|
|
97927
98982
|
.navi_text.navi_badge_count {
|
|
97928
98983
|
white-space: nowrap;
|
|
97929
|
-
--font-size: .7em;
|
|
97930
98984
|
--x-background: var(--background);
|
|
97931
|
-
--x-background-color: var(--background-color
|
|
98985
|
+
--x-background-color: var(--background-color);
|
|
97932
98986
|
--x-color-contrasting: var(--navi-color-white);
|
|
97933
98987
|
--x-color: var(--color, var(--x-color-contrasting));
|
|
97934
|
-
--badge-count-padding-x-default: .5em;
|
|
97935
|
-
--badge-count-padding-y-default: .3em;
|
|
97936
98988
|
--x-badge-count-padding-top: var(--badge-count-padding-top, var(--badge-count-padding-y, var(--badge-count-padding, var(--badge-count-padding-y-default))));
|
|
97937
98989
|
--x-badge-count-padding-right: var(--badge-count-padding-right, var(--badge-count-padding-x, var(--badge-count-padding, var(--badge-count-padding-x-default))));
|
|
97938
98990
|
--x-badge-count-padding-bottom: var(--badge-count-padding-bottom, var(--badge-count-padding-y, var(--badge-count-padding, var(--badge-count-padding-y-default))));
|
|
@@ -97964,12 +99016,9 @@ const css$a = /* css */`@layer navi;
|
|
|
97964
99016
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
97965
99017
|
padding-left: var(--x-badge-count-padding-left);
|
|
97966
99018
|
background: var(--x-background);
|
|
97967
|
-
background-color: var(--x-background-color);
|
|
97968
99019
|
border-radius: 1em;
|
|
97969
99020
|
|
|
97970
99021
|
&[data-single-char] {
|
|
97971
|
-
--badge-count-padding-x-default: 0;
|
|
97972
|
-
--badge-count-padding-y-default: 0;
|
|
97973
99022
|
box-sizing: content-box;
|
|
97974
99023
|
text-align: center;
|
|
97975
99024
|
border-radius: 100em;
|
|
@@ -97982,8 +99031,6 @@ const css$a = /* css */`@layer navi;
|
|
|
97982
99031
|
|
|
97983
99032
|
&[data-circle] {
|
|
97984
99033
|
--x-number-font-size: var(--font-size);
|
|
97985
|
-
--badge-count-padding-x-default: 0;
|
|
97986
|
-
--badge-count-padding-y-default: 0;
|
|
97987
99034
|
box-sizing: content-box;
|
|
97988
99035
|
aspect-ratio: 1;
|
|
97989
99036
|
width: var(--x-radius);
|
|
@@ -97993,7 +99040,6 @@ const css$a = /* css */`@layer navi;
|
|
|
97993
99040
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
97994
99041
|
padding-left: var(--x-badge-count-padding-left);
|
|
97995
99042
|
background: var(--x-background);
|
|
97996
|
-
background-color: var(--x-background-color);
|
|
97997
99043
|
border-radius: 100em;
|
|
97998
99044
|
justify-content: center;
|
|
97999
99045
|
align-items: center;
|
|
@@ -98018,6 +99064,10 @@ const css$a = /* css */`@layer navi;
|
|
|
98018
99064
|
font-size: var(--x-number-font-size);
|
|
98019
99065
|
}
|
|
98020
99066
|
}
|
|
99067
|
+
|
|
99068
|
+
&[data-background-color] {
|
|
99069
|
+
background-color: var(--x-background-color);
|
|
99070
|
+
}
|
|
98021
99071
|
}
|
|
98022
99072
|
`;
|
|
98023
99073
|
const BadgeCountOverflow = () => jsx("span", {
|
|
@@ -98154,6 +99204,7 @@ const BadgeCountEllipse = ({
|
|
|
98154
99204
|
"data-ellipse": "",
|
|
98155
99205
|
"data-value-overflow": hasOverflow ? "" : undefined,
|
|
98156
99206
|
"data-loading": loading ? "" : undefined,
|
|
99207
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
98157
99208
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
98158
99209
|
...props,
|
|
98159
99210
|
styleCSSVars: BadgeCountStyleCSSVars,
|
|
@@ -98176,6 +99227,7 @@ const BadgeCountCircle = ({
|
|
|
98176
99227
|
"data-circle": "",
|
|
98177
99228
|
bold: true,
|
|
98178
99229
|
"data-loading": loading ? "" : undefined,
|
|
99230
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
98179
99231
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
98180
99232
|
"data-two-chars": charCount === 2 ? "" : undefined,
|
|
98181
99233
|
"data-three-chars": charCount === 3 ? "" : undefined,
|
|
@@ -98668,7 +99720,7 @@ const css$8 = /* css */`@layer navi;
|
|
|
98668
99720
|
`;
|
|
98669
99721
|
|
|
98670
99722
|
/**
|
|
98671
|
-
* @type {import("
|
|
99723
|
+
* @type {import("preact").FunctionComponent<{
|
|
98672
99724
|
* children?: number | string,
|
|
98673
99725
|
* unit?: string | [string, string],
|
|
98674
99726
|
* unitPosition?: "right" | "bottom",
|
|
@@ -99260,8 +100312,8 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
99260
100312
|
*
|
|
99261
100313
|
* @param {Object} props - Component properties
|
|
99262
100314
|
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
99263
|
-
* @param {import("
|
|
99264
|
-
* @returns {import("
|
|
100315
|
+
* @param {import("preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
100316
|
+
* @returns {import("preact").VNode} A composed SVG with all elements properly masked
|
|
99265
100317
|
*/
|
|
99266
100318
|
const SVGMaskOverlay = ({
|
|
99267
100319
|
viewBox,
|
|
@@ -99351,7 +100403,6 @@ const css$3 = /* css */`@layer navi {
|
|
|
99351
100403
|
padding-bottom: var(--layout-padding-bottom, var(--layout-padding-y, var(--layout-padding)));
|
|
99352
100404
|
padding-left: var(--layout-padding-left, var(--layout-padding-x, var(--layout-padding)));
|
|
99353
100405
|
background: var(--layout-background);
|
|
99354
|
-
background-color: var(--layout-background-color, var(--layout-background));
|
|
99355
100406
|
border-width: var(--layout-border-width);
|
|
99356
100407
|
border-style: solid;
|
|
99357
100408
|
border-color: var(--layout-border-color);
|
|
@@ -99368,7 +100419,6 @@ const CardLayoutStyleCSSVars = {
|
|
|
99368
100419
|
borderWidth: "--layout-border-width",
|
|
99369
100420
|
borderColor: "--layout-border-color",
|
|
99370
100421
|
background: "--layout-background",
|
|
99371
|
-
backgroundColor: "--layout-background-color",
|
|
99372
100422
|
padding: "--layout-padding",
|
|
99373
100423
|
paddingTop: "--layout-padding-top",
|
|
99374
100424
|
paddingBottom: "--layout-padding-bottom",
|
|
@@ -99383,7 +100433,7 @@ const CardLayoutStyleCSSVars = {
|
|
|
99383
100433
|
* what holds it, so how tall that area is is up to the page around it, as for
|
|
99384
100434
|
* `ViewportLayout`.
|
|
99385
100435
|
*
|
|
99386
|
-
* @type {import("
|
|
100436
|
+
* @type {import("preact").FunctionComponent<{
|
|
99387
100437
|
* alignX?: string,
|
|
99388
100438
|
* alignY?: string,
|
|
99389
100439
|
* margin?: string|number,
|
|
@@ -99408,6 +100458,7 @@ const CardLayout = ({
|
|
|
99408
100458
|
children,
|
|
99409
100459
|
alignX = "center",
|
|
99410
100460
|
alignY = "center",
|
|
100461
|
+
backgroundColor,
|
|
99411
100462
|
...props
|
|
99412
100463
|
}) => {
|
|
99413
100464
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
@@ -99426,6 +100477,7 @@ const CardLayout = ({
|
|
|
99426
100477
|
className: "navi_card",
|
|
99427
100478
|
alignX: alignX,
|
|
99428
100479
|
alignY: alignY,
|
|
100480
|
+
backgroundColor: backgroundColor,
|
|
99429
100481
|
children: children
|
|
99430
100482
|
})
|
|
99431
100483
|
});
|
|
@@ -99599,7 +100651,7 @@ const LINE_GAP = 5;
|
|
|
99599
100651
|
const StepListContext = createContext(null);
|
|
99600
100652
|
|
|
99601
100653
|
/**
|
|
99602
|
-
* @type {import("
|
|
100654
|
+
* @type {import("preact").FunctionComponent<{
|
|
99603
100655
|
* current?: string,
|
|
99604
100656
|
* slideContainer?: string,
|
|
99605
100657
|
* travelByClick?: boolean,
|
|
@@ -99956,7 +101008,7 @@ const sameShallow = (previousObject, nextObject) => {
|
|
|
99956
101008
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
99957
101009
|
* SlideContainer.
|
|
99958
101010
|
*
|
|
99959
|
-
* @type {import("
|
|
101011
|
+
* @type {import("preact").FunctionComponent<{
|
|
99960
101012
|
* value: string,
|
|
99961
101013
|
* done?: boolean,
|
|
99962
101014
|
* [key: string]: any,
|
|
@@ -100018,7 +101070,7 @@ const ViewportLayoutStyleCSSVars = {
|
|
|
100018
101070
|
* same inside a route, a preview pane or a demo box, and it is up to the page
|
|
100019
101071
|
* around it to be full-height.
|
|
100020
101072
|
*
|
|
100021
|
-
* @type {import("
|
|
101073
|
+
* @type {import("preact").FunctionComponent<{
|
|
100022
101074
|
* padding?: string|number,
|
|
100023
101075
|
* paddingTop?: string|number,
|
|
100024
101076
|
* paddingBottom?: string|number,
|
|
@@ -100243,7 +101295,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
100243
101295
|
* `closeByPressOutside`/`pressOutside` only say what a
|
|
100244
101296
|
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
100245
101297
|
* doc).
|
|
100246
|
-
* @param {import("
|
|
101298
|
+
* @param {import("preact").ComponentChildren} props.children - No built-in
|
|
100247
101299
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
100248
101300
|
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
100249
101301
|
* `closeByPressOutside`/Escape instead.
|
|
@@ -100342,7 +101394,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
100342
101394
|
* @param {object} props
|
|
100343
101395
|
* @param {string} [props.className] - Merged with the shared
|
|
100344
101396
|
* `"navi_side_panel_head"` class this file's own CSS targets.
|
|
100345
|
-
* @param {import("
|
|
101397
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
100346
101398
|
*/
|
|
100347
101399
|
const SidePanelHead = props => jsx(Box, {
|
|
100348
101400
|
baseClassName: "navi_side_panel_head",
|
|
@@ -100355,7 +101407,7 @@ const SidePanelHead = props => jsx(Box, {
|
|
|
100355
101407
|
* @param {object} props
|
|
100356
101408
|
* @param {string} [props.className] - Merged with the shared
|
|
100357
101409
|
* `"navi_side_panel_foot"` class this file's own CSS targets.
|
|
100358
|
-
* @param {import("
|
|
101410
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
100359
101411
|
*/
|
|
100360
101412
|
const SidePanelFoot = props => jsx(Box, {
|
|
100361
101413
|
className: "navi_side_panel_foot",
|
|
@@ -100364,6 +101416,58 @@ const SidePanelFoot = props => jsx(Box, {
|
|
|
100364
101416
|
SidePanel.Head = SidePanelHead;
|
|
100365
101417
|
SidePanel.Foot = SidePanelFoot;
|
|
100366
101418
|
|
|
101419
|
+
/**
|
|
101420
|
+
* Creates a linked `[Slot, SlotFill]` pair so content rendered anywhere in
|
|
101421
|
+
* the tree (`SlotFill`) appears at a fixed location elsewhere (`Slot`) — a
|
|
101422
|
+
* lightweight, signal-based alternative to a DOM portal: `SlotFill` itself
|
|
101423
|
+
* renders nothing (no extra DOM node at its own call site), it just writes
|
|
101424
|
+
* its props into a shared signal that `Slot` reads reactively.
|
|
101425
|
+
*
|
|
101426
|
+
* Holds at most one filler at a time — there is no stacking/queueing. The
|
|
101427
|
+
* filler is the last `SlotFill` (from this same `createSlot()` call) to
|
|
101428
|
+
* mount, or the first to render into an empty slot; later renders of a
|
|
101429
|
+
* `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
|
|
101430
|
+
* unmounting empties the slot only while it is still that one, and so does one
|
|
101431
|
+
* in a page a route transition keeps while leaving (see keepLeavingPages in
|
|
101432
|
+
* nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
|
|
101433
|
+
* take it back from the page arriving.
|
|
101434
|
+
*
|
|
101435
|
+
* **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
|
|
101436
|
+
* the fills as they mount and unmount, in the order the tree walks them. A
|
|
101437
|
+
* slot fed from several places — a `SlotFill` in each card of a board, each
|
|
101438
|
+
* card deciding on its own whether it is the open one — reads as empty
|
|
101439
|
+
* between the leaver and the newcomer whenever they render separately, and
|
|
101440
|
+
* anything acting on `isFilled` acts on that empty frame (a `SidePanel` with
|
|
101441
|
+
* `open={isFilled}` closes for good: its `onClose` clears the state the
|
|
101442
|
+
* newcomer was waiting for). The slot cannot tell that gap from a real
|
|
101443
|
+
* emptying, and does not try to: render one `SlotFill` at the level that
|
|
101444
|
+
* holds the choice, with the chosen content as its children, and bind a
|
|
101445
|
+
* state that must not blink (a panel's `open`) to the app's own signal
|
|
101446
|
+
* rather than to `isFilled` — see docs/popup_open.md, "One panel, fed by a
|
|
101447
|
+
* slot".
|
|
101448
|
+
*
|
|
101449
|
+
* `Slot` keeps `SlotRenderer` mounted permanently — even while unfilled it
|
|
101450
|
+
* still renders it, with no props and `isFilled={false}` — instead of
|
|
101451
|
+
* unmounting it. This lets `SlotRenderer` be a persistent wrapper (a
|
|
101452
|
+
* `SidePanel` that opens and closes with an animation instead of being
|
|
101453
|
+
* mounted/unmounted alongside the filler — see 7_slot_demo.html's "Two
|
|
101454
|
+
* side-panel slots" section). A `SlotRenderer` that wants the
|
|
101455
|
+
* render-nothing-when-unfilled behavior can opt in with its own
|
|
101456
|
+
* `if (!isFilled) return null;` (the default `Box` renderer doesn't do
|
|
101457
|
+
* this — an unfilled default slot just renders an empty `<Box/>`).
|
|
101458
|
+
*
|
|
101459
|
+
* @param {import("preact").ComponentType} [SlotRenderer=Box] - Rendered by
|
|
101460
|
+
* `Slot`, always, with whatever props `SlotFill` last set (spread
|
|
101461
|
+
* directly) plus `isFilled`. Swap this out to reuse the same slot
|
|
101462
|
+
* mechanism for something other than a plain `Box` (e.g. a specific
|
|
101463
|
+
* component expecting its own particular props, or a persistent wrapper
|
|
101464
|
+
* like `SidePanel`).
|
|
101465
|
+
* @returns {[import("preact").ComponentType, import("preact").ComponentType<object>]}
|
|
101466
|
+
* `[Slot, SlotFill]` — `Slot` takes no props, render it once wherever the
|
|
101467
|
+
* content should appear. `SlotFill` takes whatever props `SlotRenderer`
|
|
101468
|
+
* expects, render it anywhere else in the tree to supply/update that
|
|
101469
|
+
* content.
|
|
101470
|
+
*/
|
|
100367
101471
|
const createSlot = (SlotRenderer = Box) => {
|
|
100368
101472
|
const slotPropsSignal = signal();
|
|
100369
101473
|
let filler = null;
|