@jsenv/navi 0.29.478 → 0.29.480
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 +1732 -681
- package/dist/dev/jsenv_navi.js.map +38 -36
- package/dist/jsenv_navi.js +2161 -551
- package/dist/jsenv_navi.js.map +38 -36
- package/docs/AI_INSTRUCTIONS.md +5 -2
- 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 +67 -4
- 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
|
|
@@ -10130,13 +10293,18 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
10130
10293
|
* Declares a route from an url pattern.
|
|
10131
10294
|
*
|
|
10132
10295
|
* @param {string} pattern - The url pattern, where `:name` is a path param and
|
|
10133
|
-
* `:name=${signal}` binds that param to a signal.
|
|
10296
|
+
* `:name=${signal}` binds that param to a signal. A pattern ending in `/` is a
|
|
10297
|
+
* frame: it matches every address below it too, and its search params hold
|
|
10298
|
+
* there (see docs/navigation.md, "A page or a frame: the trailing slash").
|
|
10134
10299
|
* @param {object} [options]
|
|
10135
10300
|
* @param {Object<string, import("@preact/signals").Signal | {signal: import("@preact/signals").Signal, default?: any}>} [options.searchParams]
|
|
10136
10301
|
* Search params this route two-way syncs with, by name. The signal and the url
|
|
10137
10302
|
* are the same state: writing the signal rewrites the address, an address
|
|
10138
10303
|
* arriving from outside writes the signal, and the param disappears from the
|
|
10139
10304
|
* url while the signal holds its default.
|
|
10305
|
+
* They exist on this route's address only, unless the pattern is a frame
|
|
10306
|
+
* (`/map/`, `/map/*`, the root): then on every address below it too.
|
|
10307
|
+
* `/places` does not lend its `?q=` to `/places/clubs`.
|
|
10140
10308
|
* `{ signal, default }` says what that state is worth ON THIS ROUTE: the same
|
|
10141
10309
|
* step opens a creation wizard on its first question and an edition one on
|
|
10142
10310
|
* its summary, each address staying clean on the step its page opens on. One
|
|
@@ -10155,7 +10323,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
10155
10323
|
* whose param declines the segment does not match at all: no signal is
|
|
10156
10324
|
* written, `<Route fallback>` is reachable, and `/:gameId` can sit at the root
|
|
10157
10325
|
* 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
|
|
10326
|
+
* is not one of the values it accepts — so `/:gameId` does not match `/`,
|
|
10327
|
+
* unless its default stands for the bare address (see docs/navigation.md,
|
|
10328
|
+
* "Declaring the sections is what makes them places").
|
|
10159
10329
|
*
|
|
10160
10330
|
* Constrain the shape, not the existence: whether that game exists is for the
|
|
10161
10331
|
* route action and the page to answer, on a route that did match.
|
|
@@ -11370,14 +11540,17 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
11370
11540
|
*
|
|
11371
11541
|
* - `isShown()` — whether the container would still show the page. False as
|
|
11372
11542
|
* 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
|
-
*
|
|
11543
|
+
* hidden until the route transition leaving it is over, nothing it reads is
|
|
11544
|
+
* worth a render any more, and nothing it holds speaks for the page shown (a
|
|
11545
|
+
* list's hold on the document, see holdDocumentScroll). False for good once
|
|
11546
|
+
* it is kept while leaving: a return to its route mounts the page anew. Asked
|
|
11547
|
+
* outside any render — by a subscription deciding whether to re-render, by
|
|
11548
|
+
* the scroll restoration when a position is due — and it reads the routes
|
|
11377
11549
|
* untracked, so asking subscribes nobody to them.
|
|
11378
11550
|
* - `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
|
|
11551
|
+
* plays: still mounted, so what it fills or moves outside its own nodes (a
|
|
11552
|
+
* slot in a bar, the document a list scrolls) steps out rather than showing
|
|
11553
|
+
* with, or moving, the page arriving. Every box of it measures 0×0 by then.
|
|
11381
11554
|
*/
|
|
11382
11555
|
const RoutePageContext = createContext(null);
|
|
11383
11556
|
|
|
@@ -12871,6 +13044,25 @@ const getHrefTargetInfo = (
|
|
|
12871
13044
|
};
|
|
12872
13045
|
};
|
|
12873
13046
|
|
|
13047
|
+
// The target of a link whose caller names none: a page of another site opens
|
|
13048
|
+
// in a new tab. Only a page: a `tel:`, `mailto:` or `sms:` address is handed to
|
|
13049
|
+
// another app, a `blob:` or `data:` one is this page's own, and a tab opened
|
|
13050
|
+
// for them would be left behind empty.
|
|
13051
|
+
const getDefaultHrefTarget = (
|
|
13052
|
+
href,
|
|
13053
|
+
currentUrlString = window.location.href,
|
|
13054
|
+
) => {
|
|
13055
|
+
const { isEmpty, isSameSite } = getHrefTargetInfo(href, currentUrlString);
|
|
13056
|
+
if (isEmpty || isSameSite) {
|
|
13057
|
+
return undefined;
|
|
13058
|
+
}
|
|
13059
|
+
const { protocol } = new URL(href, currentUrlString);
|
|
13060
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
13061
|
+
return undefined;
|
|
13062
|
+
}
|
|
13063
|
+
return "_blank";
|
|
13064
|
+
};
|
|
13065
|
+
|
|
12874
13066
|
// A field of getHrefTargetInfo that depends on the page one is on (isCurrent,
|
|
12875
13067
|
// isAnchor), read through a computed of that one boolean: the caller re-renders
|
|
12876
13068
|
// when it flips, not at every address write — a search param bound to a wheel
|
|
@@ -12911,6 +13103,27 @@ const linkAsksForDocument = (linkElement) => {
|
|
|
12911
13103
|
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
12912
13104
|
};
|
|
12913
13105
|
|
|
13106
|
+
/*
|
|
13107
|
+
* A link does not always move the tab it is in. A `target` naming another
|
|
13108
|
+
* browsing context (a new tab, a named window) loads the address there, in a
|
|
13109
|
+
* document of its own; a `download` saves it. The browser answers both
|
|
13110
|
+
* without touching this page, and that is the point of writing them: reading
|
|
13111
|
+
* the terms beside a half-filled form costs nothing. Routing such a link
|
|
13112
|
+
* would take this tab instead, and give no tab and no file.
|
|
13113
|
+
*
|
|
13114
|
+
* `_parent` and `_top` count as elsewhere too. Inside a frame they are; at the
|
|
13115
|
+
* top level the browser treats them as `_self` and loads a document, which
|
|
13116
|
+
* costs a load and nothing else.
|
|
13117
|
+
*/
|
|
13118
|
+
|
|
13119
|
+
const linkOpensElsewhere = (linkElement) => {
|
|
13120
|
+
const target = linkElement.getAttribute("target");
|
|
13121
|
+
if (target && target !== "_self") {
|
|
13122
|
+
return true;
|
|
13123
|
+
}
|
|
13124
|
+
return linkElement.hasAttribute("download");
|
|
13125
|
+
};
|
|
13126
|
+
|
|
12914
13127
|
/*
|
|
12915
13128
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
12916
13129
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -13240,8 +13453,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
13240
13453
|
// Ignore non-left clicks
|
|
13241
13454
|
return;
|
|
13242
13455
|
}
|
|
13243
|
-
if (e.metaKey) {
|
|
13244
|
-
//
|
|
13456
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
13457
|
+
// A modifier asks the browser for something other than this tab: a new
|
|
13458
|
+
// tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
|
|
13459
|
+
// (alt).
|
|
13245
13460
|
return;
|
|
13246
13461
|
}
|
|
13247
13462
|
if (e.defaultPrevented) {
|
|
@@ -13252,6 +13467,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
13252
13467
|
if (linkElement.hasAttribute("data-readonly")) {
|
|
13253
13468
|
return;
|
|
13254
13469
|
}
|
|
13470
|
+
if (linkOpensElsewhere(linkElement)) {
|
|
13471
|
+
// Another tab or window, or a download (see link_elsewhere.js).
|
|
13472
|
+
return;
|
|
13473
|
+
}
|
|
13255
13474
|
const href = linkElement.href;
|
|
13256
13475
|
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
13257
13476
|
getHrefTargetInfo(href);
|
|
@@ -17538,6 +17757,9 @@ const surfaceTextCss = /* css */ `
|
|
|
17538
17757
|
}
|
|
17539
17758
|
`;
|
|
17540
17759
|
|
|
17760
|
+
// What a callout's own JSX may know about the callout around it: how to close
|
|
17761
|
+
// it, and the element — whose id is what `commandFor` needs when the button
|
|
17762
|
+
// closing it is not inside it (inside, `--navi-close` finds it on its own).
|
|
17541
17763
|
const CalloutContext = createContext();
|
|
17542
17764
|
const useCalloutRequestClose = () => {
|
|
17543
17765
|
return useContext(CalloutContext)?.requestClose;
|
|
@@ -17671,7 +17893,7 @@ const CALLOUT_STATUS_GLYPH_VIEWBOX = "0 0 540 540";
|
|
|
17671
17893
|
const CALLOUT_STATUS_GLYPH_PATH = "m232.5,121 8,196h59l8-196zm36.5,224a37,37 0 1,0 2,0z";
|
|
17672
17894
|
|
|
17673
17895
|
/**
|
|
17674
|
-
* @type {import("
|
|
17896
|
+
* @type {import("preact").FunctionComponent<{
|
|
17675
17897
|
* status?: "info" | "warning" | "error" | "success" | "none",
|
|
17676
17898
|
* shape?: "square" | "circle",
|
|
17677
17899
|
* }>}
|
|
@@ -22617,23 +22839,12 @@ defineInteractionDetector({
|
|
|
22617
22839
|
// A link and an image are draggable without anyone asking, and a native
|
|
22618
22840
|
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
22619
22841
|
// events with it and paints a ghost of the row the hand is trying to swipe.
|
|
22620
|
-
//
|
|
22621
|
-
//
|
|
22622
|
-
//
|
|
22623
|
-
//
|
|
22624
|
-
//
|
|
22625
|
-
// browser honours (`-webkit-user-drag` is one engine's).
|
|
22626
|
-
// The cost is stated rather than worked around: an element that takes a
|
|
22627
|
-
// swipe cannot also be dragged out of the page, because there is one
|
|
22628
|
-
// gesture and it cannot mean both.
|
|
22842
|
+
// `draggable` refuses it on the element itself, before it starts; the drag
|
|
22843
|
+
// of a link or a thumbnail INSIDE it is refused by the travel reading the
|
|
22844
|
+
// press (startDragToTravel). The cost is stated rather than worked around:
|
|
22845
|
+
// an element that takes a swipe cannot also be dragged out of the page,
|
|
22846
|
+
// because there is one gesture and it cannot mean both.
|
|
22629
22847
|
mark("draggable", "false");
|
|
22630
|
-
const onDragStart = (dragStartEvent) => {
|
|
22631
|
-
dragStartEvent.preventDefault();
|
|
22632
|
-
};
|
|
22633
|
-
element.addEventListener("dragstart", onDragStart);
|
|
22634
|
-
undo.push(() => {
|
|
22635
|
-
element.removeEventListener("dragstart", onDragStart);
|
|
22636
|
-
});
|
|
22637
22848
|
|
|
22638
22849
|
// A touch this element may take has to be refusable before the finger
|
|
22639
22850
|
// lands, or the browser can cancel an established swipe mid-gesture by
|
|
@@ -23780,9 +23991,10 @@ const isMac = detectMac();
|
|
|
23780
23991
|
* place rather than about this box.
|
|
23781
23992
|
*
|
|
23782
23993
|
* `data-pan-after-hold="kept"` asks for that wait once rather than before every
|
|
23783
|
-
* pan: the surface given the hand keeps it — panning on contact,
|
|
23784
|
-
* plan opened full screen — and asks again after a press has
|
|
23785
|
-
* it, which is the hand saying it has moved on.
|
|
23994
|
+
* pan: the surface a finger has given the hand keeps it — panning on contact,
|
|
23995
|
+
* like the same plan opened full screen — and asks again after a press has
|
|
23996
|
+
* landed away from it, which is the hand saying it has moved on. A mouse never
|
|
23997
|
+
* owed the wait, so it neither spends it nor marks it. What answers "away" is the same
|
|
23786
23998
|
* thing that closes a popup, and the surface is the one that watches for it, so
|
|
23787
23999
|
* an application never keeps a `pointerdown` listener on the window to know.
|
|
23788
24000
|
* `[data-hand-kept]` is on the element for as long as it holds the hand that
|
|
@@ -24911,7 +25123,8 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
24911
25123
|
* and that field is the control the declaration speaks for: an ArrowUp declared
|
|
24912
25124
|
* on a text field is the field saying what ArrowUp does there, not a container
|
|
24913
25125
|
* taking the caret from a field inside it. A field inside a box that declares
|
|
24914
|
-
* keys for something else (a row, a list) keeps every key it has a use for
|
|
25126
|
+
* keys for something else (a row, a list) keeps every key it has a use for,
|
|
25127
|
+
* even when it is the only control in that box.
|
|
24915
25128
|
*
|
|
24916
25129
|
* A declared key that matches takes the key: its default does not happen as
|
|
24917
25130
|
* well, whether the gate lets the interaction through or refuses it and says
|
|
@@ -24937,7 +25150,10 @@ defineInteractionDetector({
|
|
|
24937
25150
|
};
|
|
24938
25151
|
}
|
|
24939
25152
|
const onKeyDown = createOnKeyDownForShortcuts(shortcuts, {
|
|
24940
|
-
|
|
25153
|
+
// Only the control's own root speaks for its host: a box that wraps a
|
|
25154
|
+
// single control is reached by that control's gate, not handed its keys.
|
|
25155
|
+
ownsTarget: (target) =>
|
|
25156
|
+
isControlRoot(element) && target === findNearestControlHost(element),
|
|
24941
25157
|
});
|
|
24942
25158
|
element.addEventListener("keydown", onKeyDown);
|
|
24943
25159
|
return () => {
|
|
@@ -29005,6 +29221,127 @@ const useForceRender = () => {
|
|
|
29005
29221
|
|
|
29006
29222
|
// https://github.com/preactjs/preact/issues/4756
|
|
29007
29223
|
|
|
29224
|
+
/**
|
|
29225
|
+
* Reads the current state of an action and returns `[data, loading, error, dismissError]`.
|
|
29226
|
+
*
|
|
29227
|
+
* By default (`loading` and `error` not set) the component suspends on load
|
|
29228
|
+
* and throws on failure, delegating both states to the nearest `<Loading>` and
|
|
29229
|
+
* `<ErrorBoundary>` ancestors. Pass `loading: true` or `error: true` to handle
|
|
29230
|
+
* either state directly inside the component instead.
|
|
29231
|
+
*
|
|
29232
|
+
* Return value: `[data, loading, error, dismissError]`
|
|
29233
|
+
* - `data` — the action's last successful data, or `undefined` if it has never completed
|
|
29234
|
+
* - `loading` — `true` while the action is running (only when `loading: true` is passed)
|
|
29235
|
+
* - `error` — the Error thrown by the action (only when `error: true` is passed)
|
|
29236
|
+
* - `dismissError` — takes that error out of what this hook returns, nothing else (only when `error: true` is passed)
|
|
29237
|
+
*
|
|
29238
|
+
* Stale data is always returned: the previous `data` remains available while
|
|
29239
|
+
* the action re-runs and after a run has failed, so a screen that had something
|
|
29240
|
+
* to show never has it taken away. What is drawn over it — a loading indicator,
|
|
29241
|
+
* the error, nothing — is entirely up to the component.
|
|
29242
|
+
*
|
|
29243
|
+
* `loading` covers the delay of a debounced binding
|
|
29244
|
+
* (`bindParams(paramsSignal, { debounce })`, `actionRunEffect({ debounce })`)
|
|
29245
|
+
* as well as the run itself: from the moment the params move, what is on screen
|
|
29246
|
+
* is the previous answer, and a screen showing "searching…" while someone types
|
|
29247
|
+
* needs to know it. Suspending has nothing to wait on during that delay, so
|
|
29248
|
+
* only `loading: true` reads it — the default keeps showing the previous answer
|
|
29249
|
+
* until the run really starts.
|
|
29250
|
+
*
|
|
29251
|
+
* When `loading` is not set (default), the component suspends until data is
|
|
29252
|
+
* ready, so `data` is always defined when the component renders and `loading`
|
|
29253
|
+
* is always `false`. With `loading: true` the component never suspends: every
|
|
29254
|
+
* state — running, settling, params nobody has started yet, a failure whose
|
|
29255
|
+
* message was dismissed — comes back as a value, so the subtree it draws is
|
|
29256
|
+
* never swapped for the boundary above and remounted.
|
|
29257
|
+
*
|
|
29258
|
+
* When `error` is not set (default), any action failure causes the component
|
|
29259
|
+
* to throw, so `error` is always `undefined` when the component renders. With
|
|
29260
|
+
* `error: true` the failure arrives beside the data the action already had — a
|
|
29261
|
+
* message on the content, not one in place of it. `dismissError()` removes the
|
|
29262
|
+
* message and nothing else: nothing is asked again, and what comes back is the
|
|
29263
|
+
* same state minus the error — the same data, or none at all when nothing ever
|
|
29264
|
+
* came back.
|
|
29265
|
+
*
|
|
29266
|
+
* `data === undefined` says there is nothing to show and `loading` says whether
|
|
29267
|
+
* anything is on its way: a skeleton reads both, or it claims a load nobody
|
|
29268
|
+
* started (see docs/data_states.md).
|
|
29269
|
+
*
|
|
29270
|
+
* @param {object | (() => Promise<any>) | Promise<any>} promiseOrAction -
|
|
29271
|
+
* an action, or the request this component owns written as a function:
|
|
29272
|
+
* made into an action once, on the first render, and kept for the life of
|
|
29273
|
+
* the instance — so it may be written inline — with what it resolves to as
|
|
29274
|
+
* the data and `run` implied. Code loaded on demand reads this way
|
|
29275
|
+
* (`() => import("./plan.jsx").then((m) => m.Plan)`, see
|
|
29276
|
+
* docs/dynamic_import.md), as does anything else asked for once per mount.
|
|
29277
|
+
* @param {{ loading?: true, error?: true, run?: true, prerun?: true, onLoad?: (data: any, context: {params: any}) => void }} [options]
|
|
29278
|
+
* @param {true} [options.run] - The fallback for data nothing else can ask for:
|
|
29279
|
+
* this component owns the request and starts it, from the render that reads
|
|
29280
|
+
* it. `loading` and `error` keep their meaning — delegated by default,
|
|
29281
|
+
* handled inline when asked for — so a run started here suspends into
|
|
29282
|
+
* `<Loading>` like any other.
|
|
29283
|
+
*
|
|
29284
|
+
* A failure is shown, not asked again: a render that retried it would retry
|
|
29285
|
+
* it in a loop while the network is down — that is `rerun()`'s job. Except a
|
|
29286
|
+
* failure a prerun left (a `prerun: true` head start, a press that preran
|
|
29287
|
+
* it): nothing on screen asked for that run, so this asks again, once.
|
|
29288
|
+
*
|
|
29289
|
+
* Prefer a `routeAction` wherever the parameter is one the address holds (a
|
|
29290
|
+
* path param, a search param bound to a `stateSignal`), popup included: it is
|
|
29291
|
+
* asked for when the address changes, with everything else that address
|
|
29292
|
+
* needs, whereas this one cannot start before the component that draws it
|
|
29293
|
+
* exists — one render late, and behind whatever gesture mounted it. What is
|
|
29294
|
+
* left for `run` is the parameter chosen inside the component and dying with
|
|
29295
|
+
* it — and any run that WRITES, whatever its parameter: an address asks again
|
|
29296
|
+
* on every arrival, which is right for a read and wrong for a write (see
|
|
29297
|
+
* docs/actions.md and docs/popup_open.md).
|
|
29298
|
+
* @param {true} [options.prerun] - A head start: this component starts a
|
|
29299
|
+
* read that another one, mounted later, owns with `run: true` — the read
|
|
29300
|
+
* leaves as soon as its params are known instead of once the owner exists.
|
|
29301
|
+
* Started from the render like `run`, as a prerun: it draws none of it, so
|
|
29302
|
+
* nothing suspends (no `<Loading>` needed) and a failure is neither thrown
|
|
29303
|
+
* nor reported — the owner asks again. A head start still running when the
|
|
29304
|
+
* owner reads it is the owner's wait.
|
|
29305
|
+
* @param {(data: any, context: {params: any}) => void} [options.onLoad] - what
|
|
29306
|
+
* this screen does with the data ONCE, when it becomes known: seed the fields
|
|
29307
|
+
* someone is about to edit, remember where a list was, focus something.
|
|
29308
|
+
*
|
|
29309
|
+
* Fired once per set of params — never again for a rerun that brings the same
|
|
29310
|
+
* thing back. That is the whole point of it: a save, a refresh, a poll all
|
|
29311
|
+
* hand the data over again, and copying it a second time would overwrite what
|
|
29312
|
+
* the person is in the middle of writing. The action knows what it ran with,
|
|
29313
|
+
* so nobody has to guess the dependency.
|
|
29314
|
+
*
|
|
29315
|
+
* Called from a layout effect, so what it writes belongs to the same tick as
|
|
29316
|
+
* the render that got the data — a `<Form pristineKey>` taking its reference
|
|
29317
|
+
* then sees the filled fields rather than the empty ones.
|
|
29318
|
+
* @returns {[data: unknown, loading: boolean, error: Error | undefined, dismissError: (() => void) | undefined]}
|
|
29319
|
+
*
|
|
29320
|
+
* @example <caption>Default — delegate both states (least code)</caption>
|
|
29321
|
+
* const [user] = useAsyncData(userAction);
|
|
29322
|
+
* // user is always defined here — never loading, never error
|
|
29323
|
+
*
|
|
29324
|
+
* @example <caption>Handle loading inline while keeping stale data</caption>
|
|
29325
|
+
* const [user, loading] = useAsyncData(userAction, { loading: true });
|
|
29326
|
+
* // First load: user === undefined, loading === true → show skeleton
|
|
29327
|
+
* // While refreshing: user === <stale>, loading === true → show stale + spinner
|
|
29328
|
+
* // Done: user === <fresh>, loading === false
|
|
29329
|
+
*
|
|
29330
|
+
* @example <caption>Handle error inline</caption>
|
|
29331
|
+
* const [user, , error] = useAsyncData(userAction, { error: true });
|
|
29332
|
+
* if (error) return <ErrorCard message={error.message} />;
|
|
29333
|
+
*
|
|
29334
|
+
* @example <caption>Say the failure over the rows already on screen</caption>
|
|
29335
|
+
* const [items, , error, dismissError] = useAsyncData(listAction, { error: true });
|
|
29336
|
+
* // First failure: items === undefined → only the message
|
|
29337
|
+
* // Failed refresh: items === <stale> → the message above the rows
|
|
29338
|
+
* return (
|
|
29339
|
+
* <>
|
|
29340
|
+
* {error && <ErrorStrip error={error} onClose={dismissError} />}
|
|
29341
|
+
* {items && <List items={items} />}
|
|
29342
|
+
* </>
|
|
29343
|
+
* );
|
|
29344
|
+
*/
|
|
29008
29345
|
const useAsyncData = (promiseOrAction, {
|
|
29009
29346
|
loading = "delegate",
|
|
29010
29347
|
error = "delegate",
|
|
@@ -29454,7 +29791,7 @@ const LoadingFallback = ({
|
|
|
29454
29791
|
* ones that would render fine.
|
|
29455
29792
|
*
|
|
29456
29793
|
* @param {object} props
|
|
29457
|
-
* @param {Function|import("
|
|
29794
|
+
* @param {Function|import("preact").VNode} [props.fallback] - what is displayed
|
|
29458
29795
|
* instead of the children: an element, or a component receiving
|
|
29459
29796
|
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
29460
29797
|
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
@@ -29813,6 +30150,12 @@ const hasWidthHeight = (context) => {
|
|
|
29813
30150
|
(context.styles.height || context.remainingProps.height)
|
|
29814
30151
|
);
|
|
29815
30152
|
};
|
|
30153
|
+
// The `stretch` sizing keyword: the containing block's width minus the box's
|
|
30154
|
+
// own margins, so a box with a margin fills its row without leaving it (100%
|
|
30155
|
+
// adds the margins on top). Spelled with the prefix because an inline style
|
|
30156
|
+
// holds one value, and this is the one name Chromium, WebKit and Firefox all
|
|
30157
|
+
// read (Firefox 155 has no unprefixed `stretch`).
|
|
30158
|
+
const FILL_AVAILABLE_WIDTH = "-webkit-fill-available";
|
|
29816
30159
|
const DIMENSION_PROPS = {
|
|
29817
30160
|
boxSizing: PASS_THROUGH,
|
|
29818
30161
|
width: PASS_THROUGH,
|
|
@@ -29865,16 +30208,16 @@ const DIMENSION_PROPS = {
|
|
|
29865
30208
|
return { flexGrow: expandWeight(value), flexBasis: "0%" };
|
|
29866
30209
|
}
|
|
29867
30210
|
if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
|
|
29868
|
-
// width
|
|
30211
|
+
// A width is what fills the cross axis; align-self stretch would do
|
|
29869
30212
|
// the same but takes the parent's alignX away — a maxWidth-capped item
|
|
29870
30213
|
// in an alignX="center" parent would then sit at the start instead of
|
|
29871
30214
|
// in the middle.
|
|
29872
30215
|
return {
|
|
29873
|
-
width:
|
|
30216
|
+
width: FILL_AVAILABLE_WIDTH,
|
|
29874
30217
|
};
|
|
29875
30218
|
}
|
|
29876
30219
|
// Can't use flexGrow — parent is not flex-x
|
|
29877
|
-
return { width:
|
|
30220
|
+
return { width: FILL_AVAILABLE_WIDTH };
|
|
29878
30221
|
},
|
|
29879
30222
|
expandY: (value, { parentBoxFlow }) => {
|
|
29880
30223
|
if (!value) {
|
|
@@ -30435,6 +30778,7 @@ const getVisualChildStylePropStrategy = (name) => {
|
|
|
30435
30778
|
};
|
|
30436
30779
|
|
|
30437
30780
|
const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
|
|
30781
|
+
const isTypoProp = (name) => TYPO_PROP_NAME_SET.has(name);
|
|
30438
30782
|
|
|
30439
30783
|
const getStringifier = (key) => {
|
|
30440
30784
|
if (SPACING_PROP_SET.has(key)) {
|
|
@@ -31076,10 +31420,10 @@ const readCSSVarName = entry => Array.isArray(entry) ? entry[0] : entry;
|
|
|
31076
31420
|
const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
31077
31421
|
|
|
31078
31422
|
/**
|
|
31079
|
-
* @type {import("
|
|
31423
|
+
* @type {import("preact").FunctionComponent<{
|
|
31080
31424
|
* as?: string,
|
|
31081
31425
|
* className?: string,
|
|
31082
|
-
* style?: import("
|
|
31426
|
+
* style?: import("preact").JSX.CSSProperties & { [pseudo: string]: import("preact").JSX.CSSProperties },
|
|
31083
31427
|
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
31084
31428
|
* inline?: boolean,
|
|
31085
31429
|
* block?: boolean,
|
|
@@ -31094,7 +31438,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
31094
31438
|
* hasChildUsingForwardedProps?: boolean,
|
|
31095
31439
|
* childPropSet?: Set<string>,
|
|
31096
31440
|
* preventInitialTransition?: boolean,
|
|
31097
|
-
* separator?: import("
|
|
31441
|
+
* separator?: import("preact").ComponentChildren | ((index: number) => import("preact").ComponentChildren),
|
|
31098
31442
|
* selfInteractions?: string,
|
|
31099
31443
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
31100
31444
|
* alignX?: string,
|
|
@@ -31106,7 +31450,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
31106
31450
|
* body?: boolean,
|
|
31107
31451
|
* sticky?: boolean,
|
|
31108
31452
|
* mount?: "always" | "after-paint",
|
|
31109
|
-
* children?: import("
|
|
31453
|
+
* children?: import("preact").ComponentChildren,
|
|
31110
31454
|
* [key: string]: any,
|
|
31111
31455
|
* }>}
|
|
31112
31456
|
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
@@ -33713,7 +34057,7 @@ const debug$1 = (...args) => {
|
|
|
33713
34057
|
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
33714
34058
|
* movement be written about it and its `matchingSignal` be read from outside
|
|
33715
34059
|
* the tree (see route_fallback.js).
|
|
33716
|
-
* @param {Function|import("
|
|
34060
|
+
* @param {Function|import("preact").VNode} [props.element] - what the branch renders
|
|
33717
34061
|
* @param {object} [props.elementProps] - props given to `element`
|
|
33718
34062
|
*
|
|
33719
34063
|
* A branch says what it renders, not what happens when it cannot: loading and
|
|
@@ -36102,7 +36446,7 @@ const css$13 = /* css */`@property --navi-route-transition-leave {
|
|
|
36102
36446
|
* that. An application that already has an element holding its pages can mark
|
|
36103
36447
|
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
36104
36448
|
*
|
|
36105
|
-
* @type {import("
|
|
36449
|
+
* @type {import("preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
36106
36450
|
*/
|
|
36107
36451
|
const RouteTransitionArea = ({
|
|
36108
36452
|
children,
|
|
@@ -36892,7 +37236,7 @@ const beginTransition = ({
|
|
|
36892
37236
|
warnAboutNamesEscapingArea(areaElement, capturedNames);
|
|
36893
37237
|
return;
|
|
36894
37238
|
}
|
|
36895
|
-
for (const name of capturedNames) {
|
|
37239
|
+
for (const name of capturedNames.keys()) {
|
|
36896
37240
|
if (name === "root") {
|
|
36897
37241
|
continue;
|
|
36898
37242
|
}
|
|
@@ -37095,9 +37439,10 @@ const ignoreSkipped$1 = () => {};
|
|
|
37095
37439
|
|
|
37096
37440
|
// The names the browser captured, read off the pictures themselves: what was
|
|
37097
37441
|
// asked for in CSS and what was taken are not the same question (see
|
|
37098
|
-
// viewTransitionReady).
|
|
37442
|
+
// viewTransitionReady). Each with the states it was taken in: a name taken in
|
|
37443
|
+
// only one of them pairs with nothing.
|
|
37099
37444
|
const capturedViewTransitionNames = () => {
|
|
37100
|
-
const names = new
|
|
37445
|
+
const names = new Map();
|
|
37101
37446
|
for (const animation of document.getAnimations()) {
|
|
37102
37447
|
const pseudoElement = animation.effect?.pseudoElement;
|
|
37103
37448
|
if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
|
|
@@ -37107,43 +37452,59 @@ const capturedViewTransitionNames = () => {
|
|
|
37107
37452
|
if (nameStart === -1) {
|
|
37108
37453
|
continue;
|
|
37109
37454
|
}
|
|
37110
|
-
|
|
37455
|
+
const name = pseudoElement.slice(nameStart + 1, -1);
|
|
37456
|
+
let states = names.get(name);
|
|
37457
|
+
if (!states) {
|
|
37458
|
+
states = {
|
|
37459
|
+
old: false,
|
|
37460
|
+
new: false
|
|
37461
|
+
};
|
|
37462
|
+
names.set(name, states);
|
|
37463
|
+
}
|
|
37464
|
+
if (pseudoElement.startsWith("::view-transition-old(")) {
|
|
37465
|
+
states.old = true;
|
|
37466
|
+
} else if (pseudoElement.startsWith("::view-transition-new(")) {
|
|
37467
|
+
states.new = true;
|
|
37468
|
+
}
|
|
37111
37469
|
}
|
|
37112
37470
|
return names;
|
|
37113
37471
|
};
|
|
37114
37472
|
|
|
37115
|
-
//
|
|
37116
|
-
//
|
|
37117
|
-
//
|
|
37118
|
-
//
|
|
37473
|
+
// A name written inside the area is a group of its own: it stands where it was
|
|
37474
|
+
// captured while the area's two pictures carry the movement, in every browser.
|
|
37475
|
+
// Nested groups only keep it cut at the pages' edge (see the @supports block in
|
|
37476
|
+
// the CSS above). Paired with the same name on the other page it is a morph;
|
|
37477
|
+
// taken in one state only it pairs with nothing, and is a piece of its page
|
|
37478
|
+
// left behind, fading, while that page moves.
|
|
37119
37479
|
const NESTED_GROUPS_SUPPORTED = window.CSS.supports("view-transition-group", "contain");
|
|
37120
37480
|
const warnAboutNamesEscapingArea = (areaElement, capturedNames) => {
|
|
37121
|
-
|
|
37122
|
-
|
|
37123
|
-
}
|
|
37124
|
-
let escapedName = null;
|
|
37125
|
-
for (const name of capturedNames) {
|
|
37481
|
+
const unpairedNames = new Set();
|
|
37482
|
+
for (const [name, states] of capturedNames) {
|
|
37126
37483
|
if (name === "root" || name === AREA_NAME || name.startsWith(FURNITURE_NAME_PREFIX)) {
|
|
37127
37484
|
continue;
|
|
37128
37485
|
}
|
|
37129
|
-
|
|
37130
|
-
|
|
37486
|
+
if (states.old && states.new) {
|
|
37487
|
+
continue;
|
|
37488
|
+
}
|
|
37489
|
+
unpairedNames.add(name);
|
|
37131
37490
|
}
|
|
37132
37491
|
// A name captured next to the area is not necessarily inside it — a bar the
|
|
37133
37492
|
// application animates on the same clock is named on purpose. The subtree is
|
|
37134
37493
|
// walked only once something is there to find, so the common case reads
|
|
37135
|
-
// nothing.
|
|
37136
|
-
|
|
37494
|
+
// nothing. The page being left is still in it, hidden (keepLeavingPages in
|
|
37495
|
+
// route.jsx), so a name taken only in the old state is found too.
|
|
37496
|
+
if (unpairedNames.size === 0) {
|
|
37137
37497
|
return;
|
|
37138
37498
|
}
|
|
37139
37499
|
for (const descendant of areaElement.querySelectorAll("*")) {
|
|
37140
37500
|
const {
|
|
37141
37501
|
viewTransitionName
|
|
37142
37502
|
} = getComputedStyle(descendant);
|
|
37143
|
-
if (!viewTransitionName
|
|
37503
|
+
if (!unpairedNames.has(viewTransitionName)) {
|
|
37144
37504
|
continue;
|
|
37145
37505
|
}
|
|
37146
|
-
|
|
37506
|
+
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.";
|
|
37507
|
+
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).`);
|
|
37147
37508
|
return;
|
|
37148
37509
|
}
|
|
37149
37510
|
};
|
|
@@ -37288,6 +37649,8 @@ const installRouteTransitionCss = () => {
|
|
|
37288
37649
|
};
|
|
37289
37650
|
|
|
37290
37651
|
installImportMetaCssBuild(import.meta);
|
|
37652
|
+
// The pictures carrying a travel, among everything else the movement takes
|
|
37653
|
+
// along (see findLeadAnimation).
|
|
37291
37654
|
const TRAVEL_LEAD_NAMES = ["navi-route-travel"];
|
|
37292
37655
|
|
|
37293
37656
|
// A browser with no view transitions of its own has no picture of the page
|
|
@@ -37555,7 +37918,7 @@ const css$12 = /* css */`.navi_route_travel {
|
|
|
37555
37918
|
`;
|
|
37556
37919
|
|
|
37557
37920
|
/**
|
|
37558
|
-
* @type {import("
|
|
37921
|
+
* @type {import("preact").FunctionComponent<{
|
|
37559
37922
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
37560
37923
|
* axis?: "x"|"y",
|
|
37561
37924
|
* travelByDrag?: boolean,
|
|
@@ -40383,7 +40746,7 @@ const resetCommand = (source, event) => {
|
|
|
40383
40746
|
// answers: "save" stays, "delete" goes back to the list. Same shape as the
|
|
40384
40747
|
// browser's own formaction/formmethod, and the same shape the action already
|
|
40385
40748
|
// has (it is told which button requested it).
|
|
40386
|
-
const resolveAfterSend = (target, requester) => {
|
|
40749
|
+
const resolveAfterSend = (target, requester, surface) => {
|
|
40387
40750
|
const askedForByRequester = requester?.getAttribute?.("data-after-send");
|
|
40388
40751
|
if (askedForByRequester) {
|
|
40389
40752
|
return askedForByRequester;
|
|
@@ -40392,11 +40755,6 @@ const resolveAfterSend = (target, requester) => {
|
|
|
40392
40755
|
if (askedFor) {
|
|
40393
40756
|
return askedFor;
|
|
40394
40757
|
}
|
|
40395
|
-
// From above the target: a popup that IS the send target is handled on its
|
|
40396
|
-
// own (see the aria-expanded branch below), and must not answer twice.
|
|
40397
|
-
const surface = target.parentElement?.closest(
|
|
40398
|
-
`[data-slide], [aria-expanded]`,
|
|
40399
|
-
);
|
|
40400
40758
|
if (!surface) {
|
|
40401
40759
|
return undefined;
|
|
40402
40760
|
}
|
|
@@ -40408,6 +40766,11 @@ const resolveAfterSend = (target, requester) => {
|
|
|
40408
40766
|
}
|
|
40409
40767
|
return undefined;
|
|
40410
40768
|
};
|
|
40769
|
+
// The surface a send answers when the control says nothing. Found from above
|
|
40770
|
+
// the target: a popup that IS the send target is handled on its own (see the
|
|
40771
|
+
// aria-expanded branch in sendCommand), and must not answer twice.
|
|
40772
|
+
const resolveSendSurface = (target) =>
|
|
40773
|
+
target.parentElement?.closest(`[data-slide], [aria-expanded]`);
|
|
40411
40774
|
// What follows a send is caused by the send, not by the gesture: the send
|
|
40412
40775
|
// answered the gesture and may have prevented it (an Enter must not also click
|
|
40413
40776
|
// the submit button, nor activate the trigger of the popup it closed), and a
|
|
@@ -40489,20 +40852,30 @@ const sendCommand = (source, event, { requester }) => {
|
|
|
40489
40852
|
// and the popup must stay open — with the form still in front of the
|
|
40490
40853
|
// user, showing what it is waiting for.
|
|
40491
40854
|
let invalid = false;
|
|
40492
|
-
// What follows the send is
|
|
40493
|
-
//
|
|
40494
|
-
//
|
|
40495
|
-
//
|
|
40496
|
-
//
|
|
40497
|
-
//
|
|
40855
|
+
// What follows the send is aimed at the press, like a button's command
|
|
40856
|
+
// waiting for its action (see resolveNaviCommand): the send may take the
|
|
40857
|
+
// form out of the document (a form keyed on what it edits, a row that
|
|
40858
|
+
// moves once saved), and resolved from there it finds no popup to close.
|
|
40859
|
+
// Which command it is gets read again once the send went through: an
|
|
40860
|
+
// action that learned where to go from the response writes it on the
|
|
40861
|
+
// form while it runs (data-after-send). That one can only be aimed when
|
|
40862
|
+
// it is read.
|
|
40863
|
+
const surface = resolveSendSurface(target);
|
|
40864
|
+
const sentEvent = createSentEvent(event);
|
|
40865
|
+
const afterSendAtPress = resolveAfterSend(target, sendRequester, surface);
|
|
40866
|
+
const runAimedAtPress = afterSendAtPress
|
|
40867
|
+
? resolveNaviCommand(source, afterSendAtPress, sentEvent)
|
|
40868
|
+
: undefined;
|
|
40498
40869
|
const runAfterSend = () => {
|
|
40499
|
-
const afterSend = resolveAfterSend(target, sendRequester);
|
|
40870
|
+
const afterSend = resolveAfterSend(target, sendRequester, surface);
|
|
40500
40871
|
if (!afterSend) {
|
|
40501
40872
|
return;
|
|
40502
40873
|
}
|
|
40503
|
-
|
|
40504
|
-
|
|
40505
|
-
|
|
40874
|
+
if (afterSend === afterSendAtPress) {
|
|
40875
|
+
runAimedAtPress?.();
|
|
40876
|
+
return;
|
|
40877
|
+
}
|
|
40878
|
+
triggerNaviCommand(source, afterSend, sentEvent);
|
|
40506
40879
|
};
|
|
40507
40880
|
const {
|
|
40508
40881
|
result: sent,
|
|
@@ -41457,6 +41830,12 @@ const isDeclaredInsidePopup = (target, popupId) =>
|
|
|
41457
41830
|
* Lives on the popup's own content element rather than in the backdrop's
|
|
41458
41831
|
* handler, for both renderers: the backdrop is a sibling behind the popup
|
|
41459
41832
|
* (never an ancestor), so a press inside the popup's box never reaches it.
|
|
41833
|
+
*
|
|
41834
|
+
* Inside a box that travels by drag — a row of slides walked by a swipe, a
|
|
41835
|
+
* sheet pushed back towards its edge — the region is also where that travel
|
|
41836
|
+
* starts, and at the mousedown nobody knows yet which of the two the press is.
|
|
41837
|
+
* The dismissal then waits for the click, which only comes if the press stayed
|
|
41838
|
+
* a press: a travel swallows the one it leaves behind.
|
|
41460
41839
|
*/
|
|
41461
41840
|
const handlePressOnOutsideRegion = (
|
|
41462
41841
|
mouseDownEvent,
|
|
@@ -41487,11 +41866,36 @@ const handlePressOnOutsideRegion = (
|
|
|
41487
41866
|
mouseDownEvent.preventDefault();
|
|
41488
41867
|
return;
|
|
41489
41868
|
}
|
|
41490
|
-
if (pressOutside
|
|
41491
|
-
|
|
41492
|
-
|
|
41869
|
+
if (pressOutside !== "close" && pressOutside !== "cancel") {
|
|
41870
|
+
return;
|
|
41871
|
+
}
|
|
41872
|
+
const isCancel = pressOutside === "cancel";
|
|
41873
|
+
if (isPressDisputedByDrag(target)) {
|
|
41874
|
+
onClickOfThisPress(target, (clickEvent) => {
|
|
41875
|
+
openController.requestClose(clickEvent, { isCancel });
|
|
41493
41876
|
});
|
|
41877
|
+
return;
|
|
41494
41878
|
}
|
|
41879
|
+
openController.requestClose(mouseDownEvent, { isCancel });
|
|
41880
|
+
};
|
|
41881
|
+
|
|
41882
|
+
// The click ending the press whose mousedown landed on `element`. A click goes
|
|
41883
|
+
// to what the press and the release have in common, so it reaches `element`
|
|
41884
|
+
// only when the release stayed on it or inside it. A press that never clicks
|
|
41885
|
+
// leaves it armed until the next one, and no longer — a click always comes
|
|
41886
|
+
// after a press, so it cannot reach the click of another (the rule
|
|
41887
|
+
// click_suppression.js in @jsenv/dom goes by).
|
|
41888
|
+
const onClickOfThisPress = (element, callback) => {
|
|
41889
|
+
const onClick = (clickEvent) => {
|
|
41890
|
+
disarm();
|
|
41891
|
+
callback(clickEvent);
|
|
41892
|
+
};
|
|
41893
|
+
const disarm = () => {
|
|
41894
|
+
element.removeEventListener("click", onClick);
|
|
41895
|
+
window.removeEventListener("pointerdown", disarm, { capture: true });
|
|
41896
|
+
};
|
|
41897
|
+
element.addEventListener("click", onClick);
|
|
41898
|
+
window.addEventListener("pointerdown", disarm, { capture: true });
|
|
41495
41899
|
};
|
|
41496
41900
|
|
|
41497
41901
|
/**
|
|
@@ -46838,6 +47242,9 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
46838
47242
|
* handles the timing centrally rather than each component having to manage its
|
|
46839
47243
|
* own debounce logic.
|
|
46840
47244
|
*/
|
|
47245
|
+
// Sentinel used as the initial value of lastActionValueRef.
|
|
47246
|
+
// Distinct from undefined so that undefined (e.g. unchecked radio) can itself
|
|
47247
|
+
// be stored as a valid "last action value" and trigger the dedup logic.
|
|
46841
47248
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
46842
47249
|
|
|
46843
47250
|
// The control types rendered as an element `<label for>` can name: HTML's
|
|
@@ -46937,13 +47344,18 @@ const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyD
|
|
|
46937
47344
|
* the control opens, so that a listener hears the control as a whole.
|
|
46938
47345
|
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
46939
47346
|
* host's either way: the control's own reactions to them call them.
|
|
47347
|
+
* @param {"host"|"root"} [options.nameOn] Where `aria-label` and
|
|
47348
|
+
* `aria-labelledby` land: where the role is. `"host"`, the default, for every
|
|
47349
|
+
* control whose host is what is announced; `"root"` for a control whose role
|
|
47350
|
+
* is on its root and whose host is an `aria-hidden` carrier (a wheel).
|
|
46940
47351
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
46941
47352
|
*/
|
|
46942
47353
|
const useControlProps = (props, {
|
|
46943
47354
|
controlType,
|
|
46944
47355
|
persists,
|
|
46945
47356
|
uiActionInternal,
|
|
46946
|
-
listenersOn = "host"
|
|
47357
|
+
listenersOn = "host",
|
|
47358
|
+
nameOn = "host"
|
|
46947
47359
|
}) => {
|
|
46948
47360
|
const debugUIState = useDebugUIState();
|
|
46949
47361
|
const debugAction = useDebugAction();
|
|
@@ -47032,6 +47444,7 @@ const useControlProps = (props, {
|
|
|
47032
47444
|
boundAction,
|
|
47033
47445
|
controlInfo,
|
|
47034
47446
|
listenersOn,
|
|
47447
|
+
nameOn,
|
|
47035
47448
|
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
47036
47449
|
});
|
|
47037
47450
|
{
|
|
@@ -48283,6 +48696,7 @@ const useInteractiveProps = (props, {
|
|
|
48283
48696
|
boundAction,
|
|
48284
48697
|
controlInfo,
|
|
48285
48698
|
listenersOn,
|
|
48699
|
+
nameOn,
|
|
48286
48700
|
listenersCalledByReactions
|
|
48287
48701
|
}) => {
|
|
48288
48702
|
const {
|
|
@@ -48290,6 +48704,7 @@ const useInteractiveProps = (props, {
|
|
|
48290
48704
|
} = props;
|
|
48291
48705
|
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
48292
48706
|
listenersOn,
|
|
48707
|
+
nameOn,
|
|
48293
48708
|
listenersCalledByReactions
|
|
48294
48709
|
});
|
|
48295
48710
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
@@ -48903,8 +49318,10 @@ const useInteractiveProps = (props, {
|
|
|
48903
49318
|
}
|
|
48904
49319
|
return [controlRootProps, controlHostProps];
|
|
48905
49320
|
};
|
|
49321
|
+
const NAME_ATTRIBUTE_SET = new Set(["aria-label", "aria-labelledby"]);
|
|
48906
49322
|
const splitControlProps = (props, {
|
|
48907
49323
|
listenersOn,
|
|
49324
|
+
nameOn,
|
|
48908
49325
|
listenersCalledByReactions
|
|
48909
49326
|
}) => {
|
|
48910
49327
|
const {
|
|
@@ -48915,6 +49332,10 @@ const splitControlProps = (props, {
|
|
|
48915
49332
|
};
|
|
48916
49333
|
const controlRootProps = {};
|
|
48917
49334
|
for (const key of Object.keys(props)) {
|
|
49335
|
+
if (nameOn === "root" && NAME_ATTRIBUTE_SET.has(key)) {
|
|
49336
|
+
controlRootProps[key] = props[key];
|
|
49337
|
+
continue;
|
|
49338
|
+
}
|
|
48918
49339
|
const attributeName = controlAttributeFromProp(key);
|
|
48919
49340
|
if (attributeName) {
|
|
48920
49341
|
controlHostProps[attributeName] = props[key];
|
|
@@ -51044,7 +51465,7 @@ const css$$ = /* css */`.navi_text_anchor {
|
|
|
51044
51465
|
* @param {{ size?: number, verticalAlign?: string }} [lineLayout]
|
|
51045
51466
|
* Describes the surrounding line context. Used as layout-effect dependencies so the correction
|
|
51046
51467
|
* reruns when the surrounding text's font-size or vertical-align changes.
|
|
51047
|
-
* @param {import("
|
|
51468
|
+
* @param {import("preact").RefObject} childRef — ref on the child element to reposition
|
|
51048
51469
|
*/
|
|
51049
51470
|
const TextAnchor = ({
|
|
51050
51471
|
childRef,
|
|
@@ -51706,25 +52127,25 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51706
52127
|
* See `docs/typography.md` for the decisions behind it — truncating, rows made
|
|
51707
52128
|
* of an icon, a text and an icon, and where a line may break.
|
|
51708
52129
|
*
|
|
51709
|
-
* @type {import("
|
|
51710
|
-
* children?: import("
|
|
52130
|
+
* @type {import("preact").FunctionComponent<{
|
|
52131
|
+
* children?: import("preact").ComponentChildren,
|
|
51711
52132
|
* as?: string,
|
|
51712
52133
|
* className?: string,
|
|
51713
|
-
* style?: import("
|
|
52134
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
51714
52135
|
* bold?: boolean,
|
|
51715
52136
|
* noWrap?: boolean,
|
|
51716
52137
|
* maxLines?: number,
|
|
51717
|
-
* spacing?: string | number | import("
|
|
52138
|
+
* spacing?: string | number | import("preact").ComponentChildren,
|
|
51718
52139
|
* loading?: boolean,
|
|
51719
52140
|
* skeleton?: boolean,
|
|
51720
52141
|
* attachLastChild?: boolean,
|
|
51721
52142
|
* preventSpaceUnderlines?: boolean,
|
|
51722
|
-
* holdSpaceForStyle?: import("
|
|
52143
|
+
* holdSpaceForStyle?: import("preact").JSX.CSSProperties,
|
|
51723
52144
|
* boldStable?: boolean,
|
|
51724
52145
|
* shrinkWrap?: boolean,
|
|
51725
52146
|
* capitalize?: boolean,
|
|
51726
52147
|
* selectRange?: string | [number, number] | [string, string],
|
|
51727
|
-
* childrenOutsideFlow?: import("
|
|
52148
|
+
* childrenOutsideFlow?: import("preact").ComponentChildren,
|
|
51728
52149
|
* [key: string]: any,
|
|
51729
52150
|
* }>}
|
|
51730
52151
|
*
|
|
@@ -51765,7 +52186,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51765
52186
|
* Replaces real space characters between children with padding-based spaces.
|
|
51766
52187
|
* Useful inside `<a>` elements where browsers draw an underline under spaces.
|
|
51767
52188
|
*
|
|
51768
|
-
* @param {import("
|
|
52189
|
+
* @param {import("preact").JSX.CSSProperties} [holdSpaceForStyle]
|
|
51769
52190
|
* Prevents layout shifts when text styles change (e.g. font-weight, font-size).
|
|
51770
52191
|
* Pass the CSS properties representing the "largest" visual state of the text.
|
|
51771
52192
|
* An invisible placeholder rendered with those styles reserves the space; the
|
|
@@ -51798,7 +52219,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
51798
52219
|
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
51799
52220
|
* substrings selecting from the first to the end of the second.
|
|
51800
52221
|
*
|
|
51801
|
-
* @param {import("
|
|
52222
|
+
* @param {import("preact").ComponentChildren} [childrenOutsideFlow]
|
|
51802
52223
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
51803
52224
|
* internally for overlays such as the skeleton container.
|
|
51804
52225
|
*/
|
|
@@ -52086,7 +52507,7 @@ const TextWithSelectRange = ({
|
|
|
52086
52507
|
* @param {object} props
|
|
52087
52508
|
* @param {string} [props.href] - URL/id of an external SVG symbol, rendered via
|
|
52088
52509
|
* `<svg><use href></svg>`. Mutually exclusive with meaningful `children`.
|
|
52089
|
-
* @param {import("
|
|
52510
|
+
* @param {import("preact").ComponentChildren} [props.children] - Inline icon
|
|
52090
52511
|
* content: an `<svg>` element, or a string (renders as a text/emoji icon).
|
|
52091
52512
|
* @param {boolean} [props.decorative] - Marks the icon `aria-hidden`. Defaults
|
|
52092
52513
|
* to `true` for an unsized, non-interactive icon; pass `false` for a
|
|
@@ -52261,6 +52682,11 @@ const usePreloadOnIntent = (ref, href, prefetch = true) => {
|
|
|
52261
52682
|
return undefined;
|
|
52262
52683
|
}
|
|
52263
52684
|
const preload = () => {
|
|
52685
|
+
// The tab or the window it opens loads its own code: what would be
|
|
52686
|
+
// fetched here is not what runs there.
|
|
52687
|
+
if (linkOpensElsewhere(element)) {
|
|
52688
|
+
return;
|
|
52689
|
+
}
|
|
52264
52690
|
preloadUrl(href);
|
|
52265
52691
|
};
|
|
52266
52692
|
element.addEventListener("pointerenter", preload);
|
|
@@ -52469,6 +52895,15 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
52469
52895
|
};
|
|
52470
52896
|
|
|
52471
52897
|
installImportMetaCssBuild(import.meta);
|
|
52898
|
+
/*
|
|
52899
|
+
* Apply opacity to child content, not the link element itself.
|
|
52900
|
+
*
|
|
52901
|
+
* Why not apply opacity directly to .navi_link?
|
|
52902
|
+
* - Would make focus outlines semi-transparent too (accessibility issue)
|
|
52903
|
+
* - We want dimmed text but full-opacity focus indicators for visibility
|
|
52904
|
+
*
|
|
52905
|
+
* This approach dims the content while preserving focus outline visibility.
|
|
52906
|
+
*/
|
|
52472
52907
|
const css$Z = /* css */`@layer navi {
|
|
52473
52908
|
.navi_link {
|
|
52474
52909
|
--link-border-radius: unset;
|
|
@@ -52489,6 +52924,19 @@ const css$Z = /* css */`@layer navi {
|
|
|
52489
52924
|
--link-current-indicator-spacing: 0;
|
|
52490
52925
|
--link-current-indicator-color: var(--navi-link-current-indicator-color);
|
|
52491
52926
|
}
|
|
52927
|
+
|
|
52928
|
+
.navi_link[data-variant="text"], .navi_link[data-variant="icon"] {
|
|
52929
|
+
--link-color: unset;
|
|
52930
|
+
--link-text-decoration: none;
|
|
52931
|
+
}
|
|
52932
|
+
|
|
52933
|
+
.navi_link[data-variant="tab"] {
|
|
52934
|
+
--link-background-hover: color-mix(in srgb,
|
|
52935
|
+
var(--link-background, transparent),
|
|
52936
|
+
var(--x-link-contrasting-color) 15%);
|
|
52937
|
+
--link-color: unset;
|
|
52938
|
+
--link-text-decoration: none;
|
|
52939
|
+
}
|
|
52492
52940
|
}
|
|
52493
52941
|
|
|
52494
52942
|
.navi_link {
|
|
@@ -52509,7 +52957,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
52509
52957
|
--x-link-padding-right: var(--link-padding-right, var(--link-padding-x, var(--link-padding, 0px)));
|
|
52510
52958
|
--x-link-padding-bottom: var(--link-padding-bottom, var(--link-padding-y, var(--link-padding, 0px)));
|
|
52511
52959
|
--x-link-padding-left: var(--link-padding-left, var(--link-padding-x, var(--link-padding, 0px)));
|
|
52512
|
-
aspect-ratio: inherit;
|
|
52513
52960
|
padding-top: var(--x-link-padding-top);
|
|
52514
52961
|
padding-right: var(--x-link-padding-right);
|
|
52515
52962
|
padding-bottom: var(--x-link-padding-bottom);
|
|
@@ -52698,17 +53145,7 @@ const css$Z = /* css */`@layer navi {
|
|
|
52698
53145
|
margin-left: -.1em;
|
|
52699
53146
|
}
|
|
52700
53147
|
|
|
52701
|
-
&[data-variant="text"], &[data-variant="icon"] {
|
|
52702
|
-
--link-color: unset;
|
|
52703
|
-
--link-text-decoration: none;
|
|
52704
|
-
}
|
|
52705
|
-
|
|
52706
53148
|
&[data-variant="tab"] {
|
|
52707
|
-
--link-background-hover: color-mix(in srgb,
|
|
52708
|
-
var(--link-background, transparent),
|
|
52709
|
-
var(--x-link-contrasting-color) 15%);
|
|
52710
|
-
--link-color: unset;
|
|
52711
|
-
--link-text-decoration: none;
|
|
52712
53149
|
white-space: nowrap;
|
|
52713
53150
|
user-select: none;
|
|
52714
53151
|
|
|
@@ -52795,8 +53232,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52795
53232
|
*
|
|
52796
53233
|
* Href-derived behavior (all computed from `href`, recomputed on navigation):
|
|
52797
53234
|
* - internal vs external target (`data-href-internal`/`data-href-external`),
|
|
52798
|
-
* with `target`/`rel` auto-defaulted
|
|
52799
|
-
* `noopener noreferrer`
|
|
53235
|
+
* with `target`/`rel` auto-defaulted unless explicitly set: `_blank` for a
|
|
53236
|
+
* page of another site (`http:`/`https:`), `noopener noreferrer` for any
|
|
53237
|
+
* external address, nothing otherwise.
|
|
52800
53238
|
* - "current page" detection (`data-href-current`, `aria-current="page"`).
|
|
52801
53239
|
* - anchor links (`href` starting with `#`) get `data-href-anchor`.
|
|
52802
53240
|
* - an automatic end icon for `tel:`/`sms:`/`mailto:`/`github.com`, external
|
|
@@ -52858,14 +53296,14 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52858
53296
|
* text reads (see `holdSpaceForStyle` in `text.jsx`).
|
|
52859
53297
|
* @param {boolean} [props.currentEffectShadow] - Inset-shadow effect while
|
|
52860
53298
|
* current (used with `variant="tab"`).
|
|
52861
|
-
* @param {boolean|import("
|
|
53299
|
+
* @param {boolean|import("preact").ComponentChild} [props.startIcon] - Icon
|
|
52862
53300
|
* placed before the text.
|
|
52863
|
-
* @param {boolean|import("
|
|
53301
|
+
* @param {boolean|import("preact").ComponentChild} [props.endIcon] - Icon
|
|
52864
53302
|
* placed after the text; when omitted, an icon may be auto-chosen from the
|
|
52865
53303
|
* href (see above).
|
|
52866
|
-
* @param {boolean|import("
|
|
53304
|
+
* @param {boolean|import("preact").ComponentChild} [props.blankTargetIcon] -
|
|
52867
53305
|
* Override/suppress the auto external-link icon.
|
|
52868
|
-
* @param {boolean|import("
|
|
53306
|
+
* @param {boolean|import("preact").ComponentChild} [props.anchorIcon] -
|
|
52869
53307
|
* Controls the auto anchor icon shown for a childless anchor link:
|
|
52870
53308
|
* `true`/`undefined` uses the default chain SVG, `false` suppresses it, any
|
|
52871
53309
|
* other node replaces it. To render a plain `#` instead, prefer writing it as
|
|
@@ -52936,7 +53374,7 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
52936
53374
|
* @param {boolean} [props.preventDefault] - Call `event.preventDefault()` on
|
|
52937
53375
|
* click (navigation suppressed; `onClick` still runs).
|
|
52938
53376
|
* @param {(event: MouseEvent) => void} [props.onClick]
|
|
52939
|
-
* @param {import("
|
|
53377
|
+
* @param {import("preact").ComponentChildren} [props.children] - Link content;
|
|
52940
53378
|
* falls back to the route's relative URL / `href` per the rules above.
|
|
52941
53379
|
* @param {string} [props.name] - Control facade name (links may be nameless).
|
|
52942
53380
|
* @param {boolean} [props.disabled]
|
|
@@ -53081,7 +53519,7 @@ const LinkPlain = props => {
|
|
|
53081
53519
|
":-navi-href-current": innerCurrent,
|
|
53082
53520
|
":-navi-selected": selected
|
|
53083
53521
|
};
|
|
53084
|
-
const innerTarget = target === undefined ?
|
|
53522
|
+
const innerTarget = target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target;
|
|
53085
53523
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
53086
53524
|
let innerEndIcon;
|
|
53087
53525
|
if (endIcon === undefined) {
|
|
@@ -53248,7 +53686,7 @@ const LinkPlain = props => {
|
|
|
53248
53686
|
},
|
|
53249
53687
|
href: href,
|
|
53250
53688
|
rel: innerRel,
|
|
53251
|
-
target: innerTarget === "_self" ? undefined :
|
|
53689
|
+
target: innerTarget === "_self" ? undefined : innerTarget
|
|
53252
53690
|
// Which slide this tab is, and which box to say it to: read by the <Nav>
|
|
53253
53691
|
// around it to place the row's own bar, and by the command above to find
|
|
53254
53692
|
// the container across the document.
|
|
@@ -53500,6 +53938,9 @@ const useSlideContainer = (target) => {
|
|
|
53500
53938
|
};
|
|
53501
53939
|
|
|
53502
53940
|
installImportMetaCssBuild(import.meta);
|
|
53941
|
+
// A name of its own for the bar under the current tab, one per nav on the page:
|
|
53942
|
+
// two bars answering to the same name are two elements claiming one picture,
|
|
53943
|
+
// and the browser refuses the whole transition rather than pick.
|
|
53503
53944
|
let navCount = 0;
|
|
53504
53945
|
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
53505
53946
|
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
@@ -53524,6 +53965,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
53524
53965
|
};
|
|
53525
53966
|
const css$Y = /* css */`@layer navi {
|
|
53526
53967
|
.navi_nav {
|
|
53968
|
+
--nav-width: fit-content;
|
|
53527
53969
|
--nav-border: none;
|
|
53528
53970
|
--nav-padding: 0px;
|
|
53529
53971
|
--nav-border-radius: 0px;
|
|
@@ -53547,7 +53989,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
53547
53989
|
var(--nav-indicator-length-delta));
|
|
53548
53990
|
--x-nav-indicator-color: var(--nav-indicator-color, var(--nav-current-indicator-color));
|
|
53549
53991
|
--x-nav-indicator-color-toward: var(--nav-indicator-color-toward, var(--x-nav-indicator-color));
|
|
53550
|
-
z-index:
|
|
53992
|
+
z-index: 2;
|
|
53551
53993
|
background: color-mix(in srgb,
|
|
53552
53994
|
var(--x-nav-indicator-color-toward)
|
|
53553
53995
|
calc(var(--slide-travel-progress) * var(--nav-indicator-toward-sign, 0) *
|
|
@@ -53604,7 +54046,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
53604
54046
|
}
|
|
53605
54047
|
|
|
53606
54048
|
.navi_nav {
|
|
53607
|
-
width:
|
|
54049
|
+
width: var(--nav-width);
|
|
53608
54050
|
padding-top: var(--nav-padding-top, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
53609
54051
|
padding-right: var(--nav-padding-right, var(--nav-padding-x, var(--nav-padding, unset)));
|
|
53610
54052
|
padding-bottom: var(--nav-padding-bottom, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
@@ -53633,12 +54075,12 @@ const css$Y = /* css */`@layer navi {
|
|
|
53633
54075
|
--x-nav-child-border-radius: calc(var(--nav-border-radius) - var(--nav-padding));
|
|
53634
54076
|
--x-nav-link-border-radius: var(--link-border-radius, var(--x-nav-child-border-radius));
|
|
53635
54077
|
|
|
53636
|
-
&:
|
|
54078
|
+
&:nth-child(1 of .navi_link) {
|
|
53637
54079
|
border-top-left-radius: var(--x-nav-link-border-radius);
|
|
53638
54080
|
border-bottom-left-radius: var(--x-nav-link-border-radius);
|
|
53639
54081
|
}
|
|
53640
54082
|
|
|
53641
|
-
&:last-child {
|
|
54083
|
+
&:nth-last-child(1 of .navi_link) {
|
|
53642
54084
|
border-top-right-radius: var(--x-nav-link-border-radius);
|
|
53643
54085
|
border-bottom-right-radius: var(--x-nav-link-border-radius);
|
|
53644
54086
|
}
|
|
@@ -53741,7 +54183,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
53741
54183
|
};
|
|
53742
54184
|
|
|
53743
54185
|
/**
|
|
53744
|
-
* @type {import("
|
|
54186
|
+
* @type {import("preact").FunctionComponent<{
|
|
53745
54187
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
53746
54188
|
* currentIndicatorSlides?: boolean,
|
|
53747
54189
|
* slideContainer?: string,
|
|
@@ -54891,7 +55333,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
54891
55333
|
};
|
|
54892
55334
|
|
|
54893
55335
|
/**
|
|
54894
|
-
* @type {import("
|
|
55336
|
+
* @type {import("preact").FunctionComponent<{
|
|
54895
55337
|
* value?: any,
|
|
54896
55338
|
* defaultValue?: any,
|
|
54897
55339
|
* onChange?: (value: any) => void,
|
|
@@ -54935,7 +55377,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
54935
55377
|
* grows the binder instead of closing in on the text.
|
|
54936
55378
|
* @param {string} [props.tabBorderColor] - The inactive tabs alone; the rest of
|
|
54937
55379
|
* the outline follows `borderColor`.
|
|
54938
|
-
* @param {import("
|
|
55380
|
+
* @param {import("preact").ComponentChildren} props.children - `Binder.Item`s.
|
|
54939
55381
|
*/
|
|
54940
55382
|
const Binder = ({
|
|
54941
55383
|
children,
|
|
@@ -55076,13 +55518,13 @@ const Binder = ({
|
|
|
55076
55518
|
/**
|
|
55077
55519
|
* @param {any} [props.value] - What identifies this item; defaults to its
|
|
55078
55520
|
* position among its siblings.
|
|
55079
|
-
* @param {import("
|
|
55521
|
+
* @param {import("preact").ComponentChildren} [props.label] - What the tab
|
|
55080
55522
|
* shows.
|
|
55081
55523
|
* @param {object} [props.route] - Makes the tab a `Link`
|
|
55082
55524
|
* to that route, and the binder follows the url: the open item is the one
|
|
55083
55525
|
* whose route matches, no `value`/`onChange` needed.
|
|
55084
55526
|
* @param {string} [props.href] - Same, for a plain url.
|
|
55085
|
-
* @param {import("
|
|
55527
|
+
* @param {import("preact").ComponentChildren} [props.children] - The page.
|
|
55086
55528
|
*
|
|
55087
55529
|
* Every other prop goes to the tab (a `Box`, or a `Link` when routed): an item
|
|
55088
55530
|
* styles and configures the control that opens it.
|
|
@@ -55492,7 +55934,7 @@ const FixedBarStyleCSSVars = {
|
|
|
55492
55934
|
};
|
|
55493
55935
|
|
|
55494
55936
|
/**
|
|
55495
|
-
* @type {import("
|
|
55937
|
+
* @type {import("preact").FunctionComponent<{
|
|
55496
55938
|
* area?: "top"|"bottom"|"left"|"right",
|
|
55497
55939
|
* width?: string|number,
|
|
55498
55940
|
* height?: string|number,
|
|
@@ -55883,7 +56325,7 @@ const css$U = /* css */`.navi_summary_marker {
|
|
|
55883
56325
|
`;
|
|
55884
56326
|
|
|
55885
56327
|
/**
|
|
55886
|
-
* @type {import("
|
|
56328
|
+
* @type {import("preact").FunctionComponent<{
|
|
55887
56329
|
* open?: boolean,
|
|
55888
56330
|
* loading?: boolean,
|
|
55889
56331
|
* openDirection?: "down" | "up" | "left",
|
|
@@ -56303,8 +56745,8 @@ const useExpandableContext = partName => {
|
|
|
56303
56745
|
};
|
|
56304
56746
|
|
|
56305
56747
|
/**
|
|
56306
|
-
* @type {import("
|
|
56307
|
-
* ui?: import("
|
|
56748
|
+
* @type {import("preact").FunctionComponent<{
|
|
56749
|
+
* ui?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
56308
56750
|
* open?: boolean,
|
|
56309
56751
|
* defaultOpen?: boolean,
|
|
56310
56752
|
* signal?: import("@preact/signals").Signal,
|
|
@@ -56944,9 +57386,9 @@ const Expandable = props => {
|
|
|
56944
57386
|
* Controls inside it keep their own behavior and do not toggle. A Box, so it
|
|
56945
57387
|
* takes the layout and style props every Box takes.
|
|
56946
57388
|
*
|
|
56947
|
-
* @type {import("
|
|
56948
|
-
* marker?: false | import("
|
|
56949
|
-
* children?: import("
|
|
57389
|
+
* @type {import("preact").FunctionComponent<{
|
|
57390
|
+
* marker?: false | import("preact").ComponentChildren,
|
|
57391
|
+
* children?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
56950
57392
|
* }>}
|
|
56951
57393
|
* @param marker - The chevron drawn before the label. `false` removes it (a UI
|
|
56952
57394
|
* part that is already an icon has no room for one); any node replaces it.
|
|
@@ -57021,7 +57463,7 @@ const ExpandableUI = ({
|
|
|
57021
57463
|
* Expandable, children may be a function `(data) => ui` or a branches object —
|
|
57022
57464
|
* see ActionRenderer.
|
|
57023
57465
|
*
|
|
57024
|
-
* @type {import("
|
|
57466
|
+
* @type {import("preact").FunctionComponent<{}>}
|
|
57025
57467
|
*/
|
|
57026
57468
|
const ExpandableContent = ({
|
|
57027
57469
|
children,
|
|
@@ -57177,6 +57619,40 @@ const createToggleEvent = open => {
|
|
|
57177
57619
|
return toggleEvent;
|
|
57178
57620
|
};
|
|
57179
57621
|
|
|
57622
|
+
/**
|
|
57623
|
+
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
57624
|
+
* Each resolver is a component function rendered in sequence (hooks are allowed).
|
|
57625
|
+
* To pass through to the next resolver, call useNextResolver() and render the
|
|
57626
|
+
* returned Next component with the desired props.
|
|
57627
|
+
* To terminate the chain early (e.g. render a specialized component), render
|
|
57628
|
+
* directly without calling Next.
|
|
57629
|
+
*
|
|
57630
|
+
* The last entry in the array is the final/target component — it receives null
|
|
57631
|
+
* from useNextResolver() indicating it is terminal.
|
|
57632
|
+
*
|
|
57633
|
+
* Usage:
|
|
57634
|
+
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
57635
|
+
* // Then inside a component render:
|
|
57636
|
+
* renderButton(props)
|
|
57637
|
+
*
|
|
57638
|
+
* A chain step is one component: a runner, defined once per position, whose
|
|
57639
|
+
* render CALLS its resolver's body rather than rendering it as a child. The
|
|
57640
|
+
* body runs synchronously inside the runner's render — its hooks are the
|
|
57641
|
+
* runner's, at the same tree position on every render — and for exactly that
|
|
57642
|
+
* long, `useNextResolver()` answers with the runner after this one (null for
|
|
57643
|
+
* the last). Nothing is handed down through a context: a chain rendered by the
|
|
57644
|
+
* hundred (a list's rows) pays one component per step, not a provider and a
|
|
57645
|
+
* wrapper around each.
|
|
57646
|
+
*
|
|
57647
|
+
* A resolver that renders ANOTHER resolver — a type resolver picking the
|
|
57648
|
+
* component for `type="date"` — cannot render it as a JSX child: that child
|
|
57649
|
+
* would render later, outside any body, with no Next to read. It renders it
|
|
57650
|
+
* with `renderResolver(Resolver, props)`, which captures the Next of the body
|
|
57651
|
+
* it is called from and hands it to a runner made for that resolver — the
|
|
57652
|
+
* child continues the chain where its parent stood. Called anywhere else, or
|
|
57653
|
+
* a `useNextResolver()` reached outside a body, throws: the chain has no
|
|
57654
|
+
* silent answer to give there.
|
|
57655
|
+
*/
|
|
57180
57656
|
const createComponentResolver = (resolvers, {
|
|
57181
57657
|
pure
|
|
57182
57658
|
} = {}) => {
|
|
@@ -57386,10 +57862,21 @@ const useAccentColorAttributes = (
|
|
|
57386
57862
|
};
|
|
57387
57863
|
|
|
57388
57864
|
installImportMetaCssBuild(import.meta);
|
|
57865
|
+
/**
|
|
57866
|
+
* We need the content to visually shrink (scale down) but the button interactive area MUST remain intact
|
|
57867
|
+
* Otherwise a click on the edges of the button cannot not trigger the click event (mouseup occurs outside the button)
|
|
57868
|
+
**/
|
|
57869
|
+
|
|
57870
|
+
/**
|
|
57871
|
+
* We have to re-define the CSS of button because getComputedStyle(button).borderColor returns
|
|
57872
|
+
* rgb(0, 0, 0) while being visually grey in chrome
|
|
57873
|
+
* So we redefine chrome styles so that loader can keep up with the actual color visible to the user
|
|
57874
|
+
*/
|
|
57389
57875
|
const css$R = /* css */`@layer navi {
|
|
57390
57876
|
.navi_button {
|
|
57391
57877
|
--button-border-radius: var(--navi-control-border-radius);
|
|
57392
57878
|
--button-border-width: var(--navi-control-border-width);
|
|
57879
|
+
--border-width: var(--button-border-width);
|
|
57393
57880
|
--button-cta-background-color: var(--navi-accent-color);
|
|
57394
57881
|
--button-outline-width: var(--navi-focus-outline-width);
|
|
57395
57882
|
--button-outline-offset: calc(-.5 * var(--button-outline-width));
|
|
@@ -57398,7 +57885,7 @@ const css$R = /* css */`@layer navi {
|
|
|
57398
57885
|
--button-padding-y-default: var(--navi-button-padding-y-default);
|
|
57399
57886
|
--button-loader-color: var(--navi-loader-color);
|
|
57400
57887
|
--button-border-color: var(--navi-control-border-color);
|
|
57401
|
-
--button-background-color: var(--
|
|
57888
|
+
--button-background-color: var(--navi-button-background-color);
|
|
57402
57889
|
--button-color: currentColor;
|
|
57403
57890
|
--button-cursor: pointer;
|
|
57404
57891
|
--button-font-size: var(--navi-control-font-size);
|
|
@@ -57431,6 +57918,92 @@ const css$R = /* css */`@layer navi {
|
|
|
57431
57918
|
line-height: var(--navi-control-line-height);
|
|
57432
57919
|
display: inline-block;
|
|
57433
57920
|
}
|
|
57921
|
+
|
|
57922
|
+
.navi_button[data-variant="discrete"] {
|
|
57923
|
+
--button-border-width: 0;
|
|
57924
|
+
--button-border-color: transparent;
|
|
57925
|
+
--button-border-color-hover: var(--button-border-color);
|
|
57926
|
+
--button-border-color-current: var(--button-border-color);
|
|
57927
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
57928
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
57929
|
+
--button-background-color: transparent;
|
|
57930
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57931
|
+
currentColor 8%,
|
|
57932
|
+
var(--button-background-color));
|
|
57933
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57934
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57935
|
+
}
|
|
57936
|
+
|
|
57937
|
+
.navi_button[data-variant="bare"] {
|
|
57938
|
+
--button-padding-x-default: 0;
|
|
57939
|
+
--button-padding-y-default: 0;
|
|
57940
|
+
--button-border-width: 0;
|
|
57941
|
+
--button-border-color: transparent;
|
|
57942
|
+
--button-border-color-hover: var(--button-border-color);
|
|
57943
|
+
--button-border-color-current: var(--button-border-color);
|
|
57944
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
57945
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
57946
|
+
--button-background-color: transparent;
|
|
57947
|
+
--button-background-color-hover: var(--button-background-color);
|
|
57948
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57949
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57950
|
+
font-size: inherit;
|
|
57951
|
+
font-family: inherit;
|
|
57952
|
+
line-height: inherit;
|
|
57953
|
+
}
|
|
57954
|
+
|
|
57955
|
+
.navi_button[data-variant="discrete-border"] {
|
|
57956
|
+
--button-background-color: transparent;
|
|
57957
|
+
--button-background-color-hover: var(--button-background-color);
|
|
57958
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57959
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57960
|
+
}
|
|
57961
|
+
|
|
57962
|
+
.navi_button[data-variant="border"] {
|
|
57963
|
+
--button-background-color: transparent;
|
|
57964
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57965
|
+
currentColor 8%,
|
|
57966
|
+
var(--button-background-color));
|
|
57967
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
57968
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
57969
|
+
}
|
|
57970
|
+
|
|
57971
|
+
.navi_button[data-icon] {
|
|
57972
|
+
--button-padding: 0;
|
|
57973
|
+
}
|
|
57974
|
+
|
|
57975
|
+
.navi_button[data-cta] {
|
|
57976
|
+
--button-background-color: var(--button-cta-background-color);
|
|
57977
|
+
--button-border-color: var(--button-cta-background-color);
|
|
57978
|
+
--button-color: white;
|
|
57979
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
57980
|
+
var(--button-background-color) 85%,
|
|
57981
|
+
white);
|
|
57982
|
+
--button-border-color-hover: color-mix(in srgb,
|
|
57983
|
+
var(--button-border-color) 85%,
|
|
57984
|
+
white);
|
|
57985
|
+
--button-background-color-readonly: color-mix(in srgb,
|
|
57986
|
+
var(--button-background-color) 50%,
|
|
57987
|
+
white);
|
|
57988
|
+
--button-border-color-readonly: color-mix(in srgb,
|
|
57989
|
+
var(--button-border-color) 50%,
|
|
57990
|
+
white);
|
|
57991
|
+
--button-background-color-disabled: color-mix(in srgb,
|
|
57992
|
+
var(--button-background-color) 40%,
|
|
57993
|
+
white);
|
|
57994
|
+
--button-border-color-disabled: color-mix(in srgb,
|
|
57995
|
+
var(--button-border-color) 40%,
|
|
57996
|
+
white);
|
|
57997
|
+
--button-color-disabled: color-mix(in srgb,
|
|
57998
|
+
var(--button-color) 60%,
|
|
57999
|
+
transparent);
|
|
58000
|
+
}
|
|
58001
|
+
|
|
58002
|
+
.navi_button[data-content-display="contents"] {
|
|
58003
|
+
background: none;
|
|
58004
|
+
border: none;
|
|
58005
|
+
padding: 0;
|
|
58006
|
+
}
|
|
57434
58007
|
}
|
|
57435
58008
|
|
|
57436
58009
|
a.navi_button {
|
|
@@ -57447,7 +58020,6 @@ a.navi_button {
|
|
|
57447
58020
|
--x-button-color: var(--button-color);
|
|
57448
58021
|
--x-button-cursor: var(--button-cursor);
|
|
57449
58022
|
box-sizing: border-box;
|
|
57450
|
-
aspect-ratio: inherit;
|
|
57451
58023
|
color: var(--x-button-color);
|
|
57452
58024
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--button-border-radius));
|
|
57453
58025
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--button-border-radius));
|
|
@@ -57456,12 +58028,15 @@ a.navi_button {
|
|
|
57456
58028
|
cursor: var(--x-button-cursor);
|
|
57457
58029
|
touch-action: manipulation;
|
|
57458
58030
|
user-select: none;
|
|
57459
|
-
background: none;
|
|
57460
|
-
border: none;
|
|
57461
58031
|
outline: none;
|
|
57462
|
-
padding: 0;
|
|
57463
58032
|
position: relative;
|
|
57464
58033
|
|
|
58034
|
+
&:where(:not([data-content-display="contents"])) {
|
|
58035
|
+
background: none;
|
|
58036
|
+
border: none;
|
|
58037
|
+
padding: 0;
|
|
58038
|
+
}
|
|
58039
|
+
|
|
57465
58040
|
& .navi_button_content {
|
|
57466
58041
|
--x-corner-top-left-radius: initial;
|
|
57467
58042
|
--x-corner-top-right-radius: initial;
|
|
@@ -57483,8 +58058,7 @@ a.navi_button {
|
|
|
57483
58058
|
gap: inherit;
|
|
57484
58059
|
color: inherit;
|
|
57485
58060
|
vertical-align: inherit;
|
|
57486
|
-
background: var(--x-button-background);
|
|
57487
|
-
background-color: var(--x-button-background-color, var(--x-button-background));
|
|
58061
|
+
background: var(--x-button-background, var(--x-button-background-color));
|
|
57488
58062
|
border: var(--x-button-border);
|
|
57489
58063
|
border-radius: inherit;
|
|
57490
58064
|
outline-width: var(--button-outline-width);
|
|
@@ -57507,6 +58081,10 @@ a.navi_button {
|
|
|
57507
58081
|
}
|
|
57508
58082
|
}
|
|
57509
58083
|
|
|
58084
|
+
&[data-background-color] .navi_button_content {
|
|
58085
|
+
background-color: var(--x-button-background-color);
|
|
58086
|
+
}
|
|
58087
|
+
|
|
57510
58088
|
&[data-href-current] {
|
|
57511
58089
|
--x-button-border-color: var(--button-border-color-current);
|
|
57512
58090
|
--x-button-background-color: var(--button-background-color-current);
|
|
@@ -57551,7 +58129,7 @@ a.navi_button {
|
|
|
57551
58129
|
}
|
|
57552
58130
|
|
|
57553
58131
|
&[data-content-display="contents"] {
|
|
57554
|
-
&
|
|
58132
|
+
&[data-focus-visible] {
|
|
57555
58133
|
outline: var(--button-outline-width) solid var(--button-outline-color);
|
|
57556
58134
|
outline-offset: var(--button-outline-offset);
|
|
57557
58135
|
}
|
|
@@ -57577,19 +58155,6 @@ a.navi_button {
|
|
|
57577
58155
|
}
|
|
57578
58156
|
|
|
57579
58157
|
&[data-variant="discrete"] {
|
|
57580
|
-
--button-border-width: 0;
|
|
57581
|
-
--button-border-color: transparent;
|
|
57582
|
-
--button-border-color-hover: var(--button-border-color);
|
|
57583
|
-
--button-border-color-current: var(--button-border-color);
|
|
57584
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
57585
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
57586
|
-
--button-background-color: var(--button-background, transparent);
|
|
57587
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57588
|
-
currentColor 8%,
|
|
57589
|
-
var(--button-background-color));
|
|
57590
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57591
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57592
|
-
|
|
57593
58158
|
@media (hover: hover) {
|
|
57594
58159
|
&[data-pressed] {
|
|
57595
58160
|
& .navi_button_content {
|
|
@@ -57600,17 +58165,6 @@ a.navi_button {
|
|
|
57600
58165
|
}
|
|
57601
58166
|
|
|
57602
58167
|
&[data-variant="bare"] {
|
|
57603
|
-
--button-border-width: 0;
|
|
57604
|
-
--button-border-color: transparent;
|
|
57605
|
-
--button-border-color-hover: var(--button-border-color);
|
|
57606
|
-
--button-border-color-current: var(--button-border-color);
|
|
57607
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
57608
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
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
|
-
|
|
57614
58168
|
&[data-pressed] {
|
|
57615
58169
|
& .navi_button_content {
|
|
57616
58170
|
transform: none;
|
|
@@ -57619,10 +58173,6 @@ a.navi_button {
|
|
|
57619
58173
|
}
|
|
57620
58174
|
|
|
57621
58175
|
&[data-variant="discrete-border"] {
|
|
57622
|
-
--button-background-color: var(--button-background, transparent);
|
|
57623
|
-
--button-background-color-hover: var(--button-background-color);
|
|
57624
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57625
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57626
58176
|
--x-button-border-color: transparent;
|
|
57627
58177
|
|
|
57628
58178
|
&[data-hover] {
|
|
@@ -57634,15 +58184,6 @@ a.navi_button {
|
|
|
57634
58184
|
}
|
|
57635
58185
|
}
|
|
57636
58186
|
|
|
57637
|
-
&[data-variant="border"] {
|
|
57638
|
-
--button-background-color: var(--button-background, transparent);
|
|
57639
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57640
|
-
currentColor 8%,
|
|
57641
|
-
var(--button-background-color));
|
|
57642
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
57643
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
57644
|
-
}
|
|
57645
|
-
|
|
57646
58187
|
&[data-press-effect="scale"][data-pressed] {
|
|
57647
58188
|
& .navi_button_content {
|
|
57648
58189
|
transform: scale(.9);
|
|
@@ -57656,37 +58197,9 @@ a.navi_button {
|
|
|
57656
58197
|
}
|
|
57657
58198
|
|
|
57658
58199
|
&[data-icon] {
|
|
57659
|
-
--button-padding: 0;
|
|
57660
58200
|
justify-content: center;
|
|
57661
58201
|
align-items: center;
|
|
57662
58202
|
}
|
|
57663
|
-
|
|
57664
|
-
&[data-cta] {
|
|
57665
|
-
--button-background-color: var(--button-background, var(--button-cta-background-color));
|
|
57666
|
-
--button-border-color: var(--button-cta-background-color);
|
|
57667
|
-
--button-color: white;
|
|
57668
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
57669
|
-
var(--button-background-color) 85%,
|
|
57670
|
-
white);
|
|
57671
|
-
--button-border-color-hover: color-mix(in srgb,
|
|
57672
|
-
var(--button-border-color) 85%,
|
|
57673
|
-
white);
|
|
57674
|
-
--button-background-color-readonly: color-mix(in srgb,
|
|
57675
|
-
var(--button-background-color) 50%,
|
|
57676
|
-
white);
|
|
57677
|
-
--button-border-color-readonly: color-mix(in srgb,
|
|
57678
|
-
var(--button-border-color) 50%,
|
|
57679
|
-
white);
|
|
57680
|
-
--button-background-color-disabled: color-mix(in srgb,
|
|
57681
|
-
var(--button-background-color) 40%,
|
|
57682
|
-
white);
|
|
57683
|
-
--button-border-color-disabled: color-mix(in srgb,
|
|
57684
|
-
var(--button-border-color) 40%,
|
|
57685
|
-
white);
|
|
57686
|
-
--button-color-disabled: color-mix(in srgb,
|
|
57687
|
-
var(--button-color) 60%,
|
|
57688
|
-
transparent);
|
|
57689
|
-
}
|
|
57690
58203
|
}
|
|
57691
58204
|
`;
|
|
57692
58205
|
const ButtonUI = props => {
|
|
@@ -57815,6 +58328,7 @@ const ButtonUI = props => {
|
|
|
57815
58328
|
"data-press-effect": pressEffect,
|
|
57816
58329
|
"data-icon": icon ? "" : undefined,
|
|
57817
58330
|
"data-cta": cta ? "" : undefined,
|
|
58331
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
57818
58332
|
"data-callout-arrow-x": "center"
|
|
57819
58333
|
// style management
|
|
57820
58334
|
,
|
|
@@ -58127,7 +58641,7 @@ const ButtonWithHref = props => {
|
|
|
58127
58641
|
} = getHrefTargetInfo(href, documentUrlSignal.peek());
|
|
58128
58642
|
return jsx(Next, {
|
|
58129
58643
|
...props,
|
|
58130
|
-
target: target === undefined ?
|
|
58644
|
+
target: target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target,
|
|
58131
58645
|
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
58132
58646
|
// Under whatever the caller already says: a route knows better whether
|
|
58133
58647
|
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
@@ -58287,7 +58801,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58287
58801
|
};
|
|
58288
58802
|
|
|
58289
58803
|
/**
|
|
58290
|
-
* @type {import("
|
|
58804
|
+
* @type {import("preact").FunctionComponent<{
|
|
58291
58805
|
* selfInteractions?: string,
|
|
58292
58806
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
58293
58807
|
* replace?: boolean,
|
|
@@ -58369,7 +58883,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58369
58883
|
* more: the work may already have happened on the other side (see
|
|
58370
58884
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
58371
58885
|
* where the screen can be re-opened on what is actually there.
|
|
58372
|
-
* @param {boolean|import("
|
|
58886
|
+
* @param {boolean|import("preact").ComponentChildren} [confirm] Ask before
|
|
58373
58887
|
* doing it, in the place the button stands: the first press replaces the
|
|
58374
58888
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
58375
58889
|
* does what the button was for. `true` asks navi's default question. The
|
|
@@ -58380,9 +58894,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
58380
58894
|
* question up, its message on the button that asked. Use `<Picker
|
|
58381
58895
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
58382
58896
|
* or when the row has no space for it.
|
|
58383
|
-
* @param {import("
|
|
58897
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] Label of the
|
|
58384
58898
|
* second press.
|
|
58385
|
-
* @param {import("
|
|
58899
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Label of the way
|
|
58386
58900
|
* out of the question.
|
|
58387
58901
|
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
58388
58902
|
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
@@ -58481,13 +58995,13 @@ const css$P = /* css */`.navi_control_swap {
|
|
|
58481
58995
|
`;
|
|
58482
58996
|
|
|
58483
58997
|
/**
|
|
58484
|
-
* @type {import("
|
|
58998
|
+
* @type {import("preact").FunctionComponent<{
|
|
58485
58999
|
* value?: string,
|
|
58486
59000
|
* defaultValue?: string,
|
|
58487
59001
|
* signal?: import("@preact/signals").Signal<string>,
|
|
58488
59002
|
* onChange?: (name: string, event: Event) => void,
|
|
58489
59003
|
* animation?: boolean,
|
|
58490
|
-
* children?: import("
|
|
59004
|
+
* children?: import("preact").ComponentChildren,
|
|
58491
59005
|
* }>}
|
|
58492
59006
|
* @param value - The name of the side holding the floor, driven from outside:
|
|
58493
59007
|
* the row swaps to match every change of it, and a press on a cap can still
|
|
@@ -58634,13 +59148,13 @@ const ControlSwap = props => {
|
|
|
58634
59148
|
* reads these and draws the caps at its ends, the controls between them. The
|
|
58635
59149
|
* props below say what the side IS; every other prop describes its cap.
|
|
58636
59150
|
*
|
|
58637
|
-
* @type {import("
|
|
59151
|
+
* @type {import("preact").FunctionComponent<{
|
|
58638
59152
|
* name?: string,
|
|
58639
|
-
* icon: import("
|
|
59153
|
+
* icon: import("preact").ComponentChildren,
|
|
58640
59154
|
* label: string,
|
|
58641
|
-
* badge?: boolean | import("
|
|
59155
|
+
* badge?: boolean | import("preact").ComponentChildren,
|
|
58642
59156
|
* autoFocus?: boolean,
|
|
58643
|
-
* children?: import("
|
|
59157
|
+
* children?: import("preact").ComponentChildren,
|
|
58644
59158
|
* [key: string]: any,
|
|
58645
59159
|
* }>}
|
|
58646
59160
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
@@ -58848,6 +59362,51 @@ const createAvailableConstraint = (
|
|
|
58848
59362
|
};
|
|
58849
59363
|
};
|
|
58850
59364
|
|
|
59365
|
+
/**
|
|
59366
|
+
* ControlGroup — a named sub-group of form controls whose combined state is
|
|
59367
|
+
* aggregated into a single key/value object, exactly like a Form but without
|
|
59368
|
+
* the <form> element semantics.
|
|
59369
|
+
*
|
|
59370
|
+
* Use it when you need a self-contained cluster of inputs that:
|
|
59371
|
+
* - produce one composite value (an object) as their joint state
|
|
59372
|
+
* - can carry their own action / uiAction / command
|
|
59373
|
+
* - may live inside or outside a <Form>
|
|
59374
|
+
*
|
|
59375
|
+
* Props:
|
|
59376
|
+
* name — the key under which the composite value is registered in a
|
|
59377
|
+
* parent Form (optional when used standalone)
|
|
59378
|
+
* action — called with the aggregated object value when any child changes
|
|
59379
|
+
* command — navi command string (e.g. "--navi-send")
|
|
59380
|
+
* disabled — propagates to all children
|
|
59381
|
+
* readOnly — propagates to all children
|
|
59382
|
+
* as — HTML element to render (default "div")
|
|
59383
|
+
* children — the controls that belong to this group
|
|
59384
|
+
*
|
|
59385
|
+
* aggregateChildStates / distributeChildUIState
|
|
59386
|
+
* — what the group is worth, in both directions, when that is not
|
|
59387
|
+
* simply "one key per child": two wheels that add up to a number
|
|
59388
|
+
* of minutes, three fields that make one date. With them the
|
|
59389
|
+
* group takes and hands back a single value, so it can be driven
|
|
59390
|
+
* by one `value`/`signal` like any other control. Same mechanism
|
|
59391
|
+
* InputDuration uses for its own hour/minute/second fields (see
|
|
59392
|
+
* input_duration.jsx). `name` is unaffected: it still says under
|
|
59393
|
+
* which key the group's value — whatever shape it takes —
|
|
59394
|
+
* lands in the form around it.
|
|
59395
|
+
*
|
|
59396
|
+
* distributeChildStates
|
|
59397
|
+
* — the same way down, asked ONCE for all the children at a time:
|
|
59398
|
+
* `(groupValue, children) => Map<child, state>`. The plural of
|
|
59399
|
+
* `distributeChildUIState`, and the mirror of
|
|
59400
|
+
* `aggregateChildStates`, which already sees every child. For a
|
|
59401
|
+
* group whose children cannot be placed one at a time — four
|
|
59402
|
+
* seats where who sits down decides who moves, a row where the
|
|
59403
|
+
* first answer changes what the others may show. A child the Map
|
|
59404
|
+
* does not name is left where it is. Given both, this one wins.
|
|
59405
|
+
* It also closes the loop: the group holds ONE answer its
|
|
59406
|
+
* children are views of, so when one of them speaks the others
|
|
59407
|
+
* are placed again (the one that acted is left where the user
|
|
59408
|
+
* put it).
|
|
59409
|
+
*/
|
|
58851
59410
|
const ControlGroup = props => {
|
|
58852
59411
|
const defaultRef = useRef();
|
|
58853
59412
|
props.ref = props.ref || defaultRef;
|
|
@@ -59040,6 +59599,41 @@ const css$N = /* css */`@layer navi {
|
|
|
59040
59599
|
var(--button-background-color) 95%,
|
|
59041
59600
|
black);
|
|
59042
59601
|
}
|
|
59602
|
+
|
|
59603
|
+
.navi_checkbox[data-variant="switch"] {
|
|
59604
|
+
--margin: var(--switch-margin);
|
|
59605
|
+
--width: var(--switch-outer-width);
|
|
59606
|
+
--height: unset;
|
|
59607
|
+
--border-radius: var(--switch-border-radius);
|
|
59608
|
+
--background-color: var(--switch-background-color);
|
|
59609
|
+
--background-color-hover: var(--switch-background-color-hover);
|
|
59610
|
+
--background-color-readonly: var(--switch-background-color-readonly);
|
|
59611
|
+
--background-color-disabled: var(--switch-background-color-disabled);
|
|
59612
|
+
--background-color-checked: var(--switch-background-color-checked);
|
|
59613
|
+
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
59614
|
+
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
59615
|
+
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
59616
|
+
}
|
|
59617
|
+
|
|
59618
|
+
.navi_checkbox[data-variant="icon"] {
|
|
59619
|
+
--margin: 0;
|
|
59620
|
+
--width: auto;
|
|
59621
|
+
--height: auto;
|
|
59622
|
+
}
|
|
59623
|
+
|
|
59624
|
+
.navi_checkbox[data-variant="button"] {
|
|
59625
|
+
--margin: 0;
|
|
59626
|
+
--width: auto;
|
|
59627
|
+
--height: auto;
|
|
59628
|
+
--border-color: var(--button-border-color);
|
|
59629
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
59630
|
+
--background-color: var(--button-background-color);
|
|
59631
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
59632
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
59633
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
59634
|
+
--border-color-checked: var(--button-border-color);
|
|
59635
|
+
--background-color-checked: var(--button-background-color);
|
|
59636
|
+
}
|
|
59043
59637
|
}
|
|
59044
59638
|
|
|
59045
59639
|
.navi_checkbox {
|
|
@@ -59180,18 +59774,6 @@ const css$N = /* css */`@layer navi {
|
|
|
59180
59774
|
|
|
59181
59775
|
&[data-variant="switch"] {
|
|
59182
59776
|
--switch-outer-width: calc(var(--switch-width) + var(--switch-padding));
|
|
59183
|
-
--margin: var(--switch-margin);
|
|
59184
|
-
--width: var(--switch-outer-width);
|
|
59185
|
-
--height: unset;
|
|
59186
|
-
--border-radius: var(--switch-border-radius);
|
|
59187
|
-
--background-color: var(--switch-background-color);
|
|
59188
|
-
--background-color-hover: var(--switch-background-color-hover);
|
|
59189
|
-
--background-color-readonly: var(--switch-background-color-readonly);
|
|
59190
|
-
--background-color-disabled: var(--switch-background-color-disabled);
|
|
59191
|
-
--background-color-checked: var(--switch-background-color-checked);
|
|
59192
|
-
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
59193
|
-
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
59194
|
-
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
59195
59777
|
min-width: var(--switch-outer-width);
|
|
59196
59778
|
padding: var(--switch-padding);
|
|
59197
59779
|
background-color: var(--x-background-color);
|
|
@@ -59201,25 +59783,11 @@ const css$N = /* css */`@layer navi {
|
|
|
59201
59783
|
}
|
|
59202
59784
|
|
|
59203
59785
|
&[data-variant="icon"] {
|
|
59204
|
-
--margin: 0;
|
|
59205
|
-
--width: auto;
|
|
59206
|
-
--height: auto;
|
|
59207
59786
|
background: none;
|
|
59208
59787
|
border: none;
|
|
59209
59788
|
}
|
|
59210
59789
|
|
|
59211
59790
|
&[data-variant="button"] {
|
|
59212
|
-
--margin: 0;
|
|
59213
|
-
--width: auto;
|
|
59214
|
-
--height: auto;
|
|
59215
|
-
--border-color: var(--button-border-color);
|
|
59216
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
59217
|
-
--background-color: var(--button-background-color);
|
|
59218
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
59219
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
59220
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
59221
|
-
--border-color-checked: var(--button-border-color);
|
|
59222
|
-
--background-color-checked: var(--button-background-color);
|
|
59223
59791
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
59224
59792
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
59225
59793
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -59416,7 +59984,7 @@ const css$M = /* css */`@layer navi {
|
|
|
59416
59984
|
[navi-field] {
|
|
59417
59985
|
--spacing-with-control: var(--navi-xs);
|
|
59418
59986
|
|
|
59419
|
-
& .navi_checkbox, & .navi_radio {
|
|
59987
|
+
& .navi_checkbox:not([data-variant="switch"]), & .navi_radio {
|
|
59420
59988
|
--margin: 0;
|
|
59421
59989
|
}
|
|
59422
59990
|
}
|
|
@@ -59771,6 +60339,28 @@ const css$L = /* css */`@layer navi {
|
|
|
59771
60339
|
--button-border-color-disabled: var(--border-color-readonly);
|
|
59772
60340
|
--button-background-color-disabled: var(--background-color-readonly);
|
|
59773
60341
|
}
|
|
60342
|
+
|
|
60343
|
+
.navi_radio[data-variant="icon"] {
|
|
60344
|
+
--width: auto;
|
|
60345
|
+
--height: auto;
|
|
60346
|
+
--outline-offset: 2px;
|
|
60347
|
+
--outline-width: 2px;
|
|
60348
|
+
}
|
|
60349
|
+
|
|
60350
|
+
.navi_radio[data-variant="button"] {
|
|
60351
|
+
--margin: 0;
|
|
60352
|
+
--outline-offset: 0px;
|
|
60353
|
+
--width: auto;
|
|
60354
|
+
--height: auto;
|
|
60355
|
+
--border-color: var(--button-border-color);
|
|
60356
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
60357
|
+
--background-color: var(--button-background-color);
|
|
60358
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
60359
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
60360
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
60361
|
+
--border-color-checked: var(--button-border-color);
|
|
60362
|
+
--background-color-checked: var(--button-background-color);
|
|
60363
|
+
}
|
|
59774
60364
|
}
|
|
59775
60365
|
|
|
59776
60366
|
.navi_radio {
|
|
@@ -59926,26 +60516,7 @@ const css$L = /* css */`@layer navi {
|
|
|
59926
60516
|
}
|
|
59927
60517
|
}
|
|
59928
60518
|
|
|
59929
|
-
&[data-variant="icon"] {
|
|
59930
|
-
--width: auto;
|
|
59931
|
-
--height: auto;
|
|
59932
|
-
--outline-offset: 2px;
|
|
59933
|
-
--outline-width: 2px;
|
|
59934
|
-
}
|
|
59935
|
-
|
|
59936
60519
|
&[data-variant="button"] {
|
|
59937
|
-
--margin: 0;
|
|
59938
|
-
--outline-offset: 0px;
|
|
59939
|
-
--width: auto;
|
|
59940
|
-
--height: auto;
|
|
59941
|
-
--border-color: var(--button-border-color);
|
|
59942
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
59943
|
-
--background-color: var(--button-background-color);
|
|
59944
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
59945
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
59946
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
59947
|
-
--border-color-checked: var(--button-border-color);
|
|
59948
|
-
--background-color-checked: var(--button-background-color);
|
|
59949
60520
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
59950
60521
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
59951
60522
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -60169,6 +60740,7 @@ const css$K = /* css */`@layer navi {
|
|
|
60169
60740
|
--outline-width: var(--navi-focus-outline-width);
|
|
60170
60741
|
--outline-offset: var(--outline-width);
|
|
60171
60742
|
--outline-color: var(--navi-focus-outline-color);
|
|
60743
|
+
--margin: 2px;
|
|
60172
60744
|
--height: 8px;
|
|
60173
60745
|
--thumb-size: 16px;
|
|
60174
60746
|
--thumb-width: var(--thumb-size);
|
|
@@ -60244,6 +60816,7 @@ const css$K = /* css */`@layer navi {
|
|
|
60244
60816
|
box-sizing: border-box;
|
|
60245
60817
|
width: fit-content;
|
|
60246
60818
|
height: var(--height);
|
|
60819
|
+
margin: var(--margin);
|
|
60247
60820
|
font-size: var(--font-size);
|
|
60248
60821
|
font-family: var(--font-family);
|
|
60249
60822
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--outline-border-radius));
|
|
@@ -60256,7 +60829,6 @@ const css$K = /* css */`@layer navi {
|
|
|
60256
60829
|
outline-offset: var(--outline-offset);
|
|
60257
60830
|
flex-direction: row;
|
|
60258
60831
|
align-items: center;
|
|
60259
|
-
margin: 2px;
|
|
60260
60832
|
position: relative;
|
|
60261
60833
|
|
|
60262
60834
|
& .navi_control_input {
|
|
@@ -60553,6 +61125,45 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60553
61125
|
grey);
|
|
60554
61126
|
--color-disabled: var(--color-dimmed);
|
|
60555
61127
|
}
|
|
61128
|
+
|
|
61129
|
+
.navi_input[data-background-transparent] {
|
|
61130
|
+
--background-color-hover: var(--background-color);
|
|
61131
|
+
--background-color-focus: var(--navi-surface-color);
|
|
61132
|
+
}
|
|
61133
|
+
|
|
61134
|
+
.navi_input[data-variant="discrete"], .navi_input[data-variant="discrete-border"] {
|
|
61135
|
+
--background-color: transparent;
|
|
61136
|
+
--background-color-hover: var(--background-color);
|
|
61137
|
+
--background-color-focus: var(--navi-surface-color);
|
|
61138
|
+
|
|
61139
|
+
&[data-background] {
|
|
61140
|
+
--background-color-focus: transparent;
|
|
61141
|
+
}
|
|
61142
|
+
}
|
|
61143
|
+
|
|
61144
|
+
.navi_input[data-variant="discrete"] {
|
|
61145
|
+
--border-color: transparent;
|
|
61146
|
+
}
|
|
61147
|
+
|
|
61148
|
+
.navi_input[data-variant="text"] {
|
|
61149
|
+
--background-color: transparent;
|
|
61150
|
+
--background-color-hover: var(--background-color);
|
|
61151
|
+
--background-color-focus: var(--background-color);
|
|
61152
|
+
--background-color-readonly: var(--background-color);
|
|
61153
|
+
--background-color-disabled: var(--background-color);
|
|
61154
|
+
--border-color: transparent;
|
|
61155
|
+
--border-color-hover: var(--border-color);
|
|
61156
|
+
--border-color-readonly: var(--border-color);
|
|
61157
|
+
--border-color-disabled: var(--border-color);
|
|
61158
|
+
}
|
|
61159
|
+
|
|
61160
|
+
.navi_input[data-variant="underline"] {
|
|
61161
|
+
--background-color: transparent;
|
|
61162
|
+
--background-color-hover: var(--background-color);
|
|
61163
|
+
--background-color-focus: var(--background-color);
|
|
61164
|
+
--background-color-readonly: var(--background-color);
|
|
61165
|
+
--background-color-disabled: var(--background-color);
|
|
61166
|
+
}
|
|
60556
61167
|
}
|
|
60557
61168
|
|
|
60558
61169
|
.navi_input {
|
|
@@ -60704,19 +61315,12 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60704
61315
|
}
|
|
60705
61316
|
|
|
60706
61317
|
&[data-background-transparent] {
|
|
60707
|
-
--background-color-hover: var(--background-color);
|
|
60708
|
-
--background-color-focus: var(--navi-surface-color);
|
|
60709
|
-
|
|
60710
61318
|
&[data-focus] {
|
|
60711
61319
|
--x-background-color: var(--background-color-focus);
|
|
60712
61320
|
}
|
|
60713
61321
|
}
|
|
60714
61322
|
|
|
60715
61323
|
&[data-variant="discrete"], &[data-variant="discrete-border"] {
|
|
60716
|
-
--background-color: transparent;
|
|
60717
|
-
--background-color-hover: var(--background-color);
|
|
60718
|
-
--background-color-focus: var(--navi-surface-color);
|
|
60719
|
-
|
|
60720
61324
|
&[data-focus] {
|
|
60721
61325
|
--x-background-color: var(--background-color-focus);
|
|
60722
61326
|
}
|
|
@@ -60724,15 +61328,9 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60724
61328
|
&[data-readonly], &[data-disabled] {
|
|
60725
61329
|
--x-background-color: var(--background-color);
|
|
60726
61330
|
}
|
|
60727
|
-
|
|
60728
|
-
&[data-background] {
|
|
60729
|
-
--background-color-focus: transparent;
|
|
60730
|
-
}
|
|
60731
61331
|
}
|
|
60732
61332
|
|
|
60733
61333
|
&[data-variant="discrete"] {
|
|
60734
|
-
--border-color: transparent;
|
|
60735
|
-
|
|
60736
61334
|
&[data-focus] {
|
|
60737
61335
|
--x-border-color: color-mix(in srgb,
|
|
60738
61336
|
var(--border-color) 55%,
|
|
@@ -60741,24 +61339,10 @@ const inputCss = /* css */ `@layer navi {
|
|
|
60741
61339
|
}
|
|
60742
61340
|
|
|
60743
61341
|
&[data-variant="text"] {
|
|
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
|
-
--border-color: transparent;
|
|
60750
|
-
--border-color-hover: var(--border-color);
|
|
60751
|
-
--border-color-readonly: var(--border-color);
|
|
60752
|
-
--border-color-disabled: var(--border-color);
|
|
60753
61342
|
cursor: inherit;
|
|
60754
61343
|
}
|
|
60755
61344
|
|
|
60756
61345
|
&[data-variant="underline"] {
|
|
60757
|
-
--background-color: transparent;
|
|
60758
|
-
--background-color-hover: var(--background-color);
|
|
60759
|
-
--background-color-focus: var(--background-color);
|
|
60760
|
-
--background-color-readonly: var(--background-color);
|
|
60761
|
-
--background-color-disabled: var(--background-color);
|
|
60762
61346
|
border: none;
|
|
60763
61347
|
border-radius: 0;
|
|
60764
61348
|
padding-left: 0;
|
|
@@ -61954,6 +62538,38 @@ const InputStyleCSSVars = {
|
|
|
61954
62538
|
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value"];
|
|
61955
62539
|
const InputPseudoElements = ["::-navi-loader"];
|
|
61956
62540
|
|
|
62541
|
+
/**
|
|
62542
|
+
* @type {import("preact").FunctionComponent<{
|
|
62543
|
+
* type?: string,
|
|
62544
|
+
* icon?: import("preact").ComponentChildren | null,
|
|
62545
|
+
* clearable?: boolean,
|
|
62546
|
+
* value?: any,
|
|
62547
|
+
* boolean?: boolean,
|
|
62548
|
+
* selectOnPress?: boolean,
|
|
62549
|
+
* [key: string]: any,
|
|
62550
|
+
* }>}
|
|
62551
|
+
* @param {any} [value] On a checkbox, what it is worth checked (`"on"` when
|
|
62552
|
+
* left out); unchecked it is `undefined`. See control_value.md, "What a
|
|
62553
|
+
* checkbox is worth".
|
|
62554
|
+
* @param {boolean} [boolean] Makes a checkbox a yes/no: `true` checked, `false`
|
|
62555
|
+
* unchecked, everywhere it hands its value. Takes no `value`.
|
|
62556
|
+
* @param {import("preact").ComponentChildren|null} [icon] What the textual
|
|
62557
|
+
* types that draw an icon (`search`, `email`, `tel`) put in their slot: left
|
|
62558
|
+
* out, the type's own glyph; `null`, nothing — for a field an application
|
|
62559
|
+
* draws its own magnifier beside, where a second one two centimetres away
|
|
62560
|
+
* would be one too many; anything else is drawn instead. A `search` field
|
|
62561
|
+
* swaps that slot for the clear cross as soon as it holds a value, whatever
|
|
62562
|
+
* this says.
|
|
62563
|
+
* @param {boolean} [clearable] Gives any other type the same slot: the icon
|
|
62564
|
+
* while empty (none, unless `icon` says otherwise), the clear cross once
|
|
62565
|
+
* filled.
|
|
62566
|
+
* @param {boolean} [selectOnPress] For a textual field whose value is usually
|
|
62567
|
+
* replaced rather than edited, like a browser's address bar: the first press
|
|
62568
|
+
* after the field took the focus selects its whole text once released, with a
|
|
62569
|
+
* mouse or a finger, and a second press places the caret. A focus that came
|
|
62570
|
+
* without a press (a dialog opening, Tab, code) selects nothing; the first key
|
|
62571
|
+
* that types selects first, so it replaces the text.
|
|
62572
|
+
*/
|
|
61957
62573
|
const Input = props => {
|
|
61958
62574
|
resolveInputProps(props);
|
|
61959
62575
|
const {
|
|
@@ -62043,7 +62659,7 @@ const TextareaStyleCSSVars = {
|
|
|
62043
62659
|
};
|
|
62044
62660
|
|
|
62045
62661
|
/**
|
|
62046
|
-
* @type {import("
|
|
62662
|
+
* @type {import("preact").FunctionComponent<{
|
|
62047
62663
|
* value?: string,
|
|
62048
62664
|
* defaultValue?: string,
|
|
62049
62665
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -62150,7 +62766,7 @@ const Textarea = ({
|
|
|
62150
62766
|
* draws: put it under the box, in a form footer, next to a label, and feed it
|
|
62151
62767
|
* the same value or signal as the textarea.
|
|
62152
62768
|
*
|
|
62153
|
-
* @type {import("
|
|
62769
|
+
* @type {import("preact").FunctionComponent<{
|
|
62154
62770
|
* value?: string,
|
|
62155
62771
|
* signal?: import("@preact/signals").Signal<string>,
|
|
62156
62772
|
* maxLength?: number,
|
|
@@ -62383,8 +62999,8 @@ const useEditionController = () => {
|
|
|
62383
62999
|
* it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
|
|
62384
63000
|
* the demo `control/demos/action/8_editable_demo.html` does it with props.
|
|
62385
63001
|
*
|
|
62386
|
-
* @type {import("
|
|
62387
|
-
* children?: import("
|
|
63002
|
+
* @type {import("preact").FunctionComponent<{
|
|
63003
|
+
* children?: import("preact").ComponentChildren,
|
|
62388
63004
|
* action: Function | object,
|
|
62389
63005
|
* editing?: { event?: Event } | null,
|
|
62390
63006
|
* onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
|
|
@@ -62747,6 +63363,77 @@ const commitSubtree = (controller, e) => {
|
|
|
62747
63363
|
* <form> element (FormNested).
|
|
62748
63364
|
*/
|
|
62749
63365
|
|
|
63366
|
+
/**
|
|
63367
|
+
* @param {object} props
|
|
63368
|
+
* @param {boolean} [props.standalone] - Its value is its own: the form does not
|
|
63369
|
+
* register with the control group around it (a Picker, a ControlGroup), so
|
|
63370
|
+
* what is typed in it never becomes part of that group's value, and nothing
|
|
63371
|
+
* that group does — distributing a value, resetting, cascading validation —
|
|
63372
|
+
* reaches it. For a form that lives INSIDE something else while answering a
|
|
63373
|
+
* different question — "create the thing I am about to pick" inside a picker,
|
|
63374
|
+
* say. Every control takes the same prop, for the same reason. A form inside
|
|
63375
|
+
* another form is that by construction and does not need the prop: the group
|
|
63376
|
+
* it would register into is a form, which never owns another form's fields.
|
|
63377
|
+
* @param {boolean} [props.canSendWhileUnchanged] - Send even when nothing changed. By
|
|
63378
|
+
* default a form only acts on an answer that is actually new: submitting a
|
|
63379
|
+
* form nobody touched — one just rendered, one reopened and left alone — runs
|
|
63380
|
+
* no action (a field still showing its `defaultValue` is an answer: confirming
|
|
63381
|
+
* a suggestion), and after the first submission it is that value the next
|
|
63382
|
+
* one is compared against. Everything around the action still happens either
|
|
63383
|
+
* way: the constraints are checked, and what follows the send still follows
|
|
63384
|
+
* it (the slide moves on, the popup closes) — the user is done regardless of
|
|
63385
|
+
* whether there was anything to send. Set this for a form where sending the
|
|
63386
|
+
* same thing twice is the point — a single button that fires off a
|
|
63387
|
+
* notification, an action whose duplicates are fine. See also
|
|
63388
|
+
* `readOnlyWhileFormUnchanged` on `Button`, for a submit that should say it
|
|
63389
|
+
* is waiting rather than accept a press that sends nothing.
|
|
63390
|
+
* @param {boolean} [props.actionAbortable] - The person waiting may give up on
|
|
63391
|
+
* the send: closing the popup this form holds calls the run off and goes
|
|
63392
|
+
* through, instead of being refused. For a send whose answer may never come —
|
|
63393
|
+
* a request over a network that stops answering — where a sheet with no way
|
|
63394
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
63395
|
+
* more: the write may already have landed on the other side (see
|
|
63396
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
63397
|
+
* where the screen can be re-opened on what is actually there. Every control
|
|
63398
|
+
* inside the form inherits it along with the wait itself.
|
|
63399
|
+
* @param {boolean} [props.optimistic] - The send is taken as done on the form's
|
|
63400
|
+
* own word: no busy state, and what follows it (`command="--navi-close"`, a
|
|
63401
|
+
* slide moving on) runs at once, the run going on behind. A failure then has
|
|
63402
|
+
* no sheet to be read in: `resetOnError` puts the form back and the error
|
|
63403
|
+
* callout is drawn on what surrounds the closed sheet. For a write not worth being
|
|
63404
|
+
* waited for — a score, a note — never for one whose refusal changes what the
|
|
63405
|
+
* person does next (see
|
|
63406
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
63407
|
+
* @param {any} [props.pristineKey] - What the form is measured against, taken
|
|
63408
|
+
* again every time this changes. A form knows what it holds as soon as its
|
|
63409
|
+
* fields have registered, which is the right moment for a form whose values
|
|
63410
|
+
* are there on the first render — and never the right one for a screen that
|
|
63411
|
+
* modifies something: the resource arrives a request later and fills the
|
|
63412
|
+
* fields, and a form comparing against what it held BEFORE that opens already
|
|
63413
|
+
* changed. Pass whatever says the filling is done (a boolean, the resource
|
|
63414
|
+
* itself, a count of what has loaded) and what the fields carry at that
|
|
63415
|
+
* moment becomes the reference — including the fields that settle in a render
|
|
63416
|
+
* of their own, so there is no tick to wait for on the caller's side. Change
|
|
63417
|
+
* it once, when the screen is ready: taken again after someone started
|
|
63418
|
+
* typing, it would call what they wrote the reference.
|
|
63419
|
+
* @param {string} [props.command] - What follows a submission that went
|
|
63420
|
+
* through: the form has answered its question, and this says what the screen
|
|
63421
|
+
* does about it. Nothing runs when the submission is refused — the form then
|
|
63422
|
+
* stays in front of the user, showing what it is waiting for.
|
|
63423
|
+
*
|
|
63424
|
+
* `"--navi-close"` dismisses the popup the form is in,
|
|
63425
|
+
* `"--navi-left"`/`"--navi-right"`/`"--navi-up"`/`"--navi-down"` move on the
|
|
63426
|
+
* slide map it is in, `"--navi-nav-to:/the/url"` takes the user to a page,
|
|
63427
|
+
* `"--navi-void"` stays put — the one for a form inside a popup that must
|
|
63428
|
+
* stay open once it is sent (a side panel one keeps editing in).
|
|
63429
|
+
* Any navi command, really: it is triggered from the form, so it finds its
|
|
63430
|
+
* target the way that command always does.
|
|
63431
|
+
*
|
|
63432
|
+
* Left out, the surface the form sits in decides (see resolveAfterSend in
|
|
63433
|
+
* commands.js): a popup closes, a slide goes on to the next one — or back to
|
|
63434
|
+
* the one it came from when there is no next — and a form on a page does
|
|
63435
|
+
* nothing.
|
|
63436
|
+
*/
|
|
62750
63437
|
const Form = props => {
|
|
62751
63438
|
const defaultRef = useRef();
|
|
62752
63439
|
props.ref = props.ref || defaultRef;
|
|
@@ -63083,6 +63770,7 @@ const useRequestSubmitThroughGates = ref => {
|
|
|
63083
63770
|
installImportMetaCssBuild(import.meta);
|
|
63084
63771
|
const css$H = /* css */`.navi_group {
|
|
63085
63772
|
--group-border-width: var(--navi-control-border-width);
|
|
63773
|
+
--border-width: initial;
|
|
63086
63774
|
|
|
63087
63775
|
& > :not([navi-out-of-flow]) {
|
|
63088
63776
|
&:hover, &[data-hover] {
|
|
@@ -66342,6 +67030,122 @@ SlideContainer.Down = SlideDown;
|
|
|
66342
67030
|
SlideContainer.First = SlideFirst;
|
|
66343
67031
|
SlideContainer.Last = SlideLast;
|
|
66344
67032
|
|
|
67033
|
+
/**
|
|
67034
|
+
* Displays a date/time value in a human-readable format using the current locale.
|
|
67035
|
+
*
|
|
67036
|
+
* @param {Date|number|string} children
|
|
67037
|
+
* The date to display. Accepts:
|
|
67038
|
+
* - a `Date` instance
|
|
67039
|
+
* - a Unix timestamp (number, in ms)
|
|
67040
|
+
* - a date string `"YYYY-MM-DD"` or any string parseable by `Date`
|
|
67041
|
+
* If the value cannot be parsed, it is rendered as-is.
|
|
67042
|
+
* If undefined/null, renders a placeholder shaped like the type: `"–"` for
|
|
67043
|
+
* `"relative"`, `"--:--"` for `"time"`, `"--"` for the durations, the
|
|
67044
|
+
* locale's field pattern for `"date"`/`"month"`/`"week"`/`"datetime"`.
|
|
67045
|
+
*
|
|
67046
|
+
* @param {"date"|"month"|"week"|"datetime"|"time"|"hour"|"minute"|"second"|"duration"|"relative"} [type="relative"]
|
|
67047
|
+
* Controls the display format:
|
|
67048
|
+
* - `"date"` → "lundi 11 mai" (long by default), "samedi 1 janvier 2050" outside the current year (see `year`);
|
|
67049
|
+
* `format="short"` → "lun. 11 mai"; `format="numeric"` → "11/05/2026";
|
|
67050
|
+
* `format={{ weekday: "long", month: "short" }}` spells the two apart → "mercredi 2 sept.";
|
|
67051
|
+
* a part set to `false` is dropped — `format={{ day: false, month: false }}` → "mercredi"
|
|
67052
|
+
* - `"month"` → "juin 2026"
|
|
67053
|
+
* - `"week"` → the ISO week as given ("2026-W38"), in a `<time datetime>`
|
|
67054
|
+
* - `"datetime"` → "lun. 11 mai, 14:30" (long); `format="short"` → "11 mai, 14:30"; `format="narrow"` → "11/05, 14:30"
|
|
67055
|
+
* - `"time"` → time-of-day as duration by default (e.g. "14:30" → "14 heures 30");
|
|
67056
|
+
* `format="timestring"` → clock "14 h 30". Midnight (00:xx) is
|
|
67057
|
+
* special-cased regardless of `format` — "0 heures 5 minutes"
|
|
67058
|
+
* would otherwise collapse to "5 minutes", indistinguishable
|
|
67059
|
+
* from an actual 5-minute duration: `format="long"` (default)
|
|
67060
|
+
* says "minuit et 5 minutes" (`format="short"/"narrow"/"compact"`
|
|
67061
|
+
* keep the zero hour instead — "0 h et 5 min"/"0h 5min"/"00h05").
|
|
67062
|
+
* `format="compact"` also zero-pads any single-digit hour, not
|
|
67063
|
+
* just midnight (e.g. "5h30" → "05h30"), closer to "05:30";
|
|
67064
|
+
* `pad={false}` writes it the way one says it instead ("8h30",
|
|
67065
|
+
* "8h"). See `<TimeRange>` for a span between two of them.
|
|
67066
|
+
* - `"hour"` → hours as duration (e.g. 1.5 → "1 heure 30 minutes")
|
|
67067
|
+
* - `"minute"` → minutes as duration (e.g. 90 → "1 heure 30 minutes")
|
|
67068
|
+
* - `"second"` → seconds as duration (e.g. 90 → "1 minute 30 secondes")
|
|
67069
|
+
* These three promote to the largest fitting unit, days included (36 hours
|
|
67070
|
+
* → "1 jour et 12 heures"); see `forceUnit` to stay in the given unit.
|
|
67071
|
+
* - `"duration"` → duration string/object/number (seconds); `format="iso"` → ISO 8601
|
|
67072
|
+
* - `"relative"` → "dans 1 heure 30" / "En cours" / "il y a 2 heures"
|
|
67073
|
+
* Handles past, present, and future.
|
|
67074
|
+
* `format="short"` → "dans 1 h et 30 min"; `format="narrow"`
|
|
67075
|
+
* → "+3 h"; `format="compact"` → "dans 1h30".
|
|
67076
|
+
* `eventDuration` defaults to 0 (instantaneous: no "En cours" window).
|
|
67077
|
+
*
|
|
67078
|
+
* @param {number|string} [eventDuration=0]
|
|
67079
|
+
* Duration of the event in milliseconds, or a duration string (`"PT1H30M"`,
|
|
67080
|
+
* `"1hour30minute"`). Only used with `type="relative"`.
|
|
67081
|
+
* When omitted, the event is instantaneous (point in time, no "En cours" window).
|
|
67082
|
+
* @param {boolean} [bare]
|
|
67083
|
+
* When true, strips the past-tense literal ("il y a", "ago") and returns only integer + unit.
|
|
67084
|
+
* Only applies to the past state of `type="relative"`.
|
|
67085
|
+
* @param {"long"|"short"|"narrow"|"compact"|"numeric"|"timestring"|"iso"|{ weekday?: "long"|"short"|"narrow"|false, day?: boolean, month?: "long"|"short"|"narrow"|"numeric"|false }} [format="long"]
|
|
67086
|
+
* Controls the verbosity of the output. Defaults to `"long"` for all types.
|
|
67087
|
+
* `"long"`, `"short"`, `"narrow"` and `"compact"` are verbosities every `type`
|
|
67088
|
+
* writes. The three under them name a shape only some types have; a `type`
|
|
67089
|
+
* without that shape writes its `"long"` form and names the dropped word in
|
|
67090
|
+
* the console.
|
|
67091
|
+
* - `"short"` → Intl short (e.g. "2 h et 15 min", short month for dates/datetimes, no weekday for datetime)
|
|
67092
|
+
* - `"narrow"` → Intl narrow (e.g. "2h 15min", numeric month for datetime)
|
|
67093
|
+
* - `"compact"` → custom compact notation (e.g. "2h15", no minute symbol
|
|
67094
|
+
* when hours present). A date has nothing tighter than its
|
|
67095
|
+
* digits, so `type="date"`/`"month"`/`"datetime"` write the
|
|
67096
|
+
* all-digit spelling ("17/09/2026", "17/09 14:30")
|
|
67097
|
+
* - `"numeric"` → numeric date, for `type="date"` and `type="month"` (e.g. "11/09/2026", "09/2026")
|
|
67098
|
+
* - `"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)
|
|
67099
|
+
* - `"iso"` → ISO 8601 string, only for `type="duration"` (e.g. "PT2H15M")
|
|
67100
|
+
* - an object → `type="date"` only, one verbosity per part
|
|
67101
|
+
* (`{ weekday: "long", month: "short" }`): a narrow card
|
|
67102
|
+
* keeps the weekday whole, since that is what the eye
|
|
67103
|
+
* lands on, and abbreviates the month, since that is
|
|
67104
|
+
* where the characters are. `false` drops a part:
|
|
67105
|
+
* `{ day: false, month: false }` is the weekday alone
|
|
67106
|
+
* ("mardi"), `{ month: false }` weekday + day-of-month
|
|
67107
|
+
* ("mardi 18").
|
|
67108
|
+
* @param {boolean} [forceUnit=false]
|
|
67109
|
+
* Keeps the value in the unit named by `type` however big it gets, for
|
|
67110
|
+
* `type="hour"`, `"minute"` and `"second"`: 36 hours reads "36 heures"
|
|
67111
|
+
* instead of the default "1 jour et 12 heures". The default promotes to the
|
|
67112
|
+
* largest fitting unit because it reads better; force the unit when the unit
|
|
67113
|
+
* itself is the information (a quota, a counter).
|
|
67114
|
+
* @param {number} [decimals]
|
|
67115
|
+
* `type="duration"` and `type="second"` only — how many decimals a fraction
|
|
67116
|
+
* of a second keeps, rounded: with `decimals={1}`, 1.234 reads "1,2 seconde"
|
|
67117
|
+
* and 1.96 "2 secondes"; a whole value is never padded ("12 secondes").
|
|
67118
|
+
* Without it the fraction is written to the millisecond ("1,234 seconde"). A
|
|
67119
|
+
* duration measured in milliseconds rarely comes out whole, and its last
|
|
67120
|
+
* digits are usually noise.
|
|
67121
|
+
* @param {boolean} [pad=true]
|
|
67122
|
+
* `type="time"` + `format="compact"` only — whether the clock is written at a
|
|
67123
|
+
* fixed width. `true` (default) zero-pads the hour and always writes the
|
|
67124
|
+
* minutes ("08h00", "08h30"), so a column of times lines up. `false` writes
|
|
67125
|
+
* the shape one says out loud instead: bare hour, minutes only when there are
|
|
67126
|
+
* any ("8h", "8h30"). The spelled-out formats put their units in words and
|
|
67127
|
+
* need neither, so they ignore it.
|
|
67128
|
+
* @param {"hour"|"minute"} [precision]
|
|
67129
|
+
* `type="time"` + `format="compact"` only, and rarely set by hand — whether a
|
|
67130
|
+
* zero minute is written ("8h00") or dropped ("8h"). Defaults to whatever
|
|
67131
|
+
* `pad` implies; the one reason to force `"minute"` on an unpadded clock is
|
|
67132
|
+
* to agree with a partner that has minutes of its own, which is what
|
|
67133
|
+
* `<TimeRange>` does for you.
|
|
67134
|
+
* @param {boolean|"auto"} [year]
|
|
67135
|
+
* `type="date"` only — whether the year is written. `"auto"` writes it only
|
|
67136
|
+
* when the date is not in the current year, the way a calendar does
|
|
67137
|
+
* ("lundi 11 mai", but "samedi 1 janvier 2050"); `false` drops it ("30/07",
|
|
67138
|
+
* the day/month order still following the locale). Defaults to `"auto"`
|
|
67139
|
+
* for the spelled formats and to `true` for `format="numeric"`. A format
|
|
67140
|
+
* without the month (`{ month: false }`) never writes it.
|
|
67141
|
+
* @param {boolean} [dayLabel]
|
|
67142
|
+
* When true and `type="date"`, appends the locale-aware relative label
|
|
67143
|
+
* ("hier", "aujourd'hui", "demain") when the date is yesterday, today, or tomorrow.
|
|
67144
|
+
* @param {string} [lang]
|
|
67145
|
+
* BCP 47 locale tag (e.g. `"fr"`, `"en-US"`).
|
|
67146
|
+
* Defaults to `languagesSignal.value`: the browser's languages, as
|
|
67147
|
+
* `setPreferredLanguage`/`setSupportedLanguages` narrow them.
|
|
67148
|
+
*/
|
|
66345
67149
|
const Time = props => {
|
|
66346
67150
|
const {
|
|
66347
67151
|
type
|
|
@@ -66606,6 +67410,20 @@ const TimeMinute = ({
|
|
|
66606
67410
|
children: String(children)
|
|
66607
67411
|
});
|
|
66608
67412
|
}
|
|
67413
|
+
if (!Number.isInteger(minutes)) {
|
|
67414
|
+
// a fraction of a minute is read in seconds, the way formatMinuteDuration
|
|
67415
|
+
// writes it ("1 minute et 30 secondes")
|
|
67416
|
+
const clock = formatClock(Math.round(minutes * 60));
|
|
67417
|
+
return jsx(TimeText, {
|
|
67418
|
+
dateTime: clock,
|
|
67419
|
+
...props,
|
|
67420
|
+
children: format === "timestring" ? clock : formatMinuteDuration(minutes, {
|
|
67421
|
+
lang,
|
|
67422
|
+
format,
|
|
67423
|
+
forceUnit
|
|
67424
|
+
})
|
|
67425
|
+
});
|
|
67426
|
+
}
|
|
66609
67427
|
const totalHours = Math.floor(minutes / 60);
|
|
66610
67428
|
const remainingMinutes = minutes % 60;
|
|
66611
67429
|
const hh = String(totalHours).padStart(2, "0");
|
|
@@ -66635,6 +67453,7 @@ const TimeSecond = ({
|
|
|
66635
67453
|
lang = languagesSignal.value,
|
|
66636
67454
|
format = "long",
|
|
66637
67455
|
forceUnit = false,
|
|
67456
|
+
decimals,
|
|
66638
67457
|
...props
|
|
66639
67458
|
}) => {
|
|
66640
67459
|
if (children === undefined || children === null) {
|
|
@@ -66650,19 +67469,17 @@ const TimeSecond = ({
|
|
|
66650
67469
|
children: String(children)
|
|
66651
67470
|
});
|
|
66652
67471
|
}
|
|
66653
|
-
const
|
|
66654
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
66655
|
-
const s = seconds % 60;
|
|
66656
|
-
const dateTime = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
67472
|
+
const dateTime = formatClock(seconds);
|
|
66657
67473
|
let text;
|
|
66658
67474
|
if (format === "timestring") {
|
|
66659
67475
|
// Always HH:MM:SS to avoid ambiguity with HH:MM time-of-day format
|
|
66660
|
-
text =
|
|
67476
|
+
text = formatClock(seconds, decimals);
|
|
66661
67477
|
} else {
|
|
66662
67478
|
text = formatSecondDuration(seconds, {
|
|
66663
67479
|
lang,
|
|
66664
67480
|
format,
|
|
66665
|
-
forceUnit
|
|
67481
|
+
forceUnit,
|
|
67482
|
+
decimals
|
|
66666
67483
|
});
|
|
66667
67484
|
}
|
|
66668
67485
|
return jsx(TimeText, {
|
|
@@ -66715,6 +67532,7 @@ const TimeDuration = ({
|
|
|
66715
67532
|
children,
|
|
66716
67533
|
lang = languagesSignal.value,
|
|
66717
67534
|
format = "long",
|
|
67535
|
+
decimals,
|
|
66718
67536
|
...props
|
|
66719
67537
|
}) => {
|
|
66720
67538
|
if (children === undefined || children === null) {
|
|
@@ -66762,7 +67580,8 @@ const TimeDuration = ({
|
|
|
66762
67580
|
...props,
|
|
66763
67581
|
children: formatDuration(duration, {
|
|
66764
67582
|
lang,
|
|
66765
|
-
format
|
|
67583
|
+
format,
|
|
67584
|
+
decimals
|
|
66766
67585
|
})
|
|
66767
67586
|
});
|
|
66768
67587
|
}
|
|
@@ -66774,7 +67593,8 @@ const TimeDuration = ({
|
|
|
66774
67593
|
}
|
|
66775
67594
|
const text = formatDuration(duration, {
|
|
66776
67595
|
lang,
|
|
66777
|
-
format
|
|
67596
|
+
format,
|
|
67597
|
+
decimals
|
|
66778
67598
|
});
|
|
66779
67599
|
return jsx(TimeText, {
|
|
66780
67600
|
dateTime: isoString,
|
|
@@ -66914,6 +67734,24 @@ const TimeRange = ({
|
|
|
66914
67734
|
});
|
|
66915
67735
|
};
|
|
66916
67736
|
|
|
67737
|
+
// "HH:MM:SS", the fraction of a second after a period ("00:00:01.234"): the
|
|
67738
|
+
// shape a `datetime` attribute reads, which takes up to 3 decimals.
|
|
67739
|
+
const formatClock = (seconds, decimals = 3) => {
|
|
67740
|
+
const factor = 10 ** decimals;
|
|
67741
|
+
const total = Math.round(seconds * factor);
|
|
67742
|
+
const fraction = total % factor;
|
|
67743
|
+
const totalSeconds = (total - fraction) / factor;
|
|
67744
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
67745
|
+
const m = Math.floor(totalSeconds % 3600 / 60);
|
|
67746
|
+
const s = totalSeconds % 60;
|
|
67747
|
+
const clock = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
67748
|
+
if (fraction === 0) {
|
|
67749
|
+
return clock;
|
|
67750
|
+
}
|
|
67751
|
+
const digits = String(fraction).padStart(decimals, "0").replace(/0+$/, "");
|
|
67752
|
+
return `${clock}.${digits}`;
|
|
67753
|
+
};
|
|
67754
|
+
|
|
66917
67755
|
/**
|
|
66918
67756
|
* Wraps multiple inputs together and handles keyboard navigation and paste
|
|
66919
67757
|
* distribution between them.
|
|
@@ -67167,6 +68005,11 @@ const renderSafe = (value) => {
|
|
|
67167
68005
|
};
|
|
67168
68006
|
|
|
67169
68007
|
installImportMetaCssBuild(import.meta);
|
|
68008
|
+
/**
|
|
68009
|
+
* The group a popup opens in, or null: what a Popover or a Dialog reads to know
|
|
68010
|
+
* that its wall is the group's. Every popup provides null again to what it
|
|
68011
|
+
* holds, so a popup opened from inside a popup is in front of it, not a peer.
|
|
68012
|
+
*/
|
|
67170
68013
|
const PopupGroupContext = createContext(null);
|
|
67171
68014
|
|
|
67172
68015
|
/**
|
|
@@ -67333,13 +68176,13 @@ const css$F = /* css */`@property --x-popup-group-top {
|
|
|
67333
68176
|
* `navi-out-of-flow` like everything navi adds to a tree. See
|
|
67334
68177
|
* docs/popup_backdrop.md.
|
|
67335
68178
|
*
|
|
67336
|
-
* @type {import("
|
|
68179
|
+
* @type {import("preact").FunctionComponent<{
|
|
67337
68180
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
67338
68181
|
* backdropColor?: string,
|
|
67339
68182
|
* backdropFilter?: string,
|
|
67340
68183
|
* themeColor?: string | false,
|
|
67341
68184
|
* role?: string,
|
|
67342
|
-
* children?: import("
|
|
68185
|
+
* children?: import("preact").ComponentChildren,
|
|
67343
68186
|
* [key: string]: any,
|
|
67344
68187
|
* }>}
|
|
67345
68188
|
* @param {"auto"|"lift"|"discrete"|"invisible"} [props.backdropVariant="auto"] -
|
|
@@ -69386,6 +70229,35 @@ const clipOf = (side, distance) => {
|
|
|
69386
70229
|
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
69387
70230
|
};
|
|
69388
70231
|
|
|
70232
|
+
/**
|
|
70233
|
+
* The cross that closes the popup it sits in — reached as `<Popup.Close />`,
|
|
70234
|
+
* `<Dialog.Close />` or `<Popover.Close />`. A discrete icon button sending
|
|
70235
|
+
* `--navi-close`, which the nearest popup above it answers; the caller only
|
|
70236
|
+
* decides where it goes (the right end of a header, most of the time).
|
|
70237
|
+
*
|
|
70238
|
+
* It is the way out of a popup, so it is the one affordance in there that the
|
|
70239
|
+
* surrounding control's read-only and disabled do not reach: a read-only picker
|
|
70240
|
+
* still opens (reading changes nothing) and hands its popup that read-only, and
|
|
70241
|
+
* a cross wearing it would trap the user in a popup they were invited to read.
|
|
70242
|
+
* A close button written by hand inherits it and refuses the press — which is
|
|
70243
|
+
* the reason to use this one rather than a `<Button command="--navi-close" />`.
|
|
70244
|
+
*
|
|
70245
|
+
* Exempt from the state around it, never from the popup's own answer: the cross
|
|
70246
|
+
* ASKS, and the popup decides — a dialog holding a form mid-send keeps the
|
|
70247
|
+
* close, a Picker holding a value that does not validate keeps it too.
|
|
70248
|
+
*
|
|
70249
|
+
* @type {import("preact").FunctionComponent<{
|
|
70250
|
+
* label?: string,
|
|
70251
|
+
* iconSize?: number | string,
|
|
70252
|
+
* } & Record<string, any>>}
|
|
70253
|
+
* @param {string} [props.label] - What assistive tech reads for the cross
|
|
70254
|
+
* (navi's own "Close" text by default, in the active language).
|
|
70255
|
+
* @param {number|string} [props.iconSize] - How big the cross is drawn, apart
|
|
70256
|
+
* from the box around it. Unset, the glyph follows the button's font size
|
|
70257
|
+
* like a character, and `size` moves glyph and padding together. Set it when
|
|
70258
|
+
* the cross stands in for an affordance the app draws elsewhere and has to
|
|
70259
|
+
* land on the same pixels: `padding="s" iconSize="m"`.
|
|
70260
|
+
*/
|
|
69389
70261
|
const PopupClose = ({
|
|
69390
70262
|
label,
|
|
69391
70263
|
iconSize,
|
|
@@ -69634,6 +70506,9 @@ const TitlePseudoClasses = [":hover"];
|
|
|
69634
70506
|
* step of a form — so only the title knows when it is there.
|
|
69635
70507
|
*/
|
|
69636
70508
|
|
|
70509
|
+
// The popup a title names: the nearest one around it. A surface that is no
|
|
70510
|
+
// popup of this kind but holds content of its own (a picker's callout) sets it
|
|
70511
|
+
// back to null, so a title in there names nothing rather than the popup around.
|
|
69637
70512
|
const PopupTitleContext = createContext(null);
|
|
69638
70513
|
|
|
69639
70514
|
/**
|
|
@@ -69643,10 +70518,10 @@ const PopupTitleContext = createContext(null);
|
|
|
69643
70518
|
* announces the popup by it, and `getByRole("dialog", { name })` finds it.
|
|
69644
70519
|
* An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
|
|
69645
70520
|
*
|
|
69646
|
-
* @type {import("
|
|
70521
|
+
* @type {import("preact").FunctionComponent<{
|
|
69647
70522
|
* as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
69648
70523
|
* id?: string,
|
|
69649
|
-
* children?: import("
|
|
70524
|
+
* children?: import("preact").ComponentChildren,
|
|
69650
70525
|
* } & Record<string, any>>}
|
|
69651
70526
|
*/
|
|
69652
70527
|
const PopupTitle = props => {
|
|
@@ -69707,6 +70582,12 @@ const usePopupName = ({
|
|
|
69707
70582
|
const NO_TITLE = [];
|
|
69708
70583
|
|
|
69709
70584
|
installImportMetaCssBuild(import.meta);
|
|
70585
|
+
// Same need popover.jsx has for its own local renderer (see its
|
|
70586
|
+
// openLocalPopoverCount): a layer="local" dialog is a plain positioned element
|
|
70587
|
+
// in normal flow, so it gets none of the top layer's free "last shown wins" and
|
|
70588
|
+
// would paint under anything declared after it. A live count, not an
|
|
70589
|
+
// ever-growing counter, so the order stays small and resets once nothing is
|
|
70590
|
+
// open.
|
|
69710
70591
|
let openLocalDialogCount = 0;
|
|
69711
70592
|
const css$C = /* css */`
|
|
69712
70593
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -70781,7 +71662,7 @@ const css$C = /* css */`
|
|
|
70781
71662
|
* the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
|
|
70782
71663
|
* nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
|
|
70783
71664
|
* @param {string} [props.openerAriaLabel] - The same, as a string.
|
|
70784
|
-
* @param {import("
|
|
71665
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
70785
71666
|
*/
|
|
70786
71667
|
const Dialog = props => {
|
|
70787
71668
|
import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
@@ -71644,8 +72525,10 @@ const useDialogProps = props => {
|
|
|
71644
72525
|
// gap — the same thing its CSS width/height say (see [data-expand-x],
|
|
71645
72526
|
// [data-expand-y]) — so the placement derives it instead of measuring
|
|
71646
72527
|
// it: a measurement here lays out the dialog's whole content, rows and
|
|
71647
|
-
// all, for a number already known.
|
|
71648
|
-
|
|
72528
|
+
// all, for a number already known. A cap (maxWidth/maxHeight, a Picker's
|
|
72529
|
+
// anchor ceiling) stretches it to less than that, and only a measured
|
|
72530
|
+
// box can be centred.
|
|
72531
|
+
const fill = expandX && expandY && !isSizeCapped(dialogEl);
|
|
71649
72532
|
const pickOptions = {
|
|
71650
72533
|
positionArea,
|
|
71651
72534
|
container: positionedAncestor,
|
|
@@ -72221,6 +73104,14 @@ const switchTransitionsOn = dialogEl => {
|
|
|
72221
73104
|
dialogEl.style.transitionProperty = "";
|
|
72222
73105
|
dialogEl.removeAttribute("navi-transitions-off");
|
|
72223
73106
|
};
|
|
73107
|
+
|
|
73108
|
+
// Reads style, not layout: an unset cap is the guaranteed-invalid value its
|
|
73109
|
+
// @property gives it, and reads back as "". Catches a cap written inline by
|
|
73110
|
+
// the prop as well as one set from a stylesheet.
|
|
73111
|
+
const isSizeCapped = dialogEl => {
|
|
73112
|
+
const computedStyle = getComputedStyle(dialogEl);
|
|
73113
|
+
return computedStyle.getPropertyValue("--dialog-max-width") !== "" || computedStyle.getPropertyValue("--dialog-max-height") !== "";
|
|
73114
|
+
};
|
|
72224
73115
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
72225
73116
|
|
|
72226
73117
|
// Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
|
|
@@ -72246,6 +73137,11 @@ Dialog.Close = PopupClose;
|
|
|
72246
73137
|
Dialog.Title = PopupTitle;
|
|
72247
73138
|
|
|
72248
73139
|
installImportMetaCssBuild(import.meta);
|
|
73140
|
+
// Custom renderer only (layer="local") — count of currently-open instances,
|
|
73141
|
+
// see --popover-stack-order's own CSS comment for why a plain "last shown"
|
|
73142
|
+
// default doesn't happen for free the way it does for the top-layer
|
|
73143
|
+
// renderer. A live count (not an ever-growing counter) so the assigned
|
|
73144
|
+
// order stays small and resets to 0 once nothing is open.
|
|
72249
73145
|
let openLocalPopoverCount = 0;
|
|
72250
73146
|
const css$B = /* css */`
|
|
72251
73147
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -72853,7 +73749,7 @@ const css$B = /* css */`
|
|
|
72853
73749
|
* something names — these, `aria-label`/`aria-labelledby`, a
|
|
72854
73750
|
* `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
|
|
72855
73751
|
* an element with no role is read by nobody.
|
|
72856
|
-
* @param {import("
|
|
73752
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
72857
73753
|
*/
|
|
72858
73754
|
const Popover = props => {
|
|
72859
73755
|
import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
|
|
@@ -74223,7 +75119,7 @@ const css$A = /* css */`@layer navi {
|
|
|
74223
75119
|
* content whose fresh state is its initial state: an edit left unsent at
|
|
74224
75120
|
* the close does not come back at the next opening (an untouched field
|
|
74225
75121
|
* needs none of this to show a new `defaultValue` — it follows one).
|
|
74226
|
-
* @param {import("
|
|
75122
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
74227
75123
|
*/
|
|
74228
75124
|
const Popup = props => {
|
|
74229
75125
|
import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
|
|
@@ -75914,6 +76810,18 @@ const PickerConfirmBody = ({
|
|
|
75914
76810
|
});
|
|
75915
76811
|
};
|
|
75916
76812
|
|
|
76813
|
+
/**
|
|
76814
|
+
* PickerNaviMinute — a custom minute picker that opens a number input.
|
|
76815
|
+
*
|
|
76816
|
+
* Renders a popover containing an `<input type="number">` pre-filled with the
|
|
76817
|
+
* current value, plus Confirm / Clear / Cancel actions. The picker holds a
|
|
76818
|
+
* number of minutes: its own field is a text one told
|
|
76819
|
+
* `navi-input-type="minute"`, so the value is read back as a number and a
|
|
76820
|
+
* min/max/step message speaks of minutes.
|
|
76821
|
+
*
|
|
76822
|
+
* Usage:
|
|
76823
|
+
* <Picker type="navi_minute" min={15} max={300} step={15} value={90} />
|
|
76824
|
+
*/
|
|
75917
76825
|
const PickerNaviMinute = props => {
|
|
75918
76826
|
const Next = useNextResolver();
|
|
75919
76827
|
const {
|
|
@@ -76297,6 +77205,16 @@ const LoadingDotsSvg = () => {
|
|
|
76297
77205
|
});
|
|
76298
77206
|
};
|
|
76299
77207
|
|
|
77208
|
+
/**
|
|
77209
|
+
* A loading indicator sized to match the surrounding text (1em × 1em).
|
|
77210
|
+
* Inherits color from the current text color by default.
|
|
77211
|
+
*
|
|
77212
|
+
* @param {"circle"|"dots"} [props.variant="circle"] - Visual style of the indicator
|
|
77213
|
+
* @param {string} [props.color="currentColor"] - Color of the indicator; inherits from CSS `color` by default
|
|
77214
|
+
*
|
|
77215
|
+
* Every other prop goes to the `Icon` it is drawn in: `size` is a font size
|
|
77216
|
+
* (the indicator being 1em), `width`/`height` size it as a block.
|
|
77217
|
+
*/
|
|
76300
77218
|
const LoadingIndicator = ({
|
|
76301
77219
|
variant = "circle",
|
|
76302
77220
|
...props
|
|
@@ -76359,7 +77277,7 @@ const css$x = /* css */`@layer navi {
|
|
|
76359
77277
|
* Everything about how it looks goes through a CSS var, so it can be set per
|
|
76360
77278
|
* separator as a prop or once for a whole surface in CSS.
|
|
76361
77279
|
*
|
|
76362
|
-
* @type {import("
|
|
77280
|
+
* @type {import("preact").FunctionComponent<{
|
|
76363
77281
|
* vertical?: boolean,
|
|
76364
77282
|
* size?: string|number,
|
|
76365
77283
|
* color?: string,
|
|
@@ -77962,6 +78880,9 @@ const applySearchHighlight = (el, highlight) => {
|
|
|
77962
78880
|
};
|
|
77963
78881
|
|
|
77964
78882
|
installImportMetaCssBuild(import.meta);
|
|
78883
|
+
// Everything the list knows about its items — see list_items.js. Filled in by
|
|
78884
|
+
// the children as they render, read by everything that must reserve room for
|
|
78885
|
+
// what is not rendered, count what is, or know what stands above it.
|
|
77965
78886
|
const ListItemsContext = createContext(null);
|
|
77966
78887
|
// The group an item is declared in, by id (see ListItemGroup): what its count
|
|
77967
78888
|
// and its separator are scoped to.
|
|
@@ -82716,7 +83637,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82716
83637
|
* of it. Props not listed here are forwarded to the rendered <li> (Box layout
|
|
82717
83638
|
* props included).
|
|
82718
83639
|
*
|
|
82719
|
-
* @type {import("
|
|
83640
|
+
* @type {import("preact").FunctionComponent<{
|
|
82720
83641
|
* id?: string,
|
|
82721
83642
|
* selectable?: boolean,
|
|
82722
83643
|
* value?: any,
|
|
@@ -82725,14 +83646,14 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82725
83646
|
* pointed?: boolean,
|
|
82726
83647
|
* selectableArea?: "all" | "manual",
|
|
82727
83648
|
* skeleton?: boolean,
|
|
82728
|
-
* error?: boolean | import("
|
|
83649
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
82729
83650
|
* onErrorDismiss?: (event: Event) => void,
|
|
82730
83651
|
* loading?: boolean | "adding" | "removing" | "updating",
|
|
82731
83652
|
* readOnly?: boolean,
|
|
82732
83653
|
* filtered?: boolean,
|
|
82733
83654
|
* hidden?: boolean,
|
|
82734
83655
|
* matchInfo?: {match: boolean, matchScore?: number, matchRanges?: Array<[number, number]>},
|
|
82735
|
-
* children?: import("
|
|
83656
|
+
* children?: import("preact").ComponentChildren,
|
|
82736
83657
|
* [key: string]: any,
|
|
82737
83658
|
* }>}
|
|
82738
83659
|
* @param {string} [props.id]
|
|
@@ -82767,7 +83688,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
82767
83688
|
* real item. This is what a `renderSkeleton` returns for an item on its way;
|
|
82768
83689
|
* Box layout props (padding…) pass through so the placeholder can match the
|
|
82769
83690
|
* real items' metrics.
|
|
82770
|
-
* @param {boolean|import("
|
|
83691
|
+
* @param {boolean|import("preact").ComponentChildren} [props.error]
|
|
82771
83692
|
* What this item stood for failed: the message replaces its content, styled
|
|
82772
83693
|
* like the list's own error. `true` shows a generic sentence. When
|
|
82773
83694
|
* `onErrorDismiss` is given, a dismiss button is drawn next to the message
|
|
@@ -82907,21 +83828,21 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
82907
83828
|
* Several runs can live in one list, next to plain `<List.Item>` children and
|
|
82908
83829
|
* inside `<List.Group>`s; each takes its place in declaration order.
|
|
82909
83830
|
*
|
|
82910
|
-
* @type {import("
|
|
82911
|
-
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("
|
|
83831
|
+
* @type {import("preact").FunctionComponent<{
|
|
83832
|
+
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("preact").ComponentChildren,
|
|
82912
83833
|
* findText?: (item: any, index: number) => string,
|
|
82913
83834
|
* items?: any[],
|
|
82914
83835
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
82915
83836
|
* debounce?: number,
|
|
82916
83837
|
* count?: number,
|
|
82917
83838
|
* groupBy?: (item: any, index: number) => any,
|
|
82918
|
-
* renderGroupLabel?: (item: any, index: number) => import("
|
|
83839
|
+
* renderGroupLabel?: (item: any, index: number) => import("preact").ComponentChildren,
|
|
82919
83840
|
* groupLabelProps?: (item: any, index: number) => object,
|
|
82920
83841
|
* pageSize?: number,
|
|
82921
83842
|
* memoryBudget?: number,
|
|
82922
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
83843
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
82923
83844
|
* itemSize?: (index: number) => number,
|
|
82924
|
-
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("
|
|
83845
|
+
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("preact").ComponentChildren,
|
|
82925
83846
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
82926
83847
|
* onCountChange?: (count: number|undefined) => void,
|
|
82927
83848
|
* }>}
|
|
@@ -84525,7 +85446,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84525
85446
|
* List — generic virtualized scroll container.
|
|
84526
85447
|
* Items must use <List.Item> to participate in tracking.
|
|
84527
85448
|
*
|
|
84528
|
-
* @type {import("
|
|
85449
|
+
* @type {import("preact").FunctionComponent<{
|
|
84529
85450
|
* selectable?: boolean,
|
|
84530
85451
|
* multiple?: boolean,
|
|
84531
85452
|
* deselectable?: boolean,
|
|
@@ -84546,16 +85467,16 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84546
85467
|
* scroller?: "self" | "parent" | "document" | Element | {current: Element},
|
|
84547
85468
|
* hoverWhileScrolling?: boolean,
|
|
84548
85469
|
* scrollResetOnNavigation?: boolean,
|
|
84549
|
-
* fallback?: import("
|
|
84550
|
-
* searchFallback?: import("
|
|
85470
|
+
* fallback?: import("preact").ComponentChildren,
|
|
85471
|
+
* searchFallback?: import("preact").ComponentChildren,
|
|
84551
85472
|
* searchText?: string,
|
|
84552
85473
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
|
|
84553
85474
|
* loading?: boolean,
|
|
84554
|
-
* loadingFallback?: "skeleton" | "loader" | import("
|
|
85475
|
+
* loadingFallback?: "skeleton" | "loader" | import("preact").ComponentChildren,
|
|
84555
85476
|
* loadingSkeletonCount?: number,
|
|
84556
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
84557
|
-
* error?: boolean | import("
|
|
84558
|
-
* separator?: boolean | import("
|
|
85477
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
85478
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
85479
|
+
* separator?: boolean | import("preact").ComponentChildren,
|
|
84559
85480
|
* itemTransition?: boolean,
|
|
84560
85481
|
* lockSize?: boolean,
|
|
84561
85482
|
* horizontal?: boolean,
|
|
@@ -84569,7 +85490,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84569
85490
|
* expandX?: boolean,
|
|
84570
85491
|
* expandY?: boolean,
|
|
84571
85492
|
* expand?: boolean,
|
|
84572
|
-
* children?: import("
|
|
85493
|
+
* children?: import("preact").ComponentChildren,
|
|
84573
85494
|
* [key: string]: any,
|
|
84574
85495
|
* }>}
|
|
84575
85496
|
* @param {string} [props.columns]
|
|
@@ -84629,7 +85550,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
84629
85550
|
* have, so nothing moves when they arrive. Used for the items a `<List.Items>`
|
|
84630
85551
|
* stands for and does not hold yet, and for the placeholder items drawn while
|
|
84631
85552
|
* the whole list is `loading`. Defaults to a bare `<List.Item skeleton>`.
|
|
84632
|
-
* @param {"skeleton"|"loader"|import("
|
|
85553
|
+
* @param {"skeleton"|"loader"|import("preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
84633
85554
|
* What to display in place of the items while `loading` — that is, while
|
|
84634
85555
|
* there is nothing to show at all: `"skeleton"` renders
|
|
84635
85556
|
* `loadingSkeletonCount` placeholder items (look:
|
|
@@ -84813,6 +85734,17 @@ const List = /*#__PURE__*/Object.assign(ListResolved, {
|
|
|
84813
85734
|
Group: ListItemGroup
|
|
84814
85735
|
});
|
|
84815
85736
|
|
|
85737
|
+
/**
|
|
85738
|
+
* PickerNaviTime — a fully custom time picker driven by min/max/step.
|
|
85739
|
+
*
|
|
85740
|
+
* Unlike the native `<input type="time">`, this picker only exposes the exact
|
|
85741
|
+
* slots generated from min/max/step, so the user can never pick an out-of-range
|
|
85742
|
+
* or out-of-step value. The popover content is a selectable `List` of the
|
|
85743
|
+
* available slots; picking one closes the popover and submits the value.
|
|
85744
|
+
*
|
|
85745
|
+
* Usage:
|
|
85746
|
+
* <Picker type="navi_time" min="08:00" max="18:00" step="00:30" />
|
|
85747
|
+
*/
|
|
84816
85748
|
const PickerNaviTime = props => {
|
|
84817
85749
|
const Next = useNextResolver();
|
|
84818
85750
|
const {
|
|
@@ -84920,18 +85852,20 @@ const createBadgeRegistry = () => {
|
|
|
84920
85852
|
};
|
|
84921
85853
|
|
|
84922
85854
|
installImportMetaCssBuild(import.meta);
|
|
84923
|
-
const css$u = /* css */`@layer navi
|
|
85855
|
+
const css$u = /* css */`@layer navi {
|
|
85856
|
+
.navi_badge {
|
|
85857
|
+
--font-size: .7em;
|
|
85858
|
+
--badge-padding-x-default: .8em;
|
|
85859
|
+
--badge-padding-y-default: .4em;
|
|
85860
|
+
}
|
|
85861
|
+
}
|
|
84924
85862
|
|
|
84925
85863
|
.navi_badge {
|
|
84926
|
-
--font-size: .7em;
|
|
84927
|
-
--badge-padding-x-default: .8em;
|
|
84928
|
-
--badge-padding-y-default: .4em;
|
|
84929
85864
|
--x-badge-padding-top: var(--badge-padding-top, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
84930
85865
|
--x-badge-padding-right: var(--badge-padding-right, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
84931
85866
|
--x-badge-padding-bottom: var(--badge-padding-bottom, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
84932
85867
|
--x-badge-padding-left: var(--badge-padding-left, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
84933
85868
|
--x-background: var(--background, light-dark(#e0e0e0, #3a3a3a));
|
|
84934
|
-
--x-background-color: var(--background-color, var(--x-background));
|
|
84935
85869
|
--x-color: var(--color, white);
|
|
84936
85870
|
max-width: 200px;
|
|
84937
85871
|
padding-top: var(--x-badge-padding-top);
|
|
@@ -84942,7 +85876,6 @@ const css$u = /* css */`@layer navi;
|
|
|
84942
85876
|
font-size: var(--font-size);
|
|
84943
85877
|
text-box: trim-both cap alphabetic;
|
|
84944
85878
|
background: var(--x-background);
|
|
84945
|
-
background-color: var(--x-background-color);
|
|
84946
85879
|
border-radius: 1em;
|
|
84947
85880
|
display: inline;
|
|
84948
85881
|
position: relative;
|
|
@@ -85068,7 +86001,6 @@ const BadgeStyleCSSVars = {
|
|
|
85068
86001
|
paddingRight: "--badge-padding-right",
|
|
85069
86002
|
paddingBottom: "--badge-padding-bottom",
|
|
85070
86003
|
paddingLeft: "--badge-padding-left",
|
|
85071
|
-
backgroundColor: "--background-color",
|
|
85072
86004
|
background: "--background",
|
|
85073
86005
|
borderColor: "--border-color",
|
|
85074
86006
|
color: "--color",
|
|
@@ -85242,7 +86174,7 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
85242
86174
|
* shrinkWrap on its own builds the measurement ghost: under maxLines the list
|
|
85243
86175
|
* is measured in place, once the rows are known.
|
|
85244
86176
|
*
|
|
85245
|
-
* @param {import("
|
|
86177
|
+
* @param {import("preact").ComponentChildren} [fallback]
|
|
85246
86178
|
* Rendered in place of the badges when there is none. Without it an empty
|
|
85247
86179
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
85248
86180
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
@@ -85876,10 +86808,10 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
85876
86808
|
* — a row being read still says what was picked, it just no longer offers to
|
|
85877
86809
|
* unpick it.
|
|
85878
86810
|
*
|
|
85879
|
-
* @type {import("
|
|
86811
|
+
* @type {import("preact").FunctionComponent<{
|
|
85880
86812
|
* value: any,
|
|
85881
86813
|
* commandFor?: string,
|
|
85882
|
-
* children?: import("
|
|
86814
|
+
* children?: import("preact").ComponentChildren,
|
|
85883
86815
|
* [key: string]: any,
|
|
85884
86816
|
* }>}
|
|
85885
86817
|
* @param {any} value The value this chip stands for — one entry of what the
|
|
@@ -86145,6 +87077,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86145
87077
|
.navi_picker {
|
|
86146
87078
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
86147
87079
|
--picker-border-width: var(--navi-control-border-width);
|
|
87080
|
+
--border-width: var(--picker-border-width);
|
|
86148
87081
|
--picker-outline-width: var(--navi-focus-outline-width);
|
|
86149
87082
|
--picker-outline-offset: calc(-.5 * var(--picker-outline-width));
|
|
86150
87083
|
--picker-outline-color: var(--navi-focus-outline-color);
|
|
@@ -86184,6 +87117,111 @@ const css$r = /* css */`@layer navi {
|
|
|
86184
87117
|
--picker-align-x-default: flex-start;
|
|
86185
87118
|
--picker-align-y-default: center;
|
|
86186
87119
|
--picker-text-align-default: initial;
|
|
87120
|
+
font-size: var(--picker-font-size);
|
|
87121
|
+
font-family: var(--picker-font-family);
|
|
87122
|
+
line-height: var(--navi-control-line-height);
|
|
87123
|
+
}
|
|
87124
|
+
|
|
87125
|
+
.navi_picker[data-variant="icon"] {
|
|
87126
|
+
--picker-border-width: 0px;
|
|
87127
|
+
--picker-border-color: transparent;
|
|
87128
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87129
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87130
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87131
|
+
--picker-background-color: transparent;
|
|
87132
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87133
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87134
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87135
|
+
--picker-icon-color: currentColor;
|
|
87136
|
+
}
|
|
87137
|
+
|
|
87138
|
+
.navi_picker[data-variant="discrete"] {
|
|
87139
|
+
--picker-border-width: 0px;
|
|
87140
|
+
--picker-border-color: transparent;
|
|
87141
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87142
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87143
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87144
|
+
--picker-background-color: transparent;
|
|
87145
|
+
--picker-background-color-hover: color-mix(in srgb,
|
|
87146
|
+
currentColor 8%,
|
|
87147
|
+
var(--picker-background-color));
|
|
87148
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87149
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87150
|
+
}
|
|
87151
|
+
|
|
87152
|
+
.navi_picker[data-variant="headless"] {
|
|
87153
|
+
--picker-padding-x-default: 0;
|
|
87154
|
+
--picker-padding-y-default: 0;
|
|
87155
|
+
--picker-border-width: 0px;
|
|
87156
|
+
--picker-border-color: transparent;
|
|
87157
|
+
--picker-outline-width: 0px;
|
|
87158
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87159
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87160
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87161
|
+
--picker-background-color: transparent;
|
|
87162
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87163
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87164
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87165
|
+
--picker-icon-color: currentColor;
|
|
87166
|
+
}
|
|
87167
|
+
|
|
87168
|
+
.navi_picker[data-variant="bare"] {
|
|
87169
|
+
--picker-padding-x-default: 0;
|
|
87170
|
+
--picker-padding-y-default: 0;
|
|
87171
|
+
--picker-align-y-default: stretch;
|
|
87172
|
+
--picker-border-width: 0px;
|
|
87173
|
+
--picker-border-color: transparent;
|
|
87174
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87175
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87176
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87177
|
+
--picker-background-color: transparent;
|
|
87178
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87179
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87180
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87181
|
+
--picker-icon-color: currentColor;
|
|
87182
|
+
font-size: inherit;
|
|
87183
|
+
font-family: inherit;
|
|
87184
|
+
line-height: inherit;
|
|
87185
|
+
}
|
|
87186
|
+
|
|
87187
|
+
.navi_picker[data-variant="button"] {
|
|
87188
|
+
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
87189
|
+
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
87190
|
+
--picker-align-x-default: center;
|
|
87191
|
+
--picker-background-color: var(--navi-button-background-color);
|
|
87192
|
+
--picker-border-color-readonly: color-mix(in srgb,
|
|
87193
|
+
var(--picker-border-color) 30%,
|
|
87194
|
+
var(--navi-surface-color));
|
|
87195
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87196
|
+
--picker-color-readonly: color-mix(in srgb,
|
|
87197
|
+
var(--picker-color) 30%,
|
|
87198
|
+
transparent);
|
|
87199
|
+
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
87200
|
+
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
87201
|
+
--picker-color-disabled: var(--picker-color-readonly);
|
|
87202
|
+
--picker-text-align-default: center;
|
|
87203
|
+
}
|
|
87204
|
+
|
|
87205
|
+
.navi_picker[data-variant="text"] {
|
|
87206
|
+
--picker-padding-x-default: 0;
|
|
87207
|
+
--picker-padding-y-default: 0;
|
|
87208
|
+
--picker-border-width: 0px;
|
|
87209
|
+
--picker-border-color: transparent;
|
|
87210
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
87211
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
87212
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
87213
|
+
--picker-background-color: transparent;
|
|
87214
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
87215
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
87216
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
87217
|
+
font-size: inherit;
|
|
87218
|
+
font-family: inherit;
|
|
87219
|
+
}
|
|
87220
|
+
|
|
87221
|
+
.navi_picker[data-icon] {
|
|
87222
|
+
--picker-padding-x-default: 0;
|
|
87223
|
+
--picker-padding-y-default: 0;
|
|
87224
|
+
--picker-align-x-default: center;
|
|
86187
87225
|
}
|
|
86188
87226
|
}
|
|
86189
87227
|
|
|
@@ -86206,10 +87244,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86206
87244
|
box-sizing: border-box;
|
|
86207
87245
|
max-width: 100%;
|
|
86208
87246
|
color: var(--x-picker-color);
|
|
86209
|
-
font-size: var(--picker-font-size);
|
|
86210
|
-
font-family: var(--picker-font-family);
|
|
86211
87247
|
text-align: inherit;
|
|
86212
|
-
line-height: var(--navi-control-line-height);
|
|
86213
87248
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--picker-border-radius));
|
|
86214
87249
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--picker-border-radius));
|
|
86215
87250
|
border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--picker-border-radius));
|
|
@@ -86451,48 +87486,7 @@ const css$r = /* css */`@layer navi {
|
|
|
86451
87486
|
--x-picker-border-color: var(--callout-color);
|
|
86452
87487
|
}
|
|
86453
87488
|
|
|
86454
|
-
&[data-variant="icon"] {
|
|
86455
|
-
--picker-border-width: 0px;
|
|
86456
|
-
--picker-border-color: transparent;
|
|
86457
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86458
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86459
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86460
|
-
--picker-background-color: transparent;
|
|
86461
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86462
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86463
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86464
|
-
--picker-icon-color: currentColor;
|
|
86465
|
-
}
|
|
86466
|
-
|
|
86467
|
-
&[data-variant="discrete"] {
|
|
86468
|
-
--picker-border-width: 0px;
|
|
86469
|
-
--picker-border-color: transparent;
|
|
86470
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86471
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86472
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86473
|
-
--picker-background-color: transparent;
|
|
86474
|
-
--picker-background-color-hover: color-mix(in srgb,
|
|
86475
|
-
currentColor 8%,
|
|
86476
|
-
var(--picker-background-color));
|
|
86477
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86478
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86479
|
-
}
|
|
86480
|
-
|
|
86481
87489
|
&[data-variant="headless"] {
|
|
86482
|
-
--picker-padding-x-default: 0;
|
|
86483
|
-
--picker-padding-y-default: 0;
|
|
86484
|
-
--picker-border-width: 0px;
|
|
86485
|
-
--picker-border-color: transparent;
|
|
86486
|
-
--picker-outline-width: 0px;
|
|
86487
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86488
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86489
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86490
|
-
--picker-background-color: transparent;
|
|
86491
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86492
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86493
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86494
|
-
--picker-icon-color: currentColor;
|
|
86495
|
-
|
|
86496
87490
|
& > .navi_picker_box {
|
|
86497
87491
|
z-index: -1;
|
|
86498
87492
|
position: absolute;
|
|
@@ -86501,23 +87495,6 @@ const css$r = /* css */`@layer navi {
|
|
|
86501
87495
|
}
|
|
86502
87496
|
|
|
86503
87497
|
&[data-variant="bare"] {
|
|
86504
|
-
--picker-padding-x-default: 0;
|
|
86505
|
-
--picker-padding-y-default: 0;
|
|
86506
|
-
--picker-align-y-default: stretch;
|
|
86507
|
-
--picker-border-width: 0px;
|
|
86508
|
-
--picker-border-color: transparent;
|
|
86509
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86510
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86511
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86512
|
-
--picker-background-color: transparent;
|
|
86513
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86514
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86515
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86516
|
-
--picker-icon-color: currentColor;
|
|
86517
|
-
font-size: inherit;
|
|
86518
|
-
font-family: inherit;
|
|
86519
|
-
line-height: inherit;
|
|
86520
|
-
|
|
86521
87498
|
& > .navi_picker_box {
|
|
86522
87499
|
min-height: 0;
|
|
86523
87500
|
}
|
|
@@ -86552,39 +87529,8 @@ const css$r = /* css */`@layer navi {
|
|
|
86552
87529
|
}
|
|
86553
87530
|
}
|
|
86554
87531
|
|
|
86555
|
-
&[data-variant="button"] {
|
|
86556
|
-
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
86557
|
-
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
86558
|
-
--picker-align-x-default: center;
|
|
86559
|
-
--picker-background-color: var(--navi-button-background-color);
|
|
86560
|
-
--picker-border-color-readonly: color-mix(in srgb,
|
|
86561
|
-
var(--picker-border-color) 30%,
|
|
86562
|
-
var(--navi-surface-color));
|
|
86563
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86564
|
-
--picker-color-readonly: color-mix(in srgb,
|
|
86565
|
-
var(--picker-color) 30%,
|
|
86566
|
-
transparent);
|
|
86567
|
-
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
86568
|
-
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
86569
|
-
--picker-color-disabled: var(--picker-color-readonly);
|
|
86570
|
-
--picker-text-align-default: center;
|
|
86571
|
-
}
|
|
86572
|
-
|
|
86573
87532
|
&[data-variant="text"] {
|
|
86574
|
-
--picker-padding-x-default: 0;
|
|
86575
|
-
--picker-padding-y-default: 0;
|
|
86576
|
-
--picker-border-width: 0px;
|
|
86577
|
-
--picker-border-color: transparent;
|
|
86578
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
86579
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
86580
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
86581
|
-
--picker-background-color: transparent;
|
|
86582
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
86583
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
86584
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
86585
|
-
font-size: inherit;
|
|
86586
87533
|
text-underline-offset: .2em;
|
|
86587
|
-
font-family: inherit;
|
|
86588
87534
|
text-decoration: underline dotted;
|
|
86589
87535
|
|
|
86590
87536
|
&[data-hover] {
|
|
@@ -86593,10 +87539,6 @@ const css$r = /* css */`@layer navi {
|
|
|
86593
87539
|
}
|
|
86594
87540
|
|
|
86595
87541
|
&[data-icon] {
|
|
86596
|
-
--picker-padding-x-default: 0;
|
|
86597
|
-
--picker-padding-y-default: 0;
|
|
86598
|
-
--picker-align-x-default: center;
|
|
86599
|
-
|
|
86600
87542
|
& > .navi_picker_box {
|
|
86601
87543
|
min-height: 0;
|
|
86602
87544
|
}
|
|
@@ -87035,13 +87977,13 @@ const PickerButton = props => {
|
|
|
87035
87977
|
* It goes when there is nothing to take out and when nothing can be changed, so
|
|
87036
87978
|
* a caller places it once and never asks whether to draw it.
|
|
87037
87979
|
*
|
|
87038
|
-
* @type {import("
|
|
87980
|
+
* @type {import("preact").FunctionComponent<{
|
|
87039
87981
|
* size?: number | string,
|
|
87040
|
-
* children?: import("
|
|
87982
|
+
* children?: import("preact").ComponentChildren,
|
|
87041
87983
|
* } & Record<string, any>>}
|
|
87042
87984
|
* @param {number|string} [size="inherit"] How big the cross is drawn, as
|
|
87043
87985
|
* `<Icon size>` reads it — "inherit" takes the size of the text around it.
|
|
87044
|
-
* @param {import("
|
|
87986
|
+
* @param {import("preact").ComponentChildren} [children] The icon, in place of
|
|
87045
87987
|
* the cross. Anything else goes to the `<Button>` underneath (`variant`,
|
|
87046
87988
|
* `paddingX`, `aria-label`, …).
|
|
87047
87989
|
*/
|
|
@@ -87361,6 +88303,115 @@ const PickerFirstResolver = props => {
|
|
|
87361
88303
|
});
|
|
87362
88304
|
};
|
|
87363
88305
|
|
|
88306
|
+
// Every picker type is resolved into one of these before it gets here (see
|
|
88307
|
+
// PickerTypeResolver and resolveInputProps): a native input type, or navi_js
|
|
88308
|
+
// for the ones whose value is a whole object. Anything else is a type nobody
|
|
88309
|
+
// implements — the picker then behaves as a plain text one, silently, and an
|
|
88310
|
+
// object value reaches the DOM as "[object Object]".
|
|
88311
|
+
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"]);
|
|
88312
|
+
const pickerTypeWarnedSet = new Set();
|
|
88313
|
+
// A headless picker draws nothing at all — no value, no slot — so anything a
|
|
88314
|
+
// caller wrote for either is silently dropped. Said out loud, because a prop
|
|
88315
|
+
// that renders nowhere looks exactly like a prop that does not work.
|
|
88316
|
+
let uiDrawnByNobodyWarned = false;
|
|
88317
|
+
const warnOnUIDrawnByNobody = props => {
|
|
88318
|
+
if (uiDrawnByNobodyWarned) {
|
|
88319
|
+
return;
|
|
88320
|
+
}
|
|
88321
|
+
const {
|
|
88322
|
+
ui,
|
|
88323
|
+
variant
|
|
88324
|
+
} = props;
|
|
88325
|
+
if (variant !== "headless" || ui === undefined || ui === "default") {
|
|
88326
|
+
return;
|
|
88327
|
+
}
|
|
88328
|
+
if (pickerUIIsNaviOwn(ui)) {
|
|
88329
|
+
// What a `type` resolver puts there for a picker that did not name a
|
|
88330
|
+
// drawing (see picker_types.jsx). Nobody wrote it, so there is nobody to
|
|
88331
|
+
// tell that it is not drawn.
|
|
88332
|
+
return;
|
|
88333
|
+
}
|
|
88334
|
+
uiDrawnByNobodyWarned = true;
|
|
88335
|
+
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 />}>.`);
|
|
88336
|
+
};
|
|
88337
|
+
|
|
88338
|
+
// `clearable` is "put the cross in the right slot", and a picker without one is
|
|
88339
|
+
// handed the prop and drops it. Said out loud, because a caller asking for a
|
|
88340
|
+
// cross and getting none has no way to tell "ignored here" from "broken".
|
|
88341
|
+
const clearableWithoutSlotWarnedSet = new Set();
|
|
88342
|
+
const warnOnClearableWithoutSlot = (props, hasRightSlot) => {
|
|
88343
|
+
const {
|
|
88344
|
+
clearable,
|
|
88345
|
+
variant,
|
|
88346
|
+
icon,
|
|
88347
|
+
rightSlot,
|
|
88348
|
+
picksNothing
|
|
88349
|
+
} = props;
|
|
88350
|
+
if (!clearable || hasRightSlot) {
|
|
88351
|
+
return;
|
|
88352
|
+
}
|
|
88353
|
+
// What took the slot away: what the message names, and what makes two
|
|
88354
|
+
// pickers the same case.
|
|
88355
|
+
const cause = picksNothing ? "picks-nothing" : rightSlot === null ? "rightSlot={null}" : icon ? "icon" : `variant="${variant}"`;
|
|
88356
|
+
if (clearableWithoutSlotWarnedSet.has(cause)) {
|
|
88357
|
+
return;
|
|
88358
|
+
}
|
|
88359
|
+
clearableWithoutSlotWarnedSet.add(cause);
|
|
88360
|
+
if (picksNothing) {
|
|
88361
|
+
console.warn(`[navi] <Picker type="confirm" clearable> — a picker that picks nothing holds nothing to clear, so no cross is drawn.`);
|
|
88362
|
+
return;
|
|
88363
|
+
}
|
|
88364
|
+
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.`);
|
|
88365
|
+
};
|
|
88366
|
+
let chevronOffBareWarned = false;
|
|
88367
|
+
const warnOnChevronOffBare = (props, isBare) => {
|
|
88368
|
+
const {
|
|
88369
|
+
chevron,
|
|
88370
|
+
variant
|
|
88371
|
+
} = props;
|
|
88372
|
+
if (chevron === undefined || isBare || chevronOffBareWarned) {
|
|
88373
|
+
return;
|
|
88374
|
+
}
|
|
88375
|
+
chevronOffBareWarned = true;
|
|
88376
|
+
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.`);
|
|
88377
|
+
};
|
|
88378
|
+
const warnOnUnknownPickerType = props => {
|
|
88379
|
+
const {
|
|
88380
|
+
type
|
|
88381
|
+
} = props;
|
|
88382
|
+
if (type === undefined || PICKER_RESOLVED_TYPE_SET.has(type)) {
|
|
88383
|
+
return;
|
|
88384
|
+
}
|
|
88385
|
+
if (pickerTypeWarnedSet.has(type)) {
|
|
88386
|
+
return;
|
|
88387
|
+
}
|
|
88388
|
+
pickerTypeWarnedSet.add(type);
|
|
88389
|
+
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.`);
|
|
88390
|
+
};
|
|
88391
|
+
|
|
88392
|
+
// PickerFirstResolver comes before the presets so a preset reads the
|
|
88393
|
+
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
88394
|
+
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
88395
|
+
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
88396
|
+
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
88397
|
+
Date: PickerDateUI,
|
|
88398
|
+
Time: PickerTimeUI,
|
|
88399
|
+
Duration: PickerDurationUI,
|
|
88400
|
+
Week: PickerWeekUI,
|
|
88401
|
+
Datetime: PickerDatetimeUI,
|
|
88402
|
+
File: PickerFileUI,
|
|
88403
|
+
Color: PickerColorUI,
|
|
88404
|
+
Object: PickerObjectUI,
|
|
88405
|
+
Multiple: PickerArrayUI,
|
|
88406
|
+
PencilSvg,
|
|
88407
|
+
ChevronDownSvg: ChevronDownSvg$1,
|
|
88408
|
+
ClockSvg,
|
|
88409
|
+
DurationSvg,
|
|
88410
|
+
CalendarSvg,
|
|
88411
|
+
FileSvg,
|
|
88412
|
+
ColorSvg
|
|
88413
|
+
});
|
|
88414
|
+
|
|
87364
88415
|
/**
|
|
87365
88416
|
* Button-like trigger that opens a picker (native or custom popup) when clicked.
|
|
87366
88417
|
*
|
|
@@ -87379,13 +88430,13 @@ const PickerFirstResolver = props => {
|
|
|
87379
88430
|
* picker_confirm.jsx, and `message`, `confirmLabel`, `cancelLabel`,
|
|
87380
88431
|
* `focusOnOpen` below.
|
|
87381
88432
|
*
|
|
87382
|
-
* @type {import("
|
|
88433
|
+
* @type {import("preact").FunctionComponent<{
|
|
87383
88434
|
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
87384
88435
|
* value?: any,
|
|
87385
88436
|
* defaultValue?: any,
|
|
87386
88437
|
* name?: string,
|
|
87387
|
-
* placeholder?: import("
|
|
87388
|
-
* ui?: import("
|
|
88438
|
+
* placeholder?: import("preact").ComponentChildren,
|
|
88439
|
+
* ui?: import("preact").ComponentChildren | "default" | import("preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
87389
88440
|
* required?: boolean,
|
|
87390
88441
|
* min?: Date | string | number,
|
|
87391
88442
|
* max?: Date | string | number,
|
|
@@ -87395,8 +88446,8 @@ const PickerFirstResolver = props => {
|
|
|
87395
88446
|
* openWhileReadOnly?: boolean,
|
|
87396
88447
|
* error?: boolean | string,
|
|
87397
88448
|
* uiAction?: (value: any, event: Event) => void,
|
|
87398
|
-
* action?: (value: any, event: Event) => void,
|
|
87399
|
-
* children?: import("
|
|
88449
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void,
|
|
88450
|
+
* children?: import("preact").ComponentChildren,
|
|
87400
88451
|
* mode?: "popover" | "dialog" | "callout",
|
|
87401
88452
|
* openOn?: "press" | "longpress" | "contextmenu" | string | string[],
|
|
87402
88453
|
* animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
|
|
@@ -87416,14 +88467,14 @@ const PickerFirstResolver = props => {
|
|
|
87416
88467
|
* icon?: boolean,
|
|
87417
88468
|
* alignX?: "start" | "center" | "end",
|
|
87418
88469
|
* alignY?: "start" | "center" | "end" | "stretch",
|
|
87419
|
-
* rightSlotIcon?: import("
|
|
88470
|
+
* rightSlotIcon?: import("preact").ComponentChildren,
|
|
87420
88471
|
* rightSlotIconSize?: number | string,
|
|
87421
|
-
* rightSlot?: import("
|
|
88472
|
+
* rightSlot?: import("preact").ComponentChildren,
|
|
87422
88473
|
* chevron?: "end" | "start" | "hang-end",
|
|
87423
|
-
* clearConfirm?: string | import("
|
|
87424
|
-
* message?: import("
|
|
87425
|
-
* confirmLabel?: import("
|
|
87426
|
-
* cancelLabel?: import("
|
|
88474
|
+
* clearConfirm?: string | import("preact").ComponentChildren,
|
|
88475
|
+
* message?: import("preact").ComponentChildren,
|
|
88476
|
+
* confirmLabel?: import("preact").ComponentChildren,
|
|
88477
|
+
* cancelLabel?: import("preact").ComponentChildren,
|
|
87427
88478
|
* focusOnOpen?: "confirm" | "cancel" | "none",
|
|
87428
88479
|
* onConfirm?: (confirmEvent: CustomEvent) => void,
|
|
87429
88480
|
* onOpen?: (openEvent: CustomEvent) => void,
|
|
@@ -87456,14 +88507,14 @@ const PickerFirstResolver = props => {
|
|
|
87456
88507
|
* dialogSizeFromAnchor?: boolean,
|
|
87457
88508
|
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
87458
88509
|
* marginWithContainer?: number | string,
|
|
87459
|
-
* anchor?: import("
|
|
88510
|
+
* anchor?: import("preact").RefObject<HTMLElement> | HTMLElement,
|
|
87460
88511
|
* escapeEffect?: "cancel" | "close",
|
|
87461
88512
|
* pressOutside?: "close" | "cancel" | "capture",
|
|
87462
88513
|
* backdrop?: boolean,
|
|
87463
88514
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
87464
88515
|
* backdropColor?: string,
|
|
87465
88516
|
* backdropFilter?: string,
|
|
87466
|
-
* ref?: import("
|
|
88517
|
+
* ref?: import("preact").RefObject<HTMLElement>,
|
|
87467
88518
|
* [key: string]: any,
|
|
87468
88519
|
* }>}
|
|
87469
88520
|
* @param {string} [popupBackgroundColor] The popup's surface. Left out it is
|
|
@@ -87502,7 +88553,7 @@ const PickerFirstResolver = props => {
|
|
|
87502
88553
|
* max_lines_context.js). `variant="text"` and `variant="bare"` are not
|
|
87503
88554
|
* clamped: their drawing is the caller's own, a `Text` in the `ui` cuts
|
|
87504
88555
|
* itself with its own `maxLines`. See docs/typography.md.
|
|
87505
|
-
* @param {import("
|
|
88556
|
+
* @param {import("preact").ComponentChildren | "default" | import("preact").ComponentType} [ui] What the
|
|
87506
88557
|
* trigger draws in place of the value's default rendering (a date, a list
|
|
87507
88558
|
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
87508
88559
|
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
@@ -87619,7 +88670,7 @@ const PickerFirstResolver = props => {
|
|
|
87619
88670
|
* popover narrower than the trigger stays narrow. Popover mode only — a
|
|
87620
88671
|
* dialog never takes the trigger's width to begin with (see
|
|
87621
88672
|
* `dialogMinWidth`).
|
|
87622
|
-
* @param {import("
|
|
88673
|
+
* @param {import("preact").ComponentChildren} [rightSlotIcon] What the right
|
|
87623
88674
|
* slot draws in place of the chevron. It is the whole slot, not an addition
|
|
87624
88675
|
* to it: the picker then no longer says on its own that it opens, so pass
|
|
87625
88676
|
* something that does. Decoration only — it is wrapped in an `<Icon>`, which
|
|
@@ -87651,7 +88702,7 @@ const PickerFirstResolver = props => {
|
|
|
87651
88702
|
* any `pressPaddingRight`. The chevron is drawn in `--picker-icon-color`
|
|
87652
88703
|
* (`currentColor` here) and `--picker-icon-color-hover`; a dimmed one is
|
|
87653
88704
|
* `--picker-icon-color: color-mix(in srgb, currentColor 55%, transparent)`.
|
|
87654
|
-
* @param {import("
|
|
88705
|
+
* @param {import("preact").ComponentChildren} [rightSlot] Same place, rendered
|
|
87655
88706
|
* as-is: no `<Icon>` around it, nothing `aria-hidden`. This is where an
|
|
87656
88707
|
* interactive right slot goes. `null` takes the slot away entirely — the
|
|
87657
88708
|
* gutter it carries with it, so the trigger measures its drawing and centers
|
|
@@ -87669,7 +88720,7 @@ const PickerFirstResolver = props => {
|
|
|
87669
88720
|
* opening it, the anchor being itself, and one popup per instance without
|
|
87670
88721
|
* an id to wire. See "The trigger wears the wait" in docs/popup_open.md for
|
|
87671
88722
|
* when that beats a `Button` opening a shared `Dialog`.
|
|
87672
|
-
* @param {string|import("
|
|
88723
|
+
* @param {string|import("preact").ComponentChildren} [clearConfirm] The
|
|
87673
88724
|
* question asked before the clear cross clears anything — the `message` of
|
|
87674
88725
|
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
87675
88726
|
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
@@ -87726,15 +88777,15 @@ const PickerFirstResolver = props => {
|
|
|
87726
88777
|
* buttons. A shape, not a paint: the variant still decides what is drawn
|
|
87727
88778
|
* around it, so `variant="discrete" icon` is the discrete hover wash on an
|
|
87728
88779
|
* icon-sized square. `variant="icon"` is this shape with no surface at all.
|
|
87729
|
-
* @param {import("
|
|
88780
|
+
* @param {import("preact").ComponentChildren} [message] `type="confirm"`: the
|
|
87730
88781
|
* question, in the popup's default body — text, or JSX when it needs an
|
|
87731
88782
|
* emphasis, a link. Left out, a generic "are you sure?" in the current
|
|
87732
88783
|
* language. To draw the whole popup instead, pass `children`: yes is then
|
|
87733
88784
|
* whatever carries `--navi-confirm`, no whatever closes (`--navi-cancel`).
|
|
87734
|
-
* @param {import("
|
|
88785
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] `type="confirm"`,
|
|
87735
88786
|
* default body: what the yes button reads. Defaults to the translated
|
|
87736
88787
|
* "Confirm".
|
|
87737
|
-
* @param {import("
|
|
88788
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Same, for the no
|
|
87738
88789
|
* button. Defaults to the translated "Cancel".
|
|
87739
88790
|
* @param {"confirm"|"cancel"|"none"} [focusOnOpen="confirm"] `type="confirm"`,
|
|
87740
88791
|
* default body: which button the keyboard lands on when the popup opens.
|
|
@@ -87851,7 +88902,7 @@ const PickerFirstResolver = props => {
|
|
|
87851
88902
|
* should go on meaning cancel, and
|
|
87852
88903
|
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
87853
88904
|
* back.
|
|
87854
|
-
* @param {import("
|
|
88905
|
+
* @param {import("preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
87855
88906
|
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
87856
88907
|
* trigger is one piece of a bigger control (the chevron half of a split
|
|
87857
88908
|
* button) points at the whole control, so the popup lines up with it and is
|
|
@@ -87935,115 +88986,6 @@ const PickerFirstResolver = props => {
|
|
|
87935
88986
|
* `type="confirm"`) never builds them early: its popup holds no value, there
|
|
87936
88987
|
* is nothing to read.
|
|
87937
88988
|
*/
|
|
87938
|
-
// Every picker type is resolved into one of these before it gets here (see
|
|
87939
|
-
// PickerTypeResolver and resolveInputProps): a native input type, or navi_js
|
|
87940
|
-
// for the ones whose value is a whole object. Anything else is a type nobody
|
|
87941
|
-
// implements — the picker then behaves as a plain text one, silently, and an
|
|
87942
|
-
// object value reaches the DOM as "[object Object]".
|
|
87943
|
-
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"]);
|
|
87944
|
-
const pickerTypeWarnedSet = new Set();
|
|
87945
|
-
// A headless picker draws nothing at all — no value, no slot — so anything a
|
|
87946
|
-
// caller wrote for either is silently dropped. Said out loud, because a prop
|
|
87947
|
-
// that renders nowhere looks exactly like a prop that does not work.
|
|
87948
|
-
let uiDrawnByNobodyWarned = false;
|
|
87949
|
-
const warnOnUIDrawnByNobody = props => {
|
|
87950
|
-
if (uiDrawnByNobodyWarned) {
|
|
87951
|
-
return;
|
|
87952
|
-
}
|
|
87953
|
-
const {
|
|
87954
|
-
ui,
|
|
87955
|
-
variant
|
|
87956
|
-
} = props;
|
|
87957
|
-
if (variant !== "headless" || ui === undefined || ui === "default") {
|
|
87958
|
-
return;
|
|
87959
|
-
}
|
|
87960
|
-
if (pickerUIIsNaviOwn(ui)) {
|
|
87961
|
-
// What a `type` resolver puts there for a picker that did not name a
|
|
87962
|
-
// drawing (see picker_types.jsx). Nobody wrote it, so there is nobody to
|
|
87963
|
-
// tell that it is not drawn.
|
|
87964
|
-
return;
|
|
87965
|
-
}
|
|
87966
|
-
uiDrawnByNobodyWarned = true;
|
|
87967
|
-
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 />}>.`);
|
|
87968
|
-
};
|
|
87969
|
-
|
|
87970
|
-
// `clearable` is "put the cross in the right slot", and a picker without one is
|
|
87971
|
-
// handed the prop and drops it. Said out loud, because a caller asking for a
|
|
87972
|
-
// cross and getting none has no way to tell "ignored here" from "broken".
|
|
87973
|
-
const clearableWithoutSlotWarnedSet = new Set();
|
|
87974
|
-
const warnOnClearableWithoutSlot = (props, hasRightSlot) => {
|
|
87975
|
-
const {
|
|
87976
|
-
clearable,
|
|
87977
|
-
variant,
|
|
87978
|
-
icon,
|
|
87979
|
-
rightSlot,
|
|
87980
|
-
picksNothing
|
|
87981
|
-
} = props;
|
|
87982
|
-
if (!clearable || hasRightSlot) {
|
|
87983
|
-
return;
|
|
87984
|
-
}
|
|
87985
|
-
// What took the slot away: what the message names, and what makes two
|
|
87986
|
-
// pickers the same case.
|
|
87987
|
-
const cause = picksNothing ? "picks-nothing" : rightSlot === null ? "rightSlot={null}" : icon ? "icon" : `variant="${variant}"`;
|
|
87988
|
-
if (clearableWithoutSlotWarnedSet.has(cause)) {
|
|
87989
|
-
return;
|
|
87990
|
-
}
|
|
87991
|
-
clearableWithoutSlotWarnedSet.add(cause);
|
|
87992
|
-
if (picksNothing) {
|
|
87993
|
-
console.warn(`[navi] <Picker type="confirm" clearable> — a picker that picks nothing holds nothing to clear, so no cross is drawn.`);
|
|
87994
|
-
return;
|
|
87995
|
-
}
|
|
87996
|
-
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.`);
|
|
87997
|
-
};
|
|
87998
|
-
let chevronOffBareWarned = false;
|
|
87999
|
-
const warnOnChevronOffBare = (props, isBare) => {
|
|
88000
|
-
const {
|
|
88001
|
-
chevron,
|
|
88002
|
-
variant
|
|
88003
|
-
} = props;
|
|
88004
|
-
if (chevron === undefined || isBare || chevronOffBareWarned) {
|
|
88005
|
-
return;
|
|
88006
|
-
}
|
|
88007
|
-
chevronOffBareWarned = true;
|
|
88008
|
-
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.`);
|
|
88009
|
-
};
|
|
88010
|
-
const warnOnUnknownPickerType = props => {
|
|
88011
|
-
const {
|
|
88012
|
-
type
|
|
88013
|
-
} = props;
|
|
88014
|
-
if (type === undefined || PICKER_RESOLVED_TYPE_SET.has(type)) {
|
|
88015
|
-
return;
|
|
88016
|
-
}
|
|
88017
|
-
if (pickerTypeWarnedSet.has(type)) {
|
|
88018
|
-
return;
|
|
88019
|
-
}
|
|
88020
|
-
pickerTypeWarnedSet.add(type);
|
|
88021
|
-
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.`);
|
|
88022
|
-
};
|
|
88023
|
-
|
|
88024
|
-
// PickerFirstResolver comes before the presets so a preset reads the
|
|
88025
|
-
// min/max/step a bound signal fills in (resolveInputProps); a preset type
|
|
88026
|
-
// therefore has no row in NAVI_TYPE_DEFAULTS (resolve_input_props.js).
|
|
88027
|
-
const PickerResolved = /*#__PURE__*/createComponentResolver([PickerFirstResolver, PickerPresetResolver, PickerConfirmResolver, PickerCustomResolver, PickerTypeResolver, PickerButton]);
|
|
88028
|
-
const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
88029
|
-
Date: PickerDateUI,
|
|
88030
|
-
Time: PickerTimeUI,
|
|
88031
|
-
Duration: PickerDurationUI,
|
|
88032
|
-
Week: PickerWeekUI,
|
|
88033
|
-
Datetime: PickerDatetimeUI,
|
|
88034
|
-
File: PickerFileUI,
|
|
88035
|
-
Color: PickerColorUI,
|
|
88036
|
-
Object: PickerObjectUI,
|
|
88037
|
-
Multiple: PickerArrayUI,
|
|
88038
|
-
PencilSvg,
|
|
88039
|
-
ChevronDownSvg: ChevronDownSvg$1,
|
|
88040
|
-
ClockSvg,
|
|
88041
|
-
DurationSvg,
|
|
88042
|
-
CalendarSvg,
|
|
88043
|
-
FileSvg,
|
|
88044
|
-
ColorSvg
|
|
88045
|
-
});
|
|
88046
|
-
|
|
88047
88989
|
// Assembled in pure expressions rather than written onto Picker afterwards
|
|
88048
88990
|
// (`Picker.Chip = PickerChip`): what createComponentResolver returns is an object
|
|
88049
88991
|
// a bundler cannot see through, so a property assigned to it later is a side
|
|
@@ -88300,7 +89242,7 @@ const css$q = /* css */`@layer navi {
|
|
|
88300
89242
|
const TRAVELS_NONE = [];
|
|
88301
89243
|
|
|
88302
89244
|
/**
|
|
88303
|
-
* @type {import("
|
|
89245
|
+
* @type {import("preact").FunctionComponent<{
|
|
88304
89246
|
* value?: any,
|
|
88305
89247
|
* defaultValue?: any,
|
|
88306
89248
|
* signal?: import("@preact/signals").Signal<any>,
|
|
@@ -88316,7 +89258,7 @@ const TRAVELS_NONE = [];
|
|
|
88316
89258
|
* fallbackValue?: any,
|
|
88317
89259
|
* valueAtStep: (value: any, count: number) => any,
|
|
88318
89260
|
* compareValues?: (a: any, b: any) => number,
|
|
88319
|
-
* renderValue?: (value: any, context: { maxLines?: number }) => import("
|
|
89261
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("preact").ComponentChildren,
|
|
88320
89262
|
* controlProps?: object,
|
|
88321
89263
|
* duration?: number,
|
|
88322
89264
|
* vertical?: boolean,
|
|
@@ -89103,14 +90045,14 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
89103
90045
|
* single `value`/`signal` like any other control. `TimeSpin` is that, for a
|
|
89104
90046
|
* time of day.
|
|
89105
90047
|
*
|
|
89106
|
-
* @type {import("
|
|
90048
|
+
* @type {import("preact").FunctionComponent<{
|
|
89107
90049
|
* name?: string,
|
|
89108
90050
|
* value?: any,
|
|
89109
90051
|
* defaultValue?: any,
|
|
89110
90052
|
* signal?: import("@preact/signals").Signal<any>,
|
|
89111
90053
|
* aggregateChildStates?: (childUIStateControllers: any[]) => any,
|
|
89112
90054
|
* distributeChildUIState?: (groupState: any, childUIStateController: any) => any,
|
|
89113
|
-
* children?: import("
|
|
90055
|
+
* children?: import("preact").ComponentChildren,
|
|
89114
90056
|
* [key: string]: any,
|
|
89115
90057
|
* }>}
|
|
89116
90058
|
* Everything a box takes is taken here too — `width`, `borderWidth`,
|
|
@@ -89213,7 +90155,7 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
89213
90155
|
* above and below it by default — sideways they would be where the caret
|
|
89214
90156
|
* moves — with the bigger number up top.
|
|
89215
90157
|
*
|
|
89216
|
-
* @type {import("
|
|
90158
|
+
* @type {import("preact").FunctionComponent<{
|
|
89217
90159
|
* value?: number|string,
|
|
89218
90160
|
* defaultValue?: number|string,
|
|
89219
90161
|
* min?: number,
|
|
@@ -89318,7 +90260,7 @@ const numberAtStep = (value, count, {
|
|
|
89318
90260
|
* A day one steps through: the date plus what it is to today when there is a
|
|
89319
90261
|
* word for it, and a calendar behind it for the day that is far from here.
|
|
89320
90262
|
*
|
|
89321
|
-
* @type {import("
|
|
90263
|
+
* @type {import("preact").FunctionComponent<{
|
|
89322
90264
|
* value?: string,
|
|
89323
90265
|
* defaultValue?: string,
|
|
89324
90266
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -89329,7 +90271,7 @@ const numberAtStep = (value, count, {
|
|
|
89329
90271
|
* maxMessage?: any,
|
|
89330
90272
|
* step?: number,
|
|
89331
90273
|
* lang?: string,
|
|
89332
|
-
* renderDay?: (day: string) => import("
|
|
90274
|
+
* renderDay?: (day: string) => import("preact").ComponentChildren,
|
|
89333
90275
|
* [key: string]: any,
|
|
89334
90276
|
* }>}
|
|
89335
90277
|
* @param {string} [value] The day shown, as "YYYY-MM-DD".
|
|
@@ -89340,7 +90282,7 @@ const numberAtStep = (value, count, {
|
|
|
89340
90282
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
89341
90283
|
* Long by default, since this is a control one reads rather than a column
|
|
89342
90284
|
* one scans; `short` where the room is not there.
|
|
89343
|
-
* @param {(day: string) => import("
|
|
90285
|
+
* @param {(day: string) => import("preact").ComponentChildren} [renderDay] What
|
|
89344
90286
|
* to write for a day. Defaults to the date plus what it is to today when
|
|
89345
90287
|
* there is a word for it ("samedi 8 août (demain)"), since a day near now is
|
|
89346
90288
|
* read as a distance from now before it is read as a date.
|
|
@@ -89457,7 +90399,7 @@ const HOUR_MAX = 23;
|
|
|
89457
90399
|
const MINUTE_MAX = 59;
|
|
89458
90400
|
|
|
89459
90401
|
/**
|
|
89460
|
-
* @type {import("
|
|
90402
|
+
* @type {import("preact").FunctionComponent<{
|
|
89461
90403
|
* name?: string,
|
|
89462
90404
|
* value?: string,
|
|
89463
90405
|
* defaultValue?: string,
|
|
@@ -89465,7 +90407,7 @@ const MINUTE_MAX = 59;
|
|
|
89465
90407
|
* minuteStep?: number,
|
|
89466
90408
|
* pad?: number,
|
|
89467
90409
|
* loop?: boolean,
|
|
89468
|
-
* separator?: import("
|
|
90410
|
+
* separator?: import("preact").ComponentChildren,
|
|
89469
90411
|
* hourLabel?: string,
|
|
89470
90412
|
* minuteLabel?: string,
|
|
89471
90413
|
* [key: string]: any,
|
|
@@ -89478,7 +90420,7 @@ const MINUTE_MAX = 59;
|
|
|
89478
90420
|
* of a day to stop at. Say `loop={false}` for two ends one cannot step past.
|
|
89479
90421
|
* @param {number} [pad=2] How many digits an hour and a minute are written on:
|
|
89480
90422
|
* a clock says "07:00", never "7:0". Say `pad={0}` for the bare numbers.
|
|
89481
|
-
* @param {import("
|
|
90423
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
89482
90424
|
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
89483
90425
|
* Everything a box takes is taken here too — `width`, `borderRadius`, `size`.
|
|
89484
90426
|
*/
|
|
@@ -89544,7 +90486,7 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
89544
90486
|
};
|
|
89545
90487
|
|
|
89546
90488
|
/**
|
|
89547
|
-
* @type {import("
|
|
90489
|
+
* @type {import("preact").FunctionComponent<{
|
|
89548
90490
|
* name?: string,
|
|
89549
90491
|
* value?: { start?: string, end?: string },
|
|
89550
90492
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -89554,13 +90496,13 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
89554
90496
|
* timeProps?: object,
|
|
89555
90497
|
* loop?: boolean,
|
|
89556
90498
|
* size?: string,
|
|
89557
|
-
* startLabel?: import("
|
|
89558
|
-
* endLabel?: import("
|
|
90499
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
90500
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
89559
90501
|
* [key: string]: any,
|
|
89560
90502
|
* }>}
|
|
89561
90503
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
89562
90504
|
* "HH:MM".
|
|
89563
|
-
* @param {import("
|
|
90505
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
89564
90506
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
89565
90507
|
* `null` for neither.
|
|
89566
90508
|
* @param {number} [minuteStep=1] How many minutes a press on a minute chevron
|
|
@@ -89657,7 +90599,7 @@ const css$p = /* css */`.navi_checkbox_group {
|
|
|
89657
90599
|
*
|
|
89658
90600
|
* See `<RadioGroup>` for the same shape holding a single value.
|
|
89659
90601
|
*
|
|
89660
|
-
* @type {import("
|
|
90602
|
+
* @type {import("preact").FunctionComponent<{
|
|
89661
90603
|
* maxLength?: number,
|
|
89662
90604
|
* maxLengthGuard?: number,
|
|
89663
90605
|
* [key: string]: any,
|
|
@@ -89718,6 +90660,19 @@ const CheckboxGroupInterface = props => {
|
|
|
89718
90660
|
});
|
|
89719
90661
|
};
|
|
89720
90662
|
|
|
90663
|
+
/**
|
|
90664
|
+
* @type {import("preact").FunctionComponent<{
|
|
90665
|
+
* unit: string,
|
|
90666
|
+
* plural?: boolean,
|
|
90667
|
+
* format?: "long" | "short" | "narrow",
|
|
90668
|
+
* lang?: string,
|
|
90669
|
+
* label?: string,
|
|
90670
|
+
* size?: string,
|
|
90671
|
+
* sizeRatio?: number,
|
|
90672
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
90673
|
+
* [key: string]: any,
|
|
90674
|
+
* }>}
|
|
90675
|
+
*/
|
|
89721
90676
|
const Unit = ({
|
|
89722
90677
|
unit,
|
|
89723
90678
|
plural,
|
|
@@ -90475,7 +91430,7 @@ const css$m = /* css */`.navi_input.navi_select {
|
|
|
90475
91430
|
`;
|
|
90476
91431
|
|
|
90477
91432
|
/**
|
|
90478
|
-
* @type {import("
|
|
91433
|
+
* @type {import("preact").FunctionComponent<{
|
|
90479
91434
|
* value?: string,
|
|
90480
91435
|
* defaultValue?: string,
|
|
90481
91436
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -90557,30 +91512,40 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90557
91512
|
|
|
90558
91513
|
& > .navi_group {
|
|
90559
91514
|
flex: auto;
|
|
91515
|
+
|
|
91516
|
+
& > .navi_button {
|
|
91517
|
+
flex: auto;
|
|
91518
|
+
}
|
|
90560
91519
|
}
|
|
90561
91520
|
}
|
|
90562
91521
|
|
|
90563
91522
|
.navi_split_button_menu {
|
|
91523
|
+
--border-width: 0px;
|
|
90564
91524
|
display: flex;
|
|
90565
91525
|
position: relative;
|
|
91526
|
+
|
|
91527
|
+
& > .navi_button {
|
|
91528
|
+
margin-left: calc(var(--border-width) * -1);
|
|
91529
|
+
}
|
|
90566
91530
|
}
|
|
90567
91531
|
`;
|
|
90568
91532
|
|
|
90569
91533
|
/**
|
|
90570
|
-
* @type {import("
|
|
90571
|
-
* options: Array<{value: any, label: import("
|
|
91534
|
+
* @type {import("preact").FunctionComponent<{
|
|
91535
|
+
* options: Array<{value: any, label: import("preact").ComponentChildren, [key: string]: any}>,
|
|
90572
91536
|
* value?: any,
|
|
90573
91537
|
* defaultValue?: any,
|
|
90574
|
-
* label?: import("
|
|
90575
|
-
* action?: (value: any, event: Event) => void | Promise<void>,
|
|
91538
|
+
* label?: import("preact").ComponentChildren,
|
|
91539
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void | Promise<void>,
|
|
90576
91540
|
* command?: string,
|
|
90577
91541
|
* commandFor?: string,
|
|
90578
91542
|
* prefetch?: boolean,
|
|
90579
|
-
* onValueChange?: (value: any, event: Event) => void,
|
|
91543
|
+
* onValueChange?: (value: any, { event }: { event: Event }) => void,
|
|
90580
91544
|
* chooseEffect?: "select" | "run",
|
|
90581
91545
|
* menuLabel?: string,
|
|
90582
|
-
* menuIcon?: import("
|
|
91546
|
+
* menuIcon?: import("preact").ComponentChildren,
|
|
90583
91547
|
* menuIconSize?: number | string,
|
|
91548
|
+
* menuTestId?: string,
|
|
90584
91549
|
* loading?: boolean,
|
|
90585
91550
|
* readOnly?: boolean,
|
|
90586
91551
|
* disabled?: boolean,
|
|
@@ -90616,19 +91581,21 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90616
91581
|
* popupTestId?: string,
|
|
90617
91582
|
* [key: string]: any,
|
|
90618
91583
|
* }>}
|
|
90619
|
-
* @param {Array<{value: any, label: import("
|
|
91584
|
+
* @param {Array<{value: any, label: import("preact").ComponentChildren}>} options
|
|
90620
91585
|
* The ways of doing the thing, in the order the menu lists them. Anything
|
|
90621
91586
|
* else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
|
|
90622
91587
|
* @param {any} [value] Which entry the button stands for — the one it runs, and
|
|
90623
91588
|
* the one it is named after. `defaultValue` is the uncontrolled form; without
|
|
90624
91589
|
* either, the first entry.
|
|
90625
|
-
* @param {import("
|
|
91590
|
+
* @param {import("preact").ComponentChildren} [label] What the button says, when
|
|
90626
91591
|
* that is not the name of the entry it stands for — a button that keeps one
|
|
90627
91592
|
* name whatever the menu does.
|
|
90628
|
-
* @param {(value: any, event
|
|
90629
|
-
* entry given. Pressed on the button it is the entry the button stands
|
|
90630
|
-
* with `chooseEffect="run"` it is also the entry just chosen.
|
|
90631
|
-
*
|
|
91593
|
+
* @param {(value: any, { event, signal }) => void} [action] Do the thing, for
|
|
91594
|
+
* the entry given. Pressed on the button it is the entry the button stands
|
|
91595
|
+
* for; with `chooseEffect="run"` it is also the entry just chosen. Run as a
|
|
91596
|
+
* navi action: the second argument is the run's (see docs/actions.md), the
|
|
91597
|
+
* press being its `event`. Awaited: the whole split button is busy until it
|
|
91598
|
+
* settles.
|
|
90632
91599
|
* @param {string} [command] What a press on the button does once its `action`
|
|
90633
91600
|
* is done, as on a `<Button>` (with `commandFor`, and `command-value` when
|
|
90634
91601
|
* what the command is about is not the entry the button stands for). A
|
|
@@ -90642,12 +91609,14 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90642
91609
|
* menu of one-offs around a primary action.
|
|
90643
91610
|
* @param {string} [menuLabel] What the chevron half is called, for a screen
|
|
90644
91611
|
* reader and for the tooltip a long press raises.
|
|
90645
|
-
* @param {import("
|
|
91612
|
+
* @param {import("preact").ComponentChildren} [menuIcon] What the chevron half
|
|
90646
91613
|
* draws, in place of the chevron. It is the whole of what that half says, so
|
|
90647
91614
|
* pass something that still reads as "there is more here" — and say what,
|
|
90648
91615
|
* through `menuLabel`.
|
|
90649
91616
|
* @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
|
|
90650
91617
|
* the button's own font size.
|
|
91618
|
+
* @param {string} [menuTestId] The `data-testid` of the chevron half, which
|
|
91619
|
+
* navi builds and the caller has no other way to name.
|
|
90651
91620
|
* @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
|
|
90652
91621
|
* picker left to itself turns into a dialog on a small screen; a split
|
|
90653
91622
|
* button's menu stays hung off the button there, so this says popover unless
|
|
@@ -90668,8 +91637,12 @@ const css$l = /* css */`.navi_split_button {
|
|
|
90668
91637
|
* `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
|
|
90669
91638
|
* An `aria-*` attribute describes the button and reaches the main half —
|
|
90670
91639
|
* `aria-pressed` for a split button whose main half toggles a mode;
|
|
90671
|
-
* `aria-hidden` excepted, which hides the whole control.
|
|
90672
|
-
*
|
|
91640
|
+
* `aria-hidden` excepted, which hides the whole control. How the words are
|
|
91641
|
+
* written (`size`, `bold`, `lineHeight`, `noWrap`…) reaches both halves, and so
|
|
91642
|
+
* does vertical padding (`paddingY`, `paddingTop`, `paddingBottom`); the rest
|
|
91643
|
+
* of the padding is the main half's — the chevron half keeps its own, which
|
|
91644
|
+
* gives it its width. Anything else (`className`, `width`, margins, `data-*`)
|
|
91645
|
+
* lands on the split button's own box.
|
|
90673
91646
|
*/
|
|
90674
91647
|
const SplitButton = props => {
|
|
90675
91648
|
import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
@@ -90688,6 +91661,7 @@ const SplitButton = props => {
|
|
|
90688
91661
|
menuLabel = buttonI18n("button.more_actions"),
|
|
90689
91662
|
menuIcon = jsx(ChevronDownSvg$1, {}),
|
|
90690
91663
|
menuIconSize,
|
|
91664
|
+
menuTestId,
|
|
90691
91665
|
loading,
|
|
90692
91666
|
readOnly,
|
|
90693
91667
|
disabled,
|
|
@@ -90707,11 +91681,13 @@ const SplitButton = props => {
|
|
|
90707
91681
|
...rest
|
|
90708
91682
|
} = props;
|
|
90709
91683
|
// Everything the popup answers to travels to the Picker; an aria-* describes
|
|
90710
|
-
// the button a screen reader meets, which is the main half;
|
|
90711
|
-
//
|
|
90712
|
-
//
|
|
90713
|
-
//
|
|
90714
|
-
|
|
91684
|
+
// the button a screen reader meets, which is the main half; how the words are
|
|
91685
|
+
// written and how far they sit from the edge belong to the halves, since a
|
|
91686
|
+
// <button> inherits neither from the box around it; everything else is the
|
|
91687
|
+
// split button's own box (margins, width, data-*). Sorted by name rather
|
|
91688
|
+
// than named one by one so a Picker popup prop is forwarded by adding it to
|
|
91689
|
+
// that list, not by threading it through here.
|
|
91690
|
+
const [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps] = sortRestProps(rest);
|
|
90715
91691
|
// A split button is a control on the page, not a place one goes: its menu
|
|
90716
91692
|
// hangs off it even on a phone, where a picker left to itself would decide a
|
|
90717
91693
|
// small screen means a dialog. Passing mode="dialog" asks for that back.
|
|
@@ -90756,6 +91732,7 @@ const SplitButton = props => {
|
|
|
90756
91732
|
borderColor,
|
|
90757
91733
|
borderWidth,
|
|
90758
91734
|
borderRadius,
|
|
91735
|
+
...halfStyleProps,
|
|
90759
91736
|
pressEffect,
|
|
90760
91737
|
loading: loadingResolved,
|
|
90761
91738
|
readOnly,
|
|
@@ -90765,7 +91742,7 @@ const SplitButton = props => {
|
|
|
90765
91742
|
};
|
|
90766
91743
|
return jsxs(Box, {
|
|
90767
91744
|
ref: rootRef,
|
|
90768
|
-
|
|
91745
|
+
baseClassName: "navi_split_button",
|
|
90769
91746
|
borderRadius: borderRadius,
|
|
90770
91747
|
...boxProps,
|
|
90771
91748
|
children: [jsx(LoadingOutline, {
|
|
@@ -90782,6 +91759,7 @@ const SplitButton = props => {
|
|
|
90782
91759
|
"command-value": commandValue,
|
|
90783
91760
|
prefetch: prefetch,
|
|
90784
91761
|
...buttonAriaProps,
|
|
91762
|
+
...mainPaddingProps,
|
|
90785
91763
|
...halfProps,
|
|
90786
91764
|
children: label === undefined ? optionShown?.label : label
|
|
90787
91765
|
}), jsxs("div", {
|
|
@@ -90791,6 +91769,7 @@ const SplitButton = props => {
|
|
|
90791
91769
|
paddingX: "s",
|
|
90792
91770
|
expandY: true,
|
|
90793
91771
|
"aria-label": menuLabel,
|
|
91772
|
+
"data-testid": menuTestId,
|
|
90794
91773
|
command: "--navi-open",
|
|
90795
91774
|
commandFor: menuId,
|
|
90796
91775
|
...halfProps,
|
|
@@ -90870,9 +91849,16 @@ const SplitButton = props => {
|
|
|
90870
91849
|
// a caller reaches all of them through the split button (see picker.jsx's JSDoc
|
|
90871
91850
|
// for what each one says).
|
|
90872
91851
|
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"]);
|
|
91852
|
+
// Vertical padding is said about both halves so they stay the same height; the
|
|
91853
|
+
// horizontal one is the main half's alone, the chevron half's own (paddingX="s")
|
|
91854
|
+
// being what gives it its width.
|
|
91855
|
+
const HALF_PADDING_PROP_SET = new Set(["paddingY", "paddingTop", "paddingBottom"]);
|
|
91856
|
+
const MAIN_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingLeft", "paddingRight"]);
|
|
90873
91857
|
const sortRestProps = props => {
|
|
90874
91858
|
const popupProps = {};
|
|
90875
91859
|
const buttonAriaProps = {};
|
|
91860
|
+
const halfStyleProps = {};
|
|
91861
|
+
const mainPaddingProps = {};
|
|
90876
91862
|
const boxProps = {};
|
|
90877
91863
|
for (const key of Object.keys(props)) {
|
|
90878
91864
|
if (POPUP_PROP_SET.has(key)) {
|
|
@@ -90881,11 +91867,15 @@ const sortRestProps = props => {
|
|
|
90881
91867
|
// aria-hidden hides a subtree rather than describing a button: on the
|
|
90882
91868
|
// main half alone it would leave the chevron half exposed.
|
|
90883
91869
|
buttonAriaProps[key] = props[key];
|
|
91870
|
+
} else if (isTypoProp(key) || HALF_PADDING_PROP_SET.has(key)) {
|
|
91871
|
+
halfStyleProps[key] = props[key];
|
|
91872
|
+
} else if (MAIN_PADDING_PROP_SET.has(key)) {
|
|
91873
|
+
mainPaddingProps[key] = props[key];
|
|
90884
91874
|
} else {
|
|
90885
91875
|
boxProps[key] = props[key];
|
|
90886
91876
|
}
|
|
90887
91877
|
}
|
|
90888
|
-
return [popupProps, buttonAriaProps, boxProps];
|
|
91878
|
+
return [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps];
|
|
90889
91879
|
};
|
|
90890
91880
|
|
|
90891
91881
|
/**
|
|
@@ -92087,7 +93077,7 @@ const WheelGroupContext = createContext(null);
|
|
|
92087
93077
|
* value, the value lives in one hidden input for the form. The visible rows are
|
|
92088
93078
|
* presentational.
|
|
92089
93079
|
*
|
|
92090
|
-
* @type {import("
|
|
93080
|
+
* @type {import("preact").FunctionComponent<{
|
|
92091
93081
|
* value?: any,
|
|
92092
93082
|
* defaultValue?: any,
|
|
92093
93083
|
* action?: (value: any) => void,
|
|
@@ -92105,7 +93095,7 @@ const WheelGroupContext = createContext(null);
|
|
|
92105
93095
|
* readOnly?: boolean,
|
|
92106
93096
|
* disabled?: boolean,
|
|
92107
93097
|
* loading?: boolean,
|
|
92108
|
-
* children?: import("
|
|
93098
|
+
* children?: import("preact").ComponentChildren,
|
|
92109
93099
|
* [key: string]: any,
|
|
92110
93100
|
* }>}
|
|
92111
93101
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
@@ -92734,8 +93724,9 @@ function WheelUI(props) {
|
|
|
92734
93724
|
}, {
|
|
92735
93725
|
controlType: "input",
|
|
92736
93726
|
// The host is the hidden input carrying the value: the container is
|
|
92737
|
-
// what is focused and spun.
|
|
92738
|
-
listenersOn: "root"
|
|
93727
|
+
// what is focused and spun, and the spinbutton its name has to name.
|
|
93728
|
+
listenersOn: "root",
|
|
93729
|
+
nameOn: "root"
|
|
92739
93730
|
});
|
|
92740
93731
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
92741
93732
|
const {
|
|
@@ -93592,9 +94583,9 @@ const WheelWindow = ({
|
|
|
93592
94583
|
* of rows and fills each from this tracked data — so the value list is the source
|
|
93593
94584
|
* of truth, not a per-item DOM node. Selection lives on the wheel's value.
|
|
93594
94585
|
*
|
|
93595
|
-
* @type {import("
|
|
94586
|
+
* @type {import("preact").FunctionComponent<{
|
|
93596
94587
|
* value: any,
|
|
93597
|
-
* children?: import("
|
|
94588
|
+
* children?: import("preact").ComponentChildren,
|
|
93598
94589
|
* [key: string]: any,
|
|
93599
94590
|
* }>}
|
|
93600
94591
|
*/
|
|
@@ -93629,9 +94620,9 @@ Wheel.Item = WheelItem;
|
|
|
93629
94620
|
* each Wheel.Item (it's a Box: paddingX="0.5ch") so the gap stays scrollable.
|
|
93630
94621
|
* `spacing` is just the Box flex gap between children (a layout gap, not padding).
|
|
93631
94622
|
*
|
|
93632
|
-
* @type {import("
|
|
94623
|
+
* @type {import("preact").FunctionComponent<{
|
|
93633
94624
|
* horizontal?: boolean,
|
|
93634
|
-
* children?: import("
|
|
94625
|
+
* children?: import("preact").ComponentChildren,
|
|
93635
94626
|
* [key: string]: any,
|
|
93636
94627
|
* }>}
|
|
93637
94628
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
@@ -93828,14 +94819,14 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
93828
94819
|
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
93829
94820
|
|
|
93830
94821
|
/**
|
|
93831
|
-
* @type {import("
|
|
94822
|
+
* @type {import("preact").FunctionComponent<{
|
|
93832
94823
|
* name?: string,
|
|
93833
94824
|
* value?: string,
|
|
93834
94825
|
* defaultValue?: string,
|
|
93835
94826
|
* signal?: import("@preact/signals").Signal<string>,
|
|
93836
94827
|
* minuteStep?: number,
|
|
93837
94828
|
* loop?: boolean,
|
|
93838
|
-
* separator?: import("
|
|
94829
|
+
* separator?: import("preact").ComponentChildren,
|
|
93839
94830
|
* hourLabel?: string,
|
|
93840
94831
|
* minuteLabel?: string,
|
|
93841
94832
|
* size?: string,
|
|
@@ -93856,7 +94847,7 @@ const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
|
93856
94847
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
93857
94848
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
93858
94849
|
* past.
|
|
93859
|
-
* @param {import("
|
|
94850
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
93860
94851
|
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
93861
94852
|
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
93862
94853
|
* is written by default outside French.
|
|
@@ -93975,7 +94966,7 @@ const TimeWheel = ({
|
|
|
93975
94966
|
};
|
|
93976
94967
|
|
|
93977
94968
|
/**
|
|
93978
|
-
* @type {import("
|
|
94969
|
+
* @type {import("preact").FunctionComponent<{
|
|
93979
94970
|
* name?: string,
|
|
93980
94971
|
* value?: { start?: string, end?: string },
|
|
93981
94972
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -93984,15 +94975,15 @@ const TimeWheel = ({
|
|
|
93984
94975
|
* minDuration?: number,
|
|
93985
94976
|
* loop?: boolean,
|
|
93986
94977
|
* size?: string,
|
|
93987
|
-
* startLabel?: import("
|
|
93988
|
-
* endLabel?: import("
|
|
94978
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
94979
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
93989
94980
|
* labelPosition?: "before" | "above",
|
|
93990
94981
|
* timeProps?: object,
|
|
93991
94982
|
* [key: string]: any,
|
|
93992
94983
|
* }>}
|
|
93993
94984
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
93994
94985
|
* "HH:MM".
|
|
93995
|
-
* @param {import("
|
|
94986
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
93996
94987
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
93997
94988
|
* `null` for neither.
|
|
93998
94989
|
* @param {"before"|"above"} [labelPosition="before"] Where each label stands.
|
|
@@ -97977,17 +98968,25 @@ const Address = ({
|
|
|
97977
98968
|
};
|
|
97978
98969
|
|
|
97979
98970
|
installImportMetaCssBuild(import.meta);
|
|
97980
|
-
const css$a = /* css */`@layer navi
|
|
98971
|
+
const css$a = /* css */`@layer navi {
|
|
98972
|
+
.navi_text.navi_badge_count {
|
|
98973
|
+
--font-size: .7em;
|
|
98974
|
+
--badge-count-padding-x-default: .5em;
|
|
98975
|
+
--badge-count-padding-y-default: .3em;
|
|
98976
|
+
|
|
98977
|
+
&[data-ellipse][data-single-char], &[data-circle] {
|
|
98978
|
+
--badge-count-padding-x-default: 0;
|
|
98979
|
+
--badge-count-padding-y-default: 0;
|
|
98980
|
+
}
|
|
98981
|
+
}
|
|
98982
|
+
}
|
|
97981
98983
|
|
|
97982
98984
|
.navi_text.navi_badge_count {
|
|
97983
98985
|
white-space: nowrap;
|
|
97984
|
-
--font-size: .7em;
|
|
97985
98986
|
--x-background: var(--background);
|
|
97986
|
-
--x-background-color: var(--background-color
|
|
98987
|
+
--x-background-color: var(--background-color);
|
|
97987
98988
|
--x-color-contrasting: var(--navi-color-white);
|
|
97988
98989
|
--x-color: var(--color, var(--x-color-contrasting));
|
|
97989
|
-
--badge-count-padding-x-default: .5em;
|
|
97990
|
-
--badge-count-padding-y-default: .3em;
|
|
97991
98990
|
--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))));
|
|
97992
98991
|
--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))));
|
|
97993
98992
|
--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))));
|
|
@@ -98019,12 +99018,9 @@ const css$a = /* css */`@layer navi;
|
|
|
98019
99018
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
98020
99019
|
padding-left: var(--x-badge-count-padding-left);
|
|
98021
99020
|
background: var(--x-background);
|
|
98022
|
-
background-color: var(--x-background-color);
|
|
98023
99021
|
border-radius: 1em;
|
|
98024
99022
|
|
|
98025
99023
|
&[data-single-char] {
|
|
98026
|
-
--badge-count-padding-x-default: 0;
|
|
98027
|
-
--badge-count-padding-y-default: 0;
|
|
98028
99024
|
box-sizing: content-box;
|
|
98029
99025
|
text-align: center;
|
|
98030
99026
|
border-radius: 100em;
|
|
@@ -98037,8 +99033,6 @@ const css$a = /* css */`@layer navi;
|
|
|
98037
99033
|
|
|
98038
99034
|
&[data-circle] {
|
|
98039
99035
|
--x-number-font-size: var(--font-size);
|
|
98040
|
-
--badge-count-padding-x-default: 0;
|
|
98041
|
-
--badge-count-padding-y-default: 0;
|
|
98042
99036
|
box-sizing: content-box;
|
|
98043
99037
|
aspect-ratio: 1;
|
|
98044
99038
|
width: var(--x-radius);
|
|
@@ -98048,7 +99042,6 @@ const css$a = /* css */`@layer navi;
|
|
|
98048
99042
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
98049
99043
|
padding-left: var(--x-badge-count-padding-left);
|
|
98050
99044
|
background: var(--x-background);
|
|
98051
|
-
background-color: var(--x-background-color);
|
|
98052
99045
|
border-radius: 100em;
|
|
98053
99046
|
justify-content: center;
|
|
98054
99047
|
align-items: center;
|
|
@@ -98073,6 +99066,10 @@ const css$a = /* css */`@layer navi;
|
|
|
98073
99066
|
font-size: var(--x-number-font-size);
|
|
98074
99067
|
}
|
|
98075
99068
|
}
|
|
99069
|
+
|
|
99070
|
+
&[data-background-color] {
|
|
99071
|
+
background-color: var(--x-background-color);
|
|
99072
|
+
}
|
|
98076
99073
|
}
|
|
98077
99074
|
`;
|
|
98078
99075
|
const BadgeCountOverflow = () => jsx("span", {
|
|
@@ -98209,6 +99206,7 @@ const BadgeCountEllipse = ({
|
|
|
98209
99206
|
"data-ellipse": "",
|
|
98210
99207
|
"data-value-overflow": hasOverflow ? "" : undefined,
|
|
98211
99208
|
"data-loading": loading ? "" : undefined,
|
|
99209
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
98212
99210
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
98213
99211
|
...props,
|
|
98214
99212
|
styleCSSVars: BadgeCountStyleCSSVars,
|
|
@@ -98231,6 +99229,7 @@ const BadgeCountCircle = ({
|
|
|
98231
99229
|
"data-circle": "",
|
|
98232
99230
|
bold: true,
|
|
98233
99231
|
"data-loading": loading ? "" : undefined,
|
|
99232
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
98234
99233
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
98235
99234
|
"data-two-chars": charCount === 2 ? "" : undefined,
|
|
98236
99235
|
"data-three-chars": charCount === 3 ? "" : undefined,
|
|
@@ -98723,7 +99722,7 @@ const css$8 = /* css */`@layer navi;
|
|
|
98723
99722
|
`;
|
|
98724
99723
|
|
|
98725
99724
|
/**
|
|
98726
|
-
* @type {import("
|
|
99725
|
+
* @type {import("preact").FunctionComponent<{
|
|
98727
99726
|
* children?: number | string,
|
|
98728
99727
|
* unit?: string | [string, string],
|
|
98729
99728
|
* unitPosition?: "right" | "bottom",
|
|
@@ -99315,8 +100314,8 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
99315
100314
|
*
|
|
99316
100315
|
* @param {Object} props - Component properties
|
|
99317
100316
|
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
99318
|
-
* @param {import("
|
|
99319
|
-
* @returns {import("
|
|
100317
|
+
* @param {import("preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
100318
|
+
* @returns {import("preact").VNode} A composed SVG with all elements properly masked
|
|
99320
100319
|
*/
|
|
99321
100320
|
const SVGMaskOverlay = ({
|
|
99322
100321
|
viewBox,
|
|
@@ -99406,7 +100405,6 @@ const css$3 = /* css */`@layer navi {
|
|
|
99406
100405
|
padding-bottom: var(--layout-padding-bottom, var(--layout-padding-y, var(--layout-padding)));
|
|
99407
100406
|
padding-left: var(--layout-padding-left, var(--layout-padding-x, var(--layout-padding)));
|
|
99408
100407
|
background: var(--layout-background);
|
|
99409
|
-
background-color: var(--layout-background-color, var(--layout-background));
|
|
99410
100408
|
border-width: var(--layout-border-width);
|
|
99411
100409
|
border-style: solid;
|
|
99412
100410
|
border-color: var(--layout-border-color);
|
|
@@ -99423,7 +100421,6 @@ const CardLayoutStyleCSSVars = {
|
|
|
99423
100421
|
borderWidth: "--layout-border-width",
|
|
99424
100422
|
borderColor: "--layout-border-color",
|
|
99425
100423
|
background: "--layout-background",
|
|
99426
|
-
backgroundColor: "--layout-background-color",
|
|
99427
100424
|
padding: "--layout-padding",
|
|
99428
100425
|
paddingTop: "--layout-padding-top",
|
|
99429
100426
|
paddingBottom: "--layout-padding-bottom",
|
|
@@ -99438,7 +100435,7 @@ const CardLayoutStyleCSSVars = {
|
|
|
99438
100435
|
* what holds it, so how tall that area is is up to the page around it, as for
|
|
99439
100436
|
* `ViewportLayout`.
|
|
99440
100437
|
*
|
|
99441
|
-
* @type {import("
|
|
100438
|
+
* @type {import("preact").FunctionComponent<{
|
|
99442
100439
|
* alignX?: string,
|
|
99443
100440
|
* alignY?: string,
|
|
99444
100441
|
* margin?: string|number,
|
|
@@ -99463,6 +100460,7 @@ const CardLayout = ({
|
|
|
99463
100460
|
children,
|
|
99464
100461
|
alignX = "center",
|
|
99465
100462
|
alignY = "center",
|
|
100463
|
+
backgroundColor,
|
|
99466
100464
|
...props
|
|
99467
100465
|
}) => {
|
|
99468
100466
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
@@ -99481,6 +100479,7 @@ const CardLayout = ({
|
|
|
99481
100479
|
className: "navi_card",
|
|
99482
100480
|
alignX: alignX,
|
|
99483
100481
|
alignY: alignY,
|
|
100482
|
+
backgroundColor: backgroundColor,
|
|
99484
100483
|
children: children
|
|
99485
100484
|
})
|
|
99486
100485
|
});
|
|
@@ -99654,7 +100653,7 @@ const LINE_GAP = 5;
|
|
|
99654
100653
|
const StepListContext = createContext(null);
|
|
99655
100654
|
|
|
99656
100655
|
/**
|
|
99657
|
-
* @type {import("
|
|
100656
|
+
* @type {import("preact").FunctionComponent<{
|
|
99658
100657
|
* current?: string,
|
|
99659
100658
|
* slideContainer?: string,
|
|
99660
100659
|
* travelByClick?: boolean,
|
|
@@ -100011,7 +101010,7 @@ const sameShallow = (previousObject, nextObject) => {
|
|
|
100011
101010
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
100012
101011
|
* SlideContainer.
|
|
100013
101012
|
*
|
|
100014
|
-
* @type {import("
|
|
101013
|
+
* @type {import("preact").FunctionComponent<{
|
|
100015
101014
|
* value: string,
|
|
100016
101015
|
* done?: boolean,
|
|
100017
101016
|
* [key: string]: any,
|
|
@@ -100073,7 +101072,7 @@ const ViewportLayoutStyleCSSVars = {
|
|
|
100073
101072
|
* same inside a route, a preview pane or a demo box, and it is up to the page
|
|
100074
101073
|
* around it to be full-height.
|
|
100075
101074
|
*
|
|
100076
|
-
* @type {import("
|
|
101075
|
+
* @type {import("preact").FunctionComponent<{
|
|
100077
101076
|
* padding?: string|number,
|
|
100078
101077
|
* paddingTop?: string|number,
|
|
100079
101078
|
* paddingBottom?: string|number,
|
|
@@ -100298,7 +101297,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
100298
101297
|
* `closeByPressOutside`/`pressOutside` only say what a
|
|
100299
101298
|
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
100300
101299
|
* doc).
|
|
100301
|
-
* @param {import("
|
|
101300
|
+
* @param {import("preact").ComponentChildren} props.children - No built-in
|
|
100302
101301
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
100303
101302
|
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
100304
101303
|
* `closeByPressOutside`/Escape instead.
|
|
@@ -100397,7 +101396,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
100397
101396
|
* @param {object} props
|
|
100398
101397
|
* @param {string} [props.className] - Merged with the shared
|
|
100399
101398
|
* `"navi_side_panel_head"` class this file's own CSS targets.
|
|
100400
|
-
* @param {import("
|
|
101399
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
100401
101400
|
*/
|
|
100402
101401
|
const SidePanelHead = props => jsx(Box, {
|
|
100403
101402
|
baseClassName: "navi_side_panel_head",
|
|
@@ -100410,7 +101409,7 @@ const SidePanelHead = props => jsx(Box, {
|
|
|
100410
101409
|
* @param {object} props
|
|
100411
101410
|
* @param {string} [props.className] - Merged with the shared
|
|
100412
101411
|
* `"navi_side_panel_foot"` class this file's own CSS targets.
|
|
100413
|
-
* @param {import("
|
|
101412
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
100414
101413
|
*/
|
|
100415
101414
|
const SidePanelFoot = props => jsx(Box, {
|
|
100416
101415
|
className: "navi_side_panel_foot",
|
|
@@ -100419,6 +101418,58 @@ const SidePanelFoot = props => jsx(Box, {
|
|
|
100419
101418
|
SidePanel.Head = SidePanelHead;
|
|
100420
101419
|
SidePanel.Foot = SidePanelFoot;
|
|
100421
101420
|
|
|
101421
|
+
/**
|
|
101422
|
+
* Creates a linked `[Slot, SlotFill]` pair so content rendered anywhere in
|
|
101423
|
+
* the tree (`SlotFill`) appears at a fixed location elsewhere (`Slot`) — a
|
|
101424
|
+
* lightweight, signal-based alternative to a DOM portal: `SlotFill` itself
|
|
101425
|
+
* renders nothing (no extra DOM node at its own call site), it just writes
|
|
101426
|
+
* its props into a shared signal that `Slot` reads reactively.
|
|
101427
|
+
*
|
|
101428
|
+
* Holds at most one filler at a time — there is no stacking/queueing. The
|
|
101429
|
+
* filler is the last `SlotFill` (from this same `createSlot()` call) to
|
|
101430
|
+
* mount, or the first to render into an empty slot; later renders of a
|
|
101431
|
+
* `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
|
|
101432
|
+
* unmounting empties the slot only while it is still that one, and so does one
|
|
101433
|
+
* in a page a route transition keeps while leaving (see keepLeavingPages in
|
|
101434
|
+
* nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
|
|
101435
|
+
* take it back from the page arriving.
|
|
101436
|
+
*
|
|
101437
|
+
* **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
|
|
101438
|
+
* the fills as they mount and unmount, in the order the tree walks them. A
|
|
101439
|
+
* slot fed from several places — a `SlotFill` in each card of a board, each
|
|
101440
|
+
* card deciding on its own whether it is the open one — reads as empty
|
|
101441
|
+
* between the leaver and the newcomer whenever they render separately, and
|
|
101442
|
+
* anything acting on `isFilled` acts on that empty frame (a `SidePanel` with
|
|
101443
|
+
* `open={isFilled}` closes for good: its `onClose` clears the state the
|
|
101444
|
+
* newcomer was waiting for). The slot cannot tell that gap from a real
|
|
101445
|
+
* emptying, and does not try to: render one `SlotFill` at the level that
|
|
101446
|
+
* holds the choice, with the chosen content as its children, and bind a
|
|
101447
|
+
* state that must not blink (a panel's `open`) to the app's own signal
|
|
101448
|
+
* rather than to `isFilled` — see docs/popup_open.md, "One panel, fed by a
|
|
101449
|
+
* slot".
|
|
101450
|
+
*
|
|
101451
|
+
* `Slot` keeps `SlotRenderer` mounted permanently — even while unfilled it
|
|
101452
|
+
* still renders it, with no props and `isFilled={false}` — instead of
|
|
101453
|
+
* unmounting it. This lets `SlotRenderer` be a persistent wrapper (a
|
|
101454
|
+
* `SidePanel` that opens and closes with an animation instead of being
|
|
101455
|
+
* mounted/unmounted alongside the filler — see 7_slot_demo.html's "Two
|
|
101456
|
+
* side-panel slots" section). A `SlotRenderer` that wants the
|
|
101457
|
+
* render-nothing-when-unfilled behavior can opt in with its own
|
|
101458
|
+
* `if (!isFilled) return null;` (the default `Box` renderer doesn't do
|
|
101459
|
+
* this — an unfilled default slot just renders an empty `<Box/>`).
|
|
101460
|
+
*
|
|
101461
|
+
* @param {import("preact").ComponentType} [SlotRenderer=Box] - Rendered by
|
|
101462
|
+
* `Slot`, always, with whatever props `SlotFill` last set (spread
|
|
101463
|
+
* directly) plus `isFilled`. Swap this out to reuse the same slot
|
|
101464
|
+
* mechanism for something other than a plain `Box` (e.g. a specific
|
|
101465
|
+
* component expecting its own particular props, or a persistent wrapper
|
|
101466
|
+
* like `SidePanel`).
|
|
101467
|
+
* @returns {[import("preact").ComponentType, import("preact").ComponentType<object>]}
|
|
101468
|
+
* `[Slot, SlotFill]` — `Slot` takes no props, render it once wherever the
|
|
101469
|
+
* content should appear. `SlotFill` takes whatever props `SlotRenderer`
|
|
101470
|
+
* expects, render it anywhere else in the tree to supply/update that
|
|
101471
|
+
* content.
|
|
101472
|
+
*/
|
|
100422
101473
|
const createSlot = (SlotRenderer = Box) => {
|
|
100423
101474
|
const slotPropsSignal = signal();
|
|
100424
101475
|
let filler = null;
|