@jsenv/navi 0.29.477 → 0.29.479
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +1810 -706
- package/dist/dev/jsenv_navi.js.map +40 -38
- package/dist/jsenv_navi.js +2239 -576
- package/dist/jsenv_navi.js.map +40 -38
- package/docs/AI_INSTRUCTIONS.md +3 -1
- package/docs/actions.md +23 -0
- package/docs/drag_to_travel.md +9 -3
- package/docs/form_changed.md +4 -3
- package/docs/interactions.md +8 -4
- package/docs/navigation.md +31 -2
- package/docs/pan_zoom.md +6 -4
- package/docs/popup_backdrop.md +5 -0
- package/docs/popup_lift.md +4 -3
- package/docs/route_transitions.md +7 -3
- package/docs/scroll.md +5 -5
- package/docs/testid.md +8 -5
- package/docs/view_transitions.md +3 -2
- package/package.json +3 -3
package/dist/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, 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, onAncestorReopen,
|
|
6
|
+
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, 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, onAncestorReopen, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange, getVirtualKeyboardOverlayHeight } 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, useReducer, useEffect, useCallback, useId } from "preact/hooks";
|
|
11
|
-
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay,
|
|
11
|
+
import { humanizeI18n, 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";
|
|
@@ -3873,6 +3873,11 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3873
3873
|
* asks nothing.
|
|
3874
3874
|
*/
|
|
3875
3875
|
const bindParams = (newParamsOrSignal, options = {}) => {
|
|
3876
|
+
if (newParamsOrSignal === null) {
|
|
3877
|
+
// A signal's `null` becomes `undefined` in mergeActionParams; a static
|
|
3878
|
+
// one never goes through it, so it is said the same way here.
|
|
3879
|
+
newParamsOrSignal = undefined;
|
|
3880
|
+
}
|
|
3876
3881
|
const { debounce, ...optionsWithoutDebounce } = options;
|
|
3877
3882
|
if (debounce) {
|
|
3878
3883
|
if (!isSignal(newParamsOrSignal)) {
|
|
@@ -4460,7 +4465,9 @@ const createActionProxyFromSignal = (
|
|
|
4460
4465
|
const inheritsAnswer =
|
|
4461
4466
|
inheritData && getActionPrivateProperties(action).inheritData;
|
|
4462
4467
|
const createTarget = (params) => {
|
|
4463
|
-
|
|
4468
|
+
// `undefined` is "nothing to ask": no answer is coming to replace a stand-in,
|
|
4469
|
+
// and that one instance is shared by every binding of the action.
|
|
4470
|
+
if (inheritsAnswer && params !== undefined) {
|
|
4464
4471
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
4465
4472
|
const previousTarget = previousActionTarget || action;
|
|
4466
4473
|
return action.bindParams(params, {
|
|
@@ -6274,9 +6281,11 @@ const CONTROL_ATTRIBUTE_SET = new Set([
|
|
|
6274
6281
|
"spellcheck",
|
|
6275
6282
|
"autoCorrect",
|
|
6276
6283
|
"aria-controls",
|
|
6277
|
-
// The name goes where the role is: the root box has none,
|
|
6278
|
-
// left on it names nothing at all, while the host is what
|
|
6279
|
-
// and what a screen reader announces (and what
|
|
6284
|
+
// The name goes where the role is: for most controls the root box has none,
|
|
6285
|
+
// so an aria-label left on it names nothing at all, while the host is what
|
|
6286
|
+
// the user focuses and what a screen reader announces (and what
|
|
6287
|
+
// getByRole({ name }) reads). A control whose role is on its root says so
|
|
6288
|
+
// with `nameOn: "root"` (see useControlProps).
|
|
6280
6289
|
"aria-label",
|
|
6281
6290
|
"aria-labelledby",
|
|
6282
6291
|
"tabIndex",
|
|
@@ -6803,7 +6812,7 @@ const useDebugUIState = useDebugNoop;
|
|
|
6803
6812
|
* @param {boolean|Function} [props.debugPopup] - Popover/dialog open/close/positioning.
|
|
6804
6813
|
* @param {boolean|Function} [props.debugAction] - Action lifecycle.
|
|
6805
6814
|
* @param {boolean|Function} [props.debugUIState] - UI-state transitions and validation.
|
|
6806
|
-
* @param {import("
|
|
6815
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
6807
6816
|
*
|
|
6808
6817
|
* @example
|
|
6809
6818
|
* // Log everything under this subtree
|
|
@@ -7020,9 +7029,11 @@ const generateSignalId = () => {
|
|
|
7020
7029
|
* @param {Array} [options.oneOf] - Array of valid values for validation. Signal will be marked invalid if value is not in this array
|
|
7021
7030
|
* @param {boolean} [options.autoFix=false] - Hold the repaired value instead of the refused one: a number
|
|
7022
7031
|
* clamped to `min`/`max` and rounded to `step`, a value outside `oneOf` replaced by the signal's
|
|
7023
|
-
* default value
|
|
7024
|
-
*
|
|
7025
|
-
*
|
|
7032
|
+
* default value, or made absent when the signal has no default (an enumeration has no nearest
|
|
7033
|
+
* value, and its order says nothing). The default must therefore pass the rules itself: one of
|
|
7034
|
+
* `oneOf`. Without it the refused value is held as-is, marked invalid, and `validSignal` reads
|
|
7035
|
+
* `undefined`. When the value came from the address, the address is rewritten with the repaired
|
|
7036
|
+
* one (a replace).
|
|
7026
7037
|
* @param {boolean} [options.weak=false] - The param qualifies one visit, not the screen: it is written into a
|
|
7027
7038
|
* url only when explicitly named (`routeParams={{ edit: id }}`), never inherited from the signal's current
|
|
7028
7039
|
* value, and it goes back to the default value when the route stops matching. Use it for params like an
|
|
@@ -7164,7 +7175,7 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7164
7175
|
|
|
7165
7176
|
// Determine localStorage key: use id if persists=true, or legacy localStorage option
|
|
7166
7177
|
const localStorageKey = signalIdString;
|
|
7167
|
-
const [validity, updateValidity] = createValidity({
|
|
7178
|
+
const [validity, updateValidity, clearValidity] = createValidity({
|
|
7168
7179
|
type,
|
|
7169
7180
|
min,
|
|
7170
7181
|
max,
|
|
@@ -7280,9 +7291,19 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7280
7291
|
// Create signal with initial value: use stored value, or undefined to indicate no explicit value
|
|
7281
7292
|
const processValue = (value) => {
|
|
7282
7293
|
if (value === undefined) {
|
|
7294
|
+
// Absence is never validated, and the verdict on the previous value
|
|
7295
|
+
// (validSignal, validity.valid) must not outlive it.
|
|
7296
|
+
clearValidity();
|
|
7283
7297
|
return undefined;
|
|
7284
7298
|
}
|
|
7285
7299
|
updateValidity(value);
|
|
7300
|
+
// A state without a default is repaired to absent. validity cannot suggest
|
|
7301
|
+
// it: `undefined` is "no suggestion" there and its rules refuse it.
|
|
7302
|
+
if (autoFix && !validity.valid && getDefaultValue(false) === undefined) {
|
|
7303
|
+
clearValidity();
|
|
7304
|
+
validity.autoFixed = true;
|
|
7305
|
+
return undefined;
|
|
7306
|
+
}
|
|
7286
7307
|
// Always return the coerced value (type coercion applies), even if invalid.
|
|
7287
7308
|
// Invalid values are preserved as-is so the UI can display them and the URL
|
|
7288
7309
|
// can reflect the current input state without silently correcting it.
|
|
@@ -7573,6 +7594,18 @@ const readSignalForUrlBuild = (connection) => {
|
|
|
7573
7594
|
return connection.signal.value;
|
|
7574
7595
|
};
|
|
7575
7596
|
|
|
7597
|
+
/**
|
|
7598
|
+
* Whether a pattern catches the addresses below its own: "/map/", "/map/*" and
|
|
7599
|
+
* the root "/" do, "/places" does not. Only such a route lends its search
|
|
7600
|
+
* params to the routes below it: it is on screen wherever they are, so its
|
|
7601
|
+
* state holds there too. Declared on "/places", a search param belongs to
|
|
7602
|
+
* that address alone — "/places/:section" beside it also matches
|
|
7603
|
+
* "/places/clubs", and borrowing the list's search would hand it to the clubs.
|
|
7604
|
+
*/
|
|
7605
|
+
const coversAddressesBelow = (patternObj) => {
|
|
7606
|
+
return patternObj.pattern.trailingSlash || patternObj.pattern.wildcard;
|
|
7607
|
+
};
|
|
7608
|
+
|
|
7576
7609
|
/**
|
|
7577
7610
|
* Creates a custom route pattern matcher
|
|
7578
7611
|
*/
|
|
@@ -7684,11 +7717,13 @@ const createRoutePattern = (
|
|
|
7684
7717
|
}
|
|
7685
7718
|
}
|
|
7686
7719
|
|
|
7687
|
-
//
|
|
7688
|
-
// Search params are global and should be inherited from any matching ancestor
|
|
7689
|
-
// regardless of path segment relationships
|
|
7720
|
+
// Search params lent by the ancestors covering this route
|
|
7690
7721
|
let ancestorPatternObj = patternObject.parent;
|
|
7691
7722
|
while (ancestorPatternObj) {
|
|
7723
|
+
if (!coversAddressesBelow(ancestorPatternObj)) {
|
|
7724
|
+
ancestorPatternObj = ancestorPatternObj.parent;
|
|
7725
|
+
continue;
|
|
7726
|
+
}
|
|
7692
7727
|
for (const [
|
|
7693
7728
|
paramName,
|
|
7694
7729
|
ancestorConnection,
|
|
@@ -7773,7 +7808,12 @@ const createRoutePattern = (
|
|
|
7773
7808
|
childParam,
|
|
7774
7809
|
childConnection,
|
|
7775
7810
|
] of childPatternObj.queryConnectionMap) {
|
|
7776
|
-
|
|
7811
|
+
// a name this route declares is its own state's, whatever a child
|
|
7812
|
+
// declares under the same name
|
|
7813
|
+
if (
|
|
7814
|
+
childParam in resolvedParams ||
|
|
7815
|
+
queryConnectionMap.has(childParam)
|
|
7816
|
+
) {
|
|
7777
7817
|
continue;
|
|
7778
7818
|
}
|
|
7779
7819
|
const childSignalValue = readSignalForUrlBuild(childConnection);
|
|
@@ -7819,7 +7859,9 @@ const createRoutePattern = (
|
|
|
7819
7859
|
* - own connection signals (path + query, inherited query included)
|
|
7820
7860
|
* - ancestor path params pinned by this pattern's own literals: being on
|
|
7821
7861
|
* "/admin/settings" MEANS section=settings, whatever the signal says
|
|
7822
|
-
* -
|
|
7862
|
+
* - custom values of the search params lent by the ancestors covering this
|
|
7863
|
+
* route (coversAddressesBelow). Another ancestor's search params ride
|
|
7864
|
+
* only on that ancestor's own url (see buildMostPreciseUrl)
|
|
7823
7865
|
* - reachable descendants' connection signals. A descendant is reachable
|
|
7824
7866
|
* when its literals agree with the state collected so far (a tab value
|
|
7825
7867
|
* is unreachable while section conflicts with the tab route's literals)
|
|
@@ -7945,6 +7987,10 @@ const createRoutePattern = (
|
|
|
7945
7987
|
setEntry(seg.name, selfSeg.value, conn, seg.index, "ancestor", false);
|
|
7946
7988
|
}
|
|
7947
7989
|
}
|
|
7990
|
+
if (!coversAddressesBelow(ancestor)) {
|
|
7991
|
+
ancestor = ancestor.parent;
|
|
7992
|
+
continue;
|
|
7993
|
+
}
|
|
7948
7994
|
for (const [name, conn] of ancestor.queryConnectionMap) {
|
|
7949
7995
|
if (intended.has(name)) {
|
|
7950
7996
|
continue;
|
|
@@ -8091,6 +8137,10 @@ const createRoutePattern = (
|
|
|
8091
8137
|
if (!queryReachable || value === undefined) {
|
|
8092
8138
|
continue;
|
|
8093
8139
|
}
|
|
8140
|
+
// a name this route declares is its own state's (see resolveParams)
|
|
8141
|
+
if (queryConnectionMap.has(paramName)) {
|
|
8142
|
+
continue;
|
|
8143
|
+
}
|
|
8094
8144
|
if (conn.isCustomValue(value)) {
|
|
8095
8145
|
setEntry(paramName, value, conn, undefined, "descendant", false);
|
|
8096
8146
|
}
|
|
@@ -8350,20 +8400,50 @@ const createRoutePattern = (
|
|
|
8350
8400
|
);
|
|
8351
8401
|
};
|
|
8352
8402
|
|
|
8403
|
+
// An ancestor's url is that ancestor's address: it carries the search params
|
|
8404
|
+
// declared there, including those it lends to nobody (coversAddressesBelow).
|
|
8405
|
+
// Each ancestor climbed past adds its own, so a higher one must keep them too.
|
|
8406
|
+
const withOwnSearchParams = (ancestorPattern, intended) => {
|
|
8407
|
+
let intendedWithOwn = intended;
|
|
8408
|
+
for (const [name, conn] of ancestorPattern.queryConnectionMap) {
|
|
8409
|
+
if (intendedWithOwn.has(name)) {
|
|
8410
|
+
continue;
|
|
8411
|
+
}
|
|
8412
|
+
const value = readSignalForUrlBuild(conn);
|
|
8413
|
+
if (value === undefined || !conn.isCustomValue(value)) {
|
|
8414
|
+
continue;
|
|
8415
|
+
}
|
|
8416
|
+
if (intendedWithOwn === intended) {
|
|
8417
|
+
intendedWithOwn = new Map(intended);
|
|
8418
|
+
}
|
|
8419
|
+
intendedWithOwn.set(name, {
|
|
8420
|
+
value,
|
|
8421
|
+
connection: conn,
|
|
8422
|
+
segmentIndex: undefined,
|
|
8423
|
+
owner: "ancestor",
|
|
8424
|
+
explicit: false,
|
|
8425
|
+
pageNaming: false,
|
|
8426
|
+
});
|
|
8427
|
+
}
|
|
8428
|
+
return intendedWithOwn;
|
|
8429
|
+
};
|
|
8430
|
+
|
|
8353
8431
|
const buildMostPreciseUrl = (params = {}) => {
|
|
8354
8432
|
const { intended, reachableDescendants } = buildIntendedState(params);
|
|
8355
8433
|
// ancestors: start at the immediate parent, climb while urls keep round-
|
|
8356
8434
|
// tripping, keep the highest one ("/" is never a target)
|
|
8357
8435
|
let ancestorUrl = null;
|
|
8358
8436
|
let ancestor = patternObject.parent;
|
|
8437
|
+
let ancestorIntended = intended;
|
|
8359
8438
|
while (ancestor && ancestor.originalPattern !== "/") {
|
|
8360
8439
|
if (!isAncestorCollapseAllowed(ancestor, intended)) {
|
|
8361
8440
|
break;
|
|
8362
8441
|
}
|
|
8363
|
-
|
|
8442
|
+
ancestorIntended = withOwnSearchParams(ancestor, ancestorIntended);
|
|
8443
|
+
const url = buildCandidateUrl(ancestor, ancestorIntended, {
|
|
8364
8444
|
dropMissing: true,
|
|
8365
8445
|
});
|
|
8366
|
-
if (!url || !verifyUrl(url,
|
|
8446
|
+
if (!url || !verifyUrl(url, ancestorIntended)) {
|
|
8367
8447
|
break;
|
|
8368
8448
|
}
|
|
8369
8449
|
debug$3(
|
|
@@ -9527,11 +9607,15 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9527
9607
|
}
|
|
9528
9608
|
routePattern.familyRoot = root;
|
|
9529
9609
|
}
|
|
9530
|
-
// Phase 3: Inherit search
|
|
9531
|
-
//
|
|
9610
|
+
// Phase 3: Inherit the search param connections of the ancestors covering
|
|
9611
|
+
// the addresses below them (see coversAddressesBelow)
|
|
9532
9612
|
for (const routePattern of routePatternSet) {
|
|
9533
9613
|
let ancestorRoutePattern = routePattern.parent;
|
|
9534
9614
|
while (ancestorRoutePattern) {
|
|
9615
|
+
if (!coversAddressesBelow(ancestorRoutePattern)) {
|
|
9616
|
+
ancestorRoutePattern = ancestorRoutePattern.parent;
|
|
9617
|
+
continue;
|
|
9618
|
+
}
|
|
9535
9619
|
// For each ancestor's query connection, check if it should be inherited
|
|
9536
9620
|
for (const [
|
|
9537
9621
|
paramName,
|
|
@@ -9641,23 +9725,6 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9641
9725
|
continue;
|
|
9642
9726
|
}
|
|
9643
9727
|
const { index } = paramSegment;
|
|
9644
|
-
const sharesPathUpTo = (otherSegments) => {
|
|
9645
|
-
for (let i = 0; i < index; i++) {
|
|
9646
|
-
const seg = routePattern.pattern.segments[i];
|
|
9647
|
-
const otherSeg = otherSegments[i];
|
|
9648
|
-
if (!otherSeg) {
|
|
9649
|
-
return false;
|
|
9650
|
-
}
|
|
9651
|
-
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
9652
|
-
if (seg.value !== otherSeg.value) {
|
|
9653
|
-
return false;
|
|
9654
|
-
}
|
|
9655
|
-
} else if (seg.type !== otherSeg.type) {
|
|
9656
|
-
return false;
|
|
9657
|
-
}
|
|
9658
|
-
}
|
|
9659
|
-
return true;
|
|
9660
|
-
};
|
|
9661
9728
|
for (const otherPattern of routePatternSet) {
|
|
9662
9729
|
if (otherPattern === routePattern) {
|
|
9663
9730
|
continue;
|
|
@@ -9667,7 +9734,9 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9667
9734
|
if (!otherSegment || otherSegment.type !== "literal") {
|
|
9668
9735
|
continue;
|
|
9669
9736
|
}
|
|
9670
|
-
if (
|
|
9737
|
+
if (
|
|
9738
|
+
!sharesPathUpTo(routePattern.pattern.segments, otherSegments, index)
|
|
9739
|
+
) {
|
|
9671
9740
|
continue;
|
|
9672
9741
|
}
|
|
9673
9742
|
connection.namedByLiteralRoutes = true;
|
|
@@ -9675,11 +9744,90 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9675
9744
|
}
|
|
9676
9745
|
}
|
|
9677
9746
|
}
|
|
9747
|
+
// Phase 4c: A default with an address of its own. "/me/:section" defaulting
|
|
9748
|
+
// to "players", beside "/me/players" and "/me": the default section has a
|
|
9749
|
+
// segment, and the bare address is another page. The param is then required,
|
|
9750
|
+
// and the address writes every value, the default included. Both routes must
|
|
9751
|
+
// be declared: with none at the bare address, that address is the default's
|
|
9752
|
+
// ("/admin" opens on "/admin/settings/:tab"); with no literal for the default,
|
|
9753
|
+
// the bare address IS the default section ("/games/me").
|
|
9754
|
+
// The state keeps its default (getDefaultValue): what the signal holds while
|
|
9755
|
+
// another page of the family says nothing about it (see route.js).
|
|
9756
|
+
for (const routePattern of routePatternSet) {
|
|
9757
|
+
const { segments } = routePattern.pattern;
|
|
9758
|
+
for (const connection of routePattern.pathConnectionMap.values()) {
|
|
9759
|
+
const defaultValue = connection.getDefaultValue();
|
|
9760
|
+
if (defaultValue === undefined) {
|
|
9761
|
+
continue;
|
|
9762
|
+
}
|
|
9763
|
+
const paramSegment = segments.find(
|
|
9764
|
+
(seg) => seg.type === "param" && seg.name === connection.paramName,
|
|
9765
|
+
);
|
|
9766
|
+
if (!paramSegment) {
|
|
9767
|
+
continue;
|
|
9768
|
+
}
|
|
9769
|
+
const { index } = paramSegment;
|
|
9770
|
+
let defaultHasLiteral = false;
|
|
9771
|
+
let bareAddressTaken = false;
|
|
9772
|
+
for (const otherPattern of routePatternSet) {
|
|
9773
|
+
if (otherPattern === routePattern) {
|
|
9774
|
+
continue;
|
|
9775
|
+
}
|
|
9776
|
+
const otherSegments = otherPattern.pattern.segments;
|
|
9777
|
+
if (!sharesPathUpTo(segments, otherSegments, index)) {
|
|
9778
|
+
continue;
|
|
9779
|
+
}
|
|
9780
|
+
const otherSegment = otherSegments[index];
|
|
9781
|
+
if (otherSegment) {
|
|
9782
|
+
if (
|
|
9783
|
+
otherSegment.type === "literal" &&
|
|
9784
|
+
otherSegment.value === String(defaultValue)
|
|
9785
|
+
) {
|
|
9786
|
+
defaultHasLiteral = true;
|
|
9787
|
+
}
|
|
9788
|
+
continue;
|
|
9789
|
+
}
|
|
9790
|
+
// a route covering the addresses below it is a frame around the
|
|
9791
|
+
// sections, not a page standing at the bare address
|
|
9792
|
+
if (!coversAddressesBelow(otherPattern)) {
|
|
9793
|
+
bareAddressTaken = true;
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
if (!defaultHasLiteral || !bareAddressTaken) {
|
|
9797
|
+
continue;
|
|
9798
|
+
}
|
|
9799
|
+
paramSegment.optional = false;
|
|
9800
|
+
// Both answer for the address: the url build, the signal -> url sync,
|
|
9801
|
+
// and checkIfLiteralCanBeOptionalWithPatternObj ("/me" does not stand for
|
|
9802
|
+
// "/me/players" either)
|
|
9803
|
+
connection.isDefaultValue = (value) => value === undefined;
|
|
9804
|
+
connection.isCustomValue = (value) => value !== undefined;
|
|
9805
|
+
}
|
|
9806
|
+
}
|
|
9678
9807
|
// Phase 5: Calculate depths for all patterns
|
|
9679
9808
|
for (const routePattern of routePatternSet) {
|
|
9680
9809
|
calculatePatternDepth(routePattern);
|
|
9681
9810
|
}
|
|
9682
9811
|
};
|
|
9812
|
+
// Whether two patterns spell the same path before `index`: the same literals,
|
|
9813
|
+
// and a param wherever the other has one
|
|
9814
|
+
const sharesPathUpTo = (segments, otherSegments, index) => {
|
|
9815
|
+
for (let i = 0; i < index; i++) {
|
|
9816
|
+
const seg = segments[i];
|
|
9817
|
+
const otherSeg = otherSegments[i];
|
|
9818
|
+
if (!otherSeg) {
|
|
9819
|
+
return false;
|
|
9820
|
+
}
|
|
9821
|
+
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
9822
|
+
if (seg.value !== otherSeg.value) {
|
|
9823
|
+
return false;
|
|
9824
|
+
}
|
|
9825
|
+
} else if (seg.type !== otherSeg.type) {
|
|
9826
|
+
return false;
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
return true;
|
|
9830
|
+
};
|
|
9683
9831
|
// Store the most specific parent (closest parent in hierarchy)
|
|
9684
9832
|
const getPathSegmentCount = (pattern) => {
|
|
9685
9833
|
// Only count path segments, not query parameters
|
|
@@ -9741,6 +9889,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9741
9889
|
* are the same state: writing the signal rewrites the address, an address
|
|
9742
9890
|
* arriving from outside writes the signal, and the param disappears from the
|
|
9743
9891
|
* url while the signal holds its default.
|
|
9892
|
+
* They exist on this route's address only, unless the pattern catches the
|
|
9893
|
+
* addresses below it (`/map/`, `/map/*`, the root): then they hold on every
|
|
9894
|
+
* route below as well. `/places` does not lend its `?q=` to `/places/clubs`.
|
|
9744
9895
|
* `{ signal, default }` says what that state is worth ON THIS ROUTE: the same
|
|
9745
9896
|
* step opens a creation wizard on its first question and an edition one on
|
|
9746
9897
|
* its summary, each address staying clean on the step its page opens on. One
|
|
@@ -9759,7 +9910,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9759
9910
|
* whose param declines the segment does not match at all: no signal is
|
|
9760
9911
|
* written, `<Route fallback>` is reachable, and `/:gameId` can sit at the root
|
|
9761
9912
|
* without swallowing `/cgu`. A constrained param is also required — no segment
|
|
9762
|
-
* is not one of the values it accepts — so `/:gameId` does not match
|
|
9913
|
+
* is not one of the values it accepts — so `/:gameId` does not match `/`,
|
|
9914
|
+
* unless its default stands for the bare address (see docs/navigation.md,
|
|
9915
|
+
* "Declaring the sections is what makes them places").
|
|
9763
9916
|
*
|
|
9764
9917
|
* Constrain the shape, not the existence: whether that game exists is for the
|
|
9765
9918
|
* route action and the page to answer, on a route that did match.
|
|
@@ -10942,14 +11095,17 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
10942
11095
|
*
|
|
10943
11096
|
* - `isShown()` — whether the container would still show the page. False as
|
|
10944
11097
|
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
10945
|
-
* hidden until the route transition leaving it is over,
|
|
10946
|
-
*
|
|
10947
|
-
*
|
|
10948
|
-
*
|
|
11098
|
+
* hidden until the route transition leaving it is over, nothing it reads is
|
|
11099
|
+
* worth a render any more, and nothing it holds speaks for the page shown (a
|
|
11100
|
+
* list's hold on the document, see holdDocumentScroll). False for good once
|
|
11101
|
+
* it is kept while leaving: a return to its route mounts the page anew. Asked
|
|
11102
|
+
* outside any render — by a subscription deciding whether to re-render, by
|
|
11103
|
+
* the scroll restoration when a position is due — and it reads the routes
|
|
10949
11104
|
* untracked, so asking subscribes nobody to them.
|
|
10950
11105
|
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
10951
|
-
* plays: still mounted, so what it fills outside its own nodes (a
|
|
10952
|
-
* bar) steps out rather than showing
|
|
11106
|
+
* plays: still mounted, so what it fills or moves outside its own nodes (a
|
|
11107
|
+
* slot in a bar, the document a list scrolls) steps out rather than showing
|
|
11108
|
+
* with, or moving, the page arriving. Every box of it measures 0×0 by then.
|
|
10953
11109
|
*/
|
|
10954
11110
|
const RoutePageContext = createContext(null);
|
|
10955
11111
|
|
|
@@ -11872,7 +12028,7 @@ const putBack = (restoration) => {
|
|
|
11872
12028
|
// The page is not whole yet: the last release puts it back.
|
|
11873
12029
|
return;
|
|
11874
12030
|
}
|
|
11875
|
-
if (
|
|
12031
|
+
if (isDocumentHeld()) {
|
|
11876
12032
|
pendingRestoration = null;
|
|
11877
12033
|
return;
|
|
11878
12034
|
}
|
|
@@ -11888,20 +12044,33 @@ const putBack = (restoration) => {
|
|
|
11888
12044
|
// height, so the same pixels now fall on other rows. Put back after the list
|
|
11889
12045
|
// has placed itself, they would undo it, in the very picture a route transition
|
|
11890
12046
|
// takes. So the list holds the document while it is placing itself, and the
|
|
11891
|
-
// url's offset is not put back meanwhile.
|
|
11892
|
-
// document.
|
|
11893
|
-
|
|
11894
|
-
|
|
11895
|
-
|
|
11896
|
-
|
|
12047
|
+
// url's offset is not put back meanwhile. One hold per list: two lists can
|
|
12048
|
+
// share one document.
|
|
12049
|
+
//
|
|
12050
|
+
// A hold is for the page its list is on, and mounted is not shown: a page kept
|
|
12051
|
+
// while its route transition plays still has its list when the page returned to
|
|
12052
|
+
// renders, and that list says nothing about where the page arriving goes. So a
|
|
12053
|
+
// hold is taken with the list's page (RoutePageContext), asked whether it is
|
|
12054
|
+
// shown when a position is due — `null` outside any route container: that list
|
|
12055
|
+
// is on whatever page is shown. Not a url: which page an address shows is the
|
|
12056
|
+
// routes' to say, and one page can be shown under several (its params moving
|
|
12057
|
+
// under it).
|
|
12058
|
+
const documentHolds = new Set();
|
|
12059
|
+
const holdDocumentScroll = (routePage) => {
|
|
12060
|
+
const hold = { routePage };
|
|
12061
|
+
documentHolds.add(hold);
|
|
11897
12062
|
return () => {
|
|
11898
|
-
|
|
11899
|
-
return;
|
|
11900
|
-
}
|
|
11901
|
-
released = true;
|
|
11902
|
-
documentHoldCount--;
|
|
12063
|
+
documentHolds.delete(hold);
|
|
11903
12064
|
};
|
|
11904
12065
|
};
|
|
12066
|
+
const isDocumentHeld = () => {
|
|
12067
|
+
for (const { routePage } of documentHolds) {
|
|
12068
|
+
if (routePage === null || routePage.isShown()) {
|
|
12069
|
+
return true;
|
|
12070
|
+
}
|
|
12071
|
+
}
|
|
12072
|
+
return false;
|
|
12073
|
+
};
|
|
11905
12074
|
|
|
11906
12075
|
// What a page holds instead: not the place, which stays the url's, but the
|
|
11907
12076
|
// moment it is put back (see useHoldScrollRestoration). Counted: several parts
|
|
@@ -12159,9 +12328,9 @@ const restoreScrollPosition = (url) => {
|
|
|
12159
12328
|
if (!position) {
|
|
12160
12329
|
return false;
|
|
12161
12330
|
}
|
|
12162
|
-
if (
|
|
12163
|
-
// There is a place to go back to, and the list holding the document
|
|
12164
|
-
// putting it back (see holdDocumentScroll).
|
|
12331
|
+
if (isDocumentHeld()) {
|
|
12332
|
+
// There is a place to go back to, and the list holding the document on
|
|
12333
|
+
// that page is putting it back (see holdDocumentScroll).
|
|
12165
12334
|
return true;
|
|
12166
12335
|
}
|
|
12167
12336
|
scrollTo(position);
|
|
@@ -12430,6 +12599,25 @@ const getHrefTargetInfo = (
|
|
|
12430
12599
|
};
|
|
12431
12600
|
};
|
|
12432
12601
|
|
|
12602
|
+
// The target of a link whose caller names none: a page of another site opens
|
|
12603
|
+
// in a new tab. Only a page: a `tel:`, `mailto:` or `sms:` address is handed to
|
|
12604
|
+
// another app, a `blob:` or `data:` one is this page's own, and a tab opened
|
|
12605
|
+
// for them would be left behind empty.
|
|
12606
|
+
const getDefaultHrefTarget = (
|
|
12607
|
+
href,
|
|
12608
|
+
currentUrlString = window.location.href,
|
|
12609
|
+
) => {
|
|
12610
|
+
const { isEmpty, isSameSite } = getHrefTargetInfo(href, currentUrlString);
|
|
12611
|
+
if (isEmpty || isSameSite) {
|
|
12612
|
+
return undefined;
|
|
12613
|
+
}
|
|
12614
|
+
const { protocol } = new URL(href, currentUrlString);
|
|
12615
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
12616
|
+
return undefined;
|
|
12617
|
+
}
|
|
12618
|
+
return "_blank";
|
|
12619
|
+
};
|
|
12620
|
+
|
|
12433
12621
|
// A field of getHrefTargetInfo that depends on the page one is on (isCurrent,
|
|
12434
12622
|
// isAnchor), read through a computed of that one boolean: the caller re-renders
|
|
12435
12623
|
// when it flips, not at every address write — a search param bound to a wheel
|
|
@@ -12470,6 +12658,27 @@ const linkAsksForDocument = (linkElement) => {
|
|
|
12470
12658
|
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
12471
12659
|
};
|
|
12472
12660
|
|
|
12661
|
+
/*
|
|
12662
|
+
* A link does not always move the tab it is in. A `target` naming another
|
|
12663
|
+
* browsing context (a new tab, a named window) loads the address there, in a
|
|
12664
|
+
* document of its own; a `download` saves it. The browser answers both
|
|
12665
|
+
* without touching this page, and that is the point of writing them: reading
|
|
12666
|
+
* the terms beside a half-filled form costs nothing. Routing such a link
|
|
12667
|
+
* would take this tab instead, and give no tab and no file.
|
|
12668
|
+
*
|
|
12669
|
+
* `_parent` and `_top` count as elsewhere too. Inside a frame they are; at the
|
|
12670
|
+
* top level the browser treats them as `_self` and loads a document, which
|
|
12671
|
+
* costs a load and nothing else.
|
|
12672
|
+
*/
|
|
12673
|
+
|
|
12674
|
+
const linkOpensElsewhere = (linkElement) => {
|
|
12675
|
+
const target = linkElement.getAttribute("target");
|
|
12676
|
+
if (target && target !== "_self") {
|
|
12677
|
+
return true;
|
|
12678
|
+
}
|
|
12679
|
+
return linkElement.hasAttribute("download");
|
|
12680
|
+
};
|
|
12681
|
+
|
|
12473
12682
|
/*
|
|
12474
12683
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
12475
12684
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -12799,8 +13008,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12799
13008
|
// Ignore non-left clicks
|
|
12800
13009
|
return;
|
|
12801
13010
|
}
|
|
12802
|
-
if (e.metaKey) {
|
|
12803
|
-
//
|
|
13011
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
13012
|
+
// A modifier asks the browser for something other than this tab: a new
|
|
13013
|
+
// tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
|
|
13014
|
+
// (alt).
|
|
12804
13015
|
return;
|
|
12805
13016
|
}
|
|
12806
13017
|
if (e.defaultPrevented) {
|
|
@@ -12811,6 +13022,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12811
13022
|
if (linkElement.hasAttribute("data-readonly")) {
|
|
12812
13023
|
return;
|
|
12813
13024
|
}
|
|
13025
|
+
if (linkOpensElsewhere(linkElement)) {
|
|
13026
|
+
// Another tab or window, or a download (see link_elsewhere.js).
|
|
13027
|
+
return;
|
|
13028
|
+
}
|
|
12814
13029
|
const href = linkElement.href;
|
|
12815
13030
|
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
12816
13031
|
getHrefTargetInfo(href);
|
|
@@ -16912,6 +17127,9 @@ const surfaceTextCss = /* css */ `
|
|
|
16912
17127
|
}
|
|
16913
17128
|
`;
|
|
16914
17129
|
|
|
17130
|
+
// What a callout's own JSX may know about the callout around it: how to close
|
|
17131
|
+
// it, and the element — whose id is what `commandFor` needs when the button
|
|
17132
|
+
// closing it is not inside it (inside, `--navi-close` finds it on its own).
|
|
16915
17133
|
const CalloutContext = createContext();
|
|
16916
17134
|
const useCalloutRequestClose = () => {
|
|
16917
17135
|
return useContext(CalloutContext)?.requestClose;
|
|
@@ -17045,7 +17263,7 @@ const CALLOUT_STATUS_GLYPH_VIEWBOX = "0 0 540 540";
|
|
|
17045
17263
|
const CALLOUT_STATUS_GLYPH_PATH = "m232.5,121 8,196h59l8-196zm36.5,224a37,37 0 1,0 2,0z";
|
|
17046
17264
|
|
|
17047
17265
|
/**
|
|
17048
|
-
* @type {import("
|
|
17266
|
+
* @type {import("preact").FunctionComponent<{
|
|
17049
17267
|
* status?: "info" | "warning" | "error" | "success" | "none",
|
|
17050
17268
|
* shape?: "square" | "circle",
|
|
17051
17269
|
* }>}
|
|
@@ -21864,23 +22082,12 @@ defineInteractionDetector({
|
|
|
21864
22082
|
// A link and an image are draggable without anyone asking, and a native
|
|
21865
22083
|
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
21866
22084
|
// events with it and paints a ghost of the row the hand is trying to swipe.
|
|
21867
|
-
//
|
|
21868
|
-
//
|
|
21869
|
-
//
|
|
21870
|
-
//
|
|
21871
|
-
//
|
|
21872
|
-
// browser honours (`-webkit-user-drag` is one engine's).
|
|
21873
|
-
// The cost is stated rather than worked around: an element that takes a
|
|
21874
|
-
// swipe cannot also be dragged out of the page, because there is one
|
|
21875
|
-
// gesture and it cannot mean both.
|
|
22085
|
+
// `draggable` refuses it on the element itself, before it starts; the drag
|
|
22086
|
+
// of a link or a thumbnail INSIDE it is refused by the travel reading the
|
|
22087
|
+
// press (startDragToTravel). The cost is stated rather than worked around:
|
|
22088
|
+
// an element that takes a swipe cannot also be dragged out of the page,
|
|
22089
|
+
// because there is one gesture and it cannot mean both.
|
|
21876
22090
|
mark("draggable", "false");
|
|
21877
|
-
const onDragStart = (dragStartEvent) => {
|
|
21878
|
-
dragStartEvent.preventDefault();
|
|
21879
|
-
};
|
|
21880
|
-
element.addEventListener("dragstart", onDragStart);
|
|
21881
|
-
undo.push(() => {
|
|
21882
|
-
element.removeEventListener("dragstart", onDragStart);
|
|
21883
|
-
});
|
|
21884
22091
|
|
|
21885
22092
|
// A touch this element may take has to be refusable before the finger
|
|
21886
22093
|
// lands, or the browser can cancel an established swipe mid-gesture by
|
|
@@ -22915,9 +23122,10 @@ const isMac = detectMac();
|
|
|
22915
23122
|
* place rather than about this box.
|
|
22916
23123
|
*
|
|
22917
23124
|
* `data-pan-after-hold="kept"` asks for that wait once rather than before every
|
|
22918
|
-
* pan: the surface given the hand keeps it — panning on contact,
|
|
22919
|
-
* plan opened full screen — and asks again after a press has
|
|
22920
|
-
* it, which is the hand saying it has moved on.
|
|
23125
|
+
* pan: the surface a finger has given the hand keeps it — panning on contact,
|
|
23126
|
+
* like the same plan opened full screen — and asks again after a press has
|
|
23127
|
+
* landed away from it, which is the hand saying it has moved on. A mouse never
|
|
23128
|
+
* owed the wait, so it neither spends it nor marks it. What answers "away" is the same
|
|
22921
23129
|
* thing that closes a popup, and the surface is the one that watches for it, so
|
|
22922
23130
|
* an application never keeps a `pointerdown` listener on the window to know.
|
|
22923
23131
|
* `[data-hand-kept]` is on the element for as long as it holds the hand that
|
|
@@ -24039,7 +24247,8 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
24039
24247
|
* and that field is the control the declaration speaks for: an ArrowUp declared
|
|
24040
24248
|
* on a text field is the field saying what ArrowUp does there, not a container
|
|
24041
24249
|
* taking the caret from a field inside it. A field inside a box that declares
|
|
24042
|
-
* keys for something else (a row, a list) keeps every key it has a use for
|
|
24250
|
+
* keys for something else (a row, a list) keeps every key it has a use for,
|
|
24251
|
+
* even when it is the only control in that box.
|
|
24043
24252
|
*
|
|
24044
24253
|
* A declared key that matches takes the key: its default does not happen as
|
|
24045
24254
|
* well, whether the gate lets the interaction through or refuses it and says
|
|
@@ -24065,7 +24274,10 @@ defineInteractionDetector({
|
|
|
24065
24274
|
};
|
|
24066
24275
|
}
|
|
24067
24276
|
const onKeyDown = createOnKeyDownForShortcuts(shortcuts, {
|
|
24068
|
-
|
|
24277
|
+
// Only the control's own root speaks for its host: a box that wraps a
|
|
24278
|
+
// single control is reached by that control's gate, not handed its keys.
|
|
24279
|
+
ownsTarget: (target) =>
|
|
24280
|
+
isControlRoot(element) && target === findNearestControlHost(element),
|
|
24069
24281
|
});
|
|
24070
24282
|
element.addEventListener("keydown", onKeyDown);
|
|
24071
24283
|
return () => {
|
|
@@ -28133,6 +28345,127 @@ const useForceRender = () => {
|
|
|
28133
28345
|
|
|
28134
28346
|
// https://github.com/preactjs/preact/issues/4756
|
|
28135
28347
|
|
|
28348
|
+
/**
|
|
28349
|
+
* Reads the current state of an action and returns `[data, loading, error, dismissError]`.
|
|
28350
|
+
*
|
|
28351
|
+
* By default (`loading` and `error` not set) the component suspends on load
|
|
28352
|
+
* and throws on failure, delegating both states to the nearest `<Loading>` and
|
|
28353
|
+
* `<ErrorBoundary>` ancestors. Pass `loading: true` or `error: true` to handle
|
|
28354
|
+
* either state directly inside the component instead.
|
|
28355
|
+
*
|
|
28356
|
+
* Return value: `[data, loading, error, dismissError]`
|
|
28357
|
+
* - `data` — the action's last successful data, or `undefined` if it has never completed
|
|
28358
|
+
* - `loading` — `true` while the action is running (only when `loading: true` is passed)
|
|
28359
|
+
* - `error` — the Error thrown by the action (only when `error: true` is passed)
|
|
28360
|
+
* - `dismissError` — takes that error out of what this hook returns, nothing else (only when `error: true` is passed)
|
|
28361
|
+
*
|
|
28362
|
+
* Stale data is always returned: the previous `data` remains available while
|
|
28363
|
+
* the action re-runs and after a run has failed, so a screen that had something
|
|
28364
|
+
* to show never has it taken away. What is drawn over it — a loading indicator,
|
|
28365
|
+
* the error, nothing — is entirely up to the component.
|
|
28366
|
+
*
|
|
28367
|
+
* `loading` covers the delay of a debounced binding
|
|
28368
|
+
* (`bindParams(paramsSignal, { debounce })`, `actionRunEffect({ debounce })`)
|
|
28369
|
+
* as well as the run itself: from the moment the params move, what is on screen
|
|
28370
|
+
* is the previous answer, and a screen showing "searching…" while someone types
|
|
28371
|
+
* needs to know it. Suspending has nothing to wait on during that delay, so
|
|
28372
|
+
* only `loading: true` reads it — the default keeps showing the previous answer
|
|
28373
|
+
* until the run really starts.
|
|
28374
|
+
*
|
|
28375
|
+
* When `loading` is not set (default), the component suspends until data is
|
|
28376
|
+
* ready, so `data` is always defined when the component renders and `loading`
|
|
28377
|
+
* is always `false`. With `loading: true` the component never suspends: every
|
|
28378
|
+
* state — running, settling, params nobody has started yet, a failure whose
|
|
28379
|
+
* message was dismissed — comes back as a value, so the subtree it draws is
|
|
28380
|
+
* never swapped for the boundary above and remounted.
|
|
28381
|
+
*
|
|
28382
|
+
* When `error` is not set (default), any action failure causes the component
|
|
28383
|
+
* to throw, so `error` is always `undefined` when the component renders. With
|
|
28384
|
+
* `error: true` the failure arrives beside the data the action already had — a
|
|
28385
|
+
* message on the content, not one in place of it. `dismissError()` removes the
|
|
28386
|
+
* message and nothing else: nothing is asked again, and what comes back is the
|
|
28387
|
+
* same state minus the error — the same data, or none at all when nothing ever
|
|
28388
|
+
* came back.
|
|
28389
|
+
*
|
|
28390
|
+
* `data === undefined` says there is nothing to show and `loading` says whether
|
|
28391
|
+
* anything is on its way: a skeleton reads both, or it claims a load nobody
|
|
28392
|
+
* started (see docs/data_states.md).
|
|
28393
|
+
*
|
|
28394
|
+
* @param {object | (() => Promise<any>) | Promise<any>} promiseOrAction -
|
|
28395
|
+
* an action, or the request this component owns written as a function:
|
|
28396
|
+
* made into an action once, on the first render, and kept for the life of
|
|
28397
|
+
* the instance — so it may be written inline — with what it resolves to as
|
|
28398
|
+
* the data and `run` implied. Code loaded on demand reads this way
|
|
28399
|
+
* (`() => import("./plan.jsx").then((m) => m.Plan)`, see
|
|
28400
|
+
* docs/dynamic_import.md), as does anything else asked for once per mount.
|
|
28401
|
+
* @param {{ loading?: true, error?: true, run?: true, prerun?: true, onLoad?: (data: any, context: {params: any}) => void }} [options]
|
|
28402
|
+
* @param {true} [options.run] - The fallback for data nothing else can ask for:
|
|
28403
|
+
* this component owns the request and starts it, from the render that reads
|
|
28404
|
+
* it. `loading` and `error` keep their meaning — delegated by default,
|
|
28405
|
+
* handled inline when asked for — so a run started here suspends into
|
|
28406
|
+
* `<Loading>` like any other.
|
|
28407
|
+
*
|
|
28408
|
+
* A failure is shown, not asked again: a render that retried it would retry
|
|
28409
|
+
* it in a loop while the network is down — that is `rerun()`'s job. Except a
|
|
28410
|
+
* failure a prerun left (a `prerun: true` head start, a press that preran
|
|
28411
|
+
* it): nothing on screen asked for that run, so this asks again, once.
|
|
28412
|
+
*
|
|
28413
|
+
* Prefer a `routeAction` wherever the parameter is one the address holds (a
|
|
28414
|
+
* path param, a search param bound to a `stateSignal`), popup included: it is
|
|
28415
|
+
* asked for when the address changes, with everything else that address
|
|
28416
|
+
* needs, whereas this one cannot start before the component that draws it
|
|
28417
|
+
* exists — one render late, and behind whatever gesture mounted it. What is
|
|
28418
|
+
* left for `run` is the parameter chosen inside the component and dying with
|
|
28419
|
+
* it — and any run that WRITES, whatever its parameter: an address asks again
|
|
28420
|
+
* on every arrival, which is right for a read and wrong for a write (see
|
|
28421
|
+
* docs/actions.md and docs/popup_open.md).
|
|
28422
|
+
* @param {true} [options.prerun] - A head start: this component starts a
|
|
28423
|
+
* read that another one, mounted later, owns with `run: true` — the read
|
|
28424
|
+
* leaves as soon as its params are known instead of once the owner exists.
|
|
28425
|
+
* Started from the render like `run`, as a prerun: it draws none of it, so
|
|
28426
|
+
* nothing suspends (no `<Loading>` needed) and a failure is neither thrown
|
|
28427
|
+
* nor reported — the owner asks again. A head start still running when the
|
|
28428
|
+
* owner reads it is the owner's wait.
|
|
28429
|
+
* @param {(data: any, context: {params: any}) => void} [options.onLoad] - what
|
|
28430
|
+
* this screen does with the data ONCE, when it becomes known: seed the fields
|
|
28431
|
+
* someone is about to edit, remember where a list was, focus something.
|
|
28432
|
+
*
|
|
28433
|
+
* Fired once per set of params — never again for a rerun that brings the same
|
|
28434
|
+
* thing back. That is the whole point of it: a save, a refresh, a poll all
|
|
28435
|
+
* hand the data over again, and copying it a second time would overwrite what
|
|
28436
|
+
* the person is in the middle of writing. The action knows what it ran with,
|
|
28437
|
+
* so nobody has to guess the dependency.
|
|
28438
|
+
*
|
|
28439
|
+
* Called from a layout effect, so what it writes belongs to the same tick as
|
|
28440
|
+
* the render that got the data — a `<Form pristineKey>` taking its reference
|
|
28441
|
+
* then sees the filled fields rather than the empty ones.
|
|
28442
|
+
* @returns {[data: unknown, loading: boolean, error: Error | undefined, dismissError: (() => void) | undefined]}
|
|
28443
|
+
*
|
|
28444
|
+
* @example <caption>Default — delegate both states (least code)</caption>
|
|
28445
|
+
* const [user] = useAsyncData(userAction);
|
|
28446
|
+
* // user is always defined here — never loading, never error
|
|
28447
|
+
*
|
|
28448
|
+
* @example <caption>Handle loading inline while keeping stale data</caption>
|
|
28449
|
+
* const [user, loading] = useAsyncData(userAction, { loading: true });
|
|
28450
|
+
* // First load: user === undefined, loading === true → show skeleton
|
|
28451
|
+
* // While refreshing: user === <stale>, loading === true → show stale + spinner
|
|
28452
|
+
* // Done: user === <fresh>, loading === false
|
|
28453
|
+
*
|
|
28454
|
+
* @example <caption>Handle error inline</caption>
|
|
28455
|
+
* const [user, , error] = useAsyncData(userAction, { error: true });
|
|
28456
|
+
* if (error) return <ErrorCard message={error.message} />;
|
|
28457
|
+
*
|
|
28458
|
+
* @example <caption>Say the failure over the rows already on screen</caption>
|
|
28459
|
+
* const [items, , error, dismissError] = useAsyncData(listAction, { error: true });
|
|
28460
|
+
* // First failure: items === undefined → only the message
|
|
28461
|
+
* // Failed refresh: items === <stale> → the message above the rows
|
|
28462
|
+
* return (
|
|
28463
|
+
* <>
|
|
28464
|
+
* {error && <ErrorStrip error={error} onClose={dismissError} />}
|
|
28465
|
+
* {items && <List items={items} />}
|
|
28466
|
+
* </>
|
|
28467
|
+
* );
|
|
28468
|
+
*/
|
|
28136
28469
|
const useAsyncData = (promiseOrAction, {
|
|
28137
28470
|
loading = "delegate",
|
|
28138
28471
|
error = "delegate",
|
|
@@ -28575,7 +28908,7 @@ const LoadingFallback = ({
|
|
|
28575
28908
|
* ones that would render fine.
|
|
28576
28909
|
*
|
|
28577
28910
|
* @param {object} props
|
|
28578
|
-
* @param {Function|import("
|
|
28911
|
+
* @param {Function|import("preact").VNode} [props.fallback] - what is displayed
|
|
28579
28912
|
* instead of the children: an element, or a component receiving
|
|
28580
28913
|
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
28581
28914
|
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
@@ -28934,6 +29267,12 @@ const hasWidthHeight = (context) => {
|
|
|
28934
29267
|
(context.styles.height || context.remainingProps.height)
|
|
28935
29268
|
);
|
|
28936
29269
|
};
|
|
29270
|
+
// The `stretch` sizing keyword: the containing block's width minus the box's
|
|
29271
|
+
// own margins, so a box with a margin fills its row without leaving it (100%
|
|
29272
|
+
// adds the margins on top). Spelled with the prefix because an inline style
|
|
29273
|
+
// holds one value, and this is the one name Chromium, WebKit and Firefox all
|
|
29274
|
+
// read (Firefox 155 has no unprefixed `stretch`).
|
|
29275
|
+
const FILL_AVAILABLE_WIDTH = "-webkit-fill-available";
|
|
28937
29276
|
const DIMENSION_PROPS = {
|
|
28938
29277
|
boxSizing: PASS_THROUGH,
|
|
28939
29278
|
width: PASS_THROUGH,
|
|
@@ -28986,16 +29325,16 @@ const DIMENSION_PROPS = {
|
|
|
28986
29325
|
return { flexGrow: expandWeight(value), flexBasis: "0%" };
|
|
28987
29326
|
}
|
|
28988
29327
|
if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
|
|
28989
|
-
// width
|
|
29328
|
+
// A width is what fills the cross axis; align-self stretch would do
|
|
28990
29329
|
// the same but takes the parent's alignX away — a maxWidth-capped item
|
|
28991
29330
|
// in an alignX="center" parent would then sit at the start instead of
|
|
28992
29331
|
// in the middle.
|
|
28993
29332
|
return {
|
|
28994
|
-
width:
|
|
29333
|
+
width: FILL_AVAILABLE_WIDTH,
|
|
28995
29334
|
};
|
|
28996
29335
|
}
|
|
28997
29336
|
// Can't use flexGrow — parent is not flex-x
|
|
28998
|
-
return { width:
|
|
29337
|
+
return { width: FILL_AVAILABLE_WIDTH };
|
|
28999
29338
|
},
|
|
29000
29339
|
expandY: (value, { parentBoxFlow }) => {
|
|
29001
29340
|
if (!value) {
|
|
@@ -29556,6 +29895,7 @@ const getVisualChildStylePropStrategy = (name) => {
|
|
|
29556
29895
|
};
|
|
29557
29896
|
|
|
29558
29897
|
const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
|
|
29898
|
+
const isTypoProp = (name) => TYPO_PROP_NAME_SET.has(name);
|
|
29559
29899
|
|
|
29560
29900
|
const getStringifier = (key) => {
|
|
29561
29901
|
if (SPACING_PROP_SET.has(key)) {
|
|
@@ -30197,10 +30537,10 @@ const readCSSVarName = entry => Array.isArray(entry) ? entry[0] : entry;
|
|
|
30197
30537
|
const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
30198
30538
|
|
|
30199
30539
|
/**
|
|
30200
|
-
* @type {import("
|
|
30540
|
+
* @type {import("preact").FunctionComponent<{
|
|
30201
30541
|
* as?: string,
|
|
30202
30542
|
* className?: string,
|
|
30203
|
-
* style?: import("
|
|
30543
|
+
* style?: import("preact").JSX.CSSProperties & { [pseudo: string]: import("preact").JSX.CSSProperties },
|
|
30204
30544
|
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
30205
30545
|
* inline?: boolean,
|
|
30206
30546
|
* block?: boolean,
|
|
@@ -30215,7 +30555,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30215
30555
|
* hasChildUsingForwardedProps?: boolean,
|
|
30216
30556
|
* childPropSet?: Set<string>,
|
|
30217
30557
|
* preventInitialTransition?: boolean,
|
|
30218
|
-
* separator?: import("
|
|
30558
|
+
* separator?: import("preact").ComponentChildren | ((index: number) => import("preact").ComponentChildren),
|
|
30219
30559
|
* selfInteractions?: string,
|
|
30220
30560
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
30221
30561
|
* alignX?: string,
|
|
@@ -30227,7 +30567,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30227
30567
|
* body?: boolean,
|
|
30228
30568
|
* sticky?: boolean,
|
|
30229
30569
|
* mount?: "always" | "after-paint",
|
|
30230
|
-
* children?: import("
|
|
30570
|
+
* children?: import("preact").ComponentChildren,
|
|
30231
30571
|
* [key: string]: any,
|
|
30232
30572
|
* }>}
|
|
30233
30573
|
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
@@ -32782,7 +33122,7 @@ const debug$1 = (...args) => {
|
|
|
32782
33122
|
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
32783
33123
|
* movement be written about it and its `matchingSignal` be read from outside
|
|
32784
33124
|
* the tree (see route_fallback.js).
|
|
32785
|
-
* @param {Function|import("
|
|
33125
|
+
* @param {Function|import("preact").VNode} [props.element] - what the branch renders
|
|
32786
33126
|
* @param {object} [props.elementProps] - props given to `element`
|
|
32787
33127
|
*
|
|
32788
33128
|
* A branch says what it renders, not what happens when it cannot: loading and
|
|
@@ -35131,7 +35471,7 @@ const css$13 = /* css */`@property --navi-route-transition-leave {
|
|
|
35131
35471
|
* that. An application that already has an element holding its pages can mark
|
|
35132
35472
|
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
35133
35473
|
*
|
|
35134
|
-
* @type {import("
|
|
35474
|
+
* @type {import("preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
35135
35475
|
*/
|
|
35136
35476
|
const RouteTransitionArea = ({
|
|
35137
35477
|
children,
|
|
@@ -35921,7 +36261,7 @@ const beginTransition = ({
|
|
|
35921
36261
|
warnAboutNamesEscapingArea(areaElement, capturedNames);
|
|
35922
36262
|
return;
|
|
35923
36263
|
}
|
|
35924
|
-
for (const name of capturedNames) {
|
|
36264
|
+
for (const name of capturedNames.keys()) {
|
|
35925
36265
|
if (name === "root") {
|
|
35926
36266
|
continue;
|
|
35927
36267
|
}
|
|
@@ -36124,9 +36464,10 @@ const ignoreSkipped$1 = () => {};
|
|
|
36124
36464
|
|
|
36125
36465
|
// The names the browser captured, read off the pictures themselves: what was
|
|
36126
36466
|
// asked for in CSS and what was taken are not the same question (see
|
|
36127
|
-
// viewTransitionReady).
|
|
36467
|
+
// viewTransitionReady). Each with the states it was taken in: a name taken in
|
|
36468
|
+
// only one of them pairs with nothing.
|
|
36128
36469
|
const capturedViewTransitionNames = () => {
|
|
36129
|
-
const names = new
|
|
36470
|
+
const names = new Map();
|
|
36130
36471
|
for (const animation of document.getAnimations()) {
|
|
36131
36472
|
const pseudoElement = animation.effect?.pseudoElement;
|
|
36132
36473
|
if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
|
|
@@ -36136,43 +36477,59 @@ const capturedViewTransitionNames = () => {
|
|
|
36136
36477
|
if (nameStart === -1) {
|
|
36137
36478
|
continue;
|
|
36138
36479
|
}
|
|
36139
|
-
|
|
36480
|
+
const name = pseudoElement.slice(nameStart + 1, -1);
|
|
36481
|
+
let states = names.get(name);
|
|
36482
|
+
if (!states) {
|
|
36483
|
+
states = {
|
|
36484
|
+
old: false,
|
|
36485
|
+
new: false
|
|
36486
|
+
};
|
|
36487
|
+
names.set(name, states);
|
|
36488
|
+
}
|
|
36489
|
+
if (pseudoElement.startsWith("::view-transition-old(")) {
|
|
36490
|
+
states.old = true;
|
|
36491
|
+
} else if (pseudoElement.startsWith("::view-transition-new(")) {
|
|
36492
|
+
states.new = true;
|
|
36493
|
+
}
|
|
36140
36494
|
}
|
|
36141
36495
|
return names;
|
|
36142
36496
|
};
|
|
36143
36497
|
|
|
36144
|
-
//
|
|
36145
|
-
//
|
|
36146
|
-
//
|
|
36147
|
-
//
|
|
36498
|
+
// A name written inside the area is a group of its own: it stands where it was
|
|
36499
|
+
// captured while the area's two pictures carry the movement, in every browser.
|
|
36500
|
+
// Nested groups only keep it cut at the pages' edge (see the @supports block in
|
|
36501
|
+
// the CSS above). Paired with the same name on the other page it is a morph;
|
|
36502
|
+
// taken in one state only it pairs with nothing, and is a piece of its page
|
|
36503
|
+
// left behind, fading, while that page moves.
|
|
36148
36504
|
const NESTED_GROUPS_SUPPORTED = window.CSS.supports("view-transition-group", "contain");
|
|
36149
36505
|
const warnAboutNamesEscapingArea = (areaElement, capturedNames) => {
|
|
36150
|
-
|
|
36151
|
-
|
|
36152
|
-
}
|
|
36153
|
-
let escapedName = null;
|
|
36154
|
-
for (const name of capturedNames) {
|
|
36506
|
+
const unpairedNames = new Set();
|
|
36507
|
+
for (const [name, states] of capturedNames) {
|
|
36155
36508
|
if (name === "root" || name === AREA_NAME || name.startsWith(FURNITURE_NAME_PREFIX)) {
|
|
36156
36509
|
continue;
|
|
36157
36510
|
}
|
|
36158
|
-
|
|
36159
|
-
|
|
36511
|
+
if (states.old && states.new) {
|
|
36512
|
+
continue;
|
|
36513
|
+
}
|
|
36514
|
+
unpairedNames.add(name);
|
|
36160
36515
|
}
|
|
36161
36516
|
// A name captured next to the area is not necessarily inside it — a bar the
|
|
36162
36517
|
// application animates on the same clock is named on purpose. The subtree is
|
|
36163
36518
|
// walked only once something is there to find, so the common case reads
|
|
36164
|
-
// nothing.
|
|
36165
|
-
|
|
36519
|
+
// nothing. The page being left is still in it, hidden (keepLeavingPages in
|
|
36520
|
+
// route.jsx), so a name taken only in the old state is found too.
|
|
36521
|
+
if (unpairedNames.size === 0) {
|
|
36166
36522
|
return;
|
|
36167
36523
|
}
|
|
36168
36524
|
for (const descendant of areaElement.querySelectorAll("*")) {
|
|
36169
36525
|
const {
|
|
36170
36526
|
viewTransitionName
|
|
36171
36527
|
} = getComputedStyle(descendant);
|
|
36172
|
-
if (!viewTransitionName
|
|
36528
|
+
if (!unpairedNames.has(viewTransitionName)) {
|
|
36173
36529
|
continue;
|
|
36174
36530
|
}
|
|
36175
|
-
|
|
36531
|
+
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.";
|
|
36532
|
+
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).`);
|
|
36176
36533
|
return;
|
|
36177
36534
|
}
|
|
36178
36535
|
};
|
|
@@ -36317,6 +36674,8 @@ const installRouteTransitionCss = () => {
|
|
|
36317
36674
|
};
|
|
36318
36675
|
|
|
36319
36676
|
installImportMetaCssBuild(import.meta);
|
|
36677
|
+
// The pictures carrying a travel, among everything else the movement takes
|
|
36678
|
+
// along (see findLeadAnimation).
|
|
36320
36679
|
const TRAVEL_LEAD_NAMES = ["navi-route-travel"];
|
|
36321
36680
|
|
|
36322
36681
|
// A browser with no view transitions of its own has no picture of the page
|
|
@@ -36584,7 +36943,7 @@ const css$12 = /* css */`.navi_route_travel {
|
|
|
36584
36943
|
`;
|
|
36585
36944
|
|
|
36586
36945
|
/**
|
|
36587
|
-
* @type {import("
|
|
36946
|
+
* @type {import("preact").FunctionComponent<{
|
|
36588
36947
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
36589
36948
|
* axis?: "x"|"y",
|
|
36590
36949
|
* travelByDrag?: boolean,
|
|
@@ -39361,7 +39720,7 @@ const resetCommand = (source, event) => {
|
|
|
39361
39720
|
// answers: "save" stays, "delete" goes back to the list. Same shape as the
|
|
39362
39721
|
// browser's own formaction/formmethod, and the same shape the action already
|
|
39363
39722
|
// has (it is told which button requested it).
|
|
39364
|
-
const resolveAfterSend = (target, requester) => {
|
|
39723
|
+
const resolveAfterSend = (target, requester, surface) => {
|
|
39365
39724
|
const askedForByRequester = requester?.getAttribute?.("data-after-send");
|
|
39366
39725
|
if (askedForByRequester) {
|
|
39367
39726
|
return askedForByRequester;
|
|
@@ -39370,11 +39729,6 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39370
39729
|
if (askedFor) {
|
|
39371
39730
|
return askedFor;
|
|
39372
39731
|
}
|
|
39373
|
-
// From above the target: a popup that IS the send target is handled on its
|
|
39374
|
-
// own (see the aria-expanded branch below), and must not answer twice.
|
|
39375
|
-
const surface = target.parentElement?.closest(
|
|
39376
|
-
`[data-slide], [aria-expanded]`,
|
|
39377
|
-
);
|
|
39378
39732
|
if (!surface) {
|
|
39379
39733
|
return undefined;
|
|
39380
39734
|
}
|
|
@@ -39386,6 +39740,11 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39386
39740
|
}
|
|
39387
39741
|
return undefined;
|
|
39388
39742
|
};
|
|
39743
|
+
// The surface a send answers when the control says nothing. Found from above
|
|
39744
|
+
// the target: a popup that IS the send target is handled on its own (see the
|
|
39745
|
+
// aria-expanded branch in sendCommand), and must not answer twice.
|
|
39746
|
+
const resolveSendSurface = (target) =>
|
|
39747
|
+
target.parentElement?.closest(`[data-slide], [aria-expanded]`);
|
|
39389
39748
|
// What follows a send is caused by the send, not by the gesture: the send
|
|
39390
39749
|
// answered the gesture and may have prevented it (an Enter must not also click
|
|
39391
39750
|
// the submit button, nor activate the trigger of the popup it closed), and a
|
|
@@ -39467,20 +39826,30 @@ const sendCommand = (source, event, { requester }) => {
|
|
|
39467
39826
|
// and the popup must stay open — with the form still in front of the
|
|
39468
39827
|
// user, showing what it is waiting for.
|
|
39469
39828
|
let invalid = false;
|
|
39470
|
-
// What follows the send is
|
|
39471
|
-
//
|
|
39472
|
-
//
|
|
39473
|
-
//
|
|
39474
|
-
//
|
|
39475
|
-
//
|
|
39829
|
+
// What follows the send is aimed at the press, like a button's command
|
|
39830
|
+
// waiting for its action (see resolveNaviCommand): the send may take the
|
|
39831
|
+
// form out of the document (a form keyed on what it edits, a row that
|
|
39832
|
+
// moves once saved), and resolved from there it finds no popup to close.
|
|
39833
|
+
// Which command it is gets read again once the send went through: an
|
|
39834
|
+
// action that learned where to go from the response writes it on the
|
|
39835
|
+
// form while it runs (data-after-send). That one can only be aimed when
|
|
39836
|
+
// it is read.
|
|
39837
|
+
const surface = resolveSendSurface(target);
|
|
39838
|
+
const sentEvent = createSentEvent(event);
|
|
39839
|
+
const afterSendAtPress = resolveAfterSend(target, sendRequester, surface);
|
|
39840
|
+
const runAimedAtPress = afterSendAtPress
|
|
39841
|
+
? resolveNaviCommand(source, afterSendAtPress, sentEvent)
|
|
39842
|
+
: undefined;
|
|
39476
39843
|
const runAfterSend = () => {
|
|
39477
|
-
const afterSend = resolveAfterSend(target, sendRequester);
|
|
39844
|
+
const afterSend = resolveAfterSend(target, sendRequester, surface);
|
|
39478
39845
|
if (!afterSend) {
|
|
39479
39846
|
return;
|
|
39480
39847
|
}
|
|
39481
|
-
|
|
39482
|
-
|
|
39483
|
-
|
|
39848
|
+
if (afterSend === afterSendAtPress) {
|
|
39849
|
+
runAimedAtPress?.();
|
|
39850
|
+
return;
|
|
39851
|
+
}
|
|
39852
|
+
triggerNaviCommand(source, afterSend, sentEvent);
|
|
39484
39853
|
};
|
|
39485
39854
|
const {
|
|
39486
39855
|
result: sent,
|
|
@@ -40419,6 +40788,12 @@ const isDeclaredInsidePopup = (target, popupId) =>
|
|
|
40419
40788
|
* Lives on the popup's own content element rather than in the backdrop's
|
|
40420
40789
|
* handler, for both renderers: the backdrop is a sibling behind the popup
|
|
40421
40790
|
* (never an ancestor), so a press inside the popup's box never reaches it.
|
|
40791
|
+
*
|
|
40792
|
+
* Inside a box that travels by drag — a row of slides walked by a swipe, a
|
|
40793
|
+
* sheet pushed back towards its edge — the region is also where that travel
|
|
40794
|
+
* starts, and at the mousedown nobody knows yet which of the two the press is.
|
|
40795
|
+
* The dismissal then waits for the click, which only comes if the press stayed
|
|
40796
|
+
* a press: a travel swallows the one it leaves behind.
|
|
40422
40797
|
*/
|
|
40423
40798
|
const handlePressOnOutsideRegion = (
|
|
40424
40799
|
mouseDownEvent,
|
|
@@ -40449,11 +40824,36 @@ const handlePressOnOutsideRegion = (
|
|
|
40449
40824
|
mouseDownEvent.preventDefault();
|
|
40450
40825
|
return;
|
|
40451
40826
|
}
|
|
40452
|
-
if (pressOutside
|
|
40453
|
-
|
|
40454
|
-
|
|
40827
|
+
if (pressOutside !== "close" && pressOutside !== "cancel") {
|
|
40828
|
+
return;
|
|
40829
|
+
}
|
|
40830
|
+
const isCancel = pressOutside === "cancel";
|
|
40831
|
+
if (isPressDisputedByDrag(target)) {
|
|
40832
|
+
onClickOfThisPress(target, (clickEvent) => {
|
|
40833
|
+
openController.requestClose(clickEvent, { isCancel });
|
|
40455
40834
|
});
|
|
40835
|
+
return;
|
|
40456
40836
|
}
|
|
40837
|
+
openController.requestClose(mouseDownEvent, { isCancel });
|
|
40838
|
+
};
|
|
40839
|
+
|
|
40840
|
+
// The click ending the press whose mousedown landed on `element`. A click goes
|
|
40841
|
+
// to what the press and the release have in common, so it reaches `element`
|
|
40842
|
+
// only when the release stayed on it or inside it. A press that never clicks
|
|
40843
|
+
// leaves it armed until the next one, and no longer — a click always comes
|
|
40844
|
+
// after a press, so it cannot reach the click of another (the rule
|
|
40845
|
+
// click_suppression.js in @jsenv/dom goes by).
|
|
40846
|
+
const onClickOfThisPress = (element, callback) => {
|
|
40847
|
+
const onClick = (clickEvent) => {
|
|
40848
|
+
disarm();
|
|
40849
|
+
callback(clickEvent);
|
|
40850
|
+
};
|
|
40851
|
+
const disarm = () => {
|
|
40852
|
+
element.removeEventListener("click", onClick);
|
|
40853
|
+
window.removeEventListener("pointerdown", disarm, { capture: true });
|
|
40854
|
+
};
|
|
40855
|
+
element.addEventListener("click", onClick);
|
|
40856
|
+
window.addEventListener("pointerdown", disarm, { capture: true });
|
|
40457
40857
|
};
|
|
40458
40858
|
|
|
40459
40859
|
/**
|
|
@@ -45661,6 +46061,9 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
45661
46061
|
* handles the timing centrally rather than each component having to manage its
|
|
45662
46062
|
* own debounce logic.
|
|
45663
46063
|
*/
|
|
46064
|
+
// Sentinel used as the initial value of lastActionValueRef.
|
|
46065
|
+
// Distinct from undefined so that undefined (e.g. unchecked radio) can itself
|
|
46066
|
+
// be stored as a valid "last action value" and trigger the dedup logic.
|
|
45664
46067
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
45665
46068
|
|
|
45666
46069
|
// The control types rendered as an element `<label for>` can name: HTML's
|
|
@@ -45760,13 +46163,18 @@ const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyD
|
|
|
45760
46163
|
* the control opens, so that a listener hears the control as a whole.
|
|
45761
46164
|
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
45762
46165
|
* host's either way: the control's own reactions to them call them.
|
|
46166
|
+
* @param {"host"|"root"} [options.nameOn] Where `aria-label` and
|
|
46167
|
+
* `aria-labelledby` land: where the role is. `"host"`, the default, for every
|
|
46168
|
+
* control whose host is what is announced; `"root"` for a control whose role
|
|
46169
|
+
* is on its root and whose host is an `aria-hidden` carrier (a wheel).
|
|
45763
46170
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
45764
46171
|
*/
|
|
45765
46172
|
const useControlProps = (props, {
|
|
45766
46173
|
controlType,
|
|
45767
46174
|
persists,
|
|
45768
46175
|
uiActionInternal,
|
|
45769
|
-
listenersOn = "host"
|
|
46176
|
+
listenersOn = "host",
|
|
46177
|
+
nameOn = "host"
|
|
45770
46178
|
}) => {
|
|
45771
46179
|
const debugUIState = useDebugUIState();
|
|
45772
46180
|
|
|
@@ -45854,6 +46262,7 @@ const useControlProps = (props, {
|
|
|
45854
46262
|
boundAction,
|
|
45855
46263
|
controlInfo,
|
|
45856
46264
|
listenersOn,
|
|
46265
|
+
nameOn,
|
|
45857
46266
|
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
45858
46267
|
});
|
|
45859
46268
|
{
|
|
@@ -47082,6 +47491,7 @@ const useInteractiveProps = (props, {
|
|
|
47082
47491
|
boundAction,
|
|
47083
47492
|
controlInfo,
|
|
47084
47493
|
listenersOn,
|
|
47494
|
+
nameOn,
|
|
47085
47495
|
listenersCalledByReactions
|
|
47086
47496
|
}) => {
|
|
47087
47497
|
const {
|
|
@@ -47089,6 +47499,7 @@ const useInteractiveProps = (props, {
|
|
|
47089
47499
|
} = props;
|
|
47090
47500
|
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
47091
47501
|
listenersOn,
|
|
47502
|
+
nameOn,
|
|
47092
47503
|
listenersCalledByReactions
|
|
47093
47504
|
});
|
|
47094
47505
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
@@ -47700,8 +48111,10 @@ const useInteractiveProps = (props, {
|
|
|
47700
48111
|
}
|
|
47701
48112
|
return [controlRootProps, controlHostProps];
|
|
47702
48113
|
};
|
|
48114
|
+
const NAME_ATTRIBUTE_SET = new Set(["aria-label", "aria-labelledby"]);
|
|
47703
48115
|
const splitControlProps = (props, {
|
|
47704
48116
|
listenersOn,
|
|
48117
|
+
nameOn,
|
|
47705
48118
|
listenersCalledByReactions
|
|
47706
48119
|
}) => {
|
|
47707
48120
|
const {
|
|
@@ -47712,6 +48125,10 @@ const splitControlProps = (props, {
|
|
|
47712
48125
|
};
|
|
47713
48126
|
const controlRootProps = {};
|
|
47714
48127
|
for (const key of Object.keys(props)) {
|
|
48128
|
+
if (nameOn === "root" && NAME_ATTRIBUTE_SET.has(key)) {
|
|
48129
|
+
controlRootProps[key] = props[key];
|
|
48130
|
+
continue;
|
|
48131
|
+
}
|
|
47715
48132
|
const attributeName = controlAttributeFromProp(key);
|
|
47716
48133
|
if (attributeName) {
|
|
47717
48134
|
controlHostProps[attributeName] = props[key];
|
|
@@ -49841,7 +50258,7 @@ const css$$ = /* css */`.navi_text_anchor {
|
|
|
49841
50258
|
* @param {{ size?: number, verticalAlign?: string }} [lineLayout]
|
|
49842
50259
|
* Describes the surrounding line context. Used as layout-effect dependencies so the correction
|
|
49843
50260
|
* reruns when the surrounding text's font-size or vertical-align changes.
|
|
49844
|
-
* @param {import("
|
|
50261
|
+
* @param {import("preact").RefObject} childRef — ref on the child element to reposition
|
|
49845
50262
|
*/
|
|
49846
50263
|
const TextAnchor = ({
|
|
49847
50264
|
childRef,
|
|
@@ -50503,25 +50920,25 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50503
50920
|
* See `docs/typography.md` for the decisions behind it — truncating, rows made
|
|
50504
50921
|
* of an icon, a text and an icon, and where a line may break.
|
|
50505
50922
|
*
|
|
50506
|
-
* @type {import("
|
|
50507
|
-
* children?: import("
|
|
50923
|
+
* @type {import("preact").FunctionComponent<{
|
|
50924
|
+
* children?: import("preact").ComponentChildren,
|
|
50508
50925
|
* as?: string,
|
|
50509
50926
|
* className?: string,
|
|
50510
|
-
* style?: import("
|
|
50927
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
50511
50928
|
* bold?: boolean,
|
|
50512
50929
|
* noWrap?: boolean,
|
|
50513
50930
|
* maxLines?: number,
|
|
50514
|
-
* spacing?: string | number | import("
|
|
50931
|
+
* spacing?: string | number | import("preact").ComponentChildren,
|
|
50515
50932
|
* loading?: boolean,
|
|
50516
50933
|
* skeleton?: boolean,
|
|
50517
50934
|
* attachLastChild?: boolean,
|
|
50518
50935
|
* preventSpaceUnderlines?: boolean,
|
|
50519
|
-
* holdSpaceForStyle?: import("
|
|
50936
|
+
* holdSpaceForStyle?: import("preact").JSX.CSSProperties,
|
|
50520
50937
|
* boldStable?: boolean,
|
|
50521
50938
|
* shrinkWrap?: boolean,
|
|
50522
50939
|
* capitalize?: boolean,
|
|
50523
50940
|
* selectRange?: string | [number, number] | [string, string],
|
|
50524
|
-
* childrenOutsideFlow?: import("
|
|
50941
|
+
* childrenOutsideFlow?: import("preact").ComponentChildren,
|
|
50525
50942
|
* [key: string]: any,
|
|
50526
50943
|
* }>}
|
|
50527
50944
|
*
|
|
@@ -50562,7 +50979,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50562
50979
|
* Replaces real space characters between children with padding-based spaces.
|
|
50563
50980
|
* Useful inside `<a>` elements where browsers draw an underline under spaces.
|
|
50564
50981
|
*
|
|
50565
|
-
* @param {import("
|
|
50982
|
+
* @param {import("preact").JSX.CSSProperties} [holdSpaceForStyle]
|
|
50566
50983
|
* Prevents layout shifts when text styles change (e.g. font-weight, font-size).
|
|
50567
50984
|
* Pass the CSS properties representing the "largest" visual state of the text.
|
|
50568
50985
|
* An invisible placeholder rendered with those styles reserves the space; the
|
|
@@ -50595,7 +51012,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50595
51012
|
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
50596
51013
|
* substrings selecting from the first to the end of the second.
|
|
50597
51014
|
*
|
|
50598
|
-
* @param {import("
|
|
51015
|
+
* @param {import("preact").ComponentChildren} [childrenOutsideFlow]
|
|
50599
51016
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
50600
51017
|
* internally for overlays such as the skeleton container.
|
|
50601
51018
|
*/
|
|
@@ -50883,7 +51300,7 @@ const TextWithSelectRange = ({
|
|
|
50883
51300
|
* @param {object} props
|
|
50884
51301
|
* @param {string} [props.href] - URL/id of an external SVG symbol, rendered via
|
|
50885
51302
|
* `<svg><use href></svg>`. Mutually exclusive with meaningful `children`.
|
|
50886
|
-
* @param {import("
|
|
51303
|
+
* @param {import("preact").ComponentChildren} [props.children] - Inline icon
|
|
50887
51304
|
* content: an `<svg>` element, or a string (renders as a text/emoji icon).
|
|
50888
51305
|
* @param {boolean} [props.decorative] - Marks the icon `aria-hidden`. Defaults
|
|
50889
51306
|
* to `true` for an unsized, non-interactive icon; pass `false` for a
|
|
@@ -51058,6 +51475,11 @@ const usePreloadOnIntent = (ref, href, prefetch = true) => {
|
|
|
51058
51475
|
return undefined;
|
|
51059
51476
|
}
|
|
51060
51477
|
const preload = () => {
|
|
51478
|
+
// The tab or the window it opens loads its own code: what would be
|
|
51479
|
+
// fetched here is not what runs there.
|
|
51480
|
+
if (linkOpensElsewhere(element)) {
|
|
51481
|
+
return;
|
|
51482
|
+
}
|
|
51061
51483
|
preloadUrl(href);
|
|
51062
51484
|
};
|
|
51063
51485
|
element.addEventListener("pointerenter", preload);
|
|
@@ -51266,6 +51688,15 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
51266
51688
|
};
|
|
51267
51689
|
|
|
51268
51690
|
installImportMetaCssBuild(import.meta);
|
|
51691
|
+
/*
|
|
51692
|
+
* Apply opacity to child content, not the link element itself.
|
|
51693
|
+
*
|
|
51694
|
+
* Why not apply opacity directly to .navi_link?
|
|
51695
|
+
* - Would make focus outlines semi-transparent too (accessibility issue)
|
|
51696
|
+
* - We want dimmed text but full-opacity focus indicators for visibility
|
|
51697
|
+
*
|
|
51698
|
+
* This approach dims the content while preserving focus outline visibility.
|
|
51699
|
+
*/
|
|
51269
51700
|
const css$Z = /* css */`@layer navi {
|
|
51270
51701
|
.navi_link {
|
|
51271
51702
|
--link-border-radius: unset;
|
|
@@ -51286,6 +51717,19 @@ const css$Z = /* css */`@layer navi {
|
|
|
51286
51717
|
--link-current-indicator-spacing: 0;
|
|
51287
51718
|
--link-current-indicator-color: var(--navi-link-current-indicator-color);
|
|
51288
51719
|
}
|
|
51720
|
+
|
|
51721
|
+
.navi_link[data-variant="text"], .navi_link[data-variant="icon"] {
|
|
51722
|
+
--link-color: unset;
|
|
51723
|
+
--link-text-decoration: none;
|
|
51724
|
+
}
|
|
51725
|
+
|
|
51726
|
+
.navi_link[data-variant="tab"] {
|
|
51727
|
+
--link-background-hover: color-mix(in srgb,
|
|
51728
|
+
var(--link-background, transparent),
|
|
51729
|
+
var(--x-link-contrasting-color) 15%);
|
|
51730
|
+
--link-color: unset;
|
|
51731
|
+
--link-text-decoration: none;
|
|
51732
|
+
}
|
|
51289
51733
|
}
|
|
51290
51734
|
|
|
51291
51735
|
.navi_link {
|
|
@@ -51306,7 +51750,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
51306
51750
|
--x-link-padding-right: var(--link-padding-right, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51307
51751
|
--x-link-padding-bottom: var(--link-padding-bottom, var(--link-padding-y, var(--link-padding, 0px)));
|
|
51308
51752
|
--x-link-padding-left: var(--link-padding-left, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51309
|
-
aspect-ratio: inherit;
|
|
51310
51753
|
padding-top: var(--x-link-padding-top);
|
|
51311
51754
|
padding-right: var(--x-link-padding-right);
|
|
51312
51755
|
padding-bottom: var(--x-link-padding-bottom);
|
|
@@ -51495,17 +51938,7 @@ const css$Z = /* css */`@layer navi {
|
|
|
51495
51938
|
margin-left: -.1em;
|
|
51496
51939
|
}
|
|
51497
51940
|
|
|
51498
|
-
&[data-variant="text"], &[data-variant="icon"] {
|
|
51499
|
-
--link-color: unset;
|
|
51500
|
-
--link-text-decoration: none;
|
|
51501
|
-
}
|
|
51502
|
-
|
|
51503
51941
|
&[data-variant="tab"] {
|
|
51504
|
-
--link-background-hover: color-mix(in srgb,
|
|
51505
|
-
var(--link-background, transparent),
|
|
51506
|
-
var(--x-link-contrasting-color) 15%);
|
|
51507
|
-
--link-color: unset;
|
|
51508
|
-
--link-text-decoration: none;
|
|
51509
51942
|
white-space: nowrap;
|
|
51510
51943
|
user-select: none;
|
|
51511
51944
|
|
|
@@ -51592,8 +52025,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51592
52025
|
*
|
|
51593
52026
|
* Href-derived behavior (all computed from `href`, recomputed on navigation):
|
|
51594
52027
|
* - internal vs external target (`data-href-internal`/`data-href-external`),
|
|
51595
|
-
* with `target`/`rel` auto-defaulted
|
|
51596
|
-
* `noopener noreferrer`
|
|
52028
|
+
* with `target`/`rel` auto-defaulted unless explicitly set: `_blank` for a
|
|
52029
|
+
* page of another site (`http:`/`https:`), `noopener noreferrer` for any
|
|
52030
|
+
* external address, nothing otherwise.
|
|
51597
52031
|
* - "current page" detection (`data-href-current`, `aria-current="page"`).
|
|
51598
52032
|
* - anchor links (`href` starting with `#`) get `data-href-anchor`.
|
|
51599
52033
|
* - an automatic end icon for `tel:`/`sms:`/`mailto:`/`github.com`, external
|
|
@@ -51655,14 +52089,14 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51655
52089
|
* text reads (see `holdSpaceForStyle` in `text.jsx`).
|
|
51656
52090
|
* @param {boolean} [props.currentEffectShadow] - Inset-shadow effect while
|
|
51657
52091
|
* current (used with `variant="tab"`).
|
|
51658
|
-
* @param {boolean|import("
|
|
52092
|
+
* @param {boolean|import("preact").ComponentChild} [props.startIcon] - Icon
|
|
51659
52093
|
* placed before the text.
|
|
51660
|
-
* @param {boolean|import("
|
|
52094
|
+
* @param {boolean|import("preact").ComponentChild} [props.endIcon] - Icon
|
|
51661
52095
|
* placed after the text; when omitted, an icon may be auto-chosen from the
|
|
51662
52096
|
* href (see above).
|
|
51663
|
-
* @param {boolean|import("
|
|
52097
|
+
* @param {boolean|import("preact").ComponentChild} [props.blankTargetIcon] -
|
|
51664
52098
|
* Override/suppress the auto external-link icon.
|
|
51665
|
-
* @param {boolean|import("
|
|
52099
|
+
* @param {boolean|import("preact").ComponentChild} [props.anchorIcon] -
|
|
51666
52100
|
* Controls the auto anchor icon shown for a childless anchor link:
|
|
51667
52101
|
* `true`/`undefined` uses the default chain SVG, `false` suppresses it, any
|
|
51668
52102
|
* other node replaces it. To render a plain `#` instead, prefer writing it as
|
|
@@ -51733,7 +52167,7 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51733
52167
|
* @param {boolean} [props.preventDefault] - Call `event.preventDefault()` on
|
|
51734
52168
|
* click (navigation suppressed; `onClick` still runs).
|
|
51735
52169
|
* @param {(event: MouseEvent) => void} [props.onClick]
|
|
51736
|
-
* @param {import("
|
|
52170
|
+
* @param {import("preact").ComponentChildren} [props.children] - Link content;
|
|
51737
52171
|
* falls back to the route's relative URL / `href` per the rules above.
|
|
51738
52172
|
* @param {string} [props.name] - Control facade name (links may be nameless).
|
|
51739
52173
|
* @param {boolean} [props.disabled]
|
|
@@ -51862,7 +52296,7 @@ const LinkPlain = props => {
|
|
|
51862
52296
|
":-navi-href-current": innerCurrent,
|
|
51863
52297
|
":-navi-selected": selected
|
|
51864
52298
|
};
|
|
51865
|
-
const innerTarget = target === undefined ?
|
|
52299
|
+
const innerTarget = target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target;
|
|
51866
52300
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
51867
52301
|
let innerEndIcon;
|
|
51868
52302
|
if (endIcon === undefined) {
|
|
@@ -52029,7 +52463,7 @@ const LinkPlain = props => {
|
|
|
52029
52463
|
},
|
|
52030
52464
|
href: href,
|
|
52031
52465
|
rel: innerRel,
|
|
52032
|
-
target: innerTarget === "_self" ? undefined :
|
|
52466
|
+
target: innerTarget === "_self" ? undefined : innerTarget
|
|
52033
52467
|
// Which slide this tab is, and which box to say it to: read by the <Nav>
|
|
52034
52468
|
// around it to place the row's own bar, and by the command above to find
|
|
52035
52469
|
// the container across the document.
|
|
@@ -52281,6 +52715,9 @@ const useSlideContainer = (target) => {
|
|
|
52281
52715
|
};
|
|
52282
52716
|
|
|
52283
52717
|
installImportMetaCssBuild(import.meta);
|
|
52718
|
+
// A name of its own for the bar under the current tab, one per nav on the page:
|
|
52719
|
+
// two bars answering to the same name are two elements claiming one picture,
|
|
52720
|
+
// and the browser refuses the whole transition rather than pick.
|
|
52284
52721
|
let navCount = 0;
|
|
52285
52722
|
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
52286
52723
|
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
@@ -52305,6 +52742,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
52305
52742
|
};
|
|
52306
52743
|
const css$Y = /* css */`@layer navi {
|
|
52307
52744
|
.navi_nav {
|
|
52745
|
+
--nav-width: fit-content;
|
|
52308
52746
|
--nav-border: none;
|
|
52309
52747
|
--nav-padding: 0px;
|
|
52310
52748
|
--nav-border-radius: 0px;
|
|
@@ -52328,7 +52766,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52328
52766
|
var(--nav-indicator-length-delta));
|
|
52329
52767
|
--x-nav-indicator-color: var(--nav-indicator-color, var(--nav-current-indicator-color));
|
|
52330
52768
|
--x-nav-indicator-color-toward: var(--nav-indicator-color-toward, var(--x-nav-indicator-color));
|
|
52331
|
-
z-index:
|
|
52769
|
+
z-index: 2;
|
|
52332
52770
|
background: color-mix(in srgb,
|
|
52333
52771
|
var(--x-nav-indicator-color-toward)
|
|
52334
52772
|
calc(var(--slide-travel-progress) * var(--nav-indicator-toward-sign, 0) *
|
|
@@ -52385,7 +52823,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52385
52823
|
}
|
|
52386
52824
|
|
|
52387
52825
|
.navi_nav {
|
|
52388
|
-
width:
|
|
52826
|
+
width: var(--nav-width);
|
|
52389
52827
|
padding-top: var(--nav-padding-top, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
52390
52828
|
padding-right: var(--nav-padding-right, var(--nav-padding-x, var(--nav-padding, unset)));
|
|
52391
52829
|
padding-bottom: var(--nav-padding-bottom, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
@@ -52414,12 +52852,12 @@ const css$Y = /* css */`@layer navi {
|
|
|
52414
52852
|
--x-nav-child-border-radius: calc(var(--nav-border-radius) - var(--nav-padding));
|
|
52415
52853
|
--x-nav-link-border-radius: var(--link-border-radius, var(--x-nav-child-border-radius));
|
|
52416
52854
|
|
|
52417
|
-
&:
|
|
52855
|
+
&:nth-child(1 of .navi_link) {
|
|
52418
52856
|
border-top-left-radius: var(--x-nav-link-border-radius);
|
|
52419
52857
|
border-bottom-left-radius: var(--x-nav-link-border-radius);
|
|
52420
52858
|
}
|
|
52421
52859
|
|
|
52422
|
-
&:last-child {
|
|
52860
|
+
&:nth-last-child(1 of .navi_link) {
|
|
52423
52861
|
border-top-right-radius: var(--x-nav-link-border-radius);
|
|
52424
52862
|
border-bottom-right-radius: var(--x-nav-link-border-radius);
|
|
52425
52863
|
}
|
|
@@ -52522,7 +52960,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
52522
52960
|
};
|
|
52523
52961
|
|
|
52524
52962
|
/**
|
|
52525
|
-
* @type {import("
|
|
52963
|
+
* @type {import("preact").FunctionComponent<{
|
|
52526
52964
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
52527
52965
|
* currentIndicatorSlides?: boolean,
|
|
52528
52966
|
* slideContainer?: string,
|
|
@@ -53672,7 +54110,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53672
54110
|
};
|
|
53673
54111
|
|
|
53674
54112
|
/**
|
|
53675
|
-
* @type {import("
|
|
54113
|
+
* @type {import("preact").FunctionComponent<{
|
|
53676
54114
|
* value?: any,
|
|
53677
54115
|
* defaultValue?: any,
|
|
53678
54116
|
* onChange?: (value: any) => void,
|
|
@@ -53716,7 +54154,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53716
54154
|
* grows the binder instead of closing in on the text.
|
|
53717
54155
|
* @param {string} [props.tabBorderColor] - The inactive tabs alone; the rest of
|
|
53718
54156
|
* the outline follows `borderColor`.
|
|
53719
|
-
* @param {import("
|
|
54157
|
+
* @param {import("preact").ComponentChildren} props.children - `Binder.Item`s.
|
|
53720
54158
|
*/
|
|
53721
54159
|
const Binder = ({
|
|
53722
54160
|
children,
|
|
@@ -53857,13 +54295,13 @@ const Binder = ({
|
|
|
53857
54295
|
/**
|
|
53858
54296
|
* @param {any} [props.value] - What identifies this item; defaults to its
|
|
53859
54297
|
* position among its siblings.
|
|
53860
|
-
* @param {import("
|
|
54298
|
+
* @param {import("preact").ComponentChildren} [props.label] - What the tab
|
|
53861
54299
|
* shows.
|
|
53862
54300
|
* @param {object} [props.route] - Makes the tab a `Link`
|
|
53863
54301
|
* to that route, and the binder follows the url: the open item is the one
|
|
53864
54302
|
* whose route matches, no `value`/`onChange` needed.
|
|
53865
54303
|
* @param {string} [props.href] - Same, for a plain url.
|
|
53866
|
-
* @param {import("
|
|
54304
|
+
* @param {import("preact").ComponentChildren} [props.children] - The page.
|
|
53867
54305
|
*
|
|
53868
54306
|
* Every other prop goes to the tab (a `Box`, or a `Link` when routed): an item
|
|
53869
54307
|
* styles and configures the control that opens it.
|
|
@@ -54273,7 +54711,7 @@ const FixedBarStyleCSSVars = {
|
|
|
54273
54711
|
};
|
|
54274
54712
|
|
|
54275
54713
|
/**
|
|
54276
|
-
* @type {import("
|
|
54714
|
+
* @type {import("preact").FunctionComponent<{
|
|
54277
54715
|
* area?: "top"|"bottom"|"left"|"right",
|
|
54278
54716
|
* width?: string|number,
|
|
54279
54717
|
* height?: string|number,
|
|
@@ -54664,7 +55102,7 @@ const css$U = /* css */`.navi_summary_marker {
|
|
|
54664
55102
|
`;
|
|
54665
55103
|
|
|
54666
55104
|
/**
|
|
54667
|
-
* @type {import("
|
|
55105
|
+
* @type {import("preact").FunctionComponent<{
|
|
54668
55106
|
* open?: boolean,
|
|
54669
55107
|
* loading?: boolean,
|
|
54670
55108
|
* openDirection?: "down" | "up" | "left",
|
|
@@ -55084,8 +55522,8 @@ const useExpandableContext = partName => {
|
|
|
55084
55522
|
};
|
|
55085
55523
|
|
|
55086
55524
|
/**
|
|
55087
|
-
* @type {import("
|
|
55088
|
-
* ui?: import("
|
|
55525
|
+
* @type {import("preact").FunctionComponent<{
|
|
55526
|
+
* ui?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55089
55527
|
* open?: boolean,
|
|
55090
55528
|
* defaultOpen?: boolean,
|
|
55091
55529
|
* signal?: import("@preact/signals").Signal,
|
|
@@ -55725,9 +56163,9 @@ const Expandable = props => {
|
|
|
55725
56163
|
* Controls inside it keep their own behavior and do not toggle. A Box, so it
|
|
55726
56164
|
* takes the layout and style props every Box takes.
|
|
55727
56165
|
*
|
|
55728
|
-
* @type {import("
|
|
55729
|
-
* marker?: false | import("
|
|
55730
|
-
* children?: import("
|
|
56166
|
+
* @type {import("preact").FunctionComponent<{
|
|
56167
|
+
* marker?: false | import("preact").ComponentChildren,
|
|
56168
|
+
* children?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55731
56169
|
* }>}
|
|
55732
56170
|
* @param marker - The chevron drawn before the label. `false` removes it (a UI
|
|
55733
56171
|
* part that is already an icon has no room for one); any node replaces it.
|
|
@@ -55802,7 +56240,7 @@ const ExpandableUI = ({
|
|
|
55802
56240
|
* Expandable, children may be a function `(data) => ui` or a branches object —
|
|
55803
56241
|
* see ActionRenderer.
|
|
55804
56242
|
*
|
|
55805
|
-
* @type {import("
|
|
56243
|
+
* @type {import("preact").FunctionComponent<{}>}
|
|
55806
56244
|
*/
|
|
55807
56245
|
const ExpandableContent = ({
|
|
55808
56246
|
children,
|
|
@@ -55958,6 +56396,40 @@ const createToggleEvent = open => {
|
|
|
55958
56396
|
return toggleEvent;
|
|
55959
56397
|
};
|
|
55960
56398
|
|
|
56399
|
+
/**
|
|
56400
|
+
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
56401
|
+
* Each resolver is a component function rendered in sequence (hooks are allowed).
|
|
56402
|
+
* To pass through to the next resolver, call useNextResolver() and render the
|
|
56403
|
+
* returned Next component with the desired props.
|
|
56404
|
+
* To terminate the chain early (e.g. render a specialized component), render
|
|
56405
|
+
* directly without calling Next.
|
|
56406
|
+
*
|
|
56407
|
+
* The last entry in the array is the final/target component — it receives null
|
|
56408
|
+
* from useNextResolver() indicating it is terminal.
|
|
56409
|
+
*
|
|
56410
|
+
* Usage:
|
|
56411
|
+
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
56412
|
+
* // Then inside a component render:
|
|
56413
|
+
* renderButton(props)
|
|
56414
|
+
*
|
|
56415
|
+
* A chain step is one component: a runner, defined once per position, whose
|
|
56416
|
+
* render CALLS its resolver's body rather than rendering it as a child. The
|
|
56417
|
+
* body runs synchronously inside the runner's render — its hooks are the
|
|
56418
|
+
* runner's, at the same tree position on every render — and for exactly that
|
|
56419
|
+
* long, `useNextResolver()` answers with the runner after this one (null for
|
|
56420
|
+
* the last). Nothing is handed down through a context: a chain rendered by the
|
|
56421
|
+
* hundred (a list's rows) pays one component per step, not a provider and a
|
|
56422
|
+
* wrapper around each.
|
|
56423
|
+
*
|
|
56424
|
+
* A resolver that renders ANOTHER resolver — a type resolver picking the
|
|
56425
|
+
* component for `type="date"` — cannot render it as a JSX child: that child
|
|
56426
|
+
* would render later, outside any body, with no Next to read. It renders it
|
|
56427
|
+
* with `renderResolver(Resolver, props)`, which captures the Next of the body
|
|
56428
|
+
* it is called from and hands it to a runner made for that resolver — the
|
|
56429
|
+
* child continues the chain where its parent stood. Called anywhere else, or
|
|
56430
|
+
* a `useNextResolver()` reached outside a body, throws: the chain has no
|
|
56431
|
+
* silent answer to give there.
|
|
56432
|
+
*/
|
|
55961
56433
|
const createComponentResolver = (resolvers, {
|
|
55962
56434
|
pure
|
|
55963
56435
|
} = {}) => {
|
|
@@ -56167,10 +56639,21 @@ const useAccentColorAttributes = (
|
|
|
56167
56639
|
};
|
|
56168
56640
|
|
|
56169
56641
|
installImportMetaCssBuild(import.meta);
|
|
56642
|
+
/**
|
|
56643
|
+
* We need the content to visually shrink (scale down) but the button interactive area MUST remain intact
|
|
56644
|
+
* Otherwise a click on the edges of the button cannot not trigger the click event (mouseup occurs outside the button)
|
|
56645
|
+
**/
|
|
56646
|
+
|
|
56647
|
+
/**
|
|
56648
|
+
* We have to re-define the CSS of button because getComputedStyle(button).borderColor returns
|
|
56649
|
+
* rgb(0, 0, 0) while being visually grey in chrome
|
|
56650
|
+
* So we redefine chrome styles so that loader can keep up with the actual color visible to the user
|
|
56651
|
+
*/
|
|
56170
56652
|
const css$R = /* css */`@layer navi {
|
|
56171
56653
|
.navi_button {
|
|
56172
56654
|
--button-border-radius: var(--navi-control-border-radius);
|
|
56173
56655
|
--button-border-width: var(--navi-control-border-width);
|
|
56656
|
+
--border-width: var(--button-border-width);
|
|
56174
56657
|
--button-cta-background-color: var(--navi-accent-color);
|
|
56175
56658
|
--button-outline-width: var(--navi-focus-outline-width);
|
|
56176
56659
|
--button-outline-offset: calc(-.5 * var(--button-outline-width));
|
|
@@ -56179,7 +56662,7 @@ const css$R = /* css */`@layer navi {
|
|
|
56179
56662
|
--button-padding-y-default: var(--navi-button-padding-y-default);
|
|
56180
56663
|
--button-loader-color: var(--navi-loader-color);
|
|
56181
56664
|
--button-border-color: var(--navi-control-border-color);
|
|
56182
|
-
--button-background-color: var(--
|
|
56665
|
+
--button-background-color: var(--navi-button-background-color);
|
|
56183
56666
|
--button-color: currentColor;
|
|
56184
56667
|
--button-cursor: pointer;
|
|
56185
56668
|
--button-font-size: var(--navi-control-font-size);
|
|
@@ -56212,6 +56695,92 @@ const css$R = /* css */`@layer navi {
|
|
|
56212
56695
|
line-height: var(--navi-control-line-height);
|
|
56213
56696
|
display: inline-block;
|
|
56214
56697
|
}
|
|
56698
|
+
|
|
56699
|
+
.navi_button[data-variant="discrete"] {
|
|
56700
|
+
--button-border-width: 0;
|
|
56701
|
+
--button-border-color: transparent;
|
|
56702
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56703
|
+
--button-border-color-current: var(--button-border-color);
|
|
56704
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56705
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56706
|
+
--button-background-color: transparent;
|
|
56707
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56708
|
+
currentColor 8%,
|
|
56709
|
+
var(--button-background-color));
|
|
56710
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56711
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56712
|
+
}
|
|
56713
|
+
|
|
56714
|
+
.navi_button[data-variant="bare"] {
|
|
56715
|
+
--button-padding-x-default: 0;
|
|
56716
|
+
--button-padding-y-default: 0;
|
|
56717
|
+
--button-border-width: 0;
|
|
56718
|
+
--button-border-color: transparent;
|
|
56719
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56720
|
+
--button-border-color-current: var(--button-border-color);
|
|
56721
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56722
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56723
|
+
--button-background-color: transparent;
|
|
56724
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56725
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56726
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56727
|
+
font-size: inherit;
|
|
56728
|
+
font-family: inherit;
|
|
56729
|
+
line-height: inherit;
|
|
56730
|
+
}
|
|
56731
|
+
|
|
56732
|
+
.navi_button[data-variant="discrete-border"] {
|
|
56733
|
+
--button-background-color: transparent;
|
|
56734
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56735
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56736
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56737
|
+
}
|
|
56738
|
+
|
|
56739
|
+
.navi_button[data-variant="border"] {
|
|
56740
|
+
--button-background-color: transparent;
|
|
56741
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56742
|
+
currentColor 8%,
|
|
56743
|
+
var(--button-background-color));
|
|
56744
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56745
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56746
|
+
}
|
|
56747
|
+
|
|
56748
|
+
.navi_button[data-icon] {
|
|
56749
|
+
--button-padding: 0;
|
|
56750
|
+
}
|
|
56751
|
+
|
|
56752
|
+
.navi_button[data-cta] {
|
|
56753
|
+
--button-background-color: var(--button-cta-background-color);
|
|
56754
|
+
--button-border-color: var(--button-cta-background-color);
|
|
56755
|
+
--button-color: white;
|
|
56756
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56757
|
+
var(--button-background-color) 85%,
|
|
56758
|
+
white);
|
|
56759
|
+
--button-border-color-hover: color-mix(in srgb,
|
|
56760
|
+
var(--button-border-color) 85%,
|
|
56761
|
+
white);
|
|
56762
|
+
--button-background-color-readonly: color-mix(in srgb,
|
|
56763
|
+
var(--button-background-color) 50%,
|
|
56764
|
+
white);
|
|
56765
|
+
--button-border-color-readonly: color-mix(in srgb,
|
|
56766
|
+
var(--button-border-color) 50%,
|
|
56767
|
+
white);
|
|
56768
|
+
--button-background-color-disabled: color-mix(in srgb,
|
|
56769
|
+
var(--button-background-color) 40%,
|
|
56770
|
+
white);
|
|
56771
|
+
--button-border-color-disabled: color-mix(in srgb,
|
|
56772
|
+
var(--button-border-color) 40%,
|
|
56773
|
+
white);
|
|
56774
|
+
--button-color-disabled: color-mix(in srgb,
|
|
56775
|
+
var(--button-color) 60%,
|
|
56776
|
+
transparent);
|
|
56777
|
+
}
|
|
56778
|
+
|
|
56779
|
+
.navi_button[data-content-display="contents"] {
|
|
56780
|
+
background: none;
|
|
56781
|
+
border: none;
|
|
56782
|
+
padding: 0;
|
|
56783
|
+
}
|
|
56215
56784
|
}
|
|
56216
56785
|
|
|
56217
56786
|
a.navi_button {
|
|
@@ -56228,7 +56797,6 @@ a.navi_button {
|
|
|
56228
56797
|
--x-button-color: var(--button-color);
|
|
56229
56798
|
--x-button-cursor: var(--button-cursor);
|
|
56230
56799
|
box-sizing: border-box;
|
|
56231
|
-
aspect-ratio: inherit;
|
|
56232
56800
|
color: var(--x-button-color);
|
|
56233
56801
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--button-border-radius));
|
|
56234
56802
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--button-border-radius));
|
|
@@ -56237,12 +56805,15 @@ a.navi_button {
|
|
|
56237
56805
|
cursor: var(--x-button-cursor);
|
|
56238
56806
|
touch-action: manipulation;
|
|
56239
56807
|
user-select: none;
|
|
56240
|
-
background: none;
|
|
56241
|
-
border: none;
|
|
56242
56808
|
outline: none;
|
|
56243
|
-
padding: 0;
|
|
56244
56809
|
position: relative;
|
|
56245
56810
|
|
|
56811
|
+
&:where(:not([data-content-display="contents"])) {
|
|
56812
|
+
background: none;
|
|
56813
|
+
border: none;
|
|
56814
|
+
padding: 0;
|
|
56815
|
+
}
|
|
56816
|
+
|
|
56246
56817
|
& .navi_button_content {
|
|
56247
56818
|
--x-corner-top-left-radius: initial;
|
|
56248
56819
|
--x-corner-top-right-radius: initial;
|
|
@@ -56264,8 +56835,7 @@ a.navi_button {
|
|
|
56264
56835
|
gap: inherit;
|
|
56265
56836
|
color: inherit;
|
|
56266
56837
|
vertical-align: inherit;
|
|
56267
|
-
background: var(--x-button-background);
|
|
56268
|
-
background-color: var(--x-button-background-color, var(--x-button-background));
|
|
56838
|
+
background: var(--x-button-background, var(--x-button-background-color));
|
|
56269
56839
|
border: var(--x-button-border);
|
|
56270
56840
|
border-radius: inherit;
|
|
56271
56841
|
outline-width: var(--button-outline-width);
|
|
@@ -56288,6 +56858,10 @@ a.navi_button {
|
|
|
56288
56858
|
}
|
|
56289
56859
|
}
|
|
56290
56860
|
|
|
56861
|
+
&[data-background-color] .navi_button_content {
|
|
56862
|
+
background-color: var(--x-button-background-color);
|
|
56863
|
+
}
|
|
56864
|
+
|
|
56291
56865
|
&[data-href-current] {
|
|
56292
56866
|
--x-button-border-color: var(--button-border-color-current);
|
|
56293
56867
|
--x-button-background-color: var(--button-background-color-current);
|
|
@@ -56332,7 +56906,7 @@ a.navi_button {
|
|
|
56332
56906
|
}
|
|
56333
56907
|
|
|
56334
56908
|
&[data-content-display="contents"] {
|
|
56335
|
-
&
|
|
56909
|
+
&[data-focus-visible] {
|
|
56336
56910
|
outline: var(--button-outline-width) solid var(--button-outline-color);
|
|
56337
56911
|
outline-offset: var(--button-outline-offset);
|
|
56338
56912
|
}
|
|
@@ -56358,19 +56932,6 @@ a.navi_button {
|
|
|
56358
56932
|
}
|
|
56359
56933
|
|
|
56360
56934
|
&[data-variant="discrete"] {
|
|
56361
|
-
--button-border-width: 0;
|
|
56362
|
-
--button-border-color: transparent;
|
|
56363
|
-
--button-border-color-hover: var(--button-border-color);
|
|
56364
|
-
--button-border-color-current: var(--button-border-color);
|
|
56365
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
56366
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
56367
|
-
--button-background-color: var(--button-background, transparent);
|
|
56368
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56369
|
-
currentColor 8%,
|
|
56370
|
-
var(--button-background-color));
|
|
56371
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56372
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56373
|
-
|
|
56374
56935
|
@media (hover: hover) {
|
|
56375
56936
|
&[data-pressed] {
|
|
56376
56937
|
& .navi_button_content {
|
|
@@ -56381,17 +56942,6 @@ a.navi_button {
|
|
|
56381
56942
|
}
|
|
56382
56943
|
|
|
56383
56944
|
&[data-variant="bare"] {
|
|
56384
|
-
--button-border-width: 0;
|
|
56385
|
-
--button-border-color: transparent;
|
|
56386
|
-
--button-border-color-hover: var(--button-border-color);
|
|
56387
|
-
--button-border-color-current: var(--button-border-color);
|
|
56388
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
56389
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
56390
|
-
--button-background-color: var(--button-background, transparent);
|
|
56391
|
-
--button-background-color-hover: var(--button-background-color);
|
|
56392
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56393
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56394
|
-
|
|
56395
56945
|
&[data-pressed] {
|
|
56396
56946
|
& .navi_button_content {
|
|
56397
56947
|
transform: none;
|
|
@@ -56400,10 +56950,6 @@ a.navi_button {
|
|
|
56400
56950
|
}
|
|
56401
56951
|
|
|
56402
56952
|
&[data-variant="discrete-border"] {
|
|
56403
|
-
--button-background-color: var(--button-background, transparent);
|
|
56404
|
-
--button-background-color-hover: var(--button-background-color);
|
|
56405
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56406
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56407
56953
|
--x-button-border-color: transparent;
|
|
56408
56954
|
|
|
56409
56955
|
&[data-hover] {
|
|
@@ -56415,15 +56961,6 @@ a.navi_button {
|
|
|
56415
56961
|
}
|
|
56416
56962
|
}
|
|
56417
56963
|
|
|
56418
|
-
&[data-variant="border"] {
|
|
56419
|
-
--button-background-color: var(--button-background, transparent);
|
|
56420
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56421
|
-
currentColor 8%,
|
|
56422
|
-
var(--button-background-color));
|
|
56423
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56424
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56425
|
-
}
|
|
56426
|
-
|
|
56427
56964
|
&[data-press-effect="scale"][data-pressed] {
|
|
56428
56965
|
& .navi_button_content {
|
|
56429
56966
|
transform: scale(.9);
|
|
@@ -56437,37 +56974,9 @@ a.navi_button {
|
|
|
56437
56974
|
}
|
|
56438
56975
|
|
|
56439
56976
|
&[data-icon] {
|
|
56440
|
-
--button-padding: 0;
|
|
56441
56977
|
justify-content: center;
|
|
56442
56978
|
align-items: center;
|
|
56443
56979
|
}
|
|
56444
|
-
|
|
56445
|
-
&[data-cta] {
|
|
56446
|
-
--button-background-color: var(--button-background, var(--button-cta-background-color));
|
|
56447
|
-
--button-border-color: var(--button-cta-background-color);
|
|
56448
|
-
--button-color: white;
|
|
56449
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56450
|
-
var(--button-background-color) 85%,
|
|
56451
|
-
white);
|
|
56452
|
-
--button-border-color-hover: color-mix(in srgb,
|
|
56453
|
-
var(--button-border-color) 85%,
|
|
56454
|
-
white);
|
|
56455
|
-
--button-background-color-readonly: color-mix(in srgb,
|
|
56456
|
-
var(--button-background-color) 50%,
|
|
56457
|
-
white);
|
|
56458
|
-
--button-border-color-readonly: color-mix(in srgb,
|
|
56459
|
-
var(--button-border-color) 50%,
|
|
56460
|
-
white);
|
|
56461
|
-
--button-background-color-disabled: color-mix(in srgb,
|
|
56462
|
-
var(--button-background-color) 40%,
|
|
56463
|
-
white);
|
|
56464
|
-
--button-border-color-disabled: color-mix(in srgb,
|
|
56465
|
-
var(--button-border-color) 40%,
|
|
56466
|
-
white);
|
|
56467
|
-
--button-color-disabled: color-mix(in srgb,
|
|
56468
|
-
var(--button-color) 60%,
|
|
56469
|
-
transparent);
|
|
56470
|
-
}
|
|
56471
56980
|
}
|
|
56472
56981
|
`;
|
|
56473
56982
|
const ButtonUI = props => {
|
|
@@ -56596,6 +57105,7 @@ const ButtonUI = props => {
|
|
|
56596
57105
|
"data-press-effect": pressEffect,
|
|
56597
57106
|
"data-icon": icon ? "" : undefined,
|
|
56598
57107
|
"data-cta": cta ? "" : undefined,
|
|
57108
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
56599
57109
|
"data-callout-arrow-x": "center"
|
|
56600
57110
|
// style management
|
|
56601
57111
|
,
|
|
@@ -56908,7 +57418,7 @@ const ButtonWithHref = props => {
|
|
|
56908
57418
|
} = getHrefTargetInfo(href, documentUrlSignal.peek());
|
|
56909
57419
|
return jsx(Next, {
|
|
56910
57420
|
...props,
|
|
56911
|
-
target: target === undefined ?
|
|
57421
|
+
target: target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target,
|
|
56912
57422
|
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
56913
57423
|
// Under whatever the caller already says: a route knows better whether
|
|
56914
57424
|
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
@@ -57062,7 +57572,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57062
57572
|
};
|
|
57063
57573
|
|
|
57064
57574
|
/**
|
|
57065
|
-
* @type {import("
|
|
57575
|
+
* @type {import("preact").FunctionComponent<{
|
|
57066
57576
|
* selfInteractions?: string,
|
|
57067
57577
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
57068
57578
|
* replace?: boolean,
|
|
@@ -57144,7 +57654,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57144
57654
|
* more: the work may already have happened on the other side (see
|
|
57145
57655
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
57146
57656
|
* where the screen can be re-opened on what is actually there.
|
|
57147
|
-
* @param {boolean|import("
|
|
57657
|
+
* @param {boolean|import("preact").ComponentChildren} [confirm] Ask before
|
|
57148
57658
|
* doing it, in the place the button stands: the first press replaces the
|
|
57149
57659
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
57150
57660
|
* does what the button was for. `true` asks navi's default question. The
|
|
@@ -57155,9 +57665,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57155
57665
|
* question up, its message on the button that asked. Use `<Picker
|
|
57156
57666
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
57157
57667
|
* or when the row has no space for it.
|
|
57158
|
-
* @param {import("
|
|
57668
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] Label of the
|
|
57159
57669
|
* second press.
|
|
57160
|
-
* @param {import("
|
|
57670
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Label of the way
|
|
57161
57671
|
* out of the question.
|
|
57162
57672
|
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
57163
57673
|
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
@@ -57256,13 +57766,13 @@ const css$P = /* css */`.navi_control_swap {
|
|
|
57256
57766
|
`;
|
|
57257
57767
|
|
|
57258
57768
|
/**
|
|
57259
|
-
* @type {import("
|
|
57769
|
+
* @type {import("preact").FunctionComponent<{
|
|
57260
57770
|
* value?: string,
|
|
57261
57771
|
* defaultValue?: string,
|
|
57262
57772
|
* signal?: import("@preact/signals").Signal<string>,
|
|
57263
57773
|
* onChange?: (name: string, event: Event) => void,
|
|
57264
57774
|
* animation?: boolean,
|
|
57265
|
-
* children?: import("
|
|
57775
|
+
* children?: import("preact").ComponentChildren,
|
|
57266
57776
|
* }>}
|
|
57267
57777
|
* @param value - The name of the side holding the floor, driven from outside:
|
|
57268
57778
|
* the row swaps to match every change of it, and a press on a cap can still
|
|
@@ -57406,13 +57916,13 @@ const ControlSwap = props => {
|
|
|
57406
57916
|
* reads these and draws the caps at its ends, the controls between them. The
|
|
57407
57917
|
* props below say what the side IS; every other prop describes its cap.
|
|
57408
57918
|
*
|
|
57409
|
-
* @type {import("
|
|
57919
|
+
* @type {import("preact").FunctionComponent<{
|
|
57410
57920
|
* name?: string,
|
|
57411
|
-
* icon: import("
|
|
57921
|
+
* icon: import("preact").ComponentChildren,
|
|
57412
57922
|
* label: string,
|
|
57413
|
-
* badge?: boolean | import("
|
|
57923
|
+
* badge?: boolean | import("preact").ComponentChildren,
|
|
57414
57924
|
* autoFocus?: boolean,
|
|
57415
|
-
* children?: import("
|
|
57925
|
+
* children?: import("preact").ComponentChildren,
|
|
57416
57926
|
* [key: string]: any,
|
|
57417
57927
|
* }>}
|
|
57418
57928
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
@@ -57620,6 +58130,51 @@ const createAvailableConstraint = (
|
|
|
57620
58130
|
};
|
|
57621
58131
|
};
|
|
57622
58132
|
|
|
58133
|
+
/**
|
|
58134
|
+
* ControlGroup — a named sub-group of form controls whose combined state is
|
|
58135
|
+
* aggregated into a single key/value object, exactly like a Form but without
|
|
58136
|
+
* the <form> element semantics.
|
|
58137
|
+
*
|
|
58138
|
+
* Use it when you need a self-contained cluster of inputs that:
|
|
58139
|
+
* - produce one composite value (an object) as their joint state
|
|
58140
|
+
* - can carry their own action / uiAction / command
|
|
58141
|
+
* - may live inside or outside a <Form>
|
|
58142
|
+
*
|
|
58143
|
+
* Props:
|
|
58144
|
+
* name — the key under which the composite value is registered in a
|
|
58145
|
+
* parent Form (optional when used standalone)
|
|
58146
|
+
* action — called with the aggregated object value when any child changes
|
|
58147
|
+
* command — navi command string (e.g. "--navi-send")
|
|
58148
|
+
* disabled — propagates to all children
|
|
58149
|
+
* readOnly — propagates to all children
|
|
58150
|
+
* as — HTML element to render (default "div")
|
|
58151
|
+
* children — the controls that belong to this group
|
|
58152
|
+
*
|
|
58153
|
+
* aggregateChildStates / distributeChildUIState
|
|
58154
|
+
* — what the group is worth, in both directions, when that is not
|
|
58155
|
+
* simply "one key per child": two wheels that add up to a number
|
|
58156
|
+
* of minutes, three fields that make one date. With them the
|
|
58157
|
+
* group takes and hands back a single value, so it can be driven
|
|
58158
|
+
* by one `value`/`signal` like any other control. Same mechanism
|
|
58159
|
+
* InputDuration uses for its own hour/minute/second fields (see
|
|
58160
|
+
* input_duration.jsx). `name` is unaffected: it still says under
|
|
58161
|
+
* which key the group's value — whatever shape it takes —
|
|
58162
|
+
* lands in the form around it.
|
|
58163
|
+
*
|
|
58164
|
+
* distributeChildStates
|
|
58165
|
+
* — the same way down, asked ONCE for all the children at a time:
|
|
58166
|
+
* `(groupValue, children) => Map<child, state>`. The plural of
|
|
58167
|
+
* `distributeChildUIState`, and the mirror of
|
|
58168
|
+
* `aggregateChildStates`, which already sees every child. For a
|
|
58169
|
+
* group whose children cannot be placed one at a time — four
|
|
58170
|
+
* seats where who sits down decides who moves, a row where the
|
|
58171
|
+
* first answer changes what the others may show. A child the Map
|
|
58172
|
+
* does not name is left where it is. Given both, this one wins.
|
|
58173
|
+
* It also closes the loop: the group holds ONE answer its
|
|
58174
|
+
* children are views of, so when one of them speaks the others
|
|
58175
|
+
* are placed again (the one that acted is left where the user
|
|
58176
|
+
* put it).
|
|
58177
|
+
*/
|
|
57623
58178
|
const ControlGroup = props => {
|
|
57624
58179
|
const defaultRef = useRef();
|
|
57625
58180
|
props.ref = props.ref || defaultRef;
|
|
@@ -57812,6 +58367,41 @@ const css$N = /* css */`@layer navi {
|
|
|
57812
58367
|
var(--button-background-color) 95%,
|
|
57813
58368
|
black);
|
|
57814
58369
|
}
|
|
58370
|
+
|
|
58371
|
+
.navi_checkbox[data-variant="switch"] {
|
|
58372
|
+
--margin: var(--switch-margin);
|
|
58373
|
+
--width: var(--switch-outer-width);
|
|
58374
|
+
--height: unset;
|
|
58375
|
+
--border-radius: var(--switch-border-radius);
|
|
58376
|
+
--background-color: var(--switch-background-color);
|
|
58377
|
+
--background-color-hover: var(--switch-background-color-hover);
|
|
58378
|
+
--background-color-readonly: var(--switch-background-color-readonly);
|
|
58379
|
+
--background-color-disabled: var(--switch-background-color-disabled);
|
|
58380
|
+
--background-color-checked: var(--switch-background-color-checked);
|
|
58381
|
+
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
58382
|
+
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
58383
|
+
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
58384
|
+
}
|
|
58385
|
+
|
|
58386
|
+
.navi_checkbox[data-variant="icon"] {
|
|
58387
|
+
--margin: 0;
|
|
58388
|
+
--width: auto;
|
|
58389
|
+
--height: auto;
|
|
58390
|
+
}
|
|
58391
|
+
|
|
58392
|
+
.navi_checkbox[data-variant="button"] {
|
|
58393
|
+
--margin: 0;
|
|
58394
|
+
--width: auto;
|
|
58395
|
+
--height: auto;
|
|
58396
|
+
--border-color: var(--button-border-color);
|
|
58397
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
58398
|
+
--background-color: var(--button-background-color);
|
|
58399
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
58400
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
58401
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
58402
|
+
--border-color-checked: var(--button-border-color);
|
|
58403
|
+
--background-color-checked: var(--button-background-color);
|
|
58404
|
+
}
|
|
57815
58405
|
}
|
|
57816
58406
|
|
|
57817
58407
|
.navi_checkbox {
|
|
@@ -57952,18 +58542,6 @@ const css$N = /* css */`@layer navi {
|
|
|
57952
58542
|
|
|
57953
58543
|
&[data-variant="switch"] {
|
|
57954
58544
|
--switch-outer-width: calc(var(--switch-width) + var(--switch-padding));
|
|
57955
|
-
--margin: var(--switch-margin);
|
|
57956
|
-
--width: var(--switch-outer-width);
|
|
57957
|
-
--height: unset;
|
|
57958
|
-
--border-radius: var(--switch-border-radius);
|
|
57959
|
-
--background-color: var(--switch-background-color);
|
|
57960
|
-
--background-color-hover: var(--switch-background-color-hover);
|
|
57961
|
-
--background-color-readonly: var(--switch-background-color-readonly);
|
|
57962
|
-
--background-color-disabled: var(--switch-background-color-disabled);
|
|
57963
|
-
--background-color-checked: var(--switch-background-color-checked);
|
|
57964
|
-
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
57965
|
-
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
57966
|
-
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
57967
58545
|
min-width: var(--switch-outer-width);
|
|
57968
58546
|
padding: var(--switch-padding);
|
|
57969
58547
|
background-color: var(--x-background-color);
|
|
@@ -57973,25 +58551,11 @@ const css$N = /* css */`@layer navi {
|
|
|
57973
58551
|
}
|
|
57974
58552
|
|
|
57975
58553
|
&[data-variant="icon"] {
|
|
57976
|
-
--margin: 0;
|
|
57977
|
-
--width: auto;
|
|
57978
|
-
--height: auto;
|
|
57979
58554
|
background: none;
|
|
57980
58555
|
border: none;
|
|
57981
58556
|
}
|
|
57982
58557
|
|
|
57983
58558
|
&[data-variant="button"] {
|
|
57984
|
-
--margin: 0;
|
|
57985
|
-
--width: auto;
|
|
57986
|
-
--height: auto;
|
|
57987
|
-
--border-color: var(--button-border-color);
|
|
57988
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
57989
|
-
--background-color: var(--button-background-color);
|
|
57990
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
57991
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
57992
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
57993
|
-
--border-color-checked: var(--button-border-color);
|
|
57994
|
-
--background-color-checked: var(--button-background-color);
|
|
57995
58559
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
57996
58560
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
57997
58561
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58177,7 +58741,7 @@ const css$M = /* css */`@layer navi {
|
|
|
58177
58741
|
[navi-field] {
|
|
58178
58742
|
--spacing-with-control: var(--navi-xs);
|
|
58179
58743
|
|
|
58180
|
-
& .navi_checkbox, & .navi_radio {
|
|
58744
|
+
& .navi_checkbox:not([data-variant="switch"]), & .navi_radio {
|
|
58181
58745
|
--margin: 0;
|
|
58182
58746
|
}
|
|
58183
58747
|
}
|
|
@@ -58532,6 +59096,28 @@ const css$L = /* css */`@layer navi {
|
|
|
58532
59096
|
--button-border-color-disabled: var(--border-color-readonly);
|
|
58533
59097
|
--button-background-color-disabled: var(--background-color-readonly);
|
|
58534
59098
|
}
|
|
59099
|
+
|
|
59100
|
+
.navi_radio[data-variant="icon"] {
|
|
59101
|
+
--width: auto;
|
|
59102
|
+
--height: auto;
|
|
59103
|
+
--outline-offset: 2px;
|
|
59104
|
+
--outline-width: 2px;
|
|
59105
|
+
}
|
|
59106
|
+
|
|
59107
|
+
.navi_radio[data-variant="button"] {
|
|
59108
|
+
--margin: 0;
|
|
59109
|
+
--outline-offset: 0px;
|
|
59110
|
+
--width: auto;
|
|
59111
|
+
--height: auto;
|
|
59112
|
+
--border-color: var(--button-border-color);
|
|
59113
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
59114
|
+
--background-color: var(--button-background-color);
|
|
59115
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
59116
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
59117
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
59118
|
+
--border-color-checked: var(--button-border-color);
|
|
59119
|
+
--background-color-checked: var(--button-background-color);
|
|
59120
|
+
}
|
|
58535
59121
|
}
|
|
58536
59122
|
|
|
58537
59123
|
.navi_radio {
|
|
@@ -58687,26 +59273,7 @@ const css$L = /* css */`@layer navi {
|
|
|
58687
59273
|
}
|
|
58688
59274
|
}
|
|
58689
59275
|
|
|
58690
|
-
&[data-variant="icon"] {
|
|
58691
|
-
--width: auto;
|
|
58692
|
-
--height: auto;
|
|
58693
|
-
--outline-offset: 2px;
|
|
58694
|
-
--outline-width: 2px;
|
|
58695
|
-
}
|
|
58696
|
-
|
|
58697
59276
|
&[data-variant="button"] {
|
|
58698
|
-
--margin: 0;
|
|
58699
|
-
--outline-offset: 0px;
|
|
58700
|
-
--width: auto;
|
|
58701
|
-
--height: auto;
|
|
58702
|
-
--border-color: var(--button-border-color);
|
|
58703
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
58704
|
-
--background-color: var(--button-background-color);
|
|
58705
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
58706
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
58707
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
58708
|
-
--border-color-checked: var(--button-border-color);
|
|
58709
|
-
--background-color-checked: var(--button-background-color);
|
|
58710
59277
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
58711
59278
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
58712
59279
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58930,6 +59497,7 @@ const css$K = /* css */`@layer navi {
|
|
|
58930
59497
|
--outline-width: var(--navi-focus-outline-width);
|
|
58931
59498
|
--outline-offset: var(--outline-width);
|
|
58932
59499
|
--outline-color: var(--navi-focus-outline-color);
|
|
59500
|
+
--margin: 2px;
|
|
58933
59501
|
--height: 8px;
|
|
58934
59502
|
--thumb-size: 16px;
|
|
58935
59503
|
--thumb-width: var(--thumb-size);
|
|
@@ -59005,6 +59573,7 @@ const css$K = /* css */`@layer navi {
|
|
|
59005
59573
|
box-sizing: border-box;
|
|
59006
59574
|
width: fit-content;
|
|
59007
59575
|
height: var(--height);
|
|
59576
|
+
margin: var(--margin);
|
|
59008
59577
|
font-size: var(--font-size);
|
|
59009
59578
|
font-family: var(--font-family);
|
|
59010
59579
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--outline-border-radius));
|
|
@@ -59017,7 +59586,6 @@ const css$K = /* css */`@layer navi {
|
|
|
59017
59586
|
outline-offset: var(--outline-offset);
|
|
59018
59587
|
flex-direction: row;
|
|
59019
59588
|
align-items: center;
|
|
59020
|
-
margin: 2px;
|
|
59021
59589
|
position: relative;
|
|
59022
59590
|
|
|
59023
59591
|
& .navi_control_input {
|
|
@@ -59314,6 +59882,45 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59314
59882
|
grey);
|
|
59315
59883
|
--color-disabled: var(--color-dimmed);
|
|
59316
59884
|
}
|
|
59885
|
+
|
|
59886
|
+
.navi_input[data-background-transparent] {
|
|
59887
|
+
--background-color-hover: var(--background-color);
|
|
59888
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59889
|
+
}
|
|
59890
|
+
|
|
59891
|
+
.navi_input[data-variant="discrete"], .navi_input[data-variant="discrete-border"] {
|
|
59892
|
+
--background-color: transparent;
|
|
59893
|
+
--background-color-hover: var(--background-color);
|
|
59894
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59895
|
+
|
|
59896
|
+
&[data-background] {
|
|
59897
|
+
--background-color-focus: transparent;
|
|
59898
|
+
}
|
|
59899
|
+
}
|
|
59900
|
+
|
|
59901
|
+
.navi_input[data-variant="discrete"] {
|
|
59902
|
+
--border-color: transparent;
|
|
59903
|
+
}
|
|
59904
|
+
|
|
59905
|
+
.navi_input[data-variant="text"] {
|
|
59906
|
+
--background-color: transparent;
|
|
59907
|
+
--background-color-hover: var(--background-color);
|
|
59908
|
+
--background-color-focus: var(--background-color);
|
|
59909
|
+
--background-color-readonly: var(--background-color);
|
|
59910
|
+
--background-color-disabled: var(--background-color);
|
|
59911
|
+
--border-color: transparent;
|
|
59912
|
+
--border-color-hover: var(--border-color);
|
|
59913
|
+
--border-color-readonly: var(--border-color);
|
|
59914
|
+
--border-color-disabled: var(--border-color);
|
|
59915
|
+
}
|
|
59916
|
+
|
|
59917
|
+
.navi_input[data-variant="underline"] {
|
|
59918
|
+
--background-color: transparent;
|
|
59919
|
+
--background-color-hover: var(--background-color);
|
|
59920
|
+
--background-color-focus: var(--background-color);
|
|
59921
|
+
--background-color-readonly: var(--background-color);
|
|
59922
|
+
--background-color-disabled: var(--background-color);
|
|
59923
|
+
}
|
|
59317
59924
|
}
|
|
59318
59925
|
|
|
59319
59926
|
.navi_input {
|
|
@@ -59465,19 +60072,12 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59465
60072
|
}
|
|
59466
60073
|
|
|
59467
60074
|
&[data-background-transparent] {
|
|
59468
|
-
--background-color-hover: var(--background-color);
|
|
59469
|
-
--background-color-focus: var(--navi-surface-color);
|
|
59470
|
-
|
|
59471
60075
|
&[data-focus] {
|
|
59472
60076
|
--x-background-color: var(--background-color-focus);
|
|
59473
60077
|
}
|
|
59474
60078
|
}
|
|
59475
60079
|
|
|
59476
60080
|
&[data-variant="discrete"], &[data-variant="discrete-border"] {
|
|
59477
|
-
--background-color: transparent;
|
|
59478
|
-
--background-color-hover: var(--background-color);
|
|
59479
|
-
--background-color-focus: var(--navi-surface-color);
|
|
59480
|
-
|
|
59481
60081
|
&[data-focus] {
|
|
59482
60082
|
--x-background-color: var(--background-color-focus);
|
|
59483
60083
|
}
|
|
@@ -59485,15 +60085,9 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59485
60085
|
&[data-readonly], &[data-disabled] {
|
|
59486
60086
|
--x-background-color: var(--background-color);
|
|
59487
60087
|
}
|
|
59488
|
-
|
|
59489
|
-
&[data-background] {
|
|
59490
|
-
--background-color-focus: transparent;
|
|
59491
|
-
}
|
|
59492
60088
|
}
|
|
59493
60089
|
|
|
59494
60090
|
&[data-variant="discrete"] {
|
|
59495
|
-
--border-color: transparent;
|
|
59496
|
-
|
|
59497
60091
|
&[data-focus] {
|
|
59498
60092
|
--x-border-color: color-mix(in srgb,
|
|
59499
60093
|
var(--border-color) 55%,
|
|
@@ -59502,24 +60096,10 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59502
60096
|
}
|
|
59503
60097
|
|
|
59504
60098
|
&[data-variant="text"] {
|
|
59505
|
-
--background-color: transparent;
|
|
59506
|
-
--background-color-hover: var(--background-color);
|
|
59507
|
-
--background-color-focus: var(--background-color);
|
|
59508
|
-
--background-color-readonly: var(--background-color);
|
|
59509
|
-
--background-color-disabled: var(--background-color);
|
|
59510
|
-
--border-color: transparent;
|
|
59511
|
-
--border-color-hover: var(--border-color);
|
|
59512
|
-
--border-color-readonly: var(--border-color);
|
|
59513
|
-
--border-color-disabled: var(--border-color);
|
|
59514
60099
|
cursor: inherit;
|
|
59515
60100
|
}
|
|
59516
60101
|
|
|
59517
60102
|
&[data-variant="underline"] {
|
|
59518
|
-
--background-color: transparent;
|
|
59519
|
-
--background-color-hover: var(--background-color);
|
|
59520
|
-
--background-color-focus: var(--background-color);
|
|
59521
|
-
--background-color-readonly: var(--background-color);
|
|
59522
|
-
--background-color-disabled: var(--background-color);
|
|
59523
60103
|
border: none;
|
|
59524
60104
|
border-radius: 0;
|
|
59525
60105
|
padding-left: 0;
|
|
@@ -60715,6 +61295,38 @@ const InputStyleCSSVars = {
|
|
|
60715
61295
|
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value"];
|
|
60716
61296
|
const InputPseudoElements = ["::-navi-loader"];
|
|
60717
61297
|
|
|
61298
|
+
/**
|
|
61299
|
+
* @type {import("preact").FunctionComponent<{
|
|
61300
|
+
* type?: string,
|
|
61301
|
+
* icon?: import("preact").ComponentChildren | null,
|
|
61302
|
+
* clearable?: boolean,
|
|
61303
|
+
* value?: any,
|
|
61304
|
+
* boolean?: boolean,
|
|
61305
|
+
* selectOnPress?: boolean,
|
|
61306
|
+
* [key: string]: any,
|
|
61307
|
+
* }>}
|
|
61308
|
+
* @param {any} [value] On a checkbox, what it is worth checked (`"on"` when
|
|
61309
|
+
* left out); unchecked it is `undefined`. See control_value.md, "What a
|
|
61310
|
+
* checkbox is worth".
|
|
61311
|
+
* @param {boolean} [boolean] Makes a checkbox a yes/no: `true` checked, `false`
|
|
61312
|
+
* unchecked, everywhere it hands its value. Takes no `value`.
|
|
61313
|
+
* @param {import("preact").ComponentChildren|null} [icon] What the textual
|
|
61314
|
+
* types that draw an icon (`search`, `email`, `tel`) put in their slot: left
|
|
61315
|
+
* out, the type's own glyph; `null`, nothing — for a field an application
|
|
61316
|
+
* draws its own magnifier beside, where a second one two centimetres away
|
|
61317
|
+
* would be one too many; anything else is drawn instead. A `search` field
|
|
61318
|
+
* swaps that slot for the clear cross as soon as it holds a value, whatever
|
|
61319
|
+
* this says.
|
|
61320
|
+
* @param {boolean} [clearable] Gives any other type the same slot: the icon
|
|
61321
|
+
* while empty (none, unless `icon` says otherwise), the clear cross once
|
|
61322
|
+
* filled.
|
|
61323
|
+
* @param {boolean} [selectOnPress] For a textual field whose value is usually
|
|
61324
|
+
* replaced rather than edited, like a browser's address bar: the first press
|
|
61325
|
+
* after the field took the focus selects its whole text once released, with a
|
|
61326
|
+
* mouse or a finger, and a second press places the caret. A focus that came
|
|
61327
|
+
* without a press (a dialog opening, Tab, code) selects nothing; the first key
|
|
61328
|
+
* that types selects first, so it replaces the text.
|
|
61329
|
+
*/
|
|
60718
61330
|
const Input = props => {
|
|
60719
61331
|
resolveInputProps(props);
|
|
60720
61332
|
const {
|
|
@@ -60804,7 +61416,7 @@ const TextareaStyleCSSVars = {
|
|
|
60804
61416
|
};
|
|
60805
61417
|
|
|
60806
61418
|
/**
|
|
60807
|
-
* @type {import("
|
|
61419
|
+
* @type {import("preact").FunctionComponent<{
|
|
60808
61420
|
* value?: string,
|
|
60809
61421
|
* defaultValue?: string,
|
|
60810
61422
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -60911,7 +61523,7 @@ const Textarea = ({
|
|
|
60911
61523
|
* draws: put it under the box, in a form footer, next to a label, and feed it
|
|
60912
61524
|
* the same value or signal as the textarea.
|
|
60913
61525
|
*
|
|
60914
|
-
* @type {import("
|
|
61526
|
+
* @type {import("preact").FunctionComponent<{
|
|
60915
61527
|
* value?: string,
|
|
60916
61528
|
* signal?: import("@preact/signals").Signal<string>,
|
|
60917
61529
|
* maxLength?: number,
|
|
@@ -61144,8 +61756,8 @@ const useEditionController = () => {
|
|
|
61144
61756
|
* it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
|
|
61145
61757
|
* the demo `control/demos/action/8_editable_demo.html` does it with props.
|
|
61146
61758
|
*
|
|
61147
|
-
* @type {import("
|
|
61148
|
-
* children?: import("
|
|
61759
|
+
* @type {import("preact").FunctionComponent<{
|
|
61760
|
+
* children?: import("preact").ComponentChildren,
|
|
61149
61761
|
* action: Function | object,
|
|
61150
61762
|
* editing?: { event?: Event } | null,
|
|
61151
61763
|
* onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
|
|
@@ -61505,6 +62117,77 @@ const commitSubtree = (controller, e) => {
|
|
|
61505
62117
|
* <form> element (FormNested).
|
|
61506
62118
|
*/
|
|
61507
62119
|
|
|
62120
|
+
/**
|
|
62121
|
+
* @param {object} props
|
|
62122
|
+
* @param {boolean} [props.standalone] - Its value is its own: the form does not
|
|
62123
|
+
* register with the control group around it (a Picker, a ControlGroup), so
|
|
62124
|
+
* what is typed in it never becomes part of that group's value, and nothing
|
|
62125
|
+
* that group does — distributing a value, resetting, cascading validation —
|
|
62126
|
+
* reaches it. For a form that lives INSIDE something else while answering a
|
|
62127
|
+
* different question — "create the thing I am about to pick" inside a picker,
|
|
62128
|
+
* say. Every control takes the same prop, for the same reason. A form inside
|
|
62129
|
+
* another form is that by construction and does not need the prop: the group
|
|
62130
|
+
* it would register into is a form, which never owns another form's fields.
|
|
62131
|
+
* @param {boolean} [props.canSendWhileUnchanged] - Send even when nothing changed. By
|
|
62132
|
+
* default a form only acts on an answer that is actually new: submitting a
|
|
62133
|
+
* form nobody touched — one just rendered, one reopened and left alone — runs
|
|
62134
|
+
* no action (a field still showing its `defaultValue` is an answer: confirming
|
|
62135
|
+
* a suggestion), and after the first submission it is that value the next
|
|
62136
|
+
* one is compared against. Everything around the action still happens either
|
|
62137
|
+
* way: the constraints are checked, and what follows the send still follows
|
|
62138
|
+
* it (the slide moves on, the popup closes) — the user is done regardless of
|
|
62139
|
+
* whether there was anything to send. Set this for a form where sending the
|
|
62140
|
+
* same thing twice is the point — a single button that fires off a
|
|
62141
|
+
* notification, an action whose duplicates are fine. See also
|
|
62142
|
+
* `readOnlyWhileFormUnchanged` on `Button`, for a submit that should say it
|
|
62143
|
+
* is waiting rather than accept a press that sends nothing.
|
|
62144
|
+
* @param {boolean} [props.actionAbortable] - The person waiting may give up on
|
|
62145
|
+
* the send: closing the popup this form holds calls the run off and goes
|
|
62146
|
+
* through, instead of being refused. For a send whose answer may never come —
|
|
62147
|
+
* a request over a network that stops answering — where a sheet with no way
|
|
62148
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
62149
|
+
* more: the write may already have landed on the other side (see
|
|
62150
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
62151
|
+
* where the screen can be re-opened on what is actually there. Every control
|
|
62152
|
+
* inside the form inherits it along with the wait itself.
|
|
62153
|
+
* @param {boolean} [props.optimistic] - The send is taken as done on the form's
|
|
62154
|
+
* own word: no busy state, and what follows it (`command="--navi-close"`, a
|
|
62155
|
+
* slide moving on) runs at once, the run going on behind. A failure then has
|
|
62156
|
+
* no sheet to be read in: `resetOnError` puts the form back and the error
|
|
62157
|
+
* callout is drawn on what surrounds the closed sheet. For a write not worth being
|
|
62158
|
+
* waited for — a score, a note — never for one whose refusal changes what the
|
|
62159
|
+
* person does next (see
|
|
62160
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
62161
|
+
* @param {any} [props.pristineKey] - What the form is measured against, taken
|
|
62162
|
+
* again every time this changes. A form knows what it holds as soon as its
|
|
62163
|
+
* fields have registered, which is the right moment for a form whose values
|
|
62164
|
+
* are there on the first render — and never the right one for a screen that
|
|
62165
|
+
* modifies something: the resource arrives a request later and fills the
|
|
62166
|
+
* fields, and a form comparing against what it held BEFORE that opens already
|
|
62167
|
+
* changed. Pass whatever says the filling is done (a boolean, the resource
|
|
62168
|
+
* itself, a count of what has loaded) and what the fields carry at that
|
|
62169
|
+
* moment becomes the reference — including the fields that settle in a render
|
|
62170
|
+
* of their own, so there is no tick to wait for on the caller's side. Change
|
|
62171
|
+
* it once, when the screen is ready: taken again after someone started
|
|
62172
|
+
* typing, it would call what they wrote the reference.
|
|
62173
|
+
* @param {string} [props.command] - What follows a submission that went
|
|
62174
|
+
* through: the form has answered its question, and this says what the screen
|
|
62175
|
+
* does about it. Nothing runs when the submission is refused — the form then
|
|
62176
|
+
* stays in front of the user, showing what it is waiting for.
|
|
62177
|
+
*
|
|
62178
|
+
* `"--navi-close"` dismisses the popup the form is in,
|
|
62179
|
+
* `"--navi-left"`/`"--navi-right"`/`"--navi-up"`/`"--navi-down"` move on the
|
|
62180
|
+
* slide map it is in, `"--navi-nav-to:/the/url"` takes the user to a page,
|
|
62181
|
+
* `"--navi-void"` stays put — the one for a form inside a popup that must
|
|
62182
|
+
* stay open once it is sent (a side panel one keeps editing in).
|
|
62183
|
+
* Any navi command, really: it is triggered from the form, so it finds its
|
|
62184
|
+
* target the way that command always does.
|
|
62185
|
+
*
|
|
62186
|
+
* Left out, the surface the form sits in decides (see resolveAfterSend in
|
|
62187
|
+
* commands.js): a popup closes, a slide goes on to the next one — or back to
|
|
62188
|
+
* the one it came from when there is no next — and a form on a page does
|
|
62189
|
+
* nothing.
|
|
62190
|
+
*/
|
|
61508
62191
|
const Form = props => {
|
|
61509
62192
|
const defaultRef = useRef();
|
|
61510
62193
|
props.ref = props.ref || defaultRef;
|
|
@@ -61821,6 +62504,7 @@ const useRequestSubmitThroughGates = ref => {
|
|
|
61821
62504
|
installImportMetaCssBuild(import.meta);
|
|
61822
62505
|
const css$H = /* css */`.navi_group {
|
|
61823
62506
|
--group-border-width: var(--navi-control-border-width);
|
|
62507
|
+
--border-width: initial;
|
|
61824
62508
|
|
|
61825
62509
|
& > :not([navi-out-of-flow]) {
|
|
61826
62510
|
&:hover, &[data-hover] {
|
|
@@ -65053,6 +65737,122 @@ SlideContainer.Down = SlideDown;
|
|
|
65053
65737
|
SlideContainer.First = SlideFirst;
|
|
65054
65738
|
SlideContainer.Last = SlideLast;
|
|
65055
65739
|
|
|
65740
|
+
/**
|
|
65741
|
+
* Displays a date/time value in a human-readable format using the current locale.
|
|
65742
|
+
*
|
|
65743
|
+
* @param {Date|number|string} children
|
|
65744
|
+
* The date to display. Accepts:
|
|
65745
|
+
* - a `Date` instance
|
|
65746
|
+
* - a Unix timestamp (number, in ms)
|
|
65747
|
+
* - a date string `"YYYY-MM-DD"` or any string parseable by `Date`
|
|
65748
|
+
* If the value cannot be parsed, it is rendered as-is.
|
|
65749
|
+
* If undefined/null, renders a placeholder shaped like the type: `"–"` for
|
|
65750
|
+
* `"relative"`, `"--:--"` for `"time"`, `"--"` for the durations, the
|
|
65751
|
+
* locale's field pattern for `"date"`/`"month"`/`"week"`/`"datetime"`.
|
|
65752
|
+
*
|
|
65753
|
+
* @param {"date"|"month"|"week"|"datetime"|"time"|"hour"|"minute"|"second"|"duration"|"relative"} [type="relative"]
|
|
65754
|
+
* Controls the display format:
|
|
65755
|
+
* - `"date"` → "lundi 11 mai" (long by default), "samedi 1 janvier 2050" outside the current year (see `year`);
|
|
65756
|
+
* `format="short"` → "lun. 11 mai"; `format="numeric"` → "11/05/2026";
|
|
65757
|
+
* `format={{ weekday: "long", month: "short" }}` spells the two apart → "mercredi 2 sept.";
|
|
65758
|
+
* a part set to `false` is dropped — `format={{ day: false, month: false }}` → "mercredi"
|
|
65759
|
+
* - `"month"` → "juin 2026"
|
|
65760
|
+
* - `"week"` → the ISO week as given ("2026-W38"), in a `<time datetime>`
|
|
65761
|
+
* - `"datetime"` → "lun. 11 mai, 14:30" (long); `format="short"` → "11 mai, 14:30"; `format="narrow"` → "11/05, 14:30"
|
|
65762
|
+
* - `"time"` → time-of-day as duration by default (e.g. "14:30" → "14 heures 30");
|
|
65763
|
+
* `format="timestring"` → clock "14 h 30". Midnight (00:xx) is
|
|
65764
|
+
* special-cased regardless of `format` — "0 heures 5 minutes"
|
|
65765
|
+
* would otherwise collapse to "5 minutes", indistinguishable
|
|
65766
|
+
* from an actual 5-minute duration: `format="long"` (default)
|
|
65767
|
+
* says "minuit et 5 minutes" (`format="short"/"narrow"/"compact"`
|
|
65768
|
+
* keep the zero hour instead — "0 h et 5 min"/"0h 5min"/"00h05").
|
|
65769
|
+
* `format="compact"` also zero-pads any single-digit hour, not
|
|
65770
|
+
* just midnight (e.g. "5h30" → "05h30"), closer to "05:30";
|
|
65771
|
+
* `pad={false}` writes it the way one says it instead ("8h30",
|
|
65772
|
+
* "8h"). See `<TimeRange>` for a span between two of them.
|
|
65773
|
+
* - `"hour"` → hours as duration (e.g. 1.5 → "1 heure 30 minutes")
|
|
65774
|
+
* - `"minute"` → minutes as duration (e.g. 90 → "1 heure 30 minutes")
|
|
65775
|
+
* - `"second"` → seconds as duration (e.g. 90 → "1 minute 30 secondes")
|
|
65776
|
+
* These three promote to the largest fitting unit, days included (36 hours
|
|
65777
|
+
* → "1 jour et 12 heures"); see `forceUnit` to stay in the given unit.
|
|
65778
|
+
* - `"duration"` → duration string/object/number (seconds); `format="iso"` → ISO 8601
|
|
65779
|
+
* - `"relative"` → "dans 1 heure 30" / "En cours" / "il y a 2 heures"
|
|
65780
|
+
* Handles past, present, and future.
|
|
65781
|
+
* `format="short"` → "dans 1 h et 30 min"; `format="narrow"`
|
|
65782
|
+
* → "+3 h"; `format="compact"` → "dans 1h30".
|
|
65783
|
+
* `eventDuration` defaults to 0 (instantaneous: no "En cours" window).
|
|
65784
|
+
*
|
|
65785
|
+
* @param {number|string} [eventDuration=0]
|
|
65786
|
+
* Duration of the event in milliseconds, or a duration string (`"PT1H30M"`,
|
|
65787
|
+
* `"1hour30minute"`). Only used with `type="relative"`.
|
|
65788
|
+
* When omitted, the event is instantaneous (point in time, no "En cours" window).
|
|
65789
|
+
* @param {boolean} [bare]
|
|
65790
|
+
* When true, strips the past-tense literal ("il y a", "ago") and returns only integer + unit.
|
|
65791
|
+
* Only applies to the past state of `type="relative"`.
|
|
65792
|
+
* @param {"long"|"short"|"narrow"|"compact"|"numeric"|"timestring"|"iso"|{ weekday?: "long"|"short"|"narrow"|false, day?: boolean, month?: "long"|"short"|"narrow"|"numeric"|false }} [format="long"]
|
|
65793
|
+
* Controls the verbosity of the output. Defaults to `"long"` for all types.
|
|
65794
|
+
* `"long"`, `"short"`, `"narrow"` and `"compact"` are verbosities every `type`
|
|
65795
|
+
* writes. The three under them name a shape only some types have; a `type`
|
|
65796
|
+
* without that shape writes its `"long"` form and names the dropped word in
|
|
65797
|
+
* the console.
|
|
65798
|
+
* - `"short"` → Intl short (e.g. "2 h et 15 min", short month for dates/datetimes, no weekday for datetime)
|
|
65799
|
+
* - `"narrow"` → Intl narrow (e.g. "2h 15min", numeric month for datetime)
|
|
65800
|
+
* - `"compact"` → custom compact notation (e.g. "2h15", no minute symbol
|
|
65801
|
+
* when hours present). A date has nothing tighter than its
|
|
65802
|
+
* digits, so `type="date"`/`"month"`/`"datetime"` write the
|
|
65803
|
+
* all-digit spelling ("17/09/2026", "17/09 14:30")
|
|
65804
|
+
* - `"numeric"` → numeric date, for `type="date"` and `type="month"` (e.g. "11/09/2026", "09/2026")
|
|
65805
|
+
* - `"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)
|
|
65806
|
+
* - `"iso"` → ISO 8601 string, only for `type="duration"` (e.g. "PT2H15M")
|
|
65807
|
+
* - an object → `type="date"` only, one verbosity per part
|
|
65808
|
+
* (`{ weekday: "long", month: "short" }`): a narrow card
|
|
65809
|
+
* keeps the weekday whole, since that is what the eye
|
|
65810
|
+
* lands on, and abbreviates the month, since that is
|
|
65811
|
+
* where the characters are. `false` drops a part:
|
|
65812
|
+
* `{ day: false, month: false }` is the weekday alone
|
|
65813
|
+
* ("mardi"), `{ month: false }` weekday + day-of-month
|
|
65814
|
+
* ("mardi 18").
|
|
65815
|
+
* @param {boolean} [forceUnit=false]
|
|
65816
|
+
* Keeps the value in the unit named by `type` however big it gets, for
|
|
65817
|
+
* `type="hour"`, `"minute"` and `"second"`: 36 hours reads "36 heures"
|
|
65818
|
+
* instead of the default "1 jour et 12 heures". The default promotes to the
|
|
65819
|
+
* largest fitting unit because it reads better; force the unit when the unit
|
|
65820
|
+
* itself is the information (a quota, a counter).
|
|
65821
|
+
* @param {number} [decimals]
|
|
65822
|
+
* `type="duration"` and `type="second"` only — how many decimals a fraction
|
|
65823
|
+
* of a second keeps, rounded: with `decimals={1}`, 1.234 reads "1,2 seconde"
|
|
65824
|
+
* and 1.96 "2 secondes"; a whole value is never padded ("12 secondes").
|
|
65825
|
+
* Without it the fraction is written to the millisecond ("1,234 seconde"). A
|
|
65826
|
+
* duration measured in milliseconds rarely comes out whole, and its last
|
|
65827
|
+
* digits are usually noise.
|
|
65828
|
+
* @param {boolean} [pad=true]
|
|
65829
|
+
* `type="time"` + `format="compact"` only — whether the clock is written at a
|
|
65830
|
+
* fixed width. `true` (default) zero-pads the hour and always writes the
|
|
65831
|
+
* minutes ("08h00", "08h30"), so a column of times lines up. `false` writes
|
|
65832
|
+
* the shape one says out loud instead: bare hour, minutes only when there are
|
|
65833
|
+
* any ("8h", "8h30"). The spelled-out formats put their units in words and
|
|
65834
|
+
* need neither, so they ignore it.
|
|
65835
|
+
* @param {"hour"|"minute"} [precision]
|
|
65836
|
+
* `type="time"` + `format="compact"` only, and rarely set by hand — whether a
|
|
65837
|
+
* zero minute is written ("8h00") or dropped ("8h"). Defaults to whatever
|
|
65838
|
+
* `pad` implies; the one reason to force `"minute"` on an unpadded clock is
|
|
65839
|
+
* to agree with a partner that has minutes of its own, which is what
|
|
65840
|
+
* `<TimeRange>` does for you.
|
|
65841
|
+
* @param {boolean|"auto"} [year]
|
|
65842
|
+
* `type="date"` only — whether the year is written. `"auto"` writes it only
|
|
65843
|
+
* when the date is not in the current year, the way a calendar does
|
|
65844
|
+
* ("lundi 11 mai", but "samedi 1 janvier 2050"); `false` drops it ("30/07",
|
|
65845
|
+
* the day/month order still following the locale). Defaults to `"auto"`
|
|
65846
|
+
* for the spelled formats and to `true` for `format="numeric"`. A format
|
|
65847
|
+
* without the month (`{ month: false }`) never writes it.
|
|
65848
|
+
* @param {boolean} [dayLabel]
|
|
65849
|
+
* When true and `type="date"`, appends the locale-aware relative label
|
|
65850
|
+
* ("hier", "aujourd'hui", "demain") when the date is yesterday, today, or tomorrow.
|
|
65851
|
+
* @param {string} [lang]
|
|
65852
|
+
* BCP 47 locale tag (e.g. `"fr"`, `"en-US"`).
|
|
65853
|
+
* Defaults to `languagesSignal.value`: the browser's languages, as
|
|
65854
|
+
* `setPreferredLanguage`/`setSupportedLanguages` narrow them.
|
|
65855
|
+
*/
|
|
65056
65856
|
const Time = props => {
|
|
65057
65857
|
const {
|
|
65058
65858
|
type
|
|
@@ -65317,6 +66117,20 @@ const TimeMinute = ({
|
|
|
65317
66117
|
children: String(children)
|
|
65318
66118
|
});
|
|
65319
66119
|
}
|
|
66120
|
+
if (!Number.isInteger(minutes)) {
|
|
66121
|
+
// a fraction of a minute is read in seconds, the way formatMinuteDuration
|
|
66122
|
+
// writes it ("1 minute et 30 secondes")
|
|
66123
|
+
const clock = formatClock(Math.round(minutes * 60));
|
|
66124
|
+
return jsx(TimeText, {
|
|
66125
|
+
dateTime: clock,
|
|
66126
|
+
...props,
|
|
66127
|
+
children: format === "timestring" ? clock : formatMinuteDuration(minutes, {
|
|
66128
|
+
lang,
|
|
66129
|
+
format,
|
|
66130
|
+
forceUnit
|
|
66131
|
+
})
|
|
66132
|
+
});
|
|
66133
|
+
}
|
|
65320
66134
|
const totalHours = Math.floor(minutes / 60);
|
|
65321
66135
|
const remainingMinutes = minutes % 60;
|
|
65322
66136
|
const hh = String(totalHours).padStart(2, "0");
|
|
@@ -65346,6 +66160,7 @@ const TimeSecond = ({
|
|
|
65346
66160
|
lang = languagesSignal.value,
|
|
65347
66161
|
format = "long",
|
|
65348
66162
|
forceUnit = false,
|
|
66163
|
+
decimals,
|
|
65349
66164
|
...props
|
|
65350
66165
|
}) => {
|
|
65351
66166
|
if (children === undefined || children === null) {
|
|
@@ -65361,19 +66176,17 @@ const TimeSecond = ({
|
|
|
65361
66176
|
children: String(children)
|
|
65362
66177
|
});
|
|
65363
66178
|
}
|
|
65364
|
-
const
|
|
65365
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
65366
|
-
const s = seconds % 60;
|
|
65367
|
-
const dateTime = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
66179
|
+
const dateTime = formatClock(seconds);
|
|
65368
66180
|
let text;
|
|
65369
66181
|
if (format === "timestring") {
|
|
65370
66182
|
// Always HH:MM:SS to avoid ambiguity with HH:MM time-of-day format
|
|
65371
|
-
text =
|
|
66183
|
+
text = formatClock(seconds, decimals);
|
|
65372
66184
|
} else {
|
|
65373
66185
|
text = formatSecondDuration(seconds, {
|
|
65374
66186
|
lang,
|
|
65375
66187
|
format,
|
|
65376
|
-
forceUnit
|
|
66188
|
+
forceUnit,
|
|
66189
|
+
decimals
|
|
65377
66190
|
});
|
|
65378
66191
|
}
|
|
65379
66192
|
return jsx(TimeText, {
|
|
@@ -65426,6 +66239,7 @@ const TimeDuration = ({
|
|
|
65426
66239
|
children,
|
|
65427
66240
|
lang = languagesSignal.value,
|
|
65428
66241
|
format = "long",
|
|
66242
|
+
decimals,
|
|
65429
66243
|
...props
|
|
65430
66244
|
}) => {
|
|
65431
66245
|
if (children === undefined || children === null) {
|
|
@@ -65473,7 +66287,8 @@ const TimeDuration = ({
|
|
|
65473
66287
|
...props,
|
|
65474
66288
|
children: formatDuration(duration, {
|
|
65475
66289
|
lang,
|
|
65476
|
-
format
|
|
66290
|
+
format,
|
|
66291
|
+
decimals
|
|
65477
66292
|
})
|
|
65478
66293
|
});
|
|
65479
66294
|
}
|
|
@@ -65485,7 +66300,8 @@ const TimeDuration = ({
|
|
|
65485
66300
|
}
|
|
65486
66301
|
const text = formatDuration(duration, {
|
|
65487
66302
|
lang,
|
|
65488
|
-
format
|
|
66303
|
+
format,
|
|
66304
|
+
decimals
|
|
65489
66305
|
});
|
|
65490
66306
|
return jsx(TimeText, {
|
|
65491
66307
|
dateTime: isoString,
|
|
@@ -65625,6 +66441,24 @@ const TimeRange = ({
|
|
|
65625
66441
|
});
|
|
65626
66442
|
};
|
|
65627
66443
|
|
|
66444
|
+
// "HH:MM:SS", the fraction of a second after a period ("00:00:01.234"): the
|
|
66445
|
+
// shape a `datetime` attribute reads, which takes up to 3 decimals.
|
|
66446
|
+
const formatClock = (seconds, decimals = 3) => {
|
|
66447
|
+
const factor = 10 ** decimals;
|
|
66448
|
+
const total = Math.round(seconds * factor);
|
|
66449
|
+
const fraction = total % factor;
|
|
66450
|
+
const totalSeconds = (total - fraction) / factor;
|
|
66451
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
66452
|
+
const m = Math.floor(totalSeconds % 3600 / 60);
|
|
66453
|
+
const s = totalSeconds % 60;
|
|
66454
|
+
const clock = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
66455
|
+
if (fraction === 0) {
|
|
66456
|
+
return clock;
|
|
66457
|
+
}
|
|
66458
|
+
const digits = String(fraction).padStart(decimals, "0").replace(/0+$/, "");
|
|
66459
|
+
return `${clock}.${digits}`;
|
|
66460
|
+
};
|
|
66461
|
+
|
|
65628
66462
|
/**
|
|
65629
66463
|
* Wraps multiple inputs together and handles keyboard navigation and paste
|
|
65630
66464
|
* distribution between them.
|
|
@@ -65851,6 +66685,11 @@ const renderSafe = (value) => {
|
|
|
65851
66685
|
};
|
|
65852
66686
|
|
|
65853
66687
|
installImportMetaCssBuild(import.meta);
|
|
66688
|
+
/**
|
|
66689
|
+
* The group a popup opens in, or null: what a Popover or a Dialog reads to know
|
|
66690
|
+
* that its wall is the group's. Every popup provides null again to what it
|
|
66691
|
+
* holds, so a popup opened from inside a popup is in front of it, not a peer.
|
|
66692
|
+
*/
|
|
65854
66693
|
const PopupGroupContext = createContext(null);
|
|
65855
66694
|
|
|
65856
66695
|
/**
|
|
@@ -66009,13 +66848,13 @@ const css$F = /* css */`@property --x-popup-group-top {
|
|
|
66009
66848
|
* `navi-out-of-flow` like everything navi adds to a tree. See
|
|
66010
66849
|
* docs/popup_backdrop.md.
|
|
66011
66850
|
*
|
|
66012
|
-
* @type {import("
|
|
66851
|
+
* @type {import("preact").FunctionComponent<{
|
|
66013
66852
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
66014
66853
|
* backdropColor?: string,
|
|
66015
66854
|
* backdropFilter?: string,
|
|
66016
66855
|
* themeColor?: string | false,
|
|
66017
66856
|
* role?: string,
|
|
66018
|
-
* children?: import("
|
|
66857
|
+
* children?: import("preact").ComponentChildren,
|
|
66019
66858
|
* [key: string]: any,
|
|
66020
66859
|
* }>}
|
|
66021
66860
|
* @param {"auto"|"lift"|"discrete"|"invisible"} [props.backdropVariant="auto"] -
|
|
@@ -68062,6 +68901,35 @@ const clipOf = (side, distance) => {
|
|
|
68062
68901
|
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
68063
68902
|
};
|
|
68064
68903
|
|
|
68904
|
+
/**
|
|
68905
|
+
* The cross that closes the popup it sits in — reached as `<Popup.Close />`,
|
|
68906
|
+
* `<Dialog.Close />` or `<Popover.Close />`. A discrete icon button sending
|
|
68907
|
+
* `--navi-close`, which the nearest popup above it answers; the caller only
|
|
68908
|
+
* decides where it goes (the right end of a header, most of the time).
|
|
68909
|
+
*
|
|
68910
|
+
* It is the way out of a popup, so it is the one affordance in there that the
|
|
68911
|
+
* surrounding control's read-only and disabled do not reach: a read-only picker
|
|
68912
|
+
* still opens (reading changes nothing) and hands its popup that read-only, and
|
|
68913
|
+
* a cross wearing it would trap the user in a popup they were invited to read.
|
|
68914
|
+
* A close button written by hand inherits it and refuses the press — which is
|
|
68915
|
+
* the reason to use this one rather than a `<Button command="--navi-close" />`.
|
|
68916
|
+
*
|
|
68917
|
+
* Exempt from the state around it, never from the popup's own answer: the cross
|
|
68918
|
+
* ASKS, and the popup decides — a dialog holding a form mid-send keeps the
|
|
68919
|
+
* close, a Picker holding a value that does not validate keeps it too.
|
|
68920
|
+
*
|
|
68921
|
+
* @type {import("preact").FunctionComponent<{
|
|
68922
|
+
* label?: string,
|
|
68923
|
+
* iconSize?: number | string,
|
|
68924
|
+
* } & Record<string, any>>}
|
|
68925
|
+
* @param {string} [props.label] - What assistive tech reads for the cross
|
|
68926
|
+
* (navi's own "Close" text by default, in the active language).
|
|
68927
|
+
* @param {number|string} [props.iconSize] - How big the cross is drawn, apart
|
|
68928
|
+
* from the box around it. Unset, the glyph follows the button's font size
|
|
68929
|
+
* like a character, and `size` moves glyph and padding together. Set it when
|
|
68930
|
+
* the cross stands in for an affordance the app draws elsewhere and has to
|
|
68931
|
+
* land on the same pixels: `padding="s" iconSize="m"`.
|
|
68932
|
+
*/
|
|
68065
68933
|
const PopupClose = ({
|
|
68066
68934
|
label,
|
|
68067
68935
|
iconSize,
|
|
@@ -68310,6 +69178,9 @@ const TitlePseudoClasses = [":hover"];
|
|
|
68310
69178
|
* step of a form — so only the title knows when it is there.
|
|
68311
69179
|
*/
|
|
68312
69180
|
|
|
69181
|
+
// The popup a title names: the nearest one around it. A surface that is no
|
|
69182
|
+
// popup of this kind but holds content of its own (a picker's callout) sets it
|
|
69183
|
+
// back to null, so a title in there names nothing rather than the popup around.
|
|
68313
69184
|
const PopupTitleContext = createContext(null);
|
|
68314
69185
|
|
|
68315
69186
|
/**
|
|
@@ -68319,10 +69190,10 @@ const PopupTitleContext = createContext(null);
|
|
|
68319
69190
|
* announces the popup by it, and `getByRole("dialog", { name })` finds it.
|
|
68320
69191
|
* An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
|
|
68321
69192
|
*
|
|
68322
|
-
* @type {import("
|
|
69193
|
+
* @type {import("preact").FunctionComponent<{
|
|
68323
69194
|
* as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
68324
69195
|
* id?: string,
|
|
68325
|
-
* children?: import("
|
|
69196
|
+
* children?: import("preact").ComponentChildren,
|
|
68326
69197
|
* } & Record<string, any>>}
|
|
68327
69198
|
*/
|
|
68328
69199
|
const PopupTitle = props => {
|
|
@@ -68383,6 +69254,12 @@ const usePopupName = ({
|
|
|
68383
69254
|
const NO_TITLE = [];
|
|
68384
69255
|
|
|
68385
69256
|
installImportMetaCssBuild(import.meta);
|
|
69257
|
+
// Same need popover.jsx has for its own local renderer (see its
|
|
69258
|
+
// openLocalPopoverCount): a layer="local" dialog is a plain positioned element
|
|
69259
|
+
// in normal flow, so it gets none of the top layer's free "last shown wins" and
|
|
69260
|
+
// would paint under anything declared after it. A live count, not an
|
|
69261
|
+
// ever-growing counter, so the order stays small and resets once nothing is
|
|
69262
|
+
// open.
|
|
68386
69263
|
let openLocalDialogCount = 0;
|
|
68387
69264
|
const css$C = /* css */`
|
|
68388
69265
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -69457,7 +70334,7 @@ const css$C = /* css */`
|
|
|
69457
70334
|
* the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
|
|
69458
70335
|
* nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
|
|
69459
70336
|
* @param {string} [props.openerAriaLabel] - The same, as a string.
|
|
69460
|
-
* @param {import("
|
|
70337
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
69461
70338
|
*/
|
|
69462
70339
|
const Dialog = props => {
|
|
69463
70340
|
import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
@@ -70293,8 +71170,10 @@ const useDialogProps = props => {
|
|
|
70293
71170
|
// gap — the same thing its CSS width/height say (see [data-expand-x],
|
|
70294
71171
|
// [data-expand-y]) — so the placement derives it instead of measuring
|
|
70295
71172
|
// it: a measurement here lays out the dialog's whole content, rows and
|
|
70296
|
-
// all, for a number already known.
|
|
70297
|
-
|
|
71173
|
+
// all, for a number already known. A cap (maxWidth/maxHeight, a Picker's
|
|
71174
|
+
// anchor ceiling) stretches it to less than that, and only a measured
|
|
71175
|
+
// box can be centred.
|
|
71176
|
+
const fill = expandX && expandY && !isSizeCapped(dialogEl);
|
|
70298
71177
|
const pickOptions = {
|
|
70299
71178
|
positionArea,
|
|
70300
71179
|
container: positionedAncestor,
|
|
@@ -70808,6 +71687,14 @@ const switchTransitionsOn = dialogEl => {
|
|
|
70808
71687
|
dialogEl.style.transitionProperty = "";
|
|
70809
71688
|
dialogEl.removeAttribute("navi-transitions-off");
|
|
70810
71689
|
};
|
|
71690
|
+
|
|
71691
|
+
// Reads style, not layout: an unset cap is the guaranteed-invalid value its
|
|
71692
|
+
// @property gives it, and reads back as "". Catches a cap written inline by
|
|
71693
|
+
// the prop as well as one set from a stylesheet.
|
|
71694
|
+
const isSizeCapped = dialogEl => {
|
|
71695
|
+
const computedStyle = getComputedStyle(dialogEl);
|
|
71696
|
+
return computedStyle.getPropertyValue("--dialog-max-width") !== "" || computedStyle.getPropertyValue("--dialog-max-height") !== "";
|
|
71697
|
+
};
|
|
70811
71698
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
70812
71699
|
|
|
70813
71700
|
// Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
|
|
@@ -70833,6 +71720,11 @@ Dialog.Close = PopupClose;
|
|
|
70833
71720
|
Dialog.Title = PopupTitle;
|
|
70834
71721
|
|
|
70835
71722
|
installImportMetaCssBuild(import.meta);
|
|
71723
|
+
// Custom renderer only (layer="local") — count of currently-open instances,
|
|
71724
|
+
// see --popover-stack-order's own CSS comment for why a plain "last shown"
|
|
71725
|
+
// default doesn't happen for free the way it does for the top-layer
|
|
71726
|
+
// renderer. A live count (not an ever-growing counter) so the assigned
|
|
71727
|
+
// order stays small and resets to 0 once nothing is open.
|
|
70836
71728
|
let openLocalPopoverCount = 0;
|
|
70837
71729
|
const css$B = /* css */`
|
|
70838
71730
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -71440,7 +72332,7 @@ const css$B = /* css */`
|
|
|
71440
72332
|
* something names — these, `aria-label`/`aria-labelledby`, a
|
|
71441
72333
|
* `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
|
|
71442
72334
|
* an element with no role is read by nobody.
|
|
71443
|
-
* @param {import("
|
|
72335
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
71444
72336
|
*/
|
|
71445
72337
|
const Popover = props => {
|
|
71446
72338
|
import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
|
|
@@ -72798,7 +73690,7 @@ const css$A = /* css */`@layer navi {
|
|
|
72798
73690
|
* content whose fresh state is its initial state: an edit left unsent at
|
|
72799
73691
|
* the close does not come back at the next opening (an untouched field
|
|
72800
73692
|
* needs none of this to show a new `defaultValue` — it follows one).
|
|
72801
|
-
* @param {import("
|
|
73693
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
72802
73694
|
*/
|
|
72803
73695
|
const Popup = props => {
|
|
72804
73696
|
import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
|
|
@@ -74475,6 +75367,18 @@ const PickerConfirmBody = ({
|
|
|
74475
75367
|
});
|
|
74476
75368
|
};
|
|
74477
75369
|
|
|
75370
|
+
/**
|
|
75371
|
+
* PickerNaviMinute — a custom minute picker that opens a number input.
|
|
75372
|
+
*
|
|
75373
|
+
* Renders a popover containing an `<input type="number">` pre-filled with the
|
|
75374
|
+
* current value, plus Confirm / Clear / Cancel actions. The picker holds a
|
|
75375
|
+
* number of minutes: its own field is a text one told
|
|
75376
|
+
* `navi-input-type="minute"`, so the value is read back as a number and a
|
|
75377
|
+
* min/max/step message speaks of minutes.
|
|
75378
|
+
*
|
|
75379
|
+
* Usage:
|
|
75380
|
+
* <Picker type="navi_minute" min={15} max={300} step={15} value={90} />
|
|
75381
|
+
*/
|
|
74478
75382
|
const PickerNaviMinute = props => {
|
|
74479
75383
|
const Next = useNextResolver();
|
|
74480
75384
|
const {
|
|
@@ -74858,6 +75762,16 @@ const LoadingDotsSvg = () => {
|
|
|
74858
75762
|
});
|
|
74859
75763
|
};
|
|
74860
75764
|
|
|
75765
|
+
/**
|
|
75766
|
+
* A loading indicator sized to match the surrounding text (1em × 1em).
|
|
75767
|
+
* Inherits color from the current text color by default.
|
|
75768
|
+
*
|
|
75769
|
+
* @param {"circle"|"dots"} [props.variant="circle"] - Visual style of the indicator
|
|
75770
|
+
* @param {string} [props.color="currentColor"] - Color of the indicator; inherits from CSS `color` by default
|
|
75771
|
+
*
|
|
75772
|
+
* Every other prop goes to the `Icon` it is drawn in: `size` is a font size
|
|
75773
|
+
* (the indicator being 1em), `width`/`height` size it as a block.
|
|
75774
|
+
*/
|
|
74861
75775
|
const LoadingIndicator = ({
|
|
74862
75776
|
variant = "circle",
|
|
74863
75777
|
...props
|
|
@@ -74920,7 +75834,7 @@ const css$x = /* css */`@layer navi {
|
|
|
74920
75834
|
* Everything about how it looks goes through a CSS var, so it can be set per
|
|
74921
75835
|
* separator as a prop or once for a whole surface in CSS.
|
|
74922
75836
|
*
|
|
74923
|
-
* @type {import("
|
|
75837
|
+
* @type {import("preact").FunctionComponent<{
|
|
74924
75838
|
* vertical?: boolean,
|
|
74925
75839
|
* size?: string|number,
|
|
74926
75840
|
* color?: string,
|
|
@@ -76523,6 +77437,9 @@ const applySearchHighlight = (el, highlight) => {
|
|
|
76523
77437
|
};
|
|
76524
77438
|
|
|
76525
77439
|
installImportMetaCssBuild(import.meta);
|
|
77440
|
+
// Everything the list knows about its items — see list_items.js. Filled in by
|
|
77441
|
+
// the children as they render, read by everything that must reserve room for
|
|
77442
|
+
// what is not rendered, count what is, or know what stands above it.
|
|
76526
77443
|
const ListItemsContext = createContext(null);
|
|
76527
77444
|
// The group an item is declared in, by id (see ListItemGroup): what its count
|
|
76528
77445
|
// and its separator are scoped to.
|
|
@@ -78038,6 +78955,19 @@ const useListScrollSync = ({
|
|
|
78038
78955
|
// eslint-disable-next-line no-unused-expressions
|
|
78039
78956
|
virtualItemSizeSignal.value;
|
|
78040
78957
|
}
|
|
78958
|
+
// The hold ending — the user reaching for the list, something else moving
|
|
78959
|
+
// it, the item it opens on never coming — ends its hold on the document with
|
|
78960
|
+
// it (see holdDocumentScroll), rather than at the next render, which nothing
|
|
78961
|
+
// owes: an offset put back on this page meanwhile is the reader's.
|
|
78962
|
+
const releaseDocumentHoldRef = useRef(null);
|
|
78963
|
+
const letGo = () => {
|
|
78964
|
+
startPlaceRef.current.userTookOver = true;
|
|
78965
|
+
const releaseDocumentHold = releaseDocumentHoldRef.current;
|
|
78966
|
+
if (releaseDocumentHold) {
|
|
78967
|
+
releaseDocumentHoldRef.current = null;
|
|
78968
|
+
releaseDocumentHold();
|
|
78969
|
+
}
|
|
78970
|
+
};
|
|
78041
78971
|
// Set around a scroll the list performs itself. What it protects against is
|
|
78042
78972
|
// not the scroll event as such, but what the listener would conclude from it:
|
|
78043
78973
|
// the position it is about to read was chosen to keep the items where they
|
|
@@ -78271,7 +79201,7 @@ const useListScrollSync = ({
|
|
|
78271
79201
|
}
|
|
78272
79202
|
openAt = scrolledFallback;
|
|
78273
79203
|
if (openAt === "start" || openAt === undefined) {
|
|
78274
|
-
|
|
79204
|
+
letGo();
|
|
78275
79205
|
return;
|
|
78276
79206
|
}
|
|
78277
79207
|
} else {
|
|
@@ -78423,13 +79353,39 @@ const useListScrollSync = ({
|
|
|
78423
79353
|
}, []);
|
|
78424
79354
|
// Held on an item of a list scrolling the document: where the document goes is
|
|
78425
79355
|
// this list's to say, measured on the item, not the url's offset in pixels
|
|
78426
|
-
// (see holdDocumentScroll).
|
|
79356
|
+
// (see holdDocumentScroll). The hold read again: a layout effect of this
|
|
79357
|
+
// commit may have let go already (see placeWhereHeld).
|
|
79358
|
+
const routePage = useContext(RoutePageContext);
|
|
78427
79359
|
useLayoutEffect(() => {
|
|
78428
|
-
if (!heldSomewhere || getScroller() !== document.scrollingElement) {
|
|
79360
|
+
if (!heldSomewhere || startPlaceRef.current.userTookOver || getScroller() !== document.scrollingElement) {
|
|
78429
79361
|
return undefined;
|
|
78430
79362
|
}
|
|
78431
|
-
|
|
78432
|
-
|
|
79363
|
+
const releaseDocumentHold = holdDocumentScroll(routePage);
|
|
79364
|
+
releaseDocumentHoldRef.current = releaseDocumentHold;
|
|
79365
|
+
return () => {
|
|
79366
|
+
releaseDocumentHoldRef.current = null;
|
|
79367
|
+
releaseDocumentHold();
|
|
79368
|
+
};
|
|
79369
|
+
}, [heldSomewhere, scrolledWanted, scrollerElResolved]);
|
|
79370
|
+
// A page kept while its route transition plays is hidden until it is taken
|
|
79371
|
+
// down: nothing of the list can be measured there any more, and its scroller
|
|
79372
|
+
// shows the page arriving (see route_page_context.js). So the list lets go of
|
|
79373
|
+
// it, and stops answering its resizes and scrolls: measured hidden, every
|
|
79374
|
+
// item stands at 0×0, and the top item put back where it was would scroll
|
|
79375
|
+
// the page arriving by that item's offset. Kept mounted on purpose, so the
|
|
79376
|
+
// listeners stay; what they would do is what ends.
|
|
79377
|
+
const pageLeftRef = useRef(false);
|
|
79378
|
+
useLayoutEffect(() => {
|
|
79379
|
+
if (!routePage) {
|
|
79380
|
+
return undefined;
|
|
79381
|
+
}
|
|
79382
|
+
return routePage.leavingSignal.subscribe(leaving => {
|
|
79383
|
+
if (leaving) {
|
|
79384
|
+
pageLeftRef.current = true;
|
|
79385
|
+
letGo();
|
|
79386
|
+
}
|
|
79387
|
+
});
|
|
79388
|
+
}, []);
|
|
78433
79389
|
// What to do when the list's own geometry moves under it, kept fresh for the
|
|
78434
79390
|
// observer below (which is installed once).
|
|
78435
79391
|
const onGeometryChangeRef = useRef(null);
|
|
@@ -78463,18 +79419,15 @@ const useListScrollSync = ({
|
|
|
78463
79419
|
return undefined;
|
|
78464
79420
|
}
|
|
78465
79421
|
const scrollerEl = getScroller();
|
|
78466
|
-
const takeOver = () => {
|
|
78467
|
-
startPlaceRef.current.userTookOver = true;
|
|
78468
|
-
};
|
|
78469
79422
|
const takeOverEvents = ["wheel", "touchstart", "pointerdown", "keydown"];
|
|
78470
79423
|
for (const type of takeOverEvents) {
|
|
78471
|
-
scrollerEl.addEventListener(type,
|
|
79424
|
+
scrollerEl.addEventListener(type, letGo, {
|
|
78472
79425
|
passive: true
|
|
78473
79426
|
});
|
|
78474
79427
|
}
|
|
78475
79428
|
return () => {
|
|
78476
79429
|
for (const type of takeOverEvents) {
|
|
78477
|
-
scrollerEl.removeEventListener(type,
|
|
79430
|
+
scrollerEl.removeEventListener(type, letGo);
|
|
78478
79431
|
}
|
|
78479
79432
|
};
|
|
78480
79433
|
}, [scrolledWanted, scrollerElResolved]);
|
|
@@ -78631,6 +79584,9 @@ const useListScrollSync = ({
|
|
|
78631
79584
|
}
|
|
78632
79585
|
};
|
|
78633
79586
|
const observer = new ResizeObserver(entries => {
|
|
79587
|
+
if (pageLeftRef.current) {
|
|
79588
|
+
return;
|
|
79589
|
+
}
|
|
78634
79590
|
// A box that grows may be a box that starts to scroll — the scroller is
|
|
78635
79591
|
// resolved again before anything is done about the resize, so that a
|
|
78636
79592
|
// list which has outgrown a bounded ancestor stops holding on to the
|
|
@@ -79035,6 +79991,9 @@ const useListScrollSync = ({
|
|
|
79035
79991
|
}
|
|
79036
79992
|
const scrollerEl = getScroller();
|
|
79037
79993
|
const onScroll = () => {
|
|
79994
|
+
if (pageLeftRef.current) {
|
|
79995
|
+
return;
|
|
79996
|
+
}
|
|
79038
79997
|
// Which way the user is going, read before the position is stored: what
|
|
79039
79998
|
// the window keeps ahead is ahead of this.
|
|
79040
79999
|
const previousScroll = currentScrollRef.current;
|
|
@@ -79092,7 +80051,7 @@ const useListScrollSync = ({
|
|
|
79092
80051
|
const low = way.from < way.to ? way.from : way.to;
|
|
79093
80052
|
const high = way.from < way.to ? way.to : way.from;
|
|
79094
80053
|
if (position < low - 1 || position > high + 1) {
|
|
79095
|
-
|
|
80054
|
+
letGo();
|
|
79096
80055
|
}
|
|
79097
80056
|
};
|
|
79098
80057
|
// A page-level scroller does not emit "scroll" on the element itself
|
|
@@ -81053,7 +82012,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81053
82012
|
* of it. Props not listed here are forwarded to the rendered <li> (Box layout
|
|
81054
82013
|
* props included).
|
|
81055
82014
|
*
|
|
81056
|
-
* @type {import("
|
|
82015
|
+
* @type {import("preact").FunctionComponent<{
|
|
81057
82016
|
* id?: string,
|
|
81058
82017
|
* selectable?: boolean,
|
|
81059
82018
|
* value?: any,
|
|
@@ -81062,14 +82021,14 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81062
82021
|
* pointed?: boolean,
|
|
81063
82022
|
* selectableArea?: "all" | "manual",
|
|
81064
82023
|
* skeleton?: boolean,
|
|
81065
|
-
* error?: boolean | import("
|
|
82024
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
81066
82025
|
* onErrorDismiss?: (event: Event) => void,
|
|
81067
82026
|
* loading?: boolean | "adding" | "removing" | "updating",
|
|
81068
82027
|
* readOnly?: boolean,
|
|
81069
82028
|
* filtered?: boolean,
|
|
81070
82029
|
* hidden?: boolean,
|
|
81071
82030
|
* matchInfo?: {match: boolean, matchScore?: number, matchRanges?: Array<[number, number]>},
|
|
81072
|
-
* children?: import("
|
|
82031
|
+
* children?: import("preact").ComponentChildren,
|
|
81073
82032
|
* [key: string]: any,
|
|
81074
82033
|
* }>}
|
|
81075
82034
|
* @param {string} [props.id]
|
|
@@ -81104,7 +82063,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81104
82063
|
* real item. This is what a `renderSkeleton` returns for an item on its way;
|
|
81105
82064
|
* Box layout props (padding…) pass through so the placeholder can match the
|
|
81106
82065
|
* real items' metrics.
|
|
81107
|
-
* @param {boolean|import("
|
|
82066
|
+
* @param {boolean|import("preact").ComponentChildren} [props.error]
|
|
81108
82067
|
* What this item stood for failed: the message replaces its content, styled
|
|
81109
82068
|
* like the list's own error. `true` shows a generic sentence. When
|
|
81110
82069
|
* `onErrorDismiss` is given, a dismiss button is drawn next to the message
|
|
@@ -81244,21 +82203,21 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
81244
82203
|
* Several runs can live in one list, next to plain `<List.Item>` children and
|
|
81245
82204
|
* inside `<List.Group>`s; each takes its place in declaration order.
|
|
81246
82205
|
*
|
|
81247
|
-
* @type {import("
|
|
81248
|
-
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("
|
|
82206
|
+
* @type {import("preact").FunctionComponent<{
|
|
82207
|
+
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("preact").ComponentChildren,
|
|
81249
82208
|
* findText?: (item: any, index: number) => string,
|
|
81250
82209
|
* items?: any[],
|
|
81251
82210
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
81252
82211
|
* debounce?: number,
|
|
81253
82212
|
* count?: number,
|
|
81254
82213
|
* groupBy?: (item: any, index: number) => any,
|
|
81255
|
-
* renderGroupLabel?: (item: any, index: number) => import("
|
|
82214
|
+
* renderGroupLabel?: (item: any, index: number) => import("preact").ComponentChildren,
|
|
81256
82215
|
* groupLabelProps?: (item: any, index: number) => object,
|
|
81257
82216
|
* pageSize?: number,
|
|
81258
82217
|
* memoryBudget?: number,
|
|
81259
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82218
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
81260
82219
|
* itemSize?: (index: number) => number,
|
|
81261
|
-
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("
|
|
82220
|
+
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("preact").ComponentChildren,
|
|
81262
82221
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
81263
82222
|
* onCountChange?: (count: number|undefined) => void,
|
|
81264
82223
|
* }>}
|
|
@@ -82853,7 +83812,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82853
83812
|
* List — generic virtualized scroll container.
|
|
82854
83813
|
* Items must use <List.Item> to participate in tracking.
|
|
82855
83814
|
*
|
|
82856
|
-
* @type {import("
|
|
83815
|
+
* @type {import("preact").FunctionComponent<{
|
|
82857
83816
|
* selectable?: boolean,
|
|
82858
83817
|
* multiple?: boolean,
|
|
82859
83818
|
* deselectable?: boolean,
|
|
@@ -82874,16 +83833,16 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82874
83833
|
* scroller?: "self" | "parent" | "document" | Element | {current: Element},
|
|
82875
83834
|
* hoverWhileScrolling?: boolean,
|
|
82876
83835
|
* scrollResetOnNavigation?: boolean,
|
|
82877
|
-
* fallback?: import("
|
|
82878
|
-
* searchFallback?: import("
|
|
83836
|
+
* fallback?: import("preact").ComponentChildren,
|
|
83837
|
+
* searchFallback?: import("preact").ComponentChildren,
|
|
82879
83838
|
* searchText?: string,
|
|
82880
83839
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
|
|
82881
83840
|
* loading?: boolean,
|
|
82882
|
-
* loadingFallback?: "skeleton" | "loader" | import("
|
|
83841
|
+
* loadingFallback?: "skeleton" | "loader" | import("preact").ComponentChildren,
|
|
82883
83842
|
* loadingSkeletonCount?: number,
|
|
82884
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82885
|
-
* error?: boolean | import("
|
|
82886
|
-
* separator?: boolean | import("
|
|
83843
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
83844
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
83845
|
+
* separator?: boolean | import("preact").ComponentChildren,
|
|
82887
83846
|
* itemTransition?: boolean,
|
|
82888
83847
|
* lockSize?: boolean,
|
|
82889
83848
|
* horizontal?: boolean,
|
|
@@ -82897,7 +83856,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82897
83856
|
* expandX?: boolean,
|
|
82898
83857
|
* expandY?: boolean,
|
|
82899
83858
|
* expand?: boolean,
|
|
82900
|
-
* children?: import("
|
|
83859
|
+
* children?: import("preact").ComponentChildren,
|
|
82901
83860
|
* [key: string]: any,
|
|
82902
83861
|
* }>}
|
|
82903
83862
|
* @param {string} [props.columns]
|
|
@@ -82957,7 +83916,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82957
83916
|
* have, so nothing moves when they arrive. Used for the items a `<List.Items>`
|
|
82958
83917
|
* stands for and does not hold yet, and for the placeholder items drawn while
|
|
82959
83918
|
* the whole list is `loading`. Defaults to a bare `<List.Item skeleton>`.
|
|
82960
|
-
* @param {"skeleton"|"loader"|import("
|
|
83919
|
+
* @param {"skeleton"|"loader"|import("preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
82961
83920
|
* What to display in place of the items while `loading` — that is, while
|
|
82962
83921
|
* there is nothing to show at all: `"skeleton"` renders
|
|
82963
83922
|
* `loadingSkeletonCount` placeholder items (look:
|
|
@@ -83141,6 +84100,17 @@ const List = /*#__PURE__*/Object.assign(ListResolved, {
|
|
|
83141
84100
|
Group: ListItemGroup
|
|
83142
84101
|
});
|
|
83143
84102
|
|
|
84103
|
+
/**
|
|
84104
|
+
* PickerNaviTime — a fully custom time picker driven by min/max/step.
|
|
84105
|
+
*
|
|
84106
|
+
* Unlike the native `<input type="time">`, this picker only exposes the exact
|
|
84107
|
+
* slots generated from min/max/step, so the user can never pick an out-of-range
|
|
84108
|
+
* or out-of-step value. The popover content is a selectable `List` of the
|
|
84109
|
+
* available slots; picking one closes the popover and submits the value.
|
|
84110
|
+
*
|
|
84111
|
+
* Usage:
|
|
84112
|
+
* <Picker type="navi_time" min="08:00" max="18:00" step="00:30" />
|
|
84113
|
+
*/
|
|
83144
84114
|
const PickerNaviTime = props => {
|
|
83145
84115
|
const Next = useNextResolver();
|
|
83146
84116
|
const {
|
|
@@ -83248,18 +84218,20 @@ const createBadgeRegistry = () => {
|
|
|
83248
84218
|
};
|
|
83249
84219
|
|
|
83250
84220
|
installImportMetaCssBuild(import.meta);
|
|
83251
|
-
const css$u = /* css */`@layer navi
|
|
84221
|
+
const css$u = /* css */`@layer navi {
|
|
84222
|
+
.navi_badge {
|
|
84223
|
+
--font-size: .7em;
|
|
84224
|
+
--badge-padding-x-default: .8em;
|
|
84225
|
+
--badge-padding-y-default: .4em;
|
|
84226
|
+
}
|
|
84227
|
+
}
|
|
83252
84228
|
|
|
83253
84229
|
.navi_badge {
|
|
83254
|
-
--font-size: .7em;
|
|
83255
|
-
--badge-padding-x-default: .8em;
|
|
83256
|
-
--badge-padding-y-default: .4em;
|
|
83257
84230
|
--x-badge-padding-top: var(--badge-padding-top, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83258
84231
|
--x-badge-padding-right: var(--badge-padding-right, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83259
84232
|
--x-badge-padding-bottom: var(--badge-padding-bottom, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83260
84233
|
--x-badge-padding-left: var(--badge-padding-left, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83261
84234
|
--x-background: var(--background, light-dark(#e0e0e0, #3a3a3a));
|
|
83262
|
-
--x-background-color: var(--background-color, var(--x-background));
|
|
83263
84235
|
--x-color: var(--color, white);
|
|
83264
84236
|
max-width: 200px;
|
|
83265
84237
|
padding-top: var(--x-badge-padding-top);
|
|
@@ -83270,7 +84242,6 @@ const css$u = /* css */`@layer navi;
|
|
|
83270
84242
|
font-size: var(--font-size);
|
|
83271
84243
|
text-box: trim-both cap alphabetic;
|
|
83272
84244
|
background: var(--x-background);
|
|
83273
|
-
background-color: var(--x-background-color);
|
|
83274
84245
|
border-radius: 1em;
|
|
83275
84246
|
display: inline;
|
|
83276
84247
|
position: relative;
|
|
@@ -83396,7 +84367,6 @@ const BadgeStyleCSSVars = {
|
|
|
83396
84367
|
paddingRight: "--badge-padding-right",
|
|
83397
84368
|
paddingBottom: "--badge-padding-bottom",
|
|
83398
84369
|
paddingLeft: "--badge-padding-left",
|
|
83399
|
-
backgroundColor: "--background-color",
|
|
83400
84370
|
background: "--background",
|
|
83401
84371
|
borderColor: "--border-color",
|
|
83402
84372
|
color: "--color",
|
|
@@ -83570,7 +84540,7 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
83570
84540
|
* shrinkWrap on its own builds the measurement ghost: under maxLines the list
|
|
83571
84541
|
* is measured in place, once the rows are known.
|
|
83572
84542
|
*
|
|
83573
|
-
* @param {import("
|
|
84543
|
+
* @param {import("preact").ComponentChildren} [fallback]
|
|
83574
84544
|
* Rendered in place of the badges when there is none. Without it an empty
|
|
83575
84545
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
83576
84546
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
@@ -84204,10 +85174,10 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
84204
85174
|
* — a row being read still says what was picked, it just no longer offers to
|
|
84205
85175
|
* unpick it.
|
|
84206
85176
|
*
|
|
84207
|
-
* @type {import("
|
|
85177
|
+
* @type {import("preact").FunctionComponent<{
|
|
84208
85178
|
* value: any,
|
|
84209
85179
|
* commandFor?: string,
|
|
84210
|
-
* children?: import("
|
|
85180
|
+
* children?: import("preact").ComponentChildren,
|
|
84211
85181
|
* [key: string]: any,
|
|
84212
85182
|
* }>}
|
|
84213
85183
|
* @param {any} value The value this chip stands for — one entry of what the
|
|
@@ -84473,6 +85443,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84473
85443
|
.navi_picker {
|
|
84474
85444
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
84475
85445
|
--picker-border-width: var(--navi-control-border-width);
|
|
85446
|
+
--border-width: var(--picker-border-width);
|
|
84476
85447
|
--picker-outline-width: var(--navi-focus-outline-width);
|
|
84477
85448
|
--picker-outline-offset: calc(-.5 * var(--picker-outline-width));
|
|
84478
85449
|
--picker-outline-color: var(--navi-focus-outline-color);
|
|
@@ -84512,6 +85483,111 @@ const css$r = /* css */`@layer navi {
|
|
|
84512
85483
|
--picker-align-x-default: flex-start;
|
|
84513
85484
|
--picker-align-y-default: center;
|
|
84514
85485
|
--picker-text-align-default: initial;
|
|
85486
|
+
font-size: var(--picker-font-size);
|
|
85487
|
+
font-family: var(--picker-font-family);
|
|
85488
|
+
line-height: var(--navi-control-line-height);
|
|
85489
|
+
}
|
|
85490
|
+
|
|
85491
|
+
.navi_picker[data-variant="icon"] {
|
|
85492
|
+
--picker-border-width: 0px;
|
|
85493
|
+
--picker-border-color: transparent;
|
|
85494
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85495
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85496
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85497
|
+
--picker-background-color: transparent;
|
|
85498
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85499
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85500
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85501
|
+
--picker-icon-color: currentColor;
|
|
85502
|
+
}
|
|
85503
|
+
|
|
85504
|
+
.navi_picker[data-variant="discrete"] {
|
|
85505
|
+
--picker-border-width: 0px;
|
|
85506
|
+
--picker-border-color: transparent;
|
|
85507
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85508
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85509
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85510
|
+
--picker-background-color: transparent;
|
|
85511
|
+
--picker-background-color-hover: color-mix(in srgb,
|
|
85512
|
+
currentColor 8%,
|
|
85513
|
+
var(--picker-background-color));
|
|
85514
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85515
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85516
|
+
}
|
|
85517
|
+
|
|
85518
|
+
.navi_picker[data-variant="headless"] {
|
|
85519
|
+
--picker-padding-x-default: 0;
|
|
85520
|
+
--picker-padding-y-default: 0;
|
|
85521
|
+
--picker-border-width: 0px;
|
|
85522
|
+
--picker-border-color: transparent;
|
|
85523
|
+
--picker-outline-width: 0px;
|
|
85524
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85525
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85526
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85527
|
+
--picker-background-color: transparent;
|
|
85528
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85529
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85530
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85531
|
+
--picker-icon-color: currentColor;
|
|
85532
|
+
}
|
|
85533
|
+
|
|
85534
|
+
.navi_picker[data-variant="bare"] {
|
|
85535
|
+
--picker-padding-x-default: 0;
|
|
85536
|
+
--picker-padding-y-default: 0;
|
|
85537
|
+
--picker-align-y-default: stretch;
|
|
85538
|
+
--picker-border-width: 0px;
|
|
85539
|
+
--picker-border-color: transparent;
|
|
85540
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85541
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85542
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85543
|
+
--picker-background-color: transparent;
|
|
85544
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85545
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85546
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85547
|
+
--picker-icon-color: currentColor;
|
|
85548
|
+
font-size: inherit;
|
|
85549
|
+
font-family: inherit;
|
|
85550
|
+
line-height: inherit;
|
|
85551
|
+
}
|
|
85552
|
+
|
|
85553
|
+
.navi_picker[data-variant="button"] {
|
|
85554
|
+
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
85555
|
+
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
85556
|
+
--picker-align-x-default: center;
|
|
85557
|
+
--picker-background-color: var(--navi-button-background-color);
|
|
85558
|
+
--picker-border-color-readonly: color-mix(in srgb,
|
|
85559
|
+
var(--picker-border-color) 30%,
|
|
85560
|
+
var(--navi-surface-color));
|
|
85561
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85562
|
+
--picker-color-readonly: color-mix(in srgb,
|
|
85563
|
+
var(--picker-color) 30%,
|
|
85564
|
+
transparent);
|
|
85565
|
+
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
85566
|
+
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
85567
|
+
--picker-color-disabled: var(--picker-color-readonly);
|
|
85568
|
+
--picker-text-align-default: center;
|
|
85569
|
+
}
|
|
85570
|
+
|
|
85571
|
+
.navi_picker[data-variant="text"] {
|
|
85572
|
+
--picker-padding-x-default: 0;
|
|
85573
|
+
--picker-padding-y-default: 0;
|
|
85574
|
+
--picker-border-width: 0px;
|
|
85575
|
+
--picker-border-color: transparent;
|
|
85576
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85577
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85578
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85579
|
+
--picker-background-color: transparent;
|
|
85580
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85581
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85582
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85583
|
+
font-size: inherit;
|
|
85584
|
+
font-family: inherit;
|
|
85585
|
+
}
|
|
85586
|
+
|
|
85587
|
+
.navi_picker[data-icon] {
|
|
85588
|
+
--picker-padding-x-default: 0;
|
|
85589
|
+
--picker-padding-y-default: 0;
|
|
85590
|
+
--picker-align-x-default: center;
|
|
84515
85591
|
}
|
|
84516
85592
|
}
|
|
84517
85593
|
|
|
@@ -84534,10 +85610,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84534
85610
|
box-sizing: border-box;
|
|
84535
85611
|
max-width: 100%;
|
|
84536
85612
|
color: var(--x-picker-color);
|
|
84537
|
-
font-size: var(--picker-font-size);
|
|
84538
|
-
font-family: var(--picker-font-family);
|
|
84539
85613
|
text-align: inherit;
|
|
84540
|
-
line-height: var(--navi-control-line-height);
|
|
84541
85614
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--picker-border-radius));
|
|
84542
85615
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--picker-border-radius));
|
|
84543
85616
|
border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--picker-border-radius));
|
|
@@ -84779,48 +85852,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84779
85852
|
--x-picker-border-color: var(--callout-color);
|
|
84780
85853
|
}
|
|
84781
85854
|
|
|
84782
|
-
&[data-variant="icon"] {
|
|
84783
|
-
--picker-border-width: 0px;
|
|
84784
|
-
--picker-border-color: transparent;
|
|
84785
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84786
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84787
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84788
|
-
--picker-background-color: transparent;
|
|
84789
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84790
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84791
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84792
|
-
--picker-icon-color: currentColor;
|
|
84793
|
-
}
|
|
84794
|
-
|
|
84795
|
-
&[data-variant="discrete"] {
|
|
84796
|
-
--picker-border-width: 0px;
|
|
84797
|
-
--picker-border-color: transparent;
|
|
84798
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84799
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84800
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84801
|
-
--picker-background-color: transparent;
|
|
84802
|
-
--picker-background-color-hover: color-mix(in srgb,
|
|
84803
|
-
currentColor 8%,
|
|
84804
|
-
var(--picker-background-color));
|
|
84805
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84806
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84807
|
-
}
|
|
84808
|
-
|
|
84809
85855
|
&[data-variant="headless"] {
|
|
84810
|
-
--picker-padding-x-default: 0;
|
|
84811
|
-
--picker-padding-y-default: 0;
|
|
84812
|
-
--picker-border-width: 0px;
|
|
84813
|
-
--picker-border-color: transparent;
|
|
84814
|
-
--picker-outline-width: 0px;
|
|
84815
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84816
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84817
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84818
|
-
--picker-background-color: transparent;
|
|
84819
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84820
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84821
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84822
|
-
--picker-icon-color: currentColor;
|
|
84823
|
-
|
|
84824
85856
|
& > .navi_picker_box {
|
|
84825
85857
|
z-index: -1;
|
|
84826
85858
|
position: absolute;
|
|
@@ -84829,23 +85861,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84829
85861
|
}
|
|
84830
85862
|
|
|
84831
85863
|
&[data-variant="bare"] {
|
|
84832
|
-
--picker-padding-x-default: 0;
|
|
84833
|
-
--picker-padding-y-default: 0;
|
|
84834
|
-
--picker-align-y-default: stretch;
|
|
84835
|
-
--picker-border-width: 0px;
|
|
84836
|
-
--picker-border-color: transparent;
|
|
84837
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84838
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84839
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84840
|
-
--picker-background-color: transparent;
|
|
84841
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84842
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84843
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84844
|
-
--picker-icon-color: currentColor;
|
|
84845
|
-
font-size: inherit;
|
|
84846
|
-
font-family: inherit;
|
|
84847
|
-
line-height: inherit;
|
|
84848
|
-
|
|
84849
85864
|
& > .navi_picker_box {
|
|
84850
85865
|
min-height: 0;
|
|
84851
85866
|
}
|
|
@@ -84880,39 +85895,8 @@ const css$r = /* css */`@layer navi {
|
|
|
84880
85895
|
}
|
|
84881
85896
|
}
|
|
84882
85897
|
|
|
84883
|
-
&[data-variant="button"] {
|
|
84884
|
-
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
84885
|
-
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
84886
|
-
--picker-align-x-default: center;
|
|
84887
|
-
--picker-background-color: var(--navi-button-background-color);
|
|
84888
|
-
--picker-border-color-readonly: color-mix(in srgb,
|
|
84889
|
-
var(--picker-border-color) 30%,
|
|
84890
|
-
var(--navi-surface-color));
|
|
84891
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84892
|
-
--picker-color-readonly: color-mix(in srgb,
|
|
84893
|
-
var(--picker-color) 30%,
|
|
84894
|
-
transparent);
|
|
84895
|
-
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
84896
|
-
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
84897
|
-
--picker-color-disabled: var(--picker-color-readonly);
|
|
84898
|
-
--picker-text-align-default: center;
|
|
84899
|
-
}
|
|
84900
|
-
|
|
84901
85898
|
&[data-variant="text"] {
|
|
84902
|
-
--picker-padding-x-default: 0;
|
|
84903
|
-
--picker-padding-y-default: 0;
|
|
84904
|
-
--picker-border-width: 0px;
|
|
84905
|
-
--picker-border-color: transparent;
|
|
84906
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84907
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84908
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84909
|
-
--picker-background-color: transparent;
|
|
84910
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84911
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84912
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84913
|
-
font-size: inherit;
|
|
84914
85899
|
text-underline-offset: .2em;
|
|
84915
|
-
font-family: inherit;
|
|
84916
85900
|
text-decoration: underline dotted;
|
|
84917
85901
|
|
|
84918
85902
|
&[data-hover] {
|
|
@@ -84921,10 +85905,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84921
85905
|
}
|
|
84922
85906
|
|
|
84923
85907
|
&[data-icon] {
|
|
84924
|
-
--picker-padding-x-default: 0;
|
|
84925
|
-
--picker-padding-y-default: 0;
|
|
84926
|
-
--picker-align-x-default: center;
|
|
84927
|
-
|
|
84928
85908
|
& > .navi_picker_box {
|
|
84929
85909
|
min-height: 0;
|
|
84930
85910
|
}
|
|
@@ -85359,13 +86339,13 @@ const PickerButton = props => {
|
|
|
85359
86339
|
* It goes when there is nothing to take out and when nothing can be changed, so
|
|
85360
86340
|
* a caller places it once and never asks whether to draw it.
|
|
85361
86341
|
*
|
|
85362
|
-
* @type {import("
|
|
86342
|
+
* @type {import("preact").FunctionComponent<{
|
|
85363
86343
|
* size?: number | string,
|
|
85364
|
-
* children?: import("
|
|
86344
|
+
* children?: import("preact").ComponentChildren,
|
|
85365
86345
|
* } & Record<string, any>>}
|
|
85366
86346
|
* @param {number|string} [size="inherit"] How big the cross is drawn, as
|
|
85367
86347
|
* `<Icon size>` reads it — "inherit" takes the size of the text around it.
|
|
85368
|
-
* @param {import("
|
|
86348
|
+
* @param {import("preact").ComponentChildren} [children] The icon, in place of
|
|
85369
86349
|
* the cross. Anything else goes to the `<Button>` underneath (`variant`,
|
|
85370
86350
|
* `paddingX`, `aria-label`, …).
|
|
85371
86351
|
*/
|
|
@@ -85696,6 +86676,580 @@ const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
|
85696
86676
|
ColorSvg
|
|
85697
86677
|
});
|
|
85698
86678
|
|
|
86679
|
+
/**
|
|
86680
|
+
* Button-like trigger that opens a picker (native or custom popup) when clicked.
|
|
86681
|
+
*
|
|
86682
|
+
* Without `children`, opens the browser-native picker for the given `type`.
|
|
86683
|
+
* With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
|
|
86684
|
+
* Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
|
|
86685
|
+
* Either way the popup is a `dialog`, named by a `<Popup.Title>` in the
|
|
86686
|
+
* children (see `popupAriaLabelledBy`), and the trigger answers to `combobox` —
|
|
86687
|
+
* `button` under `picksNothing` — with `aria-haspopup="dialog"` and
|
|
86688
|
+
* `aria-expanded` (see docs/testid.md).
|
|
86689
|
+
*
|
|
86690
|
+
* `type="confirm"` is the picker that asks "are you sure?" before doing what it
|
|
86691
|
+
* stands for: its popup is the question, `ui` its label, and yes runs its
|
|
86692
|
+
* `action` and triggers its `command` once the popup has closed (the command
|
|
86693
|
+
* follows the action, and only if it succeeded) — see
|
|
86694
|
+
* picker_confirm.jsx, and `message`, `confirmLabel`, `cancelLabel`,
|
|
86695
|
+
* `focusOnOpen` below.
|
|
86696
|
+
*
|
|
86697
|
+
* @type {import("preact").FunctionComponent<{
|
|
86698
|
+
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
86699
|
+
* value?: any,
|
|
86700
|
+
* defaultValue?: any,
|
|
86701
|
+
* name?: string,
|
|
86702
|
+
* placeholder?: import("preact").ComponentChildren,
|
|
86703
|
+
* ui?: import("preact").ComponentChildren | "default" | import("preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
86704
|
+
* required?: boolean,
|
|
86705
|
+
* min?: Date | string | number,
|
|
86706
|
+
* max?: Date | string | number,
|
|
86707
|
+
* step?: string | number,
|
|
86708
|
+
* disabled?: boolean,
|
|
86709
|
+
* readOnly?: boolean,
|
|
86710
|
+
* openWhileReadOnly?: boolean,
|
|
86711
|
+
* error?: boolean | string,
|
|
86712
|
+
* uiAction?: (value: any, event: Event) => void,
|
|
86713
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void,
|
|
86714
|
+
* children?: import("preact").ComponentChildren,
|
|
86715
|
+
* mode?: "popover" | "dialog" | "callout",
|
|
86716
|
+
* openOn?: "press" | "longpress" | "contextmenu" | string | string[],
|
|
86717
|
+
* animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
|
|
86718
|
+
* lift?: "box" | "scene",
|
|
86719
|
+
* liftAnchor?: Element | { current: Element } | string,
|
|
86720
|
+
* animationDuration?: string,
|
|
86721
|
+
* calloutStatus?: "info" | "warning" | "error" | "success" | "none",
|
|
86722
|
+
* calloutIcon?: boolean,
|
|
86723
|
+
* calloutCloseButton?: boolean,
|
|
86724
|
+
* calloutCloseByScroll?: boolean | number,
|
|
86725
|
+
* popoverMode?: "nearby" | "overlay",
|
|
86726
|
+
* positionArea?: string,
|
|
86727
|
+
* popupWidthFitContent?: boolean,
|
|
86728
|
+
* loadingOutline?: "custom",
|
|
86729
|
+
* loadingOutlineInset?: number,
|
|
86730
|
+
* variant?: "icon" | "circle" | "headless" | "discrete" | "button" | "text" | "bare" | "picker",
|
|
86731
|
+
* icon?: boolean,
|
|
86732
|
+
* alignX?: "start" | "center" | "end",
|
|
86733
|
+
* alignY?: "start" | "center" | "end" | "stretch",
|
|
86734
|
+
* rightSlotIcon?: import("preact").ComponentChildren,
|
|
86735
|
+
* rightSlotIconSize?: number | string,
|
|
86736
|
+
* rightSlot?: import("preact").ComponentChildren,
|
|
86737
|
+
* chevron?: "end" | "start" | "hang-end",
|
|
86738
|
+
* clearConfirm?: string | import("preact").ComponentChildren,
|
|
86739
|
+
* message?: import("preact").ComponentChildren,
|
|
86740
|
+
* confirmLabel?: import("preact").ComponentChildren,
|
|
86741
|
+
* cancelLabel?: import("preact").ComponentChildren,
|
|
86742
|
+
* focusOnOpen?: "confirm" | "cancel" | "none",
|
|
86743
|
+
* onConfirm?: (confirmEvent: CustomEvent) => void,
|
|
86744
|
+
* onOpen?: (openEvent: CustomEvent) => void,
|
|
86745
|
+
* onClose?: (closeEvent: CustomEvent) => void,
|
|
86746
|
+
* maxLines?: number,
|
|
86747
|
+
* slotSpacing?: number | string,
|
|
86748
|
+
* popoverMaxHeight?: number | string,
|
|
86749
|
+
* dialogMinWidth?: number | string,
|
|
86750
|
+
* dialogMinHeight?: number | string,
|
|
86751
|
+
* dialogMaxWidth?: number | string,
|
|
86752
|
+
* dialogMaxHeight?: number | string,
|
|
86753
|
+
* popupBackgroundColor?: string,
|
|
86754
|
+
* popupBorderRadius?: number | string,
|
|
86755
|
+
* popupBoxShadow?: string,
|
|
86756
|
+
* dialogBorderWidth?: number | string,
|
|
86757
|
+
* clearable?: boolean,
|
|
86758
|
+
* picksNothing?: boolean,
|
|
86759
|
+
* standalone?: boolean,
|
|
86760
|
+
* popupLayer?: "top" | "local",
|
|
86761
|
+
* popupTestId?: string,
|
|
86762
|
+
* popupAriaLabel?: string,
|
|
86763
|
+
* popupAriaLabelledBy?: string,
|
|
86764
|
+
* popupAriaDescribedBy?: string,
|
|
86765
|
+
* confirmTestId?: string,
|
|
86766
|
+
* cancelTestId?: string,
|
|
86767
|
+
* mount?: "always" | "idle" | "from-first-open" | "while-opened",
|
|
86768
|
+
* dialogExpand?: boolean,
|
|
86769
|
+
* dialogExpandX?: boolean,
|
|
86770
|
+
* dialogExpandY?: boolean,
|
|
86771
|
+
* dialogSizeFromAnchor?: boolean,
|
|
86772
|
+
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
86773
|
+
* marginWithContainer?: number | string,
|
|
86774
|
+
* anchor?: import("preact").RefObject<HTMLElement> | HTMLElement,
|
|
86775
|
+
* escapeEffect?: "cancel" | "close",
|
|
86776
|
+
* pressOutside?: "close" | "cancel" | "capture",
|
|
86777
|
+
* backdrop?: boolean,
|
|
86778
|
+
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
86779
|
+
* backdropColor?: string,
|
|
86780
|
+
* backdropFilter?: string,
|
|
86781
|
+
* ref?: import("preact").RefObject<HTMLElement>,
|
|
86782
|
+
* [key: string]: any,
|
|
86783
|
+
* }>}
|
|
86784
|
+
* @param {string} [popupBackgroundColor] The popup's surface. Left out it is
|
|
86785
|
+
* the one every navi popup uses (`--navi-popup-background-color`, which
|
|
86786
|
+
* follows the theme) — the trigger's own `backgroundColor` is not it, so a
|
|
86787
|
+
* variant that paints the trigger transparent leaves the popup a real sheet.
|
|
86788
|
+
* @param {number|string} [popupBorderRadius] The popup's corners. Left out they
|
|
86789
|
+
* echo the trigger's `borderRadius`.
|
|
86790
|
+
* @param {string} [popupBoxShadow] The popup's shadow, popover or dialog. Left
|
|
86791
|
+
* out it is the one every navi popup casts. `"none"` for a popup that is
|
|
86792
|
+
* not a sheet — a card lifted out of a page draws its own edges, and the
|
|
86793
|
+
* popup only carries it (see `dialogSizeFromAnchor`).
|
|
86794
|
+
* @param {"press"|"longpress"|"contextmenu"|string|string[]} [openOn="press"]
|
|
86795
|
+
* What opens the popup. The press, by default — the way a native select
|
|
86796
|
+
* opens. Any other name is an interaction navi detects (see
|
|
86797
|
+
* docs/interactions.md: `"longpress"`, `"contextmenu"`, `"double_click"`…), or
|
|
86798
|
+
* a list of them — `["longpress", "contextmenu"]` is the hold on a phone and
|
|
86799
|
+
* the right click at a desk. The picker declares it on itself, so it is
|
|
86800
|
+
* arbitrated like any other gesture: a swipe on the same card takes the
|
|
86801
|
+
* press a hold would have taken. A tap then opens nothing — the press stays
|
|
86802
|
+
* free for what the `ui` holds: a link in the card navigates, a button in it
|
|
86803
|
+
* presses, a picker in it opens on its own click, with nothing to declare
|
|
86804
|
+
* (`selfInteractions` is for a drawing that DOES open on the press). The
|
|
86805
|
+
* trigger says so under the mouse too: it drops the hand cursor, which is
|
|
86806
|
+
* the press's promise, and every pixel of the drawing keeps the cursor it
|
|
86807
|
+
* would have on its own. That is what lets a whole card be a picker without
|
|
86808
|
+
* every touch on it opening the sheet — and the keyboard keeps its ways in
|
|
86809
|
+
* (Enter, Space, the arrows). A hold declared INSIDE the card (`interactions={{ longpress }}`
|
|
86810
|
+
* on something it holds) answers before this one: the nearer hold takes the
|
|
86811
|
+
* press, the way a click is the innermost target's. Being a gesture, the open goes through
|
|
86812
|
+
* the interaction gate as one: a `readOnly` picker refuses it and says so
|
|
86813
|
+
* where the finger is, whatever `openWhileReadOnly` says.
|
|
86814
|
+
* @param {number} [maxLines=1] How many lines the value may take before it is
|
|
86815
|
+
* cut with an ellipsis — `Text`'s `maxLines`, put on the value box, and
|
|
86816
|
+
* granted to a `BadgeList` in the `ui`, which caps its rows to it (see
|
|
86817
|
+
* max_lines_context.js). `variant="text"` and `variant="bare"` are not
|
|
86818
|
+
* clamped: their drawing is the caller's own, a `Text` in the `ui` cuts
|
|
86819
|
+
* itself with its own `maxLines`. See docs/typography.md.
|
|
86820
|
+
* @param {import("preact").ComponentChildren | "default" | import("preact").ComponentType} [ui] What the
|
|
86821
|
+
* trigger draws in place of the value's default rendering (a date, a list
|
|
86822
|
+
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
86823
|
+
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
86824
|
+
* this draws when the picker holds nothing may well be an answer ("no
|
|
86825
|
+
* filter", "anywhere"), so its empty look is the caller's. A `ui` must
|
|
86826
|
+
* therefore say on its own that the picker is empty, and take the same room
|
|
86827
|
+
* empty as it does filled. A <BadgeList> gets both from its `fallback` — the
|
|
86828
|
+
* placeholder text, as plain text, see docs/typography.md#badgelist.
|
|
86829
|
+
* `"default"` draws nothing in the slot at all.
|
|
86830
|
+
*
|
|
86831
|
+
* Under `variant="icon"` there is no value to draw, so this IS the trigger:
|
|
86832
|
+
* left out it is the icon the right slot would have shown (`rightSlotIcon`,
|
|
86833
|
+
* or the chevron), and given it is drawn instead — one icon of the caller's
|
|
86834
|
+
* own, and nothing else.
|
|
86835
|
+
*
|
|
86836
|
+
* Under `variant="bare"` it is the trigger as well, and the picker adds
|
|
86837
|
+
* nothing around it: whatever this draws is what the picker measures. Its
|
|
86838
|
+
* own padding, its own lines, its own corners — a `<Box>` here gives a
|
|
86839
|
+
* picker the exact box that `<Box>` has on its own. Anything interactive
|
|
86840
|
+
* inside it takes its clicks back with `data-self-interactions`, the press
|
|
86841
|
+
* everywhere else opening the popup. There is no right slot beside it either,
|
|
86842
|
+
* so what a field would have got from navi this drawing places itself: a
|
|
86843
|
+
* `<Picker.Clear />` where the cross belongs, laid out like anything else it
|
|
86844
|
+
* holds. What the picker catches the press over is that box and no more, so
|
|
86845
|
+
* a drawing that paints outside it on purpose — an affordance in the margin
|
|
86846
|
+
* beside it, out of the flow so the rest keeps its width — is drawn and
|
|
86847
|
+
* opens nothing; `pressPadding` is how far the press area follows it out
|
|
86848
|
+
* there. The chevron itself is the `chevron` prop's to draw and place.
|
|
86849
|
+
*
|
|
86850
|
+
* A drawing that follows what the picker HOLDS — the value the popup just
|
|
86851
|
+
* closed on, before the server has answered, the same state navi's own
|
|
86852
|
+
* default `ui` reads, rolled back if the action fails (`resetOnError`) —
|
|
86853
|
+
* reads it one of two ways. As a component, `ui={MyCard}`: the picker
|
|
86854
|
+
* renders it and hands it `value`, `loading` and `interactive` as props.
|
|
86855
|
+
* As an element, `ui={<MyCard game={game} />}`, with `usePickerState()`
|
|
86856
|
+
* inside it returning the same three — for a drawing that needs props of
|
|
86857
|
+
* its own as well. Either way a card shows the score the moment the sheet
|
|
86858
|
+
* leaves and takes it back on a refusal, with nothing held by the caller.
|
|
86859
|
+
* `loading` is the wait it wears meanwhile: navi draws its loading outline
|
|
86860
|
+
* on every trigger, two pixels out of the box. On a card-sized
|
|
86861
|
+
* `variant="bare"` that run sits on the card's own border and reads as a
|
|
86862
|
+
* border blinking; `loadingOutlineInset` moves it clear of it. And when the
|
|
86863
|
+
* picker's box is not the shape of what waits, `loadingOutline="custom"`
|
|
86864
|
+
* lets the drawing draw the wait itself, from that flag or from
|
|
86865
|
+
* `[data-loading]` on the picker root in css.
|
|
86866
|
+
* @param {boolean} [readOnly] Nothing in this picker can be changed — and it
|
|
86867
|
+
* still opens, so what is in the popup can be read: everything in there is
|
|
86868
|
+
* held read-only in turn, each control greying out and saying why on its own.
|
|
86869
|
+
* Which is the point of opening it at all: a picker's answer often exists
|
|
86870
|
+
* only in the shape the popup draws (a plan with one tile ringed, a wheel
|
|
86871
|
+
* stopped on a time), and the trigger's one line is a summary of it, not the
|
|
86872
|
+
* whole of it. The clear cross goes, closing commits nothing, and a `value`
|
|
86873
|
+
* the popup would otherwise have confirmed stays a suggestion.
|
|
86874
|
+
* A picker with no `children` opens the browser's own picker instead, and
|
|
86875
|
+
* that one cannot be held read-only — so it goes on refusing (see
|
|
86876
|
+
* PickerNative).
|
|
86877
|
+
* @param {boolean} [openWhileReadOnly=true] Pass false to make `readOnly`
|
|
86878
|
+
* refuse the open as well, for a popup with nothing to read: a form of
|
|
86879
|
+
* controls to fill in, a menu of gestures. Opening then only shows what is
|
|
86880
|
+
* refused, so the picker says why on the trigger instead — where the
|
|
86881
|
+
* interaction happened. A busy picker follows the same rule: it still
|
|
86882
|
+
* opens to be read while its action runs, and with `false` the press is
|
|
86883
|
+
* refused with the wait as its reason — what a card that is syncing wants
|
|
86884
|
+
* for the sheet that would write it again.
|
|
86885
|
+
* @param {boolean|string} [error] Something went wrong around this picker (its
|
|
86886
|
+
* content failed to load, its value could not be resolved…). Shown as a
|
|
86887
|
+
* callout on the trigger, open or closed — the caller has nothing to place.
|
|
86888
|
+
* Dismissing it discards that error; a new `error` value raises another one.
|
|
86889
|
+
* @param {string} [id] What the popup's open state is kept under in the
|
|
86890
|
+
* history entry: a screen left and come back to finds the picker open, and
|
|
86891
|
+
* in dialog mode the opening is an entry of its own, closed by the back
|
|
86892
|
+
* button before the screen is left. Left out, the key is a generated id,
|
|
86893
|
+
* which names one mount: the state survives neither leaving the screen nor a
|
|
86894
|
+
* reload. A picker whose popup leads somewhere (a link inside it) has one.
|
|
86895
|
+
* @param {"popover"|"dialog"|"callout"} [mode] Which popup the children open
|
|
86896
|
+
* in. Left out, a popover on a large screen and a dialog on a narrow one.
|
|
86897
|
+
* `"callout"` shows them in the picker's own callout — the speech bubble its
|
|
86898
|
+
* constraints speak in, with its status icon, its cross and its arrow on
|
|
86899
|
+
* the trigger — for a tooltip that opens on a press. It closes the way a
|
|
86900
|
+
* callout does: its cross, Escape, a click outside.
|
|
86901
|
+
* @param {"info"|"warning"|"error"|"success"|"none"} [calloutStatus]
|
|
86902
|
+
* `mode="callout"`: what the callout says about what it holds — its border,
|
|
86903
|
+
* its icon, and whether a click outside closes it (an "error" stays).
|
|
86904
|
+
* `"none"` is a plain tooltip: no icon, a neutral frame. "info" by default,
|
|
86905
|
+
* "none" under `variant="text"`. The trigger's default icon is the
|
|
86906
|
+
* callout's own status icon in that status (see `CalloutStatusIcon`) — left
|
|
86907
|
+
* out, the trigger is that icon; `variant="circle"` draws it round. The callout's arrow points at
|
|
86908
|
+
* the middle of the trigger (`data-callout-arrow-x="center"` unless the
|
|
86909
|
+
* picker says otherwise).
|
|
86910
|
+
* @param {boolean} [calloutIcon] `mode="callout"`: whether the callout shows
|
|
86911
|
+
* its status icon beside what it holds. On by default, off under
|
|
86912
|
+
* `variant="text"` — a word asks for a plain bubble — and never shown
|
|
86913
|
+
* without a status, whatever this says.
|
|
86914
|
+
* @param {boolean} [calloutCloseButton=true] `mode="callout"`: pass false to
|
|
86915
|
+
* take the cross off the callout. It still closes on Escape, a click
|
|
86916
|
+
* outside, and a `--navi-close` from what it holds — for a tooltip that is
|
|
86917
|
+
* read rather than dismissed. Off by nobody's default: a text trigger
|
|
86918
|
+
* keeps the cross unless told not to.
|
|
86919
|
+
* @param {boolean|number} [calloutCloseByScroll=false] `mode="callout"`: close
|
|
86920
|
+
* the callout once the page has scrolled under it — `true` for a short
|
|
86921
|
+
* distance, a number for that many pixels. For a tooltip read in place,
|
|
86922
|
+
* which scrolling away is done with.
|
|
86923
|
+
* @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
|
|
86924
|
+
* over the trigger, "nearby" leaves a small gap below it.
|
|
86925
|
+
* @param {string} [positionArea] Where the popup goes — relative to the trigger
|
|
86926
|
+
* in popover mode, relative to the viewport in dialog mode. Same grammar as
|
|
86927
|
+
* Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
|
|
86928
|
+
* …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
|
|
86929
|
+
* popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
|
|
86930
|
+
* popover still flips to the opposite side on its own when there isn't
|
|
86931
|
+
* enough room.
|
|
86932
|
+
* @param {boolean} [popupWidthFitContent] By default the popover is at least as
|
|
86933
|
+
* wide as the trigger. Set this to let the content size it instead, so a
|
|
86934
|
+
* popover narrower than the trigger stays narrow. Popover mode only — a
|
|
86935
|
+
* dialog never takes the trigger's width to begin with (see
|
|
86936
|
+
* `dialogMinWidth`).
|
|
86937
|
+
* @param {import("preact").ComponentChildren} [rightSlotIcon] What the right
|
|
86938
|
+
* slot draws in place of the chevron. It is the whole slot, not an addition
|
|
86939
|
+
* to it: the picker then no longer says on its own that it opens, so pass
|
|
86940
|
+
* something that does. Decoration only — it is wrapped in an `<Icon>`, which
|
|
86941
|
+
* is `aria-hidden`, so anything focusable in there would be reachable by tab
|
|
86942
|
+
* while invisible to assistive tech. Use `rightSlot` for that.
|
|
86943
|
+
*
|
|
86944
|
+
* The slot only exists beside a value. `variant="icon"` has none, so there is
|
|
86945
|
+
* no slot either: the icon becomes the trigger itself and is drawn as the
|
|
86946
|
+
* `ui` — which is also what a caller overrides to draw their own. Nothing
|
|
86947
|
+
* else is drawn there, the clear cross included: one icon has room for one
|
|
86948
|
+
* thing.
|
|
86949
|
+
|
|
86950
|
+
* `variant="bare"` has no slot either, for the same reason turned around: the
|
|
86951
|
+
* picker is the drawing's box to the pixel, so nothing may be added beside it
|
|
86952
|
+
* — the drawing holds what it needs (`<Picker.Clear />`) and places it. Its
|
|
86953
|
+
* `chevron` prop is the exception: the slot, put where the drawing is not.
|
|
86954
|
+
* @param {"end"|"start"|"hang-end"} [chevron] `variant="bare"` only: the
|
|
86955
|
+
* picker draws no chevron beside the drawing, so nothing says it opens; this
|
|
86956
|
+
* draws one — the icon the right slot would hold, `rightSlotIcon` or the
|
|
86957
|
+
* chevron, at `rightSlotIconSize`, `slotSpacing` away from the drawing (xxs
|
|
86958
|
+
* by default), taking the clear cross's place under `clearable` like any
|
|
86959
|
+
* slot — and says where, so the drawing keeps the box it has when nothing
|
|
86960
|
+
* opens. Which side depends on where the drawing is anchored in its line.
|
|
86961
|
+
* `"end"`: after it, in the flow — a label with room after it. `"start"`:
|
|
86962
|
+
* before it — a label pushed against the right edge, which the chevron
|
|
86963
|
+
* would otherwise push left by its width. `"hang-end"`: after it and OUT of
|
|
86964
|
+
* the flow, in the margin beside the box — a drawing filling its whole
|
|
86965
|
+
* line; the press follows it out there by its size plus its gap, on top of
|
|
86966
|
+
* any `pressPaddingRight`. The chevron is drawn in `--picker-icon-color`
|
|
86967
|
+
* (`currentColor` here) and `--picker-icon-color-hover`; a dimmed one is
|
|
86968
|
+
* `--picker-icon-color: color-mix(in srgb, currentColor 55%, transparent)`.
|
|
86969
|
+
* @param {import("preact").ComponentChildren} [rightSlot] Same place, rendered
|
|
86970
|
+
* as-is: no `<Icon>` around it, nothing `aria-hidden`. This is where an
|
|
86971
|
+
* interactive right slot goes. `null` takes the slot away entirely — the
|
|
86972
|
+
* gutter it carries with it, so the trigger measures its drawing and centers
|
|
86973
|
+
* on it.
|
|
86974
|
+
* @param {boolean} [picksNothing] The popup asks something rather than holding
|
|
86975
|
+
* an answer, so the trigger draws a label and never a value: no chevron, no
|
|
86976
|
+
* clear cross, in any variant — and the controls in the popup are their own
|
|
86977
|
+
* (a checkbox gating the yes), never the picker's value, so a bound `action`
|
|
86978
|
+
* keeps the params it was bound with. `type="confirm"` says it for itself; say it
|
|
86979
|
+
* for a popup of your own that is a menu of actions rather than a choice —
|
|
86980
|
+
* or a DOOR: a drawing that grows to be looked at (a weather scene, a plan),
|
|
86981
|
+
* with no `action` at all. The picker is then a trigger and a popup, and
|
|
86982
|
+
* nothing more: its close does nothing, and what it keeps from being a
|
|
86983
|
+
* control is what a door still needs — the role of a button, the keyboard
|
|
86984
|
+
* opening it, the anchor being itself, and one popup per instance without
|
|
86985
|
+
* an id to wire. See "The trigger wears the wait" in docs/popup_open.md for
|
|
86986
|
+
* when that beats a `Button` opening a shared `Dialog`.
|
|
86987
|
+
* @param {string|import("preact").ComponentChildren} [clearConfirm] The
|
|
86988
|
+
* question asked before the clear cross clears anything — the `message` of
|
|
86989
|
+
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
86990
|
+
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
86991
|
+
* as it was; answering yes clears and sends, and the picker's own action
|
|
86992
|
+
* receives the cleared value like any other choice. A cut (Ctrl+X) on such a
|
|
86993
|
+
* picker copies the value and leaves it in place: no clear without the
|
|
86994
|
+
* question.
|
|
86995
|
+
* @param {"icon"|"headless"|"discrete"|"button"|"text"|"bare"|"picker"} [variant]
|
|
86996
|
+
* How the trigger is drawn. `"button"` is a Button's drawing — surface,
|
|
86997
|
+
* padding, a centered label, no chevron — and what a `type="confirm"` picker
|
|
86998
|
+
* draws unless told otherwise: nothing is picked, the popup is a question.
|
|
86999
|
+
* `"text"` is a word in a sentence, underlined with dots, no box and no
|
|
87000
|
+
* slot: what `mode="callout"` uses for a callout on a term rather than on an
|
|
87001
|
+
* icon (`"icon"` being its default, with an info icon in place of the
|
|
87002
|
+
* chevron). `"bare"` draws no trigger of its own at all: the `ui` is it, and
|
|
87003
|
+
* the picker is exactly that drawing's box — no padding, no frame, no
|
|
87004
|
+
* control line, no clamp — for a picker that is a whole piece of a layout
|
|
87005
|
+
* rather than a field, and which must measure the same as the same drawing
|
|
87006
|
+
* placed anywhere else. No right slot comes with it: a cross belongs inside
|
|
87007
|
+
* that drawing, at the place and the size the drawing decides, so it is
|
|
87008
|
+
* `<Picker.Clear />` the `ui` holds rather than `clearable` the picker
|
|
87009
|
+
* takes. What navi still draws on that box — the focus ring, the loading
|
|
87010
|
+
* outline — follows the picker's `borderRadius`, which stays the control's
|
|
87011
|
+
* unless told: a drawing with corners of its own states them on the picker
|
|
87012
|
+
* as well. `"headless"` draws nothing either, and is not the `ui`'s box but
|
|
87013
|
+
* its parent's: it stretches to whatever positioned element holds it, for a
|
|
87014
|
+
* picker put inside the thing that opens it (a button, a row) so that the
|
|
87015
|
+
* popup hangs off that whole element. A picker opened from something it does
|
|
87016
|
+
* not sit in — an object on a map saying
|
|
87017
|
+
* `triggerNaviCommand(pickerEl, "--navi-open", event, { anchor, value })` —
|
|
87018
|
+
* therefore wants an element of its own to stretch into rather than the
|
|
87019
|
+
* nearest positioned ancestor it happens to find. `"picker"` (or an explicit
|
|
87020
|
+
* `variant={undefined}`) asks a confirm or callout picker for the field-like
|
|
87021
|
+
* drawing every other picker has.
|
|
87022
|
+
* @param {"custom"} [loadingOutline] Who draws the loading outline while the
|
|
87023
|
+
* action runs. Left unsaid, the picker does, around its own box. `"custom"`
|
|
87024
|
+
* says someone else does — what it means on a `Button` too — for a picker
|
|
87025
|
+
* that carries a wait drawn elsewhere: two nested pickers sharing one run
|
|
87026
|
+
* (a score picker drawn inside the card an edit picker wraps, see
|
|
87027
|
+
* `12_picker_card_demo.html`) are one wait, and one outline; and a
|
|
87028
|
+
* `variant="bare"` whose box is not
|
|
87029
|
+
* the shape of what waits (a card with a note pinned under it) leaves the
|
|
87030
|
+
* outline to the card, which knows its own box. The wait itself is still
|
|
87031
|
+
* worn: `aria-busy`, the busy refusal, the error callout, `[data-loading]`.
|
|
87032
|
+
* @param {number} [loadingOutlineInset=-2] Where the loading outline runs, in
|
|
87033
|
+
* px from the picker's box — negative is outside. A field wants it right on
|
|
87034
|
+
* its border; a card-sized trigger wants it a few pixels out, clear of the
|
|
87035
|
+
* card's own frame, so it reads as a run around the card rather than as its
|
|
87036
|
+
* border blinking.
|
|
87037
|
+
* @param {boolean} [icon] The trigger is one icon — the `ui`, or the chevron
|
|
87038
|
+
* when there is none. Its box is that icon's box plus the padding asked for
|
|
87039
|
+
* and nothing else: no right slot, and no control line to be as tall as, so
|
|
87040
|
+
* it measures the same as `<Button icon>` and stands in a row of icon
|
|
87041
|
+
* buttons. A shape, not a paint: the variant still decides what is drawn
|
|
87042
|
+
* around it, so `variant="discrete" icon` is the discrete hover wash on an
|
|
87043
|
+
* icon-sized square. `variant="icon"` is this shape with no surface at all.
|
|
87044
|
+
* @param {import("preact").ComponentChildren} [message] `type="confirm"`: the
|
|
87045
|
+
* question, in the popup's default body — text, or JSX when it needs an
|
|
87046
|
+
* emphasis, a link. Left out, a generic "are you sure?" in the current
|
|
87047
|
+
* language. To draw the whole popup instead, pass `children`: yes is then
|
|
87048
|
+
* whatever carries `--navi-confirm`, no whatever closes (`--navi-cancel`).
|
|
87049
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] `type="confirm"`,
|
|
87050
|
+
* default body: what the yes button reads. Defaults to the translated
|
|
87051
|
+
* "Confirm".
|
|
87052
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Same, for the no
|
|
87053
|
+
* button. Defaults to the translated "Cancel".
|
|
87054
|
+
* @param {"confirm"|"cancel"|"none"} [focusOnOpen="confirm"] `type="confirm"`,
|
|
87055
|
+
* default body: which button the keyboard lands on when the popup opens.
|
|
87056
|
+
* `"cancel"` is the careful choice before something that cannot be undone —
|
|
87057
|
+
* a stray Enter then answers no.
|
|
87058
|
+
* @param {(confirmEvent: CustomEvent) => void} [onConfirm] Called once the
|
|
87059
|
+
* popup has closed on a `--navi-confirm` said inside it. What a confirm
|
|
87060
|
+
* picker uses to run its press; a picker of any type may listen too.
|
|
87061
|
+
* @param {boolean} [canSendWhileUnchanged] Run `action` when a choice pressed
|
|
87062
|
+
* inside the popup — a row's `--navi-send`, a `--navi-confirm` — is the
|
|
87063
|
+
* value the picker already held. By default that close runs nothing: the
|
|
87064
|
+
* answer has not moved, so there is nothing new to say, same as `Form`.
|
|
87065
|
+
* Set this for a picker whose value is a setting that can be valid while
|
|
87066
|
+
* what it points to is not — a channel whose account came unlinked — so
|
|
87067
|
+
* picking it again is how the person asks for it to be put right, and the
|
|
87068
|
+
* action sees the same value and re-asks what there is to re-ask. A press
|
|
87069
|
+
* outside, Escape and the cross still send nothing: nothing was pressed.
|
|
87070
|
+
* @param {(openEvent: CustomEvent) => void} [onOpen] Called when the popup
|
|
87071
|
+
* opens, whatever asked for it (a press, a command, history bringing it
|
|
87072
|
+
* back). You most likely do NOT need this pair: "the user validated" is
|
|
87073
|
+
* already `action` — it fires on every close that keeps (click outside, the
|
|
87074
|
+
* cross, Enter, a `--navi-send` inside the popup) and never on a cancel.
|
|
87075
|
+
* Reach for `onOpen`/`onClose` only for the one thing `action` cannot say:
|
|
87076
|
+
* the popup being up at all, independent of the value — e.g. a derived
|
|
87077
|
+
* computation held while the sheet is open, released on close.
|
|
87078
|
+
* @param {(closeEvent: CustomEvent) => void} [onClose] Called when the popup
|
|
87079
|
+
* actually closes — not preventable, and (see `onOpen`) not the way to know
|
|
87080
|
+
* the user validated: that is `action`. `closeEvent.detail.isCancel` tells a
|
|
87081
|
+
* cancel (Escape, the back gesture) from a close that keeps. Fires after the
|
|
87082
|
+
* picker's value has settled — committed, or restored on a cancel — so the
|
|
87083
|
+
* value read in here is the one the picker ends up holding, and before the
|
|
87084
|
+
* popup leaves the history entry it pushed, so a route param cleared in here
|
|
87085
|
+
* is part of the url a close that keeps carries back.
|
|
87086
|
+
* @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
|
|
87087
|
+
* right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
|
|
87088
|
+
* cross. "inherit" takes the picker's own font size.
|
|
87089
|
+
* @param {number|string} [slotSpacing] Gap kept between what sits in the right
|
|
87090
|
+
* slot (the chevron, or the clear button) and the picker's own edge — same
|
|
87091
|
+
* prop name Input uses for its own slots. Accepts a spacing token ("s",
|
|
87092
|
+
* "m"…) like any other spacing prop, or a length. Defaults to half the
|
|
87093
|
+
* horizontal padding.
|
|
87094
|
+
* @param {number|string} [pressPadding] How far a press counts as the trigger
|
|
87095
|
+
* past the picker's own box — a spacing token ("s", "m"…) or a length, zero
|
|
87096
|
+
* by default. What the picker draws is transparent to the pointer: the press
|
|
87097
|
+
* lands on an invisible input covering the box, so the box is the whole
|
|
87098
|
+
* answer to "does this open". That is right while what is drawn stays inside
|
|
87099
|
+
* it, which is what `variant="bare"` measures. A `ui` that paints OUTSIDE
|
|
87100
|
+
* its box on purpose — a chevron pushed into the margin beside it, so the
|
|
87101
|
+
* label under it keeps the width it has when nothing opens — draws something
|
|
87102
|
+
* nobody can press, and this is how far the press area goes to reach it. The
|
|
87103
|
+
* press area only; nothing moves and nothing is drawn. Font-relative lengths
|
|
87104
|
+
* are read against the picker's own text, so "0.75em" is the drawing's em.
|
|
87105
|
+
* Only the sides that overhang: the area grows over whatever is next to the
|
|
87106
|
+
* picker, and takes the presses aimed at it.
|
|
87107
|
+
* @param {number|string} [pressPaddingX] Same, left and right only;
|
|
87108
|
+
* `pressPaddingY` top and bottom, `pressPaddingTop`/`Right`/`Bottom`/`Left`
|
|
87109
|
+
* one side each.
|
|
87110
|
+
* @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
|
|
87111
|
+
* (default 300px). The popover shrinks below it when space is tight.
|
|
87112
|
+
* @param {number|string} [dialogMinWidth] Floor on the dialog's width. A dialog
|
|
87113
|
+
* picker is not attached to its trigger and does not take its width (unlike
|
|
87114
|
+
* the popover, which uses it as a floor) — this is how a dialog too narrow
|
|
87115
|
+
* for what it holds is widened. Clamped by `dialogMaxWidth`/the container.
|
|
87116
|
+
* @param {number|string} [dialogMinHeight] Same, on the height.
|
|
87117
|
+
* @param {number|string} [dialogMaxWidth] Ceiling on the dialog's width, the
|
|
87118
|
+
* one `dialogExpand`/`dialogExpandX` grows up to — what makes an expanded
|
|
87119
|
+
* dialog a large sheet rather than a full screen. Capped in turn by the
|
|
87120
|
+
* container minus `marginWithContainer`. Describes the centered shape only:
|
|
87121
|
+
* a dialog docked by `dockedOnSmallTouchScreen` withdraws it and stays
|
|
87122
|
+
* container-wide, so both can be stated at once.
|
|
87123
|
+
* @param {number|string} [dialogMaxHeight] Same, on the height.
|
|
87124
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"lifting"|`slide-from-${string}`} [animation]
|
|
87125
|
+
* The popup's own (see Popover/Dialog). `"lifting"` is the one a picker
|
|
87126
|
+
* whose `ui` is a whole piece of the page wants: the popup is the browser
|
|
87127
|
+
* morphing the trigger's box into what carries `data-lift` inside the popup
|
|
87128
|
+
* (the popup itself when it carries it), and back on close. The trigger IS
|
|
87129
|
+
* the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
|
|
87130
|
+
* for a card that must keep its width on the way, left out for a drawing
|
|
87131
|
+
* that opens precisely to get bigger. It brings an opaque, blurred backdrop
|
|
87132
|
+
* with it (`--navi-backdrop-lift-*`); `backdropVariant="discrete"` asks for
|
|
87133
|
+
* the light wash back.
|
|
87134
|
+
* @param {Element|{current: Element}|string} [liftAnchor] Dialog mode,
|
|
87135
|
+
* Dialog's own: where a lifting close lands when that is not the trigger
|
|
87136
|
+
* (`animation="lifting"`, or `animation={{ open, close: "lifting" }}` for a
|
|
87137
|
+
* popup that opens the plain way and only lands on close),
|
|
87138
|
+
* read once the close is made (after `onClose`).
|
|
87139
|
+
* @param {"box"|"scene"} [lift="box"] Dialog's own, under `animation="lifting"`:
|
|
87140
|
+
* `"box"` for a card that extends (its top stays, the box uncovers the rest),
|
|
87141
|
+
* `"scene"` for a thumbnail that is a band cut from the middle of the bigger
|
|
87142
|
+
* view, as wide as it (each picture at the box's width, centred in it).
|
|
87143
|
+
* @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
|
|
87144
|
+
* @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
|
|
87145
|
+
* as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and
|
|
87146
|
+
* as a ceiling, unless a `dialogMaxWidth` says otherwise. What keeps a card
|
|
87147
|
+
* its own width once lifted out of the page (`animation="lifting"`, with
|
|
87148
|
+
* `data-lift` on the card inside the popup): it moves, and nothing else
|
|
87149
|
+
* about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
|
|
87150
|
+
* ceiling by hand: the popup props are written on the dialog element, where
|
|
87151
|
+
* that variable lives.
|
|
87152
|
+
* @param {boolean|"top"|"bottom"} [dockedOnSmallTouchScreen] Dialog mode,
|
|
87153
|
+
* Dialog's own: on a small touch screen the dialog becomes a sheet docked
|
|
87154
|
+
* full width to one edge — `true` or `"top"` the top edge, out of the
|
|
87155
|
+
* virtual keyboard's way; `"bottom"` the bottom one, for a sheet that is
|
|
87156
|
+
* read and tapped. Anywhere else it stays the centered box. Ignored in
|
|
87157
|
+
* popover mode.
|
|
87158
|
+
* @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
|
|
87159
|
+
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
87160
|
+
* picker with an explicit `id` (its opening being an entry of its own)
|
|
87161
|
+
* also goes back in history — so anything written to the url while it was
|
|
87162
|
+
* open (a route `stateSignal`, a search param) goes back with it. "close"
|
|
87163
|
+
* makes Escape say what clicking outside says: keep what was chosen,
|
|
87164
|
+
* close — a last resort, see
|
|
87165
|
+
* docs/popup_open.md#escape-cancels-the-other-gestures-keep for why Escape
|
|
87166
|
+
* should go on meaning cancel, and
|
|
87167
|
+
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
87168
|
+
* back.
|
|
87169
|
+
* @param {import("preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
87170
|
+
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
87171
|
+
* trigger is one piece of a bigger control (the chevron half of a split
|
|
87172
|
+
* button) points at the whole control, so the popup lines up with it and is
|
|
87173
|
+
* at least as wide as it.
|
|
87174
|
+
* @param {boolean} [standalone] - This picker answers for itself: it does not
|
|
87175
|
+
* register with the control group around it, so what it holds never becomes
|
|
87176
|
+
* part of that group's value and nothing that group does — distributing,
|
|
87177
|
+
* resetting, validating — reaches it. For a door that only opens something,
|
|
87178
|
+
* and for a picker that commits on its own inside a form.
|
|
87179
|
+
* @param {"close"|"cancel"|"capture"} [pressOutside="close"]
|
|
87180
|
+
* What a press outside the popup does: close and keep ("close"), close and
|
|
87181
|
+
* put back the value at open ("cancel"), or nothing at all ("capture"). The
|
|
87182
|
+
* default is what gives a popup with no confirm button its way out that
|
|
87183
|
+
* keeps — see the same section.
|
|
87184
|
+
* @param {boolean} [backdrop=true] Whether anything is laid between the popup
|
|
87185
|
+
* and the page at all. `false` lets a press outside both close the popup and
|
|
87186
|
+
* reach whatever it landed on, in one gesture — for a picker opened over a
|
|
87187
|
+
* page that stays as pressable as it looks (a plan, a map, a canvas), where a
|
|
87188
|
+
* wall would spend the first press on dismissing. Honoured in every mode: a
|
|
87189
|
+
* dialog with no wall is shown through the Popover API rather than
|
|
87190
|
+
* `showModal()`, so a picker docked as a bottom sheet on a phone still
|
|
87191
|
+
* leaves the plan behind it live. `false` by default under `mode="callout"`,
|
|
87192
|
+
* a bubble that dims nothing; `true` there spends the press outside on
|
|
87193
|
+
* closing it — the link under it not followed, the card under it not held —
|
|
87194
|
+
* without a wall: the scroll a finger starts beside the bubble still
|
|
87195
|
+
* scrolls. Not read inside a `<PopupGroup>`, whose wall it is.
|
|
87196
|
+
* @param {"auto"|"lift"|"discrete"|"invisible"} [backdropVariant="auto"] How
|
|
87197
|
+
* visible the popup's backdrop is, independently of what a click outside
|
|
87198
|
+
* does: `"auto"` is the paint `pressOutside` implies,
|
|
87199
|
+
* `"lift"` the opaque blurred wall `animation="lifting"` brings, asked for
|
|
87200
|
+
* without the movement, `"discrete"` a barely-there dim, `"invisible"` fully
|
|
87201
|
+
* transparent — a wall that is not seen is still a wall, which is what
|
|
87202
|
+
* `backdrop` above answers. For a picker that closes on an outside click
|
|
87203
|
+
* without wanting to dim the page for it.
|
|
87204
|
+
* @param {string} [backdropColor] The wash the popup paints over what is
|
|
87205
|
+
* behind, for this picker alone. See Dialog's own doc.
|
|
87206
|
+
* @param {string} [backdropFilter] What that wash does to the picture
|
|
87207
|
+
* underneath (`"blur(4px)"`), independently of what a click outside does.
|
|
87208
|
+
* See Dialog's own doc.
|
|
87209
|
+
* @param {number|string} [marginWithContainer] Minimum gap kept between the
|
|
87210
|
+
* popup and the edges of what contains it (the viewport, or the picker's own
|
|
87211
|
+
* positioned ancestor for `popupLayer="local"`). Caps the popup's size as
|
|
87212
|
+
* well as its placement, so what an expanded dialog leaves visible around
|
|
87213
|
+
* itself is set here. Defaults to `popoverSpacing` in popover mode, and to
|
|
87214
|
+
* Dialog's own in dialog mode: `3appw` in the top layer, `3cqw` under
|
|
87215
|
+
* `popupLayer="local"`, 0 for a dialog docked by
|
|
87216
|
+
* `dockedOnSmallTouchScreen`.
|
|
87217
|
+
* @param {string} [popupTestId] The `data-testid` of the popup element — the
|
|
87218
|
+
* popover or dialog the children open in. A `data-testid` on the picker
|
|
87219
|
+
* itself names the trigger (see docs/testid.md); this one is for a test that
|
|
87220
|
+
* needs the popup's own box — its surface, its position, its backdrop — and
|
|
87221
|
+
* not only what it holds, which a testid inside the children already names.
|
|
87222
|
+
* Under `mode="callout"` it names the callout element — the control's own,
|
|
87223
|
+
* shared with its constraint messages — while the children are shown in it.
|
|
87224
|
+
* @param {string} [popupAriaLabelledBy] The id(s) of what names the popup:
|
|
87225
|
+
* `aria-labelledby` on the popup element, which is a dialog in either mode
|
|
87226
|
+
* (the trigger says `aria-haspopup="dialog"`). `aria-label`/`aria-labelledby`
|
|
87227
|
+
* on the picker itself name the trigger. Rarely needed: a `<Popup.Title>`
|
|
87228
|
+
* in the children names the popup with nothing to wire, and without one
|
|
87229
|
+
* the popup takes the trigger's own name — the picker's `aria-labelledby`,
|
|
87230
|
+
* the label a picker that picks nothing draws, its `aria-label`, or the
|
|
87231
|
+
* `<Label>`/`<label for>` pointing at it (one that has an id, and does not
|
|
87232
|
+
* wrap the picker). A `mode="callout"` popup is read as its message.
|
|
87233
|
+
* @param {string} [popupAriaLabel] The same, as a string, for a popup whose
|
|
87234
|
+
* title is not an element.
|
|
87235
|
+
* @param {string} [popupAriaDescribedBy] The id(s) of what describes the
|
|
87236
|
+
* popup: `aria-describedby` on the popup element.
|
|
87237
|
+
* @param {string} [confirmTestId] `type="confirm"`, default body: the
|
|
87238
|
+
* `data-testid` of the yes button; `cancelTestId` that of the no button.
|
|
87239
|
+
* Both are elements navi builds, which a test may not name by class (see
|
|
87240
|
+
* docs/testid.md).
|
|
87241
|
+
* @param {string} [cancelTestId]
|
|
87242
|
+
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [mount] The popup's own
|
|
87243
|
+
* (see Popup): when the children are built and thrown away. Left out, a
|
|
87244
|
+
* picker told no value holds what the control they hold holds, and builds
|
|
87245
|
+
* them as soon as that value is read: right away (`"always"`) when navi draws
|
|
87246
|
+
* it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
|
|
87247
|
+
* cross, or when a form or group around the picker collects it; when an
|
|
87248
|
+
* element `ui` calls `usePickerState()`. Read by nothing, it waits for the
|
|
87249
|
+
* first open, like a picker told a value. A `picksNothing` picker (and so
|
|
87250
|
+
* `type="confirm"`) never builds them early: its popup holds no value, there
|
|
87251
|
+
* is nothing to read.
|
|
87252
|
+
*/
|
|
85699
87253
|
// Assembled in pure expressions rather than written onto Picker afterwards
|
|
85700
87254
|
// (`Picker.Chip = PickerChip`): what createComponentResolver returns is an object
|
|
85701
87255
|
// a bundler cannot see through, so a property assigned to it later is a side
|
|
@@ -85952,7 +87506,7 @@ const css$q = /* css */`@layer navi {
|
|
|
85952
87506
|
const TRAVELS_NONE = [];
|
|
85953
87507
|
|
|
85954
87508
|
/**
|
|
85955
|
-
* @type {import("
|
|
87509
|
+
* @type {import("preact").FunctionComponent<{
|
|
85956
87510
|
* value?: any,
|
|
85957
87511
|
* defaultValue?: any,
|
|
85958
87512
|
* signal?: import("@preact/signals").Signal<any>,
|
|
@@ -85968,7 +87522,7 @@ const TRAVELS_NONE = [];
|
|
|
85968
87522
|
* fallbackValue?: any,
|
|
85969
87523
|
* valueAtStep: (value: any, count: number) => any,
|
|
85970
87524
|
* compareValues?: (a: any, b: any) => number,
|
|
85971
|
-
* renderValue?: (value: any, context: { maxLines?: number }) => import("
|
|
87525
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("preact").ComponentChildren,
|
|
85972
87526
|
* controlProps?: object,
|
|
85973
87527
|
* duration?: number,
|
|
85974
87528
|
* vertical?: boolean,
|
|
@@ -86755,14 +88309,14 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
86755
88309
|
* single `value`/`signal` like any other control. `TimeSpin` is that, for a
|
|
86756
88310
|
* time of day.
|
|
86757
88311
|
*
|
|
86758
|
-
* @type {import("
|
|
88312
|
+
* @type {import("preact").FunctionComponent<{
|
|
86759
88313
|
* name?: string,
|
|
86760
88314
|
* value?: any,
|
|
86761
88315
|
* defaultValue?: any,
|
|
86762
88316
|
* signal?: import("@preact/signals").Signal<any>,
|
|
86763
88317
|
* aggregateChildStates?: (childUIStateControllers: any[]) => any,
|
|
86764
88318
|
* distributeChildUIState?: (groupState: any, childUIStateController: any) => any,
|
|
86765
|
-
* children?: import("
|
|
88319
|
+
* children?: import("preact").ComponentChildren,
|
|
86766
88320
|
* [key: string]: any,
|
|
86767
88321
|
* }>}
|
|
86768
88322
|
* Everything a box takes is taken here too — `width`, `borderWidth`,
|
|
@@ -86865,7 +88419,7 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
86865
88419
|
* above and below it by default — sideways they would be where the caret
|
|
86866
88420
|
* moves — with the bigger number up top.
|
|
86867
88421
|
*
|
|
86868
|
-
* @type {import("
|
|
88422
|
+
* @type {import("preact").FunctionComponent<{
|
|
86869
88423
|
* value?: number|string,
|
|
86870
88424
|
* defaultValue?: number|string,
|
|
86871
88425
|
* min?: number,
|
|
@@ -86970,7 +88524,7 @@ const numberAtStep = (value, count, {
|
|
|
86970
88524
|
* A day one steps through: the date plus what it is to today when there is a
|
|
86971
88525
|
* word for it, and a calendar behind it for the day that is far from here.
|
|
86972
88526
|
*
|
|
86973
|
-
* @type {import("
|
|
88527
|
+
* @type {import("preact").FunctionComponent<{
|
|
86974
88528
|
* value?: string,
|
|
86975
88529
|
* defaultValue?: string,
|
|
86976
88530
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -86981,7 +88535,7 @@ const numberAtStep = (value, count, {
|
|
|
86981
88535
|
* maxMessage?: any,
|
|
86982
88536
|
* step?: number,
|
|
86983
88537
|
* lang?: string,
|
|
86984
|
-
* renderDay?: (day: string) => import("
|
|
88538
|
+
* renderDay?: (day: string) => import("preact").ComponentChildren,
|
|
86985
88539
|
* [key: string]: any,
|
|
86986
88540
|
* }>}
|
|
86987
88541
|
* @param {string} [value] The day shown, as "YYYY-MM-DD".
|
|
@@ -86992,7 +88546,7 @@ const numberAtStep = (value, count, {
|
|
|
86992
88546
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
86993
88547
|
* Long by default, since this is a control one reads rather than a column
|
|
86994
88548
|
* one scans; `short` where the room is not there.
|
|
86995
|
-
* @param {(day: string) => import("
|
|
88549
|
+
* @param {(day: string) => import("preact").ComponentChildren} [renderDay] What
|
|
86996
88550
|
* to write for a day. Defaults to the date plus what it is to today when
|
|
86997
88551
|
* there is a word for it ("samedi 8 août (demain)"), since a day near now is
|
|
86998
88552
|
* read as a distance from now before it is read as a date.
|
|
@@ -87109,7 +88663,7 @@ const HOUR_MAX = 23;
|
|
|
87109
88663
|
const MINUTE_MAX = 59;
|
|
87110
88664
|
|
|
87111
88665
|
/**
|
|
87112
|
-
* @type {import("
|
|
88666
|
+
* @type {import("preact").FunctionComponent<{
|
|
87113
88667
|
* name?: string,
|
|
87114
88668
|
* value?: string,
|
|
87115
88669
|
* defaultValue?: string,
|
|
@@ -87117,7 +88671,7 @@ const MINUTE_MAX = 59;
|
|
|
87117
88671
|
* minuteStep?: number,
|
|
87118
88672
|
* pad?: number,
|
|
87119
88673
|
* loop?: boolean,
|
|
87120
|
-
* separator?: import("
|
|
88674
|
+
* separator?: import("preact").ComponentChildren,
|
|
87121
88675
|
* hourLabel?: string,
|
|
87122
88676
|
* minuteLabel?: string,
|
|
87123
88677
|
* [key: string]: any,
|
|
@@ -87130,7 +88684,7 @@ const MINUTE_MAX = 59;
|
|
|
87130
88684
|
* of a day to stop at. Say `loop={false}` for two ends one cannot step past.
|
|
87131
88685
|
* @param {number} [pad=2] How many digits an hour and a minute are written on:
|
|
87132
88686
|
* a clock says "07:00", never "7:0". Say `pad={0}` for the bare numbers.
|
|
87133
|
-
* @param {import("
|
|
88687
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
87134
88688
|
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
87135
88689
|
* Everything a box takes is taken here too — `width`, `borderRadius`, `size`.
|
|
87136
88690
|
*/
|
|
@@ -87196,7 +88750,7 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87196
88750
|
};
|
|
87197
88751
|
|
|
87198
88752
|
/**
|
|
87199
|
-
* @type {import("
|
|
88753
|
+
* @type {import("preact").FunctionComponent<{
|
|
87200
88754
|
* name?: string,
|
|
87201
88755
|
* value?: { start?: string, end?: string },
|
|
87202
88756
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -87206,13 +88760,13 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87206
88760
|
* timeProps?: object,
|
|
87207
88761
|
* loop?: boolean,
|
|
87208
88762
|
* size?: string,
|
|
87209
|
-
* startLabel?: import("
|
|
87210
|
-
* endLabel?: import("
|
|
88763
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
88764
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
87211
88765
|
* [key: string]: any,
|
|
87212
88766
|
* }>}
|
|
87213
88767
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
87214
88768
|
* "HH:MM".
|
|
87215
|
-
* @param {import("
|
|
88769
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
87216
88770
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
87217
88771
|
* `null` for neither.
|
|
87218
88772
|
* @param {number} [minuteStep=1] How many minutes a press on a minute chevron
|
|
@@ -87309,7 +88863,7 @@ const css$p = /* css */`.navi_checkbox_group {
|
|
|
87309
88863
|
*
|
|
87310
88864
|
* See `<RadioGroup>` for the same shape holding a single value.
|
|
87311
88865
|
*
|
|
87312
|
-
* @type {import("
|
|
88866
|
+
* @type {import("preact").FunctionComponent<{
|
|
87313
88867
|
* maxLength?: number,
|
|
87314
88868
|
* maxLengthGuard?: number,
|
|
87315
88869
|
* [key: string]: any,
|
|
@@ -87370,6 +88924,19 @@ const CheckboxGroupInterface = props => {
|
|
|
87370
88924
|
});
|
|
87371
88925
|
};
|
|
87372
88926
|
|
|
88927
|
+
/**
|
|
88928
|
+
* @type {import("preact").FunctionComponent<{
|
|
88929
|
+
* unit: string,
|
|
88930
|
+
* plural?: boolean,
|
|
88931
|
+
* format?: "long" | "short" | "narrow",
|
|
88932
|
+
* lang?: string,
|
|
88933
|
+
* label?: string,
|
|
88934
|
+
* size?: string,
|
|
88935
|
+
* sizeRatio?: number,
|
|
88936
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
88937
|
+
* [key: string]: any,
|
|
88938
|
+
* }>}
|
|
88939
|
+
*/
|
|
87373
88940
|
const Unit = ({
|
|
87374
88941
|
unit,
|
|
87375
88942
|
plural,
|
|
@@ -88127,7 +89694,7 @@ const css$m = /* css */`.navi_input.navi_select {
|
|
|
88127
89694
|
`;
|
|
88128
89695
|
|
|
88129
89696
|
/**
|
|
88130
|
-
* @type {import("
|
|
89697
|
+
* @type {import("preact").FunctionComponent<{
|
|
88131
89698
|
* value?: string,
|
|
88132
89699
|
* defaultValue?: string,
|
|
88133
89700
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -88206,30 +89773,40 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88206
89773
|
|
|
88207
89774
|
& > .navi_group {
|
|
88208
89775
|
flex: auto;
|
|
89776
|
+
|
|
89777
|
+
& > .navi_button {
|
|
89778
|
+
flex: auto;
|
|
89779
|
+
}
|
|
88209
89780
|
}
|
|
88210
89781
|
}
|
|
88211
89782
|
|
|
88212
89783
|
.navi_split_button_menu {
|
|
89784
|
+
--border-width: 0px;
|
|
88213
89785
|
display: flex;
|
|
88214
89786
|
position: relative;
|
|
89787
|
+
|
|
89788
|
+
& > .navi_button {
|
|
89789
|
+
margin-left: calc(var(--border-width) * -1);
|
|
89790
|
+
}
|
|
88215
89791
|
}
|
|
88216
89792
|
`;
|
|
88217
89793
|
|
|
88218
89794
|
/**
|
|
88219
|
-
* @type {import("
|
|
88220
|
-
* options: Array<{value: any, label: import("
|
|
89795
|
+
* @type {import("preact").FunctionComponent<{
|
|
89796
|
+
* options: Array<{value: any, label: import("preact").ComponentChildren, [key: string]: any}>,
|
|
88221
89797
|
* value?: any,
|
|
88222
89798
|
* defaultValue?: any,
|
|
88223
|
-
* label?: import("
|
|
88224
|
-
* action?: (value: any, event: Event) => void | Promise<void>,
|
|
89799
|
+
* label?: import("preact").ComponentChildren,
|
|
89800
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void | Promise<void>,
|
|
88225
89801
|
* command?: string,
|
|
88226
89802
|
* commandFor?: string,
|
|
88227
89803
|
* prefetch?: boolean,
|
|
88228
|
-
* onValueChange?: (value: any, event: Event) => void,
|
|
89804
|
+
* onValueChange?: (value: any, { event }: { event: Event }) => void,
|
|
88229
89805
|
* chooseEffect?: "select" | "run",
|
|
88230
89806
|
* menuLabel?: string,
|
|
88231
|
-
* menuIcon?: import("
|
|
89807
|
+
* menuIcon?: import("preact").ComponentChildren,
|
|
88232
89808
|
* menuIconSize?: number | string,
|
|
89809
|
+
* menuTestId?: string,
|
|
88233
89810
|
* loading?: boolean,
|
|
88234
89811
|
* readOnly?: boolean,
|
|
88235
89812
|
* disabled?: boolean,
|
|
@@ -88265,19 +89842,21 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88265
89842
|
* popupTestId?: string,
|
|
88266
89843
|
* [key: string]: any,
|
|
88267
89844
|
* }>}
|
|
88268
|
-
* @param {Array<{value: any, label: import("
|
|
89845
|
+
* @param {Array<{value: any, label: import("preact").ComponentChildren}>} options
|
|
88269
89846
|
* The ways of doing the thing, in the order the menu lists them. Anything
|
|
88270
89847
|
* else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
|
|
88271
89848
|
* @param {any} [value] Which entry the button stands for — the one it runs, and
|
|
88272
89849
|
* the one it is named after. `defaultValue` is the uncontrolled form; without
|
|
88273
89850
|
* either, the first entry.
|
|
88274
|
-
* @param {import("
|
|
89851
|
+
* @param {import("preact").ComponentChildren} [label] What the button says, when
|
|
88275
89852
|
* that is not the name of the entry it stands for — a button that keeps one
|
|
88276
89853
|
* name whatever the menu does.
|
|
88277
|
-
* @param {(value: any, event
|
|
88278
|
-
* entry given. Pressed on the button it is the entry the button stands
|
|
88279
|
-
* with `chooseEffect="run"` it is also the entry just chosen.
|
|
88280
|
-
*
|
|
89854
|
+
* @param {(value: any, { event, signal }) => void} [action] Do the thing, for
|
|
89855
|
+
* the entry given. Pressed on the button it is the entry the button stands
|
|
89856
|
+
* for; with `chooseEffect="run"` it is also the entry just chosen. Run as a
|
|
89857
|
+
* navi action: the second argument is the run's (see docs/actions.md), the
|
|
89858
|
+
* press being its `event`. Awaited: the whole split button is busy until it
|
|
89859
|
+
* settles.
|
|
88281
89860
|
* @param {string} [command] What a press on the button does once its `action`
|
|
88282
89861
|
* is done, as on a `<Button>` (with `commandFor`, and `command-value` when
|
|
88283
89862
|
* what the command is about is not the entry the button stands for). A
|
|
@@ -88291,12 +89870,14 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88291
89870
|
* menu of one-offs around a primary action.
|
|
88292
89871
|
* @param {string} [menuLabel] What the chevron half is called, for a screen
|
|
88293
89872
|
* reader and for the tooltip a long press raises.
|
|
88294
|
-
* @param {import("
|
|
89873
|
+
* @param {import("preact").ComponentChildren} [menuIcon] What the chevron half
|
|
88295
89874
|
* draws, in place of the chevron. It is the whole of what that half says, so
|
|
88296
89875
|
* pass something that still reads as "there is more here" — and say what,
|
|
88297
89876
|
* through `menuLabel`.
|
|
88298
89877
|
* @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
|
|
88299
89878
|
* the button's own font size.
|
|
89879
|
+
* @param {string} [menuTestId] The `data-testid` of the chevron half, which
|
|
89880
|
+
* navi builds and the caller has no other way to name.
|
|
88300
89881
|
* @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
|
|
88301
89882
|
* picker left to itself turns into a dialog on a small screen; a split
|
|
88302
89883
|
* button's menu stays hung off the button there, so this says popover unless
|
|
@@ -88317,8 +89898,12 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88317
89898
|
* `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
|
|
88318
89899
|
* An `aria-*` attribute describes the button and reaches the main half —
|
|
88319
89900
|
* `aria-pressed` for a split button whose main half toggles a mode;
|
|
88320
|
-
* `aria-hidden` excepted, which hides the whole control.
|
|
88321
|
-
*
|
|
89901
|
+
* `aria-hidden` excepted, which hides the whole control. How the words are
|
|
89902
|
+
* written (`size`, `bold`, `lineHeight`, `noWrap`…) reaches both halves, and so
|
|
89903
|
+
* does vertical padding (`paddingY`, `paddingTop`, `paddingBottom`); the rest
|
|
89904
|
+
* of the padding is the main half's — the chevron half keeps its own, which
|
|
89905
|
+
* gives it its width. Anything else (`className`, `width`, margins, `data-*`)
|
|
89906
|
+
* lands on the split button's own box.
|
|
88322
89907
|
*/
|
|
88323
89908
|
const SplitButton = props => {
|
|
88324
89909
|
import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
@@ -88337,6 +89922,7 @@ const SplitButton = props => {
|
|
|
88337
89922
|
menuLabel = buttonI18n("button.more_actions"),
|
|
88338
89923
|
menuIcon = jsx(ChevronDownSvg$1, {}),
|
|
88339
89924
|
menuIconSize,
|
|
89925
|
+
menuTestId,
|
|
88340
89926
|
loading,
|
|
88341
89927
|
readOnly,
|
|
88342
89928
|
disabled,
|
|
@@ -88356,11 +89942,13 @@ const SplitButton = props => {
|
|
|
88356
89942
|
...rest
|
|
88357
89943
|
} = props;
|
|
88358
89944
|
// Everything the popup answers to travels to the Picker; an aria-* describes
|
|
88359
|
-
// the button a screen reader meets, which is the main half;
|
|
88360
|
-
//
|
|
88361
|
-
//
|
|
88362
|
-
//
|
|
88363
|
-
|
|
89945
|
+
// the button a screen reader meets, which is the main half; how the words are
|
|
89946
|
+
// written and how far they sit from the edge belong to the halves, since a
|
|
89947
|
+
// <button> inherits neither from the box around it; everything else is the
|
|
89948
|
+
// split button's own box (margins, width, data-*). Sorted by name rather
|
|
89949
|
+
// than named one by one so a Picker popup prop is forwarded by adding it to
|
|
89950
|
+
// that list, not by threading it through here.
|
|
89951
|
+
const [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps] = sortRestProps(rest);
|
|
88364
89952
|
// A split button is a control on the page, not a place one goes: its menu
|
|
88365
89953
|
// hangs off it even on a phone, where a picker left to itself would decide a
|
|
88366
89954
|
// small screen means a dialog. Passing mode="dialog" asks for that back.
|
|
@@ -88405,6 +89993,7 @@ const SplitButton = props => {
|
|
|
88405
89993
|
borderColor,
|
|
88406
89994
|
borderWidth,
|
|
88407
89995
|
borderRadius,
|
|
89996
|
+
...halfStyleProps,
|
|
88408
89997
|
pressEffect,
|
|
88409
89998
|
loading: loadingResolved,
|
|
88410
89999
|
readOnly,
|
|
@@ -88414,7 +90003,7 @@ const SplitButton = props => {
|
|
|
88414
90003
|
};
|
|
88415
90004
|
return jsxs(Box, {
|
|
88416
90005
|
ref: rootRef,
|
|
88417
|
-
|
|
90006
|
+
baseClassName: "navi_split_button",
|
|
88418
90007
|
borderRadius: borderRadius,
|
|
88419
90008
|
...boxProps,
|
|
88420
90009
|
children: [jsx(LoadingOutline, {
|
|
@@ -88431,6 +90020,7 @@ const SplitButton = props => {
|
|
|
88431
90020
|
"command-value": commandValue,
|
|
88432
90021
|
prefetch: prefetch,
|
|
88433
90022
|
...buttonAriaProps,
|
|
90023
|
+
...mainPaddingProps,
|
|
88434
90024
|
...halfProps,
|
|
88435
90025
|
children: label === undefined ? optionShown?.label : label
|
|
88436
90026
|
}), jsxs("div", {
|
|
@@ -88440,6 +90030,7 @@ const SplitButton = props => {
|
|
|
88440
90030
|
paddingX: "s",
|
|
88441
90031
|
expandY: true,
|
|
88442
90032
|
"aria-label": menuLabel,
|
|
90033
|
+
"data-testid": menuTestId,
|
|
88443
90034
|
command: "--navi-open",
|
|
88444
90035
|
commandFor: menuId,
|
|
88445
90036
|
...halfProps,
|
|
@@ -88519,9 +90110,16 @@ const SplitButton = props => {
|
|
|
88519
90110
|
// a caller reaches all of them through the split button (see picker.jsx's JSDoc
|
|
88520
90111
|
// for what each one says).
|
|
88521
90112
|
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"]);
|
|
90113
|
+
// Vertical padding is said about both halves so they stay the same height; the
|
|
90114
|
+
// horizontal one is the main half's alone, the chevron half's own (paddingX="s")
|
|
90115
|
+
// being what gives it its width.
|
|
90116
|
+
const HALF_PADDING_PROP_SET = new Set(["paddingY", "paddingTop", "paddingBottom"]);
|
|
90117
|
+
const MAIN_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingLeft", "paddingRight"]);
|
|
88522
90118
|
const sortRestProps = props => {
|
|
88523
90119
|
const popupProps = {};
|
|
88524
90120
|
const buttonAriaProps = {};
|
|
90121
|
+
const halfStyleProps = {};
|
|
90122
|
+
const mainPaddingProps = {};
|
|
88525
90123
|
const boxProps = {};
|
|
88526
90124
|
for (const key of Object.keys(props)) {
|
|
88527
90125
|
if (POPUP_PROP_SET.has(key)) {
|
|
@@ -88530,11 +90128,15 @@ const sortRestProps = props => {
|
|
|
88530
90128
|
// aria-hidden hides a subtree rather than describing a button: on the
|
|
88531
90129
|
// main half alone it would leave the chevron half exposed.
|
|
88532
90130
|
buttonAriaProps[key] = props[key];
|
|
90131
|
+
} else if (isTypoProp(key) || HALF_PADDING_PROP_SET.has(key)) {
|
|
90132
|
+
halfStyleProps[key] = props[key];
|
|
90133
|
+
} else if (MAIN_PADDING_PROP_SET.has(key)) {
|
|
90134
|
+
mainPaddingProps[key] = props[key];
|
|
88533
90135
|
} else {
|
|
88534
90136
|
boxProps[key] = props[key];
|
|
88535
90137
|
}
|
|
88536
90138
|
}
|
|
88537
|
-
return [popupProps, buttonAriaProps, boxProps];
|
|
90139
|
+
return [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps];
|
|
88538
90140
|
};
|
|
88539
90141
|
|
|
88540
90142
|
/**
|
|
@@ -89736,7 +91338,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89736
91338
|
* value, the value lives in one hidden input for the form. The visible rows are
|
|
89737
91339
|
* presentational.
|
|
89738
91340
|
*
|
|
89739
|
-
* @type {import("
|
|
91341
|
+
* @type {import("preact").FunctionComponent<{
|
|
89740
91342
|
* value?: any,
|
|
89741
91343
|
* defaultValue?: any,
|
|
89742
91344
|
* action?: (value: any) => void,
|
|
@@ -89754,7 +91356,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89754
91356
|
* readOnly?: boolean,
|
|
89755
91357
|
* disabled?: boolean,
|
|
89756
91358
|
* loading?: boolean,
|
|
89757
|
-
* children?: import("
|
|
91359
|
+
* children?: import("preact").ComponentChildren,
|
|
89758
91360
|
* [key: string]: any,
|
|
89759
91361
|
* }>}
|
|
89760
91362
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
@@ -90382,8 +91984,9 @@ function WheelUI(props) {
|
|
|
90382
91984
|
}, {
|
|
90383
91985
|
controlType: "input",
|
|
90384
91986
|
// The host is the hidden input carrying the value: the container is
|
|
90385
|
-
// what is focused and spun.
|
|
90386
|
-
listenersOn: "root"
|
|
91987
|
+
// what is focused and spun, and the spinbutton its name has to name.
|
|
91988
|
+
listenersOn: "root",
|
|
91989
|
+
nameOn: "root"
|
|
90387
91990
|
});
|
|
90388
91991
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
90389
91992
|
const {
|
|
@@ -91233,9 +92836,9 @@ const WheelWindow = ({
|
|
|
91233
92836
|
* of rows and fills each from this tracked data — so the value list is the source
|
|
91234
92837
|
* of truth, not a per-item DOM node. Selection lives on the wheel's value.
|
|
91235
92838
|
*
|
|
91236
|
-
* @type {import("
|
|
92839
|
+
* @type {import("preact").FunctionComponent<{
|
|
91237
92840
|
* value: any,
|
|
91238
|
-
* children?: import("
|
|
92841
|
+
* children?: import("preact").ComponentChildren,
|
|
91239
92842
|
* [key: string]: any,
|
|
91240
92843
|
* }>}
|
|
91241
92844
|
*/
|
|
@@ -91270,9 +92873,9 @@ Wheel.Item = WheelItem;
|
|
|
91270
92873
|
* each Wheel.Item (it's a Box: paddingX="0.5ch") so the gap stays scrollable.
|
|
91271
92874
|
* `spacing` is just the Box flex gap between children (a layout gap, not padding).
|
|
91272
92875
|
*
|
|
91273
|
-
* @type {import("
|
|
92876
|
+
* @type {import("preact").FunctionComponent<{
|
|
91274
92877
|
* horizontal?: boolean,
|
|
91275
|
-
* children?: import("
|
|
92878
|
+
* children?: import("preact").ComponentChildren,
|
|
91276
92879
|
* [key: string]: any,
|
|
91277
92880
|
* }>}
|
|
91278
92881
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
@@ -91469,14 +93072,14 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
91469
93072
|
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
91470
93073
|
|
|
91471
93074
|
/**
|
|
91472
|
-
* @type {import("
|
|
93075
|
+
* @type {import("preact").FunctionComponent<{
|
|
91473
93076
|
* name?: string,
|
|
91474
93077
|
* value?: string,
|
|
91475
93078
|
* defaultValue?: string,
|
|
91476
93079
|
* signal?: import("@preact/signals").Signal<string>,
|
|
91477
93080
|
* minuteStep?: number,
|
|
91478
93081
|
* loop?: boolean,
|
|
91479
|
-
* separator?: import("
|
|
93082
|
+
* separator?: import("preact").ComponentChildren,
|
|
91480
93083
|
* hourLabel?: string,
|
|
91481
93084
|
* minuteLabel?: string,
|
|
91482
93085
|
* size?: string,
|
|
@@ -91497,7 +93100,7 @@ const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
|
91497
93100
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
91498
93101
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
91499
93102
|
* past.
|
|
91500
|
-
* @param {import("
|
|
93103
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
91501
93104
|
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
91502
93105
|
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
91503
93106
|
* is written by default outside French.
|
|
@@ -91616,7 +93219,7 @@ const TimeWheel = ({
|
|
|
91616
93219
|
};
|
|
91617
93220
|
|
|
91618
93221
|
/**
|
|
91619
|
-
* @type {import("
|
|
93222
|
+
* @type {import("preact").FunctionComponent<{
|
|
91620
93223
|
* name?: string,
|
|
91621
93224
|
* value?: { start?: string, end?: string },
|
|
91622
93225
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -91625,15 +93228,15 @@ const TimeWheel = ({
|
|
|
91625
93228
|
* minDuration?: number,
|
|
91626
93229
|
* loop?: boolean,
|
|
91627
93230
|
* size?: string,
|
|
91628
|
-
* startLabel?: import("
|
|
91629
|
-
* endLabel?: import("
|
|
93231
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
93232
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
91630
93233
|
* labelPosition?: "before" | "above",
|
|
91631
93234
|
* timeProps?: object,
|
|
91632
93235
|
* [key: string]: any,
|
|
91633
93236
|
* }>}
|
|
91634
93237
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
91635
93238
|
* "HH:MM".
|
|
91636
|
-
* @param {import("
|
|
93239
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
91637
93240
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
91638
93241
|
* `null` for neither.
|
|
91639
93242
|
* @param {"before"|"above"} [labelPosition="before"] Where each label stands.
|
|
@@ -95618,17 +97221,25 @@ const Address = ({
|
|
|
95618
97221
|
};
|
|
95619
97222
|
|
|
95620
97223
|
installImportMetaCssBuild(import.meta);
|
|
95621
|
-
const css$a = /* css */`@layer navi
|
|
97224
|
+
const css$a = /* css */`@layer navi {
|
|
97225
|
+
.navi_text.navi_badge_count {
|
|
97226
|
+
--font-size: .7em;
|
|
97227
|
+
--badge-count-padding-x-default: .5em;
|
|
97228
|
+
--badge-count-padding-y-default: .3em;
|
|
97229
|
+
|
|
97230
|
+
&[data-ellipse][data-single-char], &[data-circle] {
|
|
97231
|
+
--badge-count-padding-x-default: 0;
|
|
97232
|
+
--badge-count-padding-y-default: 0;
|
|
97233
|
+
}
|
|
97234
|
+
}
|
|
97235
|
+
}
|
|
95622
97236
|
|
|
95623
97237
|
.navi_text.navi_badge_count {
|
|
95624
97238
|
white-space: nowrap;
|
|
95625
|
-
--font-size: .7em;
|
|
95626
97239
|
--x-background: var(--background);
|
|
95627
|
-
--x-background-color: var(--background-color
|
|
97240
|
+
--x-background-color: var(--background-color);
|
|
95628
97241
|
--x-color-contrasting: var(--navi-color-white);
|
|
95629
97242
|
--x-color: var(--color, var(--x-color-contrasting));
|
|
95630
|
-
--badge-count-padding-x-default: .5em;
|
|
95631
|
-
--badge-count-padding-y-default: .3em;
|
|
95632
97243
|
--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))));
|
|
95633
97244
|
--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))));
|
|
95634
97245
|
--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))));
|
|
@@ -95660,12 +97271,9 @@ const css$a = /* css */`@layer navi;
|
|
|
95660
97271
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95661
97272
|
padding-left: var(--x-badge-count-padding-left);
|
|
95662
97273
|
background: var(--x-background);
|
|
95663
|
-
background-color: var(--x-background-color);
|
|
95664
97274
|
border-radius: 1em;
|
|
95665
97275
|
|
|
95666
97276
|
&[data-single-char] {
|
|
95667
|
-
--badge-count-padding-x-default: 0;
|
|
95668
|
-
--badge-count-padding-y-default: 0;
|
|
95669
97277
|
box-sizing: content-box;
|
|
95670
97278
|
text-align: center;
|
|
95671
97279
|
border-radius: 100em;
|
|
@@ -95678,8 +97286,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95678
97286
|
|
|
95679
97287
|
&[data-circle] {
|
|
95680
97288
|
--x-number-font-size: var(--font-size);
|
|
95681
|
-
--badge-count-padding-x-default: 0;
|
|
95682
|
-
--badge-count-padding-y-default: 0;
|
|
95683
97289
|
box-sizing: content-box;
|
|
95684
97290
|
aspect-ratio: 1;
|
|
95685
97291
|
width: var(--x-radius);
|
|
@@ -95689,7 +97295,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95689
97295
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95690
97296
|
padding-left: var(--x-badge-count-padding-left);
|
|
95691
97297
|
background: var(--x-background);
|
|
95692
|
-
background-color: var(--x-background-color);
|
|
95693
97298
|
border-radius: 100em;
|
|
95694
97299
|
justify-content: center;
|
|
95695
97300
|
align-items: center;
|
|
@@ -95714,6 +97319,10 @@ const css$a = /* css */`@layer navi;
|
|
|
95714
97319
|
font-size: var(--x-number-font-size);
|
|
95715
97320
|
}
|
|
95716
97321
|
}
|
|
97322
|
+
|
|
97323
|
+
&[data-background-color] {
|
|
97324
|
+
background-color: var(--x-background-color);
|
|
97325
|
+
}
|
|
95717
97326
|
}
|
|
95718
97327
|
`;
|
|
95719
97328
|
const BadgeCountOverflow = () => jsx("span", {
|
|
@@ -95850,6 +97459,7 @@ const BadgeCountEllipse = ({
|
|
|
95850
97459
|
"data-ellipse": "",
|
|
95851
97460
|
"data-value-overflow": hasOverflow ? "" : undefined,
|
|
95852
97461
|
"data-loading": loading ? "" : undefined,
|
|
97462
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95853
97463
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95854
97464
|
...props,
|
|
95855
97465
|
styleCSSVars: BadgeCountStyleCSSVars,
|
|
@@ -95872,6 +97482,7 @@ const BadgeCountCircle = ({
|
|
|
95872
97482
|
"data-circle": "",
|
|
95873
97483
|
bold: true,
|
|
95874
97484
|
"data-loading": loading ? "" : undefined,
|
|
97485
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95875
97486
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95876
97487
|
"data-two-chars": charCount === 2 ? "" : undefined,
|
|
95877
97488
|
"data-three-chars": charCount === 3 ? "" : undefined,
|
|
@@ -96364,7 +97975,7 @@ const css$8 = /* css */`@layer navi;
|
|
|
96364
97975
|
`;
|
|
96365
97976
|
|
|
96366
97977
|
/**
|
|
96367
|
-
* @type {import("
|
|
97978
|
+
* @type {import("preact").FunctionComponent<{
|
|
96368
97979
|
* children?: number | string,
|
|
96369
97980
|
* unit?: string | [string, string],
|
|
96370
97981
|
* unitPosition?: "right" | "bottom",
|
|
@@ -96956,8 +98567,8 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
96956
98567
|
*
|
|
96957
98568
|
* @param {Object} props - Component properties
|
|
96958
98569
|
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
96959
|
-
* @param {import("
|
|
96960
|
-
* @returns {import("
|
|
98570
|
+
* @param {import("preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
98571
|
+
* @returns {import("preact").VNode} A composed SVG with all elements properly masked
|
|
96961
98572
|
*/
|
|
96962
98573
|
const SVGMaskOverlay = ({
|
|
96963
98574
|
viewBox,
|
|
@@ -97047,7 +98658,6 @@ const css$3 = /* css */`@layer navi {
|
|
|
97047
98658
|
padding-bottom: var(--layout-padding-bottom, var(--layout-padding-y, var(--layout-padding)));
|
|
97048
98659
|
padding-left: var(--layout-padding-left, var(--layout-padding-x, var(--layout-padding)));
|
|
97049
98660
|
background: var(--layout-background);
|
|
97050
|
-
background-color: var(--layout-background-color, var(--layout-background));
|
|
97051
98661
|
border-width: var(--layout-border-width);
|
|
97052
98662
|
border-style: solid;
|
|
97053
98663
|
border-color: var(--layout-border-color);
|
|
@@ -97064,7 +98674,6 @@ const CardLayoutStyleCSSVars = {
|
|
|
97064
98674
|
borderWidth: "--layout-border-width",
|
|
97065
98675
|
borderColor: "--layout-border-color",
|
|
97066
98676
|
background: "--layout-background",
|
|
97067
|
-
backgroundColor: "--layout-background-color",
|
|
97068
98677
|
padding: "--layout-padding",
|
|
97069
98678
|
paddingTop: "--layout-padding-top",
|
|
97070
98679
|
paddingBottom: "--layout-padding-bottom",
|
|
@@ -97079,7 +98688,7 @@ const CardLayoutStyleCSSVars = {
|
|
|
97079
98688
|
* what holds it, so how tall that area is is up to the page around it, as for
|
|
97080
98689
|
* `ViewportLayout`.
|
|
97081
98690
|
*
|
|
97082
|
-
* @type {import("
|
|
98691
|
+
* @type {import("preact").FunctionComponent<{
|
|
97083
98692
|
* alignX?: string,
|
|
97084
98693
|
* alignY?: string,
|
|
97085
98694
|
* margin?: string|number,
|
|
@@ -97104,6 +98713,7 @@ const CardLayout = ({
|
|
|
97104
98713
|
children,
|
|
97105
98714
|
alignX = "center",
|
|
97106
98715
|
alignY = "center",
|
|
98716
|
+
backgroundColor,
|
|
97107
98717
|
...props
|
|
97108
98718
|
}) => {
|
|
97109
98719
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
@@ -97122,6 +98732,7 @@ const CardLayout = ({
|
|
|
97122
98732
|
className: "navi_card",
|
|
97123
98733
|
alignX: alignX,
|
|
97124
98734
|
alignY: alignY,
|
|
98735
|
+
backgroundColor: backgroundColor,
|
|
97125
98736
|
children: children
|
|
97126
98737
|
})
|
|
97127
98738
|
});
|
|
@@ -97295,7 +98906,7 @@ const LINE_GAP = 5;
|
|
|
97295
98906
|
const StepListContext = createContext(null);
|
|
97296
98907
|
|
|
97297
98908
|
/**
|
|
97298
|
-
* @type {import("
|
|
98909
|
+
* @type {import("preact").FunctionComponent<{
|
|
97299
98910
|
* current?: string,
|
|
97300
98911
|
* slideContainer?: string,
|
|
97301
98912
|
* travelByClick?: boolean,
|
|
@@ -97652,7 +99263,7 @@ const sameShallow = (previousObject, nextObject) => {
|
|
|
97652
99263
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
97653
99264
|
* SlideContainer.
|
|
97654
99265
|
*
|
|
97655
|
-
* @type {import("
|
|
99266
|
+
* @type {import("preact").FunctionComponent<{
|
|
97656
99267
|
* value: string,
|
|
97657
99268
|
* done?: boolean,
|
|
97658
99269
|
* [key: string]: any,
|
|
@@ -97714,7 +99325,7 @@ const ViewportLayoutStyleCSSVars = {
|
|
|
97714
99325
|
* same inside a route, a preview pane or a demo box, and it is up to the page
|
|
97715
99326
|
* around it to be full-height.
|
|
97716
99327
|
*
|
|
97717
|
-
* @type {import("
|
|
99328
|
+
* @type {import("preact").FunctionComponent<{
|
|
97718
99329
|
* padding?: string|number,
|
|
97719
99330
|
* paddingTop?: string|number,
|
|
97720
99331
|
* paddingBottom?: string|number,
|
|
@@ -97939,7 +99550,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
97939
99550
|
* `closeByPressOutside`/`pressOutside` only say what a
|
|
97940
99551
|
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
97941
99552
|
* doc).
|
|
97942
|
-
* @param {import("
|
|
99553
|
+
* @param {import("preact").ComponentChildren} props.children - No built-in
|
|
97943
99554
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
97944
99555
|
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
97945
99556
|
* `closeByPressOutside`/Escape instead.
|
|
@@ -98038,7 +99649,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
98038
99649
|
* @param {object} props
|
|
98039
99650
|
* @param {string} [props.className] - Merged with the shared
|
|
98040
99651
|
* `"navi_side_panel_head"` class this file's own CSS targets.
|
|
98041
|
-
* @param {import("
|
|
99652
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98042
99653
|
*/
|
|
98043
99654
|
const SidePanelHead = props => jsx(Box, {
|
|
98044
99655
|
baseClassName: "navi_side_panel_head",
|
|
@@ -98051,7 +99662,7 @@ const SidePanelHead = props => jsx(Box, {
|
|
|
98051
99662
|
* @param {object} props
|
|
98052
99663
|
* @param {string} [props.className] - Merged with the shared
|
|
98053
99664
|
* `"navi_side_panel_foot"` class this file's own CSS targets.
|
|
98054
|
-
* @param {import("
|
|
99665
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98055
99666
|
*/
|
|
98056
99667
|
const SidePanelFoot = props => jsx(Box, {
|
|
98057
99668
|
className: "navi_side_panel_foot",
|
|
@@ -98060,6 +99671,58 @@ const SidePanelFoot = props => jsx(Box, {
|
|
|
98060
99671
|
SidePanel.Head = SidePanelHead;
|
|
98061
99672
|
SidePanel.Foot = SidePanelFoot;
|
|
98062
99673
|
|
|
99674
|
+
/**
|
|
99675
|
+
* Creates a linked `[Slot, SlotFill]` pair so content rendered anywhere in
|
|
99676
|
+
* the tree (`SlotFill`) appears at a fixed location elsewhere (`Slot`) — a
|
|
99677
|
+
* lightweight, signal-based alternative to a DOM portal: `SlotFill` itself
|
|
99678
|
+
* renders nothing (no extra DOM node at its own call site), it just writes
|
|
99679
|
+
* its props into a shared signal that `Slot` reads reactively.
|
|
99680
|
+
*
|
|
99681
|
+
* Holds at most one filler at a time — there is no stacking/queueing. The
|
|
99682
|
+
* filler is the last `SlotFill` (from this same `createSlot()` call) to
|
|
99683
|
+
* mount, or the first to render into an empty slot; later renders of a
|
|
99684
|
+
* `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
|
|
99685
|
+
* unmounting empties the slot only while it is still that one, and so does one
|
|
99686
|
+
* in a page a route transition keeps while leaving (see keepLeavingPages in
|
|
99687
|
+
* nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
|
|
99688
|
+
* take it back from the page arriving.
|
|
99689
|
+
*
|
|
99690
|
+
* **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
|
|
99691
|
+
* the fills as they mount and unmount, in the order the tree walks them. A
|
|
99692
|
+
* slot fed from several places — a `SlotFill` in each card of a board, each
|
|
99693
|
+
* card deciding on its own whether it is the open one — reads as empty
|
|
99694
|
+
* between the leaver and the newcomer whenever they render separately, and
|
|
99695
|
+
* anything acting on `isFilled` acts on that empty frame (a `SidePanel` with
|
|
99696
|
+
* `open={isFilled}` closes for good: its `onClose` clears the state the
|
|
99697
|
+
* newcomer was waiting for). The slot cannot tell that gap from a real
|
|
99698
|
+
* emptying, and does not try to: render one `SlotFill` at the level that
|
|
99699
|
+
* holds the choice, with the chosen content as its children, and bind a
|
|
99700
|
+
* state that must not blink (a panel's `open`) to the app's own signal
|
|
99701
|
+
* rather than to `isFilled` — see docs/popup_open.md, "One panel, fed by a
|
|
99702
|
+
* slot".
|
|
99703
|
+
*
|
|
99704
|
+
* `Slot` keeps `SlotRenderer` mounted permanently — even while unfilled it
|
|
99705
|
+
* still renders it, with no props and `isFilled={false}` — instead of
|
|
99706
|
+
* unmounting it. This lets `SlotRenderer` be a persistent wrapper (a
|
|
99707
|
+
* `SidePanel` that opens and closes with an animation instead of being
|
|
99708
|
+
* mounted/unmounted alongside the filler — see 7_slot_demo.html's "Two
|
|
99709
|
+
* side-panel slots" section). A `SlotRenderer` that wants the
|
|
99710
|
+
* render-nothing-when-unfilled behavior can opt in with its own
|
|
99711
|
+
* `if (!isFilled) return null;` (the default `Box` renderer doesn't do
|
|
99712
|
+
* this — an unfilled default slot just renders an empty `<Box/>`).
|
|
99713
|
+
*
|
|
99714
|
+
* @param {import("preact").ComponentType} [SlotRenderer=Box] - Rendered by
|
|
99715
|
+
* `Slot`, always, with whatever props `SlotFill` last set (spread
|
|
99716
|
+
* directly) plus `isFilled`. Swap this out to reuse the same slot
|
|
99717
|
+
* mechanism for something other than a plain `Box` (e.g. a specific
|
|
99718
|
+
* component expecting its own particular props, or a persistent wrapper
|
|
99719
|
+
* like `SidePanel`).
|
|
99720
|
+
* @returns {[import("preact").ComponentType, import("preact").ComponentType<object>]}
|
|
99721
|
+
* `[Slot, SlotFill]` — `Slot` takes no props, render it once wherever the
|
|
99722
|
+
* content should appear. `SlotFill` takes whatever props `SlotRenderer`
|
|
99723
|
+
* expects, render it anywhere else in the tree to supply/update that
|
|
99724
|
+
* content.
|
|
99725
|
+
*/
|
|
98063
99726
|
const createSlot = (SlotRenderer = Box) => {
|
|
98064
99727
|
const slotPropsSignal = signal();
|
|
98065
99728
|
let filler = null;
|