@jsenv/navi 0.29.478 → 0.29.480
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +1732 -681
- package/dist/dev/jsenv_navi.js.map +38 -36
- package/dist/jsenv_navi.js +2161 -551
- package/dist/jsenv_navi.js.map +38 -36
- package/docs/AI_INSTRUCTIONS.md +5 -2
- package/docs/actions.md +23 -0
- package/docs/drag_to_travel.md +9 -3
- package/docs/form_changed.md +4 -3
- package/docs/interactions.md +8 -4
- package/docs/navigation.md +67 -4
- package/docs/pan_zoom.md +6 -4
- package/docs/popup_backdrop.md +5 -0
- package/docs/popup_lift.md +4 -3
- package/docs/route_transitions.md +7 -3
- package/docs/scroll.md +5 -5
- package/docs/testid.md +8 -5
- package/docs/view_transitions.md +3 -2
- package/package.json +3 -3
package/dist/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
|
|
@@ -9734,13 +9882,18 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9734
9882
|
* Declares a route from an url pattern.
|
|
9735
9883
|
*
|
|
9736
9884
|
* @param {string} pattern - The url pattern, where `:name` is a path param and
|
|
9737
|
-
* `:name=${signal}` binds that param to a signal.
|
|
9885
|
+
* `:name=${signal}` binds that param to a signal. A pattern ending in `/` is a
|
|
9886
|
+
* frame: it matches every address below it too, and its search params hold
|
|
9887
|
+
* there (see docs/navigation.md, "A page or a frame: the trailing slash").
|
|
9738
9888
|
* @param {object} [options]
|
|
9739
9889
|
* @param {Object<string, import("@preact/signals").Signal | {signal: import("@preact/signals").Signal, default?: any}>} [options.searchParams]
|
|
9740
9890
|
* Search params this route two-way syncs with, by name. The signal and the url
|
|
9741
9891
|
* are the same state: writing the signal rewrites the address, an address
|
|
9742
9892
|
* arriving from outside writes the signal, and the param disappears from the
|
|
9743
9893
|
* url while the signal holds its default.
|
|
9894
|
+
* They exist on this route's address only, unless the pattern is a frame
|
|
9895
|
+
* (`/map/`, `/map/*`, the root): then on every address below it too.
|
|
9896
|
+
* `/places` does not lend its `?q=` to `/places/clubs`.
|
|
9744
9897
|
* `{ signal, default }` says what that state is worth ON THIS ROUTE: the same
|
|
9745
9898
|
* step opens a creation wizard on its first question and an edition one on
|
|
9746
9899
|
* its summary, each address staying clean on the step its page opens on. One
|
|
@@ -9759,7 +9912,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9759
9912
|
* whose param declines the segment does not match at all: no signal is
|
|
9760
9913
|
* written, `<Route fallback>` is reachable, and `/:gameId` can sit at the root
|
|
9761
9914
|
* 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
|
|
9915
|
+
* is not one of the values it accepts — so `/:gameId` does not match `/`,
|
|
9916
|
+
* unless its default stands for the bare address (see docs/navigation.md,
|
|
9917
|
+
* "Declaring the sections is what makes them places").
|
|
9763
9918
|
*
|
|
9764
9919
|
* Constrain the shape, not the existence: whether that game exists is for the
|
|
9765
9920
|
* route action and the page to answer, on a route that did match.
|
|
@@ -10942,14 +11097,17 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
10942
11097
|
*
|
|
10943
11098
|
* - `isShown()` — whether the container would still show the page. False as
|
|
10944
11099
|
* 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
|
-
*
|
|
11100
|
+
* hidden until the route transition leaving it is over, nothing it reads is
|
|
11101
|
+
* worth a render any more, and nothing it holds speaks for the page shown (a
|
|
11102
|
+
* list's hold on the document, see holdDocumentScroll). False for good once
|
|
11103
|
+
* it is kept while leaving: a return to its route mounts the page anew. Asked
|
|
11104
|
+
* outside any render — by a subscription deciding whether to re-render, by
|
|
11105
|
+
* the scroll restoration when a position is due — and it reads the routes
|
|
10949
11106
|
* untracked, so asking subscribes nobody to them.
|
|
10950
11107
|
* - `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
|
|
11108
|
+
* plays: still mounted, so what it fills or moves outside its own nodes (a
|
|
11109
|
+
* slot in a bar, the document a list scrolls) steps out rather than showing
|
|
11110
|
+
* with, or moving, the page arriving. Every box of it measures 0×0 by then.
|
|
10953
11111
|
*/
|
|
10954
11112
|
const RoutePageContext = createContext(null);
|
|
10955
11113
|
|
|
@@ -12443,6 +12601,25 @@ const getHrefTargetInfo = (
|
|
|
12443
12601
|
};
|
|
12444
12602
|
};
|
|
12445
12603
|
|
|
12604
|
+
// The target of a link whose caller names none: a page of another site opens
|
|
12605
|
+
// in a new tab. Only a page: a `tel:`, `mailto:` or `sms:` address is handed to
|
|
12606
|
+
// another app, a `blob:` or `data:` one is this page's own, and a tab opened
|
|
12607
|
+
// for them would be left behind empty.
|
|
12608
|
+
const getDefaultHrefTarget = (
|
|
12609
|
+
href,
|
|
12610
|
+
currentUrlString = window.location.href,
|
|
12611
|
+
) => {
|
|
12612
|
+
const { isEmpty, isSameSite } = getHrefTargetInfo(href, currentUrlString);
|
|
12613
|
+
if (isEmpty || isSameSite) {
|
|
12614
|
+
return undefined;
|
|
12615
|
+
}
|
|
12616
|
+
const { protocol } = new URL(href, currentUrlString);
|
|
12617
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
12618
|
+
return undefined;
|
|
12619
|
+
}
|
|
12620
|
+
return "_blank";
|
|
12621
|
+
};
|
|
12622
|
+
|
|
12446
12623
|
// A field of getHrefTargetInfo that depends on the page one is on (isCurrent,
|
|
12447
12624
|
// isAnchor), read through a computed of that one boolean: the caller re-renders
|
|
12448
12625
|
// when it flips, not at every address write — a search param bound to a wheel
|
|
@@ -12483,6 +12660,27 @@ const linkAsksForDocument = (linkElement) => {
|
|
|
12483
12660
|
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
12484
12661
|
};
|
|
12485
12662
|
|
|
12663
|
+
/*
|
|
12664
|
+
* A link does not always move the tab it is in. A `target` naming another
|
|
12665
|
+
* browsing context (a new tab, a named window) loads the address there, in a
|
|
12666
|
+
* document of its own; a `download` saves it. The browser answers both
|
|
12667
|
+
* without touching this page, and that is the point of writing them: reading
|
|
12668
|
+
* the terms beside a half-filled form costs nothing. Routing such a link
|
|
12669
|
+
* would take this tab instead, and give no tab and no file.
|
|
12670
|
+
*
|
|
12671
|
+
* `_parent` and `_top` count as elsewhere too. Inside a frame they are; at the
|
|
12672
|
+
* top level the browser treats them as `_self` and loads a document, which
|
|
12673
|
+
* costs a load and nothing else.
|
|
12674
|
+
*/
|
|
12675
|
+
|
|
12676
|
+
const linkOpensElsewhere = (linkElement) => {
|
|
12677
|
+
const target = linkElement.getAttribute("target");
|
|
12678
|
+
if (target && target !== "_self") {
|
|
12679
|
+
return true;
|
|
12680
|
+
}
|
|
12681
|
+
return linkElement.hasAttribute("download");
|
|
12682
|
+
};
|
|
12683
|
+
|
|
12486
12684
|
/*
|
|
12487
12685
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
12488
12686
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -12812,8 +13010,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12812
13010
|
// Ignore non-left clicks
|
|
12813
13011
|
return;
|
|
12814
13012
|
}
|
|
12815
|
-
if (e.metaKey) {
|
|
12816
|
-
//
|
|
13013
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
13014
|
+
// A modifier asks the browser for something other than this tab: a new
|
|
13015
|
+
// tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
|
|
13016
|
+
// (alt).
|
|
12817
13017
|
return;
|
|
12818
13018
|
}
|
|
12819
13019
|
if (e.defaultPrevented) {
|
|
@@ -12824,6 +13024,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12824
13024
|
if (linkElement.hasAttribute("data-readonly")) {
|
|
12825
13025
|
return;
|
|
12826
13026
|
}
|
|
13027
|
+
if (linkOpensElsewhere(linkElement)) {
|
|
13028
|
+
// Another tab or window, or a download (see link_elsewhere.js).
|
|
13029
|
+
return;
|
|
13030
|
+
}
|
|
12827
13031
|
const href = linkElement.href;
|
|
12828
13032
|
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
12829
13033
|
getHrefTargetInfo(href);
|
|
@@ -16925,6 +17129,9 @@ const surfaceTextCss = /* css */ `
|
|
|
16925
17129
|
}
|
|
16926
17130
|
`;
|
|
16927
17131
|
|
|
17132
|
+
// What a callout's own JSX may know about the callout around it: how to close
|
|
17133
|
+
// it, and the element — whose id is what `commandFor` needs when the button
|
|
17134
|
+
// closing it is not inside it (inside, `--navi-close` finds it on its own).
|
|
16928
17135
|
const CalloutContext = createContext();
|
|
16929
17136
|
const useCalloutRequestClose = () => {
|
|
16930
17137
|
return useContext(CalloutContext)?.requestClose;
|
|
@@ -17058,7 +17265,7 @@ const CALLOUT_STATUS_GLYPH_VIEWBOX = "0 0 540 540";
|
|
|
17058
17265
|
const CALLOUT_STATUS_GLYPH_PATH = "m232.5,121 8,196h59l8-196zm36.5,224a37,37 0 1,0 2,0z";
|
|
17059
17266
|
|
|
17060
17267
|
/**
|
|
17061
|
-
* @type {import("
|
|
17268
|
+
* @type {import("preact").FunctionComponent<{
|
|
17062
17269
|
* status?: "info" | "warning" | "error" | "success" | "none",
|
|
17063
17270
|
* shape?: "square" | "circle",
|
|
17064
17271
|
* }>}
|
|
@@ -21877,23 +22084,12 @@ defineInteractionDetector({
|
|
|
21877
22084
|
// A link and an image are draggable without anyone asking, and a native
|
|
21878
22085
|
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
21879
22086
|
// events with it and paints a ghost of the row the hand is trying to swipe.
|
|
21880
|
-
//
|
|
21881
|
-
//
|
|
21882
|
-
//
|
|
21883
|
-
//
|
|
21884
|
-
//
|
|
21885
|
-
// browser honours (`-webkit-user-drag` is one engine's).
|
|
21886
|
-
// The cost is stated rather than worked around: an element that takes a
|
|
21887
|
-
// swipe cannot also be dragged out of the page, because there is one
|
|
21888
|
-
// gesture and it cannot mean both.
|
|
22087
|
+
// `draggable` refuses it on the element itself, before it starts; the drag
|
|
22088
|
+
// of a link or a thumbnail INSIDE it is refused by the travel reading the
|
|
22089
|
+
// press (startDragToTravel). The cost is stated rather than worked around:
|
|
22090
|
+
// an element that takes a swipe cannot also be dragged out of the page,
|
|
22091
|
+
// because there is one gesture and it cannot mean both.
|
|
21889
22092
|
mark("draggable", "false");
|
|
21890
|
-
const onDragStart = (dragStartEvent) => {
|
|
21891
|
-
dragStartEvent.preventDefault();
|
|
21892
|
-
};
|
|
21893
|
-
element.addEventListener("dragstart", onDragStart);
|
|
21894
|
-
undo.push(() => {
|
|
21895
|
-
element.removeEventListener("dragstart", onDragStart);
|
|
21896
|
-
});
|
|
21897
22093
|
|
|
21898
22094
|
// A touch this element may take has to be refusable before the finger
|
|
21899
22095
|
// lands, or the browser can cancel an established swipe mid-gesture by
|
|
@@ -22928,9 +23124,10 @@ const isMac = detectMac();
|
|
|
22928
23124
|
* place rather than about this box.
|
|
22929
23125
|
*
|
|
22930
23126
|
* `data-pan-after-hold="kept"` asks for that wait once rather than before every
|
|
22931
|
-
* pan: the surface given the hand keeps it — panning on contact,
|
|
22932
|
-
* plan opened full screen — and asks again after a press has
|
|
22933
|
-
* it, which is the hand saying it has moved on.
|
|
23127
|
+
* pan: the surface a finger has given the hand keeps it — panning on contact,
|
|
23128
|
+
* like the same plan opened full screen — and asks again after a press has
|
|
23129
|
+
* landed away from it, which is the hand saying it has moved on. A mouse never
|
|
23130
|
+
* owed the wait, so it neither spends it nor marks it. What answers "away" is the same
|
|
22934
23131
|
* thing that closes a popup, and the surface is the one that watches for it, so
|
|
22935
23132
|
* an application never keeps a `pointerdown` listener on the window to know.
|
|
22936
23133
|
* `[data-hand-kept]` is on the element for as long as it holds the hand that
|
|
@@ -24052,7 +24249,8 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
24052
24249
|
* and that field is the control the declaration speaks for: an ArrowUp declared
|
|
24053
24250
|
* on a text field is the field saying what ArrowUp does there, not a container
|
|
24054
24251
|
* taking the caret from a field inside it. A field inside a box that declares
|
|
24055
|
-
* keys for something else (a row, a list) keeps every key it has a use for
|
|
24252
|
+
* keys for something else (a row, a list) keeps every key it has a use for,
|
|
24253
|
+
* even when it is the only control in that box.
|
|
24056
24254
|
*
|
|
24057
24255
|
* A declared key that matches takes the key: its default does not happen as
|
|
24058
24256
|
* well, whether the gate lets the interaction through or refuses it and says
|
|
@@ -24078,7 +24276,10 @@ defineInteractionDetector({
|
|
|
24078
24276
|
};
|
|
24079
24277
|
}
|
|
24080
24278
|
const onKeyDown = createOnKeyDownForShortcuts(shortcuts, {
|
|
24081
|
-
|
|
24279
|
+
// Only the control's own root speaks for its host: a box that wraps a
|
|
24280
|
+
// single control is reached by that control's gate, not handed its keys.
|
|
24281
|
+
ownsTarget: (target) =>
|
|
24282
|
+
isControlRoot(element) && target === findNearestControlHost(element),
|
|
24082
24283
|
});
|
|
24083
24284
|
element.addEventListener("keydown", onKeyDown);
|
|
24084
24285
|
return () => {
|
|
@@ -28146,6 +28347,127 @@ const useForceRender = () => {
|
|
|
28146
28347
|
|
|
28147
28348
|
// https://github.com/preactjs/preact/issues/4756
|
|
28148
28349
|
|
|
28350
|
+
/**
|
|
28351
|
+
* Reads the current state of an action and returns `[data, loading, error, dismissError]`.
|
|
28352
|
+
*
|
|
28353
|
+
* By default (`loading` and `error` not set) the component suspends on load
|
|
28354
|
+
* and throws on failure, delegating both states to the nearest `<Loading>` and
|
|
28355
|
+
* `<ErrorBoundary>` ancestors. Pass `loading: true` or `error: true` to handle
|
|
28356
|
+
* either state directly inside the component instead.
|
|
28357
|
+
*
|
|
28358
|
+
* Return value: `[data, loading, error, dismissError]`
|
|
28359
|
+
* - `data` — the action's last successful data, or `undefined` if it has never completed
|
|
28360
|
+
* - `loading` — `true` while the action is running (only when `loading: true` is passed)
|
|
28361
|
+
* - `error` — the Error thrown by the action (only when `error: true` is passed)
|
|
28362
|
+
* - `dismissError` — takes that error out of what this hook returns, nothing else (only when `error: true` is passed)
|
|
28363
|
+
*
|
|
28364
|
+
* Stale data is always returned: the previous `data` remains available while
|
|
28365
|
+
* the action re-runs and after a run has failed, so a screen that had something
|
|
28366
|
+
* to show never has it taken away. What is drawn over it — a loading indicator,
|
|
28367
|
+
* the error, nothing — is entirely up to the component.
|
|
28368
|
+
*
|
|
28369
|
+
* `loading` covers the delay of a debounced binding
|
|
28370
|
+
* (`bindParams(paramsSignal, { debounce })`, `actionRunEffect({ debounce })`)
|
|
28371
|
+
* as well as the run itself: from the moment the params move, what is on screen
|
|
28372
|
+
* is the previous answer, and a screen showing "searching…" while someone types
|
|
28373
|
+
* needs to know it. Suspending has nothing to wait on during that delay, so
|
|
28374
|
+
* only `loading: true` reads it — the default keeps showing the previous answer
|
|
28375
|
+
* until the run really starts.
|
|
28376
|
+
*
|
|
28377
|
+
* When `loading` is not set (default), the component suspends until data is
|
|
28378
|
+
* ready, so `data` is always defined when the component renders and `loading`
|
|
28379
|
+
* is always `false`. With `loading: true` the component never suspends: every
|
|
28380
|
+
* state — running, settling, params nobody has started yet, a failure whose
|
|
28381
|
+
* message was dismissed — comes back as a value, so the subtree it draws is
|
|
28382
|
+
* never swapped for the boundary above and remounted.
|
|
28383
|
+
*
|
|
28384
|
+
* When `error` is not set (default), any action failure causes the component
|
|
28385
|
+
* to throw, so `error` is always `undefined` when the component renders. With
|
|
28386
|
+
* `error: true` the failure arrives beside the data the action already had — a
|
|
28387
|
+
* message on the content, not one in place of it. `dismissError()` removes the
|
|
28388
|
+
* message and nothing else: nothing is asked again, and what comes back is the
|
|
28389
|
+
* same state minus the error — the same data, or none at all when nothing ever
|
|
28390
|
+
* came back.
|
|
28391
|
+
*
|
|
28392
|
+
* `data === undefined` says there is nothing to show and `loading` says whether
|
|
28393
|
+
* anything is on its way: a skeleton reads both, or it claims a load nobody
|
|
28394
|
+
* started (see docs/data_states.md).
|
|
28395
|
+
*
|
|
28396
|
+
* @param {object | (() => Promise<any>) | Promise<any>} promiseOrAction -
|
|
28397
|
+
* an action, or the request this component owns written as a function:
|
|
28398
|
+
* made into an action once, on the first render, and kept for the life of
|
|
28399
|
+
* the instance — so it may be written inline — with what it resolves to as
|
|
28400
|
+
* the data and `run` implied. Code loaded on demand reads this way
|
|
28401
|
+
* (`() => import("./plan.jsx").then((m) => m.Plan)`, see
|
|
28402
|
+
* docs/dynamic_import.md), as does anything else asked for once per mount.
|
|
28403
|
+
* @param {{ loading?: true, error?: true, run?: true, prerun?: true, onLoad?: (data: any, context: {params: any}) => void }} [options]
|
|
28404
|
+
* @param {true} [options.run] - The fallback for data nothing else can ask for:
|
|
28405
|
+
* this component owns the request and starts it, from the render that reads
|
|
28406
|
+
* it. `loading` and `error` keep their meaning — delegated by default,
|
|
28407
|
+
* handled inline when asked for — so a run started here suspends into
|
|
28408
|
+
* `<Loading>` like any other.
|
|
28409
|
+
*
|
|
28410
|
+
* A failure is shown, not asked again: a render that retried it would retry
|
|
28411
|
+
* it in a loop while the network is down — that is `rerun()`'s job. Except a
|
|
28412
|
+
* failure a prerun left (a `prerun: true` head start, a press that preran
|
|
28413
|
+
* it): nothing on screen asked for that run, so this asks again, once.
|
|
28414
|
+
*
|
|
28415
|
+
* Prefer a `routeAction` wherever the parameter is one the address holds (a
|
|
28416
|
+
* path param, a search param bound to a `stateSignal`), popup included: it is
|
|
28417
|
+
* asked for when the address changes, with everything else that address
|
|
28418
|
+
* needs, whereas this one cannot start before the component that draws it
|
|
28419
|
+
* exists — one render late, and behind whatever gesture mounted it. What is
|
|
28420
|
+
* left for `run` is the parameter chosen inside the component and dying with
|
|
28421
|
+
* it — and any run that WRITES, whatever its parameter: an address asks again
|
|
28422
|
+
* on every arrival, which is right for a read and wrong for a write (see
|
|
28423
|
+
* docs/actions.md and docs/popup_open.md).
|
|
28424
|
+
* @param {true} [options.prerun] - A head start: this component starts a
|
|
28425
|
+
* read that another one, mounted later, owns with `run: true` — the read
|
|
28426
|
+
* leaves as soon as its params are known instead of once the owner exists.
|
|
28427
|
+
* Started from the render like `run`, as a prerun: it draws none of it, so
|
|
28428
|
+
* nothing suspends (no `<Loading>` needed) and a failure is neither thrown
|
|
28429
|
+
* nor reported — the owner asks again. A head start still running when the
|
|
28430
|
+
* owner reads it is the owner's wait.
|
|
28431
|
+
* @param {(data: any, context: {params: any}) => void} [options.onLoad] - what
|
|
28432
|
+
* this screen does with the data ONCE, when it becomes known: seed the fields
|
|
28433
|
+
* someone is about to edit, remember where a list was, focus something.
|
|
28434
|
+
*
|
|
28435
|
+
* Fired once per set of params — never again for a rerun that brings the same
|
|
28436
|
+
* thing back. That is the whole point of it: a save, a refresh, a poll all
|
|
28437
|
+
* hand the data over again, and copying it a second time would overwrite what
|
|
28438
|
+
* the person is in the middle of writing. The action knows what it ran with,
|
|
28439
|
+
* so nobody has to guess the dependency.
|
|
28440
|
+
*
|
|
28441
|
+
* Called from a layout effect, so what it writes belongs to the same tick as
|
|
28442
|
+
* the render that got the data — a `<Form pristineKey>` taking its reference
|
|
28443
|
+
* then sees the filled fields rather than the empty ones.
|
|
28444
|
+
* @returns {[data: unknown, loading: boolean, error: Error | undefined, dismissError: (() => void) | undefined]}
|
|
28445
|
+
*
|
|
28446
|
+
* @example <caption>Default — delegate both states (least code)</caption>
|
|
28447
|
+
* const [user] = useAsyncData(userAction);
|
|
28448
|
+
* // user is always defined here — never loading, never error
|
|
28449
|
+
*
|
|
28450
|
+
* @example <caption>Handle loading inline while keeping stale data</caption>
|
|
28451
|
+
* const [user, loading] = useAsyncData(userAction, { loading: true });
|
|
28452
|
+
* // First load: user === undefined, loading === true → show skeleton
|
|
28453
|
+
* // While refreshing: user === <stale>, loading === true → show stale + spinner
|
|
28454
|
+
* // Done: user === <fresh>, loading === false
|
|
28455
|
+
*
|
|
28456
|
+
* @example <caption>Handle error inline</caption>
|
|
28457
|
+
* const [user, , error] = useAsyncData(userAction, { error: true });
|
|
28458
|
+
* if (error) return <ErrorCard message={error.message} />;
|
|
28459
|
+
*
|
|
28460
|
+
* @example <caption>Say the failure over the rows already on screen</caption>
|
|
28461
|
+
* const [items, , error, dismissError] = useAsyncData(listAction, { error: true });
|
|
28462
|
+
* // First failure: items === undefined → only the message
|
|
28463
|
+
* // Failed refresh: items === <stale> → the message above the rows
|
|
28464
|
+
* return (
|
|
28465
|
+
* <>
|
|
28466
|
+
* {error && <ErrorStrip error={error} onClose={dismissError} />}
|
|
28467
|
+
* {items && <List items={items} />}
|
|
28468
|
+
* </>
|
|
28469
|
+
* );
|
|
28470
|
+
*/
|
|
28149
28471
|
const useAsyncData = (promiseOrAction, {
|
|
28150
28472
|
loading = "delegate",
|
|
28151
28473
|
error = "delegate",
|
|
@@ -28588,7 +28910,7 @@ const LoadingFallback = ({
|
|
|
28588
28910
|
* ones that would render fine.
|
|
28589
28911
|
*
|
|
28590
28912
|
* @param {object} props
|
|
28591
|
-
* @param {Function|import("
|
|
28913
|
+
* @param {Function|import("preact").VNode} [props.fallback] - what is displayed
|
|
28592
28914
|
* instead of the children: an element, or a component receiving
|
|
28593
28915
|
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
28594
28916
|
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
@@ -28947,6 +29269,12 @@ const hasWidthHeight = (context) => {
|
|
|
28947
29269
|
(context.styles.height || context.remainingProps.height)
|
|
28948
29270
|
);
|
|
28949
29271
|
};
|
|
29272
|
+
// The `stretch` sizing keyword: the containing block's width minus the box's
|
|
29273
|
+
// own margins, so a box with a margin fills its row without leaving it (100%
|
|
29274
|
+
// adds the margins on top). Spelled with the prefix because an inline style
|
|
29275
|
+
// holds one value, and this is the one name Chromium, WebKit and Firefox all
|
|
29276
|
+
// read (Firefox 155 has no unprefixed `stretch`).
|
|
29277
|
+
const FILL_AVAILABLE_WIDTH = "-webkit-fill-available";
|
|
28950
29278
|
const DIMENSION_PROPS = {
|
|
28951
29279
|
boxSizing: PASS_THROUGH,
|
|
28952
29280
|
width: PASS_THROUGH,
|
|
@@ -28999,16 +29327,16 @@ const DIMENSION_PROPS = {
|
|
|
28999
29327
|
return { flexGrow: expandWeight(value), flexBasis: "0%" };
|
|
29000
29328
|
}
|
|
29001
29329
|
if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
|
|
29002
|
-
// width
|
|
29330
|
+
// A width is what fills the cross axis; align-self stretch would do
|
|
29003
29331
|
// the same but takes the parent's alignX away — a maxWidth-capped item
|
|
29004
29332
|
// in an alignX="center" parent would then sit at the start instead of
|
|
29005
29333
|
// in the middle.
|
|
29006
29334
|
return {
|
|
29007
|
-
width:
|
|
29335
|
+
width: FILL_AVAILABLE_WIDTH,
|
|
29008
29336
|
};
|
|
29009
29337
|
}
|
|
29010
29338
|
// Can't use flexGrow — parent is not flex-x
|
|
29011
|
-
return { width:
|
|
29339
|
+
return { width: FILL_AVAILABLE_WIDTH };
|
|
29012
29340
|
},
|
|
29013
29341
|
expandY: (value, { parentBoxFlow }) => {
|
|
29014
29342
|
if (!value) {
|
|
@@ -29569,6 +29897,7 @@ const getVisualChildStylePropStrategy = (name) => {
|
|
|
29569
29897
|
};
|
|
29570
29898
|
|
|
29571
29899
|
const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
|
|
29900
|
+
const isTypoProp = (name) => TYPO_PROP_NAME_SET.has(name);
|
|
29572
29901
|
|
|
29573
29902
|
const getStringifier = (key) => {
|
|
29574
29903
|
if (SPACING_PROP_SET.has(key)) {
|
|
@@ -30210,10 +30539,10 @@ const readCSSVarName = entry => Array.isArray(entry) ? entry[0] : entry;
|
|
|
30210
30539
|
const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
30211
30540
|
|
|
30212
30541
|
/**
|
|
30213
|
-
* @type {import("
|
|
30542
|
+
* @type {import("preact").FunctionComponent<{
|
|
30214
30543
|
* as?: string,
|
|
30215
30544
|
* className?: string,
|
|
30216
|
-
* style?: import("
|
|
30545
|
+
* style?: import("preact").JSX.CSSProperties & { [pseudo: string]: import("preact").JSX.CSSProperties },
|
|
30217
30546
|
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
30218
30547
|
* inline?: boolean,
|
|
30219
30548
|
* block?: boolean,
|
|
@@ -30228,7 +30557,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30228
30557
|
* hasChildUsingForwardedProps?: boolean,
|
|
30229
30558
|
* childPropSet?: Set<string>,
|
|
30230
30559
|
* preventInitialTransition?: boolean,
|
|
30231
|
-
* separator?: import("
|
|
30560
|
+
* separator?: import("preact").ComponentChildren | ((index: number) => import("preact").ComponentChildren),
|
|
30232
30561
|
* selfInteractions?: string,
|
|
30233
30562
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
30234
30563
|
* alignX?: string,
|
|
@@ -30240,7 +30569,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30240
30569
|
* body?: boolean,
|
|
30241
30570
|
* sticky?: boolean,
|
|
30242
30571
|
* mount?: "always" | "after-paint",
|
|
30243
|
-
* children?: import("
|
|
30572
|
+
* children?: import("preact").ComponentChildren,
|
|
30244
30573
|
* [key: string]: any,
|
|
30245
30574
|
* }>}
|
|
30246
30575
|
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
@@ -32795,7 +33124,7 @@ const debug$1 = (...args) => {
|
|
|
32795
33124
|
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
32796
33125
|
* movement be written about it and its `matchingSignal` be read from outside
|
|
32797
33126
|
* the tree (see route_fallback.js).
|
|
32798
|
-
* @param {Function|import("
|
|
33127
|
+
* @param {Function|import("preact").VNode} [props.element] - what the branch renders
|
|
32799
33128
|
* @param {object} [props.elementProps] - props given to `element`
|
|
32800
33129
|
*
|
|
32801
33130
|
* A branch says what it renders, not what happens when it cannot: loading and
|
|
@@ -35144,7 +35473,7 @@ const css$13 = /* css */`@property --navi-route-transition-leave {
|
|
|
35144
35473
|
* that. An application that already has an element holding its pages can mark
|
|
35145
35474
|
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
35146
35475
|
*
|
|
35147
|
-
* @type {import("
|
|
35476
|
+
* @type {import("preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
35148
35477
|
*/
|
|
35149
35478
|
const RouteTransitionArea = ({
|
|
35150
35479
|
children,
|
|
@@ -35934,7 +36263,7 @@ const beginTransition = ({
|
|
|
35934
36263
|
warnAboutNamesEscapingArea(areaElement, capturedNames);
|
|
35935
36264
|
return;
|
|
35936
36265
|
}
|
|
35937
|
-
for (const name of capturedNames) {
|
|
36266
|
+
for (const name of capturedNames.keys()) {
|
|
35938
36267
|
if (name === "root") {
|
|
35939
36268
|
continue;
|
|
35940
36269
|
}
|
|
@@ -36137,9 +36466,10 @@ const ignoreSkipped$1 = () => {};
|
|
|
36137
36466
|
|
|
36138
36467
|
// The names the browser captured, read off the pictures themselves: what was
|
|
36139
36468
|
// asked for in CSS and what was taken are not the same question (see
|
|
36140
|
-
// viewTransitionReady).
|
|
36469
|
+
// viewTransitionReady). Each with the states it was taken in: a name taken in
|
|
36470
|
+
// only one of them pairs with nothing.
|
|
36141
36471
|
const capturedViewTransitionNames = () => {
|
|
36142
|
-
const names = new
|
|
36472
|
+
const names = new Map();
|
|
36143
36473
|
for (const animation of document.getAnimations()) {
|
|
36144
36474
|
const pseudoElement = animation.effect?.pseudoElement;
|
|
36145
36475
|
if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
|
|
@@ -36149,43 +36479,59 @@ const capturedViewTransitionNames = () => {
|
|
|
36149
36479
|
if (nameStart === -1) {
|
|
36150
36480
|
continue;
|
|
36151
36481
|
}
|
|
36152
|
-
|
|
36482
|
+
const name = pseudoElement.slice(nameStart + 1, -1);
|
|
36483
|
+
let states = names.get(name);
|
|
36484
|
+
if (!states) {
|
|
36485
|
+
states = {
|
|
36486
|
+
old: false,
|
|
36487
|
+
new: false
|
|
36488
|
+
};
|
|
36489
|
+
names.set(name, states);
|
|
36490
|
+
}
|
|
36491
|
+
if (pseudoElement.startsWith("::view-transition-old(")) {
|
|
36492
|
+
states.old = true;
|
|
36493
|
+
} else if (pseudoElement.startsWith("::view-transition-new(")) {
|
|
36494
|
+
states.new = true;
|
|
36495
|
+
}
|
|
36153
36496
|
}
|
|
36154
36497
|
return names;
|
|
36155
36498
|
};
|
|
36156
36499
|
|
|
36157
|
-
//
|
|
36158
|
-
//
|
|
36159
|
-
//
|
|
36160
|
-
//
|
|
36500
|
+
// A name written inside the area is a group of its own: it stands where it was
|
|
36501
|
+
// captured while the area's two pictures carry the movement, in every browser.
|
|
36502
|
+
// Nested groups only keep it cut at the pages' edge (see the @supports block in
|
|
36503
|
+
// the CSS above). Paired with the same name on the other page it is a morph;
|
|
36504
|
+
// taken in one state only it pairs with nothing, and is a piece of its page
|
|
36505
|
+
// left behind, fading, while that page moves.
|
|
36161
36506
|
const NESTED_GROUPS_SUPPORTED = window.CSS.supports("view-transition-group", "contain");
|
|
36162
36507
|
const warnAboutNamesEscapingArea = (areaElement, capturedNames) => {
|
|
36163
|
-
|
|
36164
|
-
|
|
36165
|
-
}
|
|
36166
|
-
let escapedName = null;
|
|
36167
|
-
for (const name of capturedNames) {
|
|
36508
|
+
const unpairedNames = new Set();
|
|
36509
|
+
for (const [name, states] of capturedNames) {
|
|
36168
36510
|
if (name === "root" || name === AREA_NAME || name.startsWith(FURNITURE_NAME_PREFIX)) {
|
|
36169
36511
|
continue;
|
|
36170
36512
|
}
|
|
36171
|
-
|
|
36172
|
-
|
|
36513
|
+
if (states.old && states.new) {
|
|
36514
|
+
continue;
|
|
36515
|
+
}
|
|
36516
|
+
unpairedNames.add(name);
|
|
36173
36517
|
}
|
|
36174
36518
|
// A name captured next to the area is not necessarily inside it — a bar the
|
|
36175
36519
|
// application animates on the same clock is named on purpose. The subtree is
|
|
36176
36520
|
// walked only once something is there to find, so the common case reads
|
|
36177
|
-
// nothing.
|
|
36178
|
-
|
|
36521
|
+
// nothing. The page being left is still in it, hidden (keepLeavingPages in
|
|
36522
|
+
// route.jsx), so a name taken only in the old state is found too.
|
|
36523
|
+
if (unpairedNames.size === 0) {
|
|
36179
36524
|
return;
|
|
36180
36525
|
}
|
|
36181
36526
|
for (const descendant of areaElement.querySelectorAll("*")) {
|
|
36182
36527
|
const {
|
|
36183
36528
|
viewTransitionName
|
|
36184
36529
|
} = getComputedStyle(descendant);
|
|
36185
|
-
if (!viewTransitionName
|
|
36530
|
+
if (!unpairedNames.has(viewTransitionName)) {
|
|
36186
36531
|
continue;
|
|
36187
36532
|
}
|
|
36188
|
-
|
|
36533
|
+
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.";
|
|
36534
|
+
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).`);
|
|
36189
36535
|
return;
|
|
36190
36536
|
}
|
|
36191
36537
|
};
|
|
@@ -36330,6 +36676,8 @@ const installRouteTransitionCss = () => {
|
|
|
36330
36676
|
};
|
|
36331
36677
|
|
|
36332
36678
|
installImportMetaCssBuild(import.meta);
|
|
36679
|
+
// The pictures carrying a travel, among everything else the movement takes
|
|
36680
|
+
// along (see findLeadAnimation).
|
|
36333
36681
|
const TRAVEL_LEAD_NAMES = ["navi-route-travel"];
|
|
36334
36682
|
|
|
36335
36683
|
// A browser with no view transitions of its own has no picture of the page
|
|
@@ -36597,7 +36945,7 @@ const css$12 = /* css */`.navi_route_travel {
|
|
|
36597
36945
|
`;
|
|
36598
36946
|
|
|
36599
36947
|
/**
|
|
36600
|
-
* @type {import("
|
|
36948
|
+
* @type {import("preact").FunctionComponent<{
|
|
36601
36949
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
36602
36950
|
* axis?: "x"|"y",
|
|
36603
36951
|
* travelByDrag?: boolean,
|
|
@@ -39374,7 +39722,7 @@ const resetCommand = (source, event) => {
|
|
|
39374
39722
|
// answers: "save" stays, "delete" goes back to the list. Same shape as the
|
|
39375
39723
|
// browser's own formaction/formmethod, and the same shape the action already
|
|
39376
39724
|
// has (it is told which button requested it).
|
|
39377
|
-
const resolveAfterSend = (target, requester) => {
|
|
39725
|
+
const resolveAfterSend = (target, requester, surface) => {
|
|
39378
39726
|
const askedForByRequester = requester?.getAttribute?.("data-after-send");
|
|
39379
39727
|
if (askedForByRequester) {
|
|
39380
39728
|
return askedForByRequester;
|
|
@@ -39383,11 +39731,6 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39383
39731
|
if (askedFor) {
|
|
39384
39732
|
return askedFor;
|
|
39385
39733
|
}
|
|
39386
|
-
// From above the target: a popup that IS the send target is handled on its
|
|
39387
|
-
// own (see the aria-expanded branch below), and must not answer twice.
|
|
39388
|
-
const surface = target.parentElement?.closest(
|
|
39389
|
-
`[data-slide], [aria-expanded]`,
|
|
39390
|
-
);
|
|
39391
39734
|
if (!surface) {
|
|
39392
39735
|
return undefined;
|
|
39393
39736
|
}
|
|
@@ -39399,6 +39742,11 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39399
39742
|
}
|
|
39400
39743
|
return undefined;
|
|
39401
39744
|
};
|
|
39745
|
+
// The surface a send answers when the control says nothing. Found from above
|
|
39746
|
+
// the target: a popup that IS the send target is handled on its own (see the
|
|
39747
|
+
// aria-expanded branch in sendCommand), and must not answer twice.
|
|
39748
|
+
const resolveSendSurface = (target) =>
|
|
39749
|
+
target.parentElement?.closest(`[data-slide], [aria-expanded]`);
|
|
39402
39750
|
// What follows a send is caused by the send, not by the gesture: the send
|
|
39403
39751
|
// answered the gesture and may have prevented it (an Enter must not also click
|
|
39404
39752
|
// the submit button, nor activate the trigger of the popup it closed), and a
|
|
@@ -39480,20 +39828,30 @@ const sendCommand = (source, event, { requester }) => {
|
|
|
39480
39828
|
// and the popup must stay open — with the form still in front of the
|
|
39481
39829
|
// user, showing what it is waiting for.
|
|
39482
39830
|
let invalid = false;
|
|
39483
|
-
// What follows the send is
|
|
39484
|
-
//
|
|
39485
|
-
//
|
|
39486
|
-
//
|
|
39487
|
-
//
|
|
39488
|
-
//
|
|
39831
|
+
// What follows the send is aimed at the press, like a button's command
|
|
39832
|
+
// waiting for its action (see resolveNaviCommand): the send may take the
|
|
39833
|
+
// form out of the document (a form keyed on what it edits, a row that
|
|
39834
|
+
// moves once saved), and resolved from there it finds no popup to close.
|
|
39835
|
+
// Which command it is gets read again once the send went through: an
|
|
39836
|
+
// action that learned where to go from the response writes it on the
|
|
39837
|
+
// form while it runs (data-after-send). That one can only be aimed when
|
|
39838
|
+
// it is read.
|
|
39839
|
+
const surface = resolveSendSurface(target);
|
|
39840
|
+
const sentEvent = createSentEvent(event);
|
|
39841
|
+
const afterSendAtPress = resolveAfterSend(target, sendRequester, surface);
|
|
39842
|
+
const runAimedAtPress = afterSendAtPress
|
|
39843
|
+
? resolveNaviCommand(source, afterSendAtPress, sentEvent)
|
|
39844
|
+
: undefined;
|
|
39489
39845
|
const runAfterSend = () => {
|
|
39490
|
-
const afterSend = resolveAfterSend(target, sendRequester);
|
|
39846
|
+
const afterSend = resolveAfterSend(target, sendRequester, surface);
|
|
39491
39847
|
if (!afterSend) {
|
|
39492
39848
|
return;
|
|
39493
39849
|
}
|
|
39494
|
-
|
|
39495
|
-
|
|
39496
|
-
|
|
39850
|
+
if (afterSend === afterSendAtPress) {
|
|
39851
|
+
runAimedAtPress?.();
|
|
39852
|
+
return;
|
|
39853
|
+
}
|
|
39854
|
+
triggerNaviCommand(source, afterSend, sentEvent);
|
|
39497
39855
|
};
|
|
39498
39856
|
const {
|
|
39499
39857
|
result: sent,
|
|
@@ -40432,6 +40790,12 @@ const isDeclaredInsidePopup = (target, popupId) =>
|
|
|
40432
40790
|
* Lives on the popup's own content element rather than in the backdrop's
|
|
40433
40791
|
* handler, for both renderers: the backdrop is a sibling behind the popup
|
|
40434
40792
|
* (never an ancestor), so a press inside the popup's box never reaches it.
|
|
40793
|
+
*
|
|
40794
|
+
* Inside a box that travels by drag — a row of slides walked by a swipe, a
|
|
40795
|
+
* sheet pushed back towards its edge — the region is also where that travel
|
|
40796
|
+
* starts, and at the mousedown nobody knows yet which of the two the press is.
|
|
40797
|
+
* The dismissal then waits for the click, which only comes if the press stayed
|
|
40798
|
+
* a press: a travel swallows the one it leaves behind.
|
|
40435
40799
|
*/
|
|
40436
40800
|
const handlePressOnOutsideRegion = (
|
|
40437
40801
|
mouseDownEvent,
|
|
@@ -40462,11 +40826,36 @@ const handlePressOnOutsideRegion = (
|
|
|
40462
40826
|
mouseDownEvent.preventDefault();
|
|
40463
40827
|
return;
|
|
40464
40828
|
}
|
|
40465
|
-
if (pressOutside
|
|
40466
|
-
|
|
40467
|
-
|
|
40829
|
+
if (pressOutside !== "close" && pressOutside !== "cancel") {
|
|
40830
|
+
return;
|
|
40831
|
+
}
|
|
40832
|
+
const isCancel = pressOutside === "cancel";
|
|
40833
|
+
if (isPressDisputedByDrag(target)) {
|
|
40834
|
+
onClickOfThisPress(target, (clickEvent) => {
|
|
40835
|
+
openController.requestClose(clickEvent, { isCancel });
|
|
40468
40836
|
});
|
|
40837
|
+
return;
|
|
40469
40838
|
}
|
|
40839
|
+
openController.requestClose(mouseDownEvent, { isCancel });
|
|
40840
|
+
};
|
|
40841
|
+
|
|
40842
|
+
// The click ending the press whose mousedown landed on `element`. A click goes
|
|
40843
|
+
// to what the press and the release have in common, so it reaches `element`
|
|
40844
|
+
// only when the release stayed on it or inside it. A press that never clicks
|
|
40845
|
+
// leaves it armed until the next one, and no longer — a click always comes
|
|
40846
|
+
// after a press, so it cannot reach the click of another (the rule
|
|
40847
|
+
// click_suppression.js in @jsenv/dom goes by).
|
|
40848
|
+
const onClickOfThisPress = (element, callback) => {
|
|
40849
|
+
const onClick = (clickEvent) => {
|
|
40850
|
+
disarm();
|
|
40851
|
+
callback(clickEvent);
|
|
40852
|
+
};
|
|
40853
|
+
const disarm = () => {
|
|
40854
|
+
element.removeEventListener("click", onClick);
|
|
40855
|
+
window.removeEventListener("pointerdown", disarm, { capture: true });
|
|
40856
|
+
};
|
|
40857
|
+
element.addEventListener("click", onClick);
|
|
40858
|
+
window.addEventListener("pointerdown", disarm, { capture: true });
|
|
40470
40859
|
};
|
|
40471
40860
|
|
|
40472
40861
|
/**
|
|
@@ -45674,6 +46063,9 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
45674
46063
|
* handles the timing centrally rather than each component having to manage its
|
|
45675
46064
|
* own debounce logic.
|
|
45676
46065
|
*/
|
|
46066
|
+
// Sentinel used as the initial value of lastActionValueRef.
|
|
46067
|
+
// Distinct from undefined so that undefined (e.g. unchecked radio) can itself
|
|
46068
|
+
// be stored as a valid "last action value" and trigger the dedup logic.
|
|
45677
46069
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
45678
46070
|
|
|
45679
46071
|
// The control types rendered as an element `<label for>` can name: HTML's
|
|
@@ -45773,13 +46165,18 @@ const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyD
|
|
|
45773
46165
|
* the control opens, so that a listener hears the control as a whole.
|
|
45774
46166
|
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
45775
46167
|
* host's either way: the control's own reactions to them call them.
|
|
46168
|
+
* @param {"host"|"root"} [options.nameOn] Where `aria-label` and
|
|
46169
|
+
* `aria-labelledby` land: where the role is. `"host"`, the default, for every
|
|
46170
|
+
* control whose host is what is announced; `"root"` for a control whose role
|
|
46171
|
+
* is on its root and whose host is an `aria-hidden` carrier (a wheel).
|
|
45776
46172
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
45777
46173
|
*/
|
|
45778
46174
|
const useControlProps = (props, {
|
|
45779
46175
|
controlType,
|
|
45780
46176
|
persists,
|
|
45781
46177
|
uiActionInternal,
|
|
45782
|
-
listenersOn = "host"
|
|
46178
|
+
listenersOn = "host",
|
|
46179
|
+
nameOn = "host"
|
|
45783
46180
|
}) => {
|
|
45784
46181
|
const debugUIState = useDebugUIState();
|
|
45785
46182
|
|
|
@@ -45867,6 +46264,7 @@ const useControlProps = (props, {
|
|
|
45867
46264
|
boundAction,
|
|
45868
46265
|
controlInfo,
|
|
45869
46266
|
listenersOn,
|
|
46267
|
+
nameOn,
|
|
45870
46268
|
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
45871
46269
|
});
|
|
45872
46270
|
{
|
|
@@ -47095,6 +47493,7 @@ const useInteractiveProps = (props, {
|
|
|
47095
47493
|
boundAction,
|
|
47096
47494
|
controlInfo,
|
|
47097
47495
|
listenersOn,
|
|
47496
|
+
nameOn,
|
|
47098
47497
|
listenersCalledByReactions
|
|
47099
47498
|
}) => {
|
|
47100
47499
|
const {
|
|
@@ -47102,6 +47501,7 @@ const useInteractiveProps = (props, {
|
|
|
47102
47501
|
} = props;
|
|
47103
47502
|
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
47104
47503
|
listenersOn,
|
|
47504
|
+
nameOn,
|
|
47105
47505
|
listenersCalledByReactions
|
|
47106
47506
|
});
|
|
47107
47507
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
@@ -47713,8 +48113,10 @@ const useInteractiveProps = (props, {
|
|
|
47713
48113
|
}
|
|
47714
48114
|
return [controlRootProps, controlHostProps];
|
|
47715
48115
|
};
|
|
48116
|
+
const NAME_ATTRIBUTE_SET = new Set(["aria-label", "aria-labelledby"]);
|
|
47716
48117
|
const splitControlProps = (props, {
|
|
47717
48118
|
listenersOn,
|
|
48119
|
+
nameOn,
|
|
47718
48120
|
listenersCalledByReactions
|
|
47719
48121
|
}) => {
|
|
47720
48122
|
const {
|
|
@@ -47725,6 +48127,10 @@ const splitControlProps = (props, {
|
|
|
47725
48127
|
};
|
|
47726
48128
|
const controlRootProps = {};
|
|
47727
48129
|
for (const key of Object.keys(props)) {
|
|
48130
|
+
if (nameOn === "root" && NAME_ATTRIBUTE_SET.has(key)) {
|
|
48131
|
+
controlRootProps[key] = props[key];
|
|
48132
|
+
continue;
|
|
48133
|
+
}
|
|
47728
48134
|
const attributeName = controlAttributeFromProp(key);
|
|
47729
48135
|
if (attributeName) {
|
|
47730
48136
|
controlHostProps[attributeName] = props[key];
|
|
@@ -49854,7 +50260,7 @@ const css$$ = /* css */`.navi_text_anchor {
|
|
|
49854
50260
|
* @param {{ size?: number, verticalAlign?: string }} [lineLayout]
|
|
49855
50261
|
* Describes the surrounding line context. Used as layout-effect dependencies so the correction
|
|
49856
50262
|
* reruns when the surrounding text's font-size or vertical-align changes.
|
|
49857
|
-
* @param {import("
|
|
50263
|
+
* @param {import("preact").RefObject} childRef — ref on the child element to reposition
|
|
49858
50264
|
*/
|
|
49859
50265
|
const TextAnchor = ({
|
|
49860
50266
|
childRef,
|
|
@@ -50516,25 +50922,25 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50516
50922
|
* See `docs/typography.md` for the decisions behind it — truncating, rows made
|
|
50517
50923
|
* of an icon, a text and an icon, and where a line may break.
|
|
50518
50924
|
*
|
|
50519
|
-
* @type {import("
|
|
50520
|
-
* children?: import("
|
|
50925
|
+
* @type {import("preact").FunctionComponent<{
|
|
50926
|
+
* children?: import("preact").ComponentChildren,
|
|
50521
50927
|
* as?: string,
|
|
50522
50928
|
* className?: string,
|
|
50523
|
-
* style?: import("
|
|
50929
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
50524
50930
|
* bold?: boolean,
|
|
50525
50931
|
* noWrap?: boolean,
|
|
50526
50932
|
* maxLines?: number,
|
|
50527
|
-
* spacing?: string | number | import("
|
|
50933
|
+
* spacing?: string | number | import("preact").ComponentChildren,
|
|
50528
50934
|
* loading?: boolean,
|
|
50529
50935
|
* skeleton?: boolean,
|
|
50530
50936
|
* attachLastChild?: boolean,
|
|
50531
50937
|
* preventSpaceUnderlines?: boolean,
|
|
50532
|
-
* holdSpaceForStyle?: import("
|
|
50938
|
+
* holdSpaceForStyle?: import("preact").JSX.CSSProperties,
|
|
50533
50939
|
* boldStable?: boolean,
|
|
50534
50940
|
* shrinkWrap?: boolean,
|
|
50535
50941
|
* capitalize?: boolean,
|
|
50536
50942
|
* selectRange?: string | [number, number] | [string, string],
|
|
50537
|
-
* childrenOutsideFlow?: import("
|
|
50943
|
+
* childrenOutsideFlow?: import("preact").ComponentChildren,
|
|
50538
50944
|
* [key: string]: any,
|
|
50539
50945
|
* }>}
|
|
50540
50946
|
*
|
|
@@ -50575,7 +50981,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50575
50981
|
* Replaces real space characters between children with padding-based spaces.
|
|
50576
50982
|
* Useful inside `<a>` elements where browsers draw an underline under spaces.
|
|
50577
50983
|
*
|
|
50578
|
-
* @param {import("
|
|
50984
|
+
* @param {import("preact").JSX.CSSProperties} [holdSpaceForStyle]
|
|
50579
50985
|
* Prevents layout shifts when text styles change (e.g. font-weight, font-size).
|
|
50580
50986
|
* Pass the CSS properties representing the "largest" visual state of the text.
|
|
50581
50987
|
* An invisible placeholder rendered with those styles reserves the space; the
|
|
@@ -50608,7 +51014,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50608
51014
|
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
50609
51015
|
* substrings selecting from the first to the end of the second.
|
|
50610
51016
|
*
|
|
50611
|
-
* @param {import("
|
|
51017
|
+
* @param {import("preact").ComponentChildren} [childrenOutsideFlow]
|
|
50612
51018
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
50613
51019
|
* internally for overlays such as the skeleton container.
|
|
50614
51020
|
*/
|
|
@@ -50896,7 +51302,7 @@ const TextWithSelectRange = ({
|
|
|
50896
51302
|
* @param {object} props
|
|
50897
51303
|
* @param {string} [props.href] - URL/id of an external SVG symbol, rendered via
|
|
50898
51304
|
* `<svg><use href></svg>`. Mutually exclusive with meaningful `children`.
|
|
50899
|
-
* @param {import("
|
|
51305
|
+
* @param {import("preact").ComponentChildren} [props.children] - Inline icon
|
|
50900
51306
|
* content: an `<svg>` element, or a string (renders as a text/emoji icon).
|
|
50901
51307
|
* @param {boolean} [props.decorative] - Marks the icon `aria-hidden`. Defaults
|
|
50902
51308
|
* to `true` for an unsized, non-interactive icon; pass `false` for a
|
|
@@ -51071,6 +51477,11 @@ const usePreloadOnIntent = (ref, href, prefetch = true) => {
|
|
|
51071
51477
|
return undefined;
|
|
51072
51478
|
}
|
|
51073
51479
|
const preload = () => {
|
|
51480
|
+
// The tab or the window it opens loads its own code: what would be
|
|
51481
|
+
// fetched here is not what runs there.
|
|
51482
|
+
if (linkOpensElsewhere(element)) {
|
|
51483
|
+
return;
|
|
51484
|
+
}
|
|
51074
51485
|
preloadUrl(href);
|
|
51075
51486
|
};
|
|
51076
51487
|
element.addEventListener("pointerenter", preload);
|
|
@@ -51279,6 +51690,15 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
51279
51690
|
};
|
|
51280
51691
|
|
|
51281
51692
|
installImportMetaCssBuild(import.meta);
|
|
51693
|
+
/*
|
|
51694
|
+
* Apply opacity to child content, not the link element itself.
|
|
51695
|
+
*
|
|
51696
|
+
* Why not apply opacity directly to .navi_link?
|
|
51697
|
+
* - Would make focus outlines semi-transparent too (accessibility issue)
|
|
51698
|
+
* - We want dimmed text but full-opacity focus indicators for visibility
|
|
51699
|
+
*
|
|
51700
|
+
* This approach dims the content while preserving focus outline visibility.
|
|
51701
|
+
*/
|
|
51282
51702
|
const css$Z = /* css */`@layer navi {
|
|
51283
51703
|
.navi_link {
|
|
51284
51704
|
--link-border-radius: unset;
|
|
@@ -51299,6 +51719,19 @@ const css$Z = /* css */`@layer navi {
|
|
|
51299
51719
|
--link-current-indicator-spacing: 0;
|
|
51300
51720
|
--link-current-indicator-color: var(--navi-link-current-indicator-color);
|
|
51301
51721
|
}
|
|
51722
|
+
|
|
51723
|
+
.navi_link[data-variant="text"], .navi_link[data-variant="icon"] {
|
|
51724
|
+
--link-color: unset;
|
|
51725
|
+
--link-text-decoration: none;
|
|
51726
|
+
}
|
|
51727
|
+
|
|
51728
|
+
.navi_link[data-variant="tab"] {
|
|
51729
|
+
--link-background-hover: color-mix(in srgb,
|
|
51730
|
+
var(--link-background, transparent),
|
|
51731
|
+
var(--x-link-contrasting-color) 15%);
|
|
51732
|
+
--link-color: unset;
|
|
51733
|
+
--link-text-decoration: none;
|
|
51734
|
+
}
|
|
51302
51735
|
}
|
|
51303
51736
|
|
|
51304
51737
|
.navi_link {
|
|
@@ -51319,7 +51752,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
51319
51752
|
--x-link-padding-right: var(--link-padding-right, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51320
51753
|
--x-link-padding-bottom: var(--link-padding-bottom, var(--link-padding-y, var(--link-padding, 0px)));
|
|
51321
51754
|
--x-link-padding-left: var(--link-padding-left, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51322
|
-
aspect-ratio: inherit;
|
|
51323
51755
|
padding-top: var(--x-link-padding-top);
|
|
51324
51756
|
padding-right: var(--x-link-padding-right);
|
|
51325
51757
|
padding-bottom: var(--x-link-padding-bottom);
|
|
@@ -51508,17 +51940,7 @@ const css$Z = /* css */`@layer navi {
|
|
|
51508
51940
|
margin-left: -.1em;
|
|
51509
51941
|
}
|
|
51510
51942
|
|
|
51511
|
-
&[data-variant="text"], &[data-variant="icon"] {
|
|
51512
|
-
--link-color: unset;
|
|
51513
|
-
--link-text-decoration: none;
|
|
51514
|
-
}
|
|
51515
|
-
|
|
51516
51943
|
&[data-variant="tab"] {
|
|
51517
|
-
--link-background-hover: color-mix(in srgb,
|
|
51518
|
-
var(--link-background, transparent),
|
|
51519
|
-
var(--x-link-contrasting-color) 15%);
|
|
51520
|
-
--link-color: unset;
|
|
51521
|
-
--link-text-decoration: none;
|
|
51522
51944
|
white-space: nowrap;
|
|
51523
51945
|
user-select: none;
|
|
51524
51946
|
|
|
@@ -51605,8 +52027,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51605
52027
|
*
|
|
51606
52028
|
* Href-derived behavior (all computed from `href`, recomputed on navigation):
|
|
51607
52029
|
* - internal vs external target (`data-href-internal`/`data-href-external`),
|
|
51608
|
-
* with `target`/`rel` auto-defaulted
|
|
51609
|
-
* `noopener noreferrer`
|
|
52030
|
+
* with `target`/`rel` auto-defaulted unless explicitly set: `_blank` for a
|
|
52031
|
+
* page of another site (`http:`/`https:`), `noopener noreferrer` for any
|
|
52032
|
+
* external address, nothing otherwise.
|
|
51610
52033
|
* - "current page" detection (`data-href-current`, `aria-current="page"`).
|
|
51611
52034
|
* - anchor links (`href` starting with `#`) get `data-href-anchor`.
|
|
51612
52035
|
* - an automatic end icon for `tel:`/`sms:`/`mailto:`/`github.com`, external
|
|
@@ -51668,14 +52091,14 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51668
52091
|
* text reads (see `holdSpaceForStyle` in `text.jsx`).
|
|
51669
52092
|
* @param {boolean} [props.currentEffectShadow] - Inset-shadow effect while
|
|
51670
52093
|
* current (used with `variant="tab"`).
|
|
51671
|
-
* @param {boolean|import("
|
|
52094
|
+
* @param {boolean|import("preact").ComponentChild} [props.startIcon] - Icon
|
|
51672
52095
|
* placed before the text.
|
|
51673
|
-
* @param {boolean|import("
|
|
52096
|
+
* @param {boolean|import("preact").ComponentChild} [props.endIcon] - Icon
|
|
51674
52097
|
* placed after the text; when omitted, an icon may be auto-chosen from the
|
|
51675
52098
|
* href (see above).
|
|
51676
|
-
* @param {boolean|import("
|
|
52099
|
+
* @param {boolean|import("preact").ComponentChild} [props.blankTargetIcon] -
|
|
51677
52100
|
* Override/suppress the auto external-link icon.
|
|
51678
|
-
* @param {boolean|import("
|
|
52101
|
+
* @param {boolean|import("preact").ComponentChild} [props.anchorIcon] -
|
|
51679
52102
|
* Controls the auto anchor icon shown for a childless anchor link:
|
|
51680
52103
|
* `true`/`undefined` uses the default chain SVG, `false` suppresses it, any
|
|
51681
52104
|
* other node replaces it. To render a plain `#` instead, prefer writing it as
|
|
@@ -51746,7 +52169,7 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51746
52169
|
* @param {boolean} [props.preventDefault] - Call `event.preventDefault()` on
|
|
51747
52170
|
* click (navigation suppressed; `onClick` still runs).
|
|
51748
52171
|
* @param {(event: MouseEvent) => void} [props.onClick]
|
|
51749
|
-
* @param {import("
|
|
52172
|
+
* @param {import("preact").ComponentChildren} [props.children] - Link content;
|
|
51750
52173
|
* falls back to the route's relative URL / `href` per the rules above.
|
|
51751
52174
|
* @param {string} [props.name] - Control facade name (links may be nameless).
|
|
51752
52175
|
* @param {boolean} [props.disabled]
|
|
@@ -51875,7 +52298,7 @@ const LinkPlain = props => {
|
|
|
51875
52298
|
":-navi-href-current": innerCurrent,
|
|
51876
52299
|
":-navi-selected": selected
|
|
51877
52300
|
};
|
|
51878
|
-
const innerTarget = target === undefined ?
|
|
52301
|
+
const innerTarget = target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target;
|
|
51879
52302
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
51880
52303
|
let innerEndIcon;
|
|
51881
52304
|
if (endIcon === undefined) {
|
|
@@ -52042,7 +52465,7 @@ const LinkPlain = props => {
|
|
|
52042
52465
|
},
|
|
52043
52466
|
href: href,
|
|
52044
52467
|
rel: innerRel,
|
|
52045
|
-
target: innerTarget === "_self" ? undefined :
|
|
52468
|
+
target: innerTarget === "_self" ? undefined : innerTarget
|
|
52046
52469
|
// Which slide this tab is, and which box to say it to: read by the <Nav>
|
|
52047
52470
|
// around it to place the row's own bar, and by the command above to find
|
|
52048
52471
|
// the container across the document.
|
|
@@ -52294,6 +52717,9 @@ const useSlideContainer = (target) => {
|
|
|
52294
52717
|
};
|
|
52295
52718
|
|
|
52296
52719
|
installImportMetaCssBuild(import.meta);
|
|
52720
|
+
// A name of its own for the bar under the current tab, one per nav on the page:
|
|
52721
|
+
// two bars answering to the same name are two elements claiming one picture,
|
|
52722
|
+
// and the browser refuses the whole transition rather than pick.
|
|
52297
52723
|
let navCount = 0;
|
|
52298
52724
|
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
52299
52725
|
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
@@ -52318,6 +52744,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
52318
52744
|
};
|
|
52319
52745
|
const css$Y = /* css */`@layer navi {
|
|
52320
52746
|
.navi_nav {
|
|
52747
|
+
--nav-width: fit-content;
|
|
52321
52748
|
--nav-border: none;
|
|
52322
52749
|
--nav-padding: 0px;
|
|
52323
52750
|
--nav-border-radius: 0px;
|
|
@@ -52341,7 +52768,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52341
52768
|
var(--nav-indicator-length-delta));
|
|
52342
52769
|
--x-nav-indicator-color: var(--nav-indicator-color, var(--nav-current-indicator-color));
|
|
52343
52770
|
--x-nav-indicator-color-toward: var(--nav-indicator-color-toward, var(--x-nav-indicator-color));
|
|
52344
|
-
z-index:
|
|
52771
|
+
z-index: 2;
|
|
52345
52772
|
background: color-mix(in srgb,
|
|
52346
52773
|
var(--x-nav-indicator-color-toward)
|
|
52347
52774
|
calc(var(--slide-travel-progress) * var(--nav-indicator-toward-sign, 0) *
|
|
@@ -52398,7 +52825,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52398
52825
|
}
|
|
52399
52826
|
|
|
52400
52827
|
.navi_nav {
|
|
52401
|
-
width:
|
|
52828
|
+
width: var(--nav-width);
|
|
52402
52829
|
padding-top: var(--nav-padding-top, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
52403
52830
|
padding-right: var(--nav-padding-right, var(--nav-padding-x, var(--nav-padding, unset)));
|
|
52404
52831
|
padding-bottom: var(--nav-padding-bottom, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
@@ -52427,12 +52854,12 @@ const css$Y = /* css */`@layer navi {
|
|
|
52427
52854
|
--x-nav-child-border-radius: calc(var(--nav-border-radius) - var(--nav-padding));
|
|
52428
52855
|
--x-nav-link-border-radius: var(--link-border-radius, var(--x-nav-child-border-radius));
|
|
52429
52856
|
|
|
52430
|
-
&:
|
|
52857
|
+
&:nth-child(1 of .navi_link) {
|
|
52431
52858
|
border-top-left-radius: var(--x-nav-link-border-radius);
|
|
52432
52859
|
border-bottom-left-radius: var(--x-nav-link-border-radius);
|
|
52433
52860
|
}
|
|
52434
52861
|
|
|
52435
|
-
&:last-child {
|
|
52862
|
+
&:nth-last-child(1 of .navi_link) {
|
|
52436
52863
|
border-top-right-radius: var(--x-nav-link-border-radius);
|
|
52437
52864
|
border-bottom-right-radius: var(--x-nav-link-border-radius);
|
|
52438
52865
|
}
|
|
@@ -52535,7 +52962,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
52535
52962
|
};
|
|
52536
52963
|
|
|
52537
52964
|
/**
|
|
52538
|
-
* @type {import("
|
|
52965
|
+
* @type {import("preact").FunctionComponent<{
|
|
52539
52966
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
52540
52967
|
* currentIndicatorSlides?: boolean,
|
|
52541
52968
|
* slideContainer?: string,
|
|
@@ -53685,7 +54112,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53685
54112
|
};
|
|
53686
54113
|
|
|
53687
54114
|
/**
|
|
53688
|
-
* @type {import("
|
|
54115
|
+
* @type {import("preact").FunctionComponent<{
|
|
53689
54116
|
* value?: any,
|
|
53690
54117
|
* defaultValue?: any,
|
|
53691
54118
|
* onChange?: (value: any) => void,
|
|
@@ -53729,7 +54156,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53729
54156
|
* grows the binder instead of closing in on the text.
|
|
53730
54157
|
* @param {string} [props.tabBorderColor] - The inactive tabs alone; the rest of
|
|
53731
54158
|
* the outline follows `borderColor`.
|
|
53732
|
-
* @param {import("
|
|
54159
|
+
* @param {import("preact").ComponentChildren} props.children - `Binder.Item`s.
|
|
53733
54160
|
*/
|
|
53734
54161
|
const Binder = ({
|
|
53735
54162
|
children,
|
|
@@ -53870,13 +54297,13 @@ const Binder = ({
|
|
|
53870
54297
|
/**
|
|
53871
54298
|
* @param {any} [props.value] - What identifies this item; defaults to its
|
|
53872
54299
|
* position among its siblings.
|
|
53873
|
-
* @param {import("
|
|
54300
|
+
* @param {import("preact").ComponentChildren} [props.label] - What the tab
|
|
53874
54301
|
* shows.
|
|
53875
54302
|
* @param {object} [props.route] - Makes the tab a `Link`
|
|
53876
54303
|
* to that route, and the binder follows the url: the open item is the one
|
|
53877
54304
|
* whose route matches, no `value`/`onChange` needed.
|
|
53878
54305
|
* @param {string} [props.href] - Same, for a plain url.
|
|
53879
|
-
* @param {import("
|
|
54306
|
+
* @param {import("preact").ComponentChildren} [props.children] - The page.
|
|
53880
54307
|
*
|
|
53881
54308
|
* Every other prop goes to the tab (a `Box`, or a `Link` when routed): an item
|
|
53882
54309
|
* styles and configures the control that opens it.
|
|
@@ -54286,7 +54713,7 @@ const FixedBarStyleCSSVars = {
|
|
|
54286
54713
|
};
|
|
54287
54714
|
|
|
54288
54715
|
/**
|
|
54289
|
-
* @type {import("
|
|
54716
|
+
* @type {import("preact").FunctionComponent<{
|
|
54290
54717
|
* area?: "top"|"bottom"|"left"|"right",
|
|
54291
54718
|
* width?: string|number,
|
|
54292
54719
|
* height?: string|number,
|
|
@@ -54677,7 +55104,7 @@ const css$U = /* css */`.navi_summary_marker {
|
|
|
54677
55104
|
`;
|
|
54678
55105
|
|
|
54679
55106
|
/**
|
|
54680
|
-
* @type {import("
|
|
55107
|
+
* @type {import("preact").FunctionComponent<{
|
|
54681
55108
|
* open?: boolean,
|
|
54682
55109
|
* loading?: boolean,
|
|
54683
55110
|
* openDirection?: "down" | "up" | "left",
|
|
@@ -55097,8 +55524,8 @@ const useExpandableContext = partName => {
|
|
|
55097
55524
|
};
|
|
55098
55525
|
|
|
55099
55526
|
/**
|
|
55100
|
-
* @type {import("
|
|
55101
|
-
* ui?: import("
|
|
55527
|
+
* @type {import("preact").FunctionComponent<{
|
|
55528
|
+
* ui?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55102
55529
|
* open?: boolean,
|
|
55103
55530
|
* defaultOpen?: boolean,
|
|
55104
55531
|
* signal?: import("@preact/signals").Signal,
|
|
@@ -55738,9 +56165,9 @@ const Expandable = props => {
|
|
|
55738
56165
|
* Controls inside it keep their own behavior and do not toggle. A Box, so it
|
|
55739
56166
|
* takes the layout and style props every Box takes.
|
|
55740
56167
|
*
|
|
55741
|
-
* @type {import("
|
|
55742
|
-
* marker?: false | import("
|
|
55743
|
-
* children?: import("
|
|
56168
|
+
* @type {import("preact").FunctionComponent<{
|
|
56169
|
+
* marker?: false | import("preact").ComponentChildren,
|
|
56170
|
+
* children?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55744
56171
|
* }>}
|
|
55745
56172
|
* @param marker - The chevron drawn before the label. `false` removes it (a UI
|
|
55746
56173
|
* part that is already an icon has no room for one); any node replaces it.
|
|
@@ -55815,7 +56242,7 @@ const ExpandableUI = ({
|
|
|
55815
56242
|
* Expandable, children may be a function `(data) => ui` or a branches object —
|
|
55816
56243
|
* see ActionRenderer.
|
|
55817
56244
|
*
|
|
55818
|
-
* @type {import("
|
|
56245
|
+
* @type {import("preact").FunctionComponent<{}>}
|
|
55819
56246
|
*/
|
|
55820
56247
|
const ExpandableContent = ({
|
|
55821
56248
|
children,
|
|
@@ -55971,6 +56398,40 @@ const createToggleEvent = open => {
|
|
|
55971
56398
|
return toggleEvent;
|
|
55972
56399
|
};
|
|
55973
56400
|
|
|
56401
|
+
/**
|
|
56402
|
+
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
56403
|
+
* Each resolver is a component function rendered in sequence (hooks are allowed).
|
|
56404
|
+
* To pass through to the next resolver, call useNextResolver() and render the
|
|
56405
|
+
* returned Next component with the desired props.
|
|
56406
|
+
* To terminate the chain early (e.g. render a specialized component), render
|
|
56407
|
+
* directly without calling Next.
|
|
56408
|
+
*
|
|
56409
|
+
* The last entry in the array is the final/target component — it receives null
|
|
56410
|
+
* from useNextResolver() indicating it is terminal.
|
|
56411
|
+
*
|
|
56412
|
+
* Usage:
|
|
56413
|
+
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
56414
|
+
* // Then inside a component render:
|
|
56415
|
+
* renderButton(props)
|
|
56416
|
+
*
|
|
56417
|
+
* A chain step is one component: a runner, defined once per position, whose
|
|
56418
|
+
* render CALLS its resolver's body rather than rendering it as a child. The
|
|
56419
|
+
* body runs synchronously inside the runner's render — its hooks are the
|
|
56420
|
+
* runner's, at the same tree position on every render — and for exactly that
|
|
56421
|
+
* long, `useNextResolver()` answers with the runner after this one (null for
|
|
56422
|
+
* the last). Nothing is handed down through a context: a chain rendered by the
|
|
56423
|
+
* hundred (a list's rows) pays one component per step, not a provider and a
|
|
56424
|
+
* wrapper around each.
|
|
56425
|
+
*
|
|
56426
|
+
* A resolver that renders ANOTHER resolver — a type resolver picking the
|
|
56427
|
+
* component for `type="date"` — cannot render it as a JSX child: that child
|
|
56428
|
+
* would render later, outside any body, with no Next to read. It renders it
|
|
56429
|
+
* with `renderResolver(Resolver, props)`, which captures the Next of the body
|
|
56430
|
+
* it is called from and hands it to a runner made for that resolver — the
|
|
56431
|
+
* child continues the chain where its parent stood. Called anywhere else, or
|
|
56432
|
+
* a `useNextResolver()` reached outside a body, throws: the chain has no
|
|
56433
|
+
* silent answer to give there.
|
|
56434
|
+
*/
|
|
55974
56435
|
const createComponentResolver = (resolvers, {
|
|
55975
56436
|
pure
|
|
55976
56437
|
} = {}) => {
|
|
@@ -56180,10 +56641,21 @@ const useAccentColorAttributes = (
|
|
|
56180
56641
|
};
|
|
56181
56642
|
|
|
56182
56643
|
installImportMetaCssBuild(import.meta);
|
|
56644
|
+
/**
|
|
56645
|
+
* We need the content to visually shrink (scale down) but the button interactive area MUST remain intact
|
|
56646
|
+
* Otherwise a click on the edges of the button cannot not trigger the click event (mouseup occurs outside the button)
|
|
56647
|
+
**/
|
|
56648
|
+
|
|
56649
|
+
/**
|
|
56650
|
+
* We have to re-define the CSS of button because getComputedStyle(button).borderColor returns
|
|
56651
|
+
* rgb(0, 0, 0) while being visually grey in chrome
|
|
56652
|
+
* So we redefine chrome styles so that loader can keep up with the actual color visible to the user
|
|
56653
|
+
*/
|
|
56183
56654
|
const css$R = /* css */`@layer navi {
|
|
56184
56655
|
.navi_button {
|
|
56185
56656
|
--button-border-radius: var(--navi-control-border-radius);
|
|
56186
56657
|
--button-border-width: var(--navi-control-border-width);
|
|
56658
|
+
--border-width: var(--button-border-width);
|
|
56187
56659
|
--button-cta-background-color: var(--navi-accent-color);
|
|
56188
56660
|
--button-outline-width: var(--navi-focus-outline-width);
|
|
56189
56661
|
--button-outline-offset: calc(-.5 * var(--button-outline-width));
|
|
@@ -56192,7 +56664,7 @@ const css$R = /* css */`@layer navi {
|
|
|
56192
56664
|
--button-padding-y-default: var(--navi-button-padding-y-default);
|
|
56193
56665
|
--button-loader-color: var(--navi-loader-color);
|
|
56194
56666
|
--button-border-color: var(--navi-control-border-color);
|
|
56195
|
-
--button-background-color: var(--
|
|
56667
|
+
--button-background-color: var(--navi-button-background-color);
|
|
56196
56668
|
--button-color: currentColor;
|
|
56197
56669
|
--button-cursor: pointer;
|
|
56198
56670
|
--button-font-size: var(--navi-control-font-size);
|
|
@@ -56225,6 +56697,92 @@ const css$R = /* css */`@layer navi {
|
|
|
56225
56697
|
line-height: var(--navi-control-line-height);
|
|
56226
56698
|
display: inline-block;
|
|
56227
56699
|
}
|
|
56700
|
+
|
|
56701
|
+
.navi_button[data-variant="discrete"] {
|
|
56702
|
+
--button-border-width: 0;
|
|
56703
|
+
--button-border-color: transparent;
|
|
56704
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56705
|
+
--button-border-color-current: var(--button-border-color);
|
|
56706
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56707
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56708
|
+
--button-background-color: transparent;
|
|
56709
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56710
|
+
currentColor 8%,
|
|
56711
|
+
var(--button-background-color));
|
|
56712
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56713
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56714
|
+
}
|
|
56715
|
+
|
|
56716
|
+
.navi_button[data-variant="bare"] {
|
|
56717
|
+
--button-padding-x-default: 0;
|
|
56718
|
+
--button-padding-y-default: 0;
|
|
56719
|
+
--button-border-width: 0;
|
|
56720
|
+
--button-border-color: transparent;
|
|
56721
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56722
|
+
--button-border-color-current: var(--button-border-color);
|
|
56723
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56724
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56725
|
+
--button-background-color: transparent;
|
|
56726
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56727
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56728
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56729
|
+
font-size: inherit;
|
|
56730
|
+
font-family: inherit;
|
|
56731
|
+
line-height: inherit;
|
|
56732
|
+
}
|
|
56733
|
+
|
|
56734
|
+
.navi_button[data-variant="discrete-border"] {
|
|
56735
|
+
--button-background-color: transparent;
|
|
56736
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56737
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56738
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56739
|
+
}
|
|
56740
|
+
|
|
56741
|
+
.navi_button[data-variant="border"] {
|
|
56742
|
+
--button-background-color: transparent;
|
|
56743
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56744
|
+
currentColor 8%,
|
|
56745
|
+
var(--button-background-color));
|
|
56746
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56747
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56748
|
+
}
|
|
56749
|
+
|
|
56750
|
+
.navi_button[data-icon] {
|
|
56751
|
+
--button-padding: 0;
|
|
56752
|
+
}
|
|
56753
|
+
|
|
56754
|
+
.navi_button[data-cta] {
|
|
56755
|
+
--button-background-color: var(--button-cta-background-color);
|
|
56756
|
+
--button-border-color: var(--button-cta-background-color);
|
|
56757
|
+
--button-color: white;
|
|
56758
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56759
|
+
var(--button-background-color) 85%,
|
|
56760
|
+
white);
|
|
56761
|
+
--button-border-color-hover: color-mix(in srgb,
|
|
56762
|
+
var(--button-border-color) 85%,
|
|
56763
|
+
white);
|
|
56764
|
+
--button-background-color-readonly: color-mix(in srgb,
|
|
56765
|
+
var(--button-background-color) 50%,
|
|
56766
|
+
white);
|
|
56767
|
+
--button-border-color-readonly: color-mix(in srgb,
|
|
56768
|
+
var(--button-border-color) 50%,
|
|
56769
|
+
white);
|
|
56770
|
+
--button-background-color-disabled: color-mix(in srgb,
|
|
56771
|
+
var(--button-background-color) 40%,
|
|
56772
|
+
white);
|
|
56773
|
+
--button-border-color-disabled: color-mix(in srgb,
|
|
56774
|
+
var(--button-border-color) 40%,
|
|
56775
|
+
white);
|
|
56776
|
+
--button-color-disabled: color-mix(in srgb,
|
|
56777
|
+
var(--button-color) 60%,
|
|
56778
|
+
transparent);
|
|
56779
|
+
}
|
|
56780
|
+
|
|
56781
|
+
.navi_button[data-content-display="contents"] {
|
|
56782
|
+
background: none;
|
|
56783
|
+
border: none;
|
|
56784
|
+
padding: 0;
|
|
56785
|
+
}
|
|
56228
56786
|
}
|
|
56229
56787
|
|
|
56230
56788
|
a.navi_button {
|
|
@@ -56241,7 +56799,6 @@ a.navi_button {
|
|
|
56241
56799
|
--x-button-color: var(--button-color);
|
|
56242
56800
|
--x-button-cursor: var(--button-cursor);
|
|
56243
56801
|
box-sizing: border-box;
|
|
56244
|
-
aspect-ratio: inherit;
|
|
56245
56802
|
color: var(--x-button-color);
|
|
56246
56803
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--button-border-radius));
|
|
56247
56804
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--button-border-radius));
|
|
@@ -56250,12 +56807,15 @@ a.navi_button {
|
|
|
56250
56807
|
cursor: var(--x-button-cursor);
|
|
56251
56808
|
touch-action: manipulation;
|
|
56252
56809
|
user-select: none;
|
|
56253
|
-
background: none;
|
|
56254
|
-
border: none;
|
|
56255
56810
|
outline: none;
|
|
56256
|
-
padding: 0;
|
|
56257
56811
|
position: relative;
|
|
56258
56812
|
|
|
56813
|
+
&:where(:not([data-content-display="contents"])) {
|
|
56814
|
+
background: none;
|
|
56815
|
+
border: none;
|
|
56816
|
+
padding: 0;
|
|
56817
|
+
}
|
|
56818
|
+
|
|
56259
56819
|
& .navi_button_content {
|
|
56260
56820
|
--x-corner-top-left-radius: initial;
|
|
56261
56821
|
--x-corner-top-right-radius: initial;
|
|
@@ -56277,8 +56837,7 @@ a.navi_button {
|
|
|
56277
56837
|
gap: inherit;
|
|
56278
56838
|
color: inherit;
|
|
56279
56839
|
vertical-align: inherit;
|
|
56280
|
-
background: var(--x-button-background);
|
|
56281
|
-
background-color: var(--x-button-background-color, var(--x-button-background));
|
|
56840
|
+
background: var(--x-button-background, var(--x-button-background-color));
|
|
56282
56841
|
border: var(--x-button-border);
|
|
56283
56842
|
border-radius: inherit;
|
|
56284
56843
|
outline-width: var(--button-outline-width);
|
|
@@ -56301,6 +56860,10 @@ a.navi_button {
|
|
|
56301
56860
|
}
|
|
56302
56861
|
}
|
|
56303
56862
|
|
|
56863
|
+
&[data-background-color] .navi_button_content {
|
|
56864
|
+
background-color: var(--x-button-background-color);
|
|
56865
|
+
}
|
|
56866
|
+
|
|
56304
56867
|
&[data-href-current] {
|
|
56305
56868
|
--x-button-border-color: var(--button-border-color-current);
|
|
56306
56869
|
--x-button-background-color: var(--button-background-color-current);
|
|
@@ -56345,7 +56908,7 @@ a.navi_button {
|
|
|
56345
56908
|
}
|
|
56346
56909
|
|
|
56347
56910
|
&[data-content-display="contents"] {
|
|
56348
|
-
&
|
|
56911
|
+
&[data-focus-visible] {
|
|
56349
56912
|
outline: var(--button-outline-width) solid var(--button-outline-color);
|
|
56350
56913
|
outline-offset: var(--button-outline-offset);
|
|
56351
56914
|
}
|
|
@@ -56371,19 +56934,6 @@ a.navi_button {
|
|
|
56371
56934
|
}
|
|
56372
56935
|
|
|
56373
56936
|
&[data-variant="discrete"] {
|
|
56374
|
-
--button-border-width: 0;
|
|
56375
|
-
--button-border-color: transparent;
|
|
56376
|
-
--button-border-color-hover: var(--button-border-color);
|
|
56377
|
-
--button-border-color-current: var(--button-border-color);
|
|
56378
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
56379
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
56380
|
-
--button-background-color: var(--button-background, transparent);
|
|
56381
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56382
|
-
currentColor 8%,
|
|
56383
|
-
var(--button-background-color));
|
|
56384
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56385
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56386
|
-
|
|
56387
56937
|
@media (hover: hover) {
|
|
56388
56938
|
&[data-pressed] {
|
|
56389
56939
|
& .navi_button_content {
|
|
@@ -56394,17 +56944,6 @@ a.navi_button {
|
|
|
56394
56944
|
}
|
|
56395
56945
|
|
|
56396
56946
|
&[data-variant="bare"] {
|
|
56397
|
-
--button-border-width: 0;
|
|
56398
|
-
--button-border-color: transparent;
|
|
56399
|
-
--button-border-color-hover: var(--button-border-color);
|
|
56400
|
-
--button-border-color-current: var(--button-border-color);
|
|
56401
|
-
--button-border-color-readonly: var(--button-border-color);
|
|
56402
|
-
--button-border-color-disabled: var(--button-border-color);
|
|
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
|
-
|
|
56408
56947
|
&[data-pressed] {
|
|
56409
56948
|
& .navi_button_content {
|
|
56410
56949
|
transform: none;
|
|
@@ -56413,10 +56952,6 @@ a.navi_button {
|
|
|
56413
56952
|
}
|
|
56414
56953
|
|
|
56415
56954
|
&[data-variant="discrete-border"] {
|
|
56416
|
-
--button-background-color: var(--button-background, transparent);
|
|
56417
|
-
--button-background-color-hover: var(--button-background-color);
|
|
56418
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56419
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56420
56955
|
--x-button-border-color: transparent;
|
|
56421
56956
|
|
|
56422
56957
|
&[data-hover] {
|
|
@@ -56428,15 +56963,6 @@ a.navi_button {
|
|
|
56428
56963
|
}
|
|
56429
56964
|
}
|
|
56430
56965
|
|
|
56431
|
-
&[data-variant="border"] {
|
|
56432
|
-
--button-background-color: var(--button-background, transparent);
|
|
56433
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56434
|
-
currentColor 8%,
|
|
56435
|
-
var(--button-background-color));
|
|
56436
|
-
--button-background-color-readonly: var(--button-background-color);
|
|
56437
|
-
--button-background-color-disabled: var(--button-background-color);
|
|
56438
|
-
}
|
|
56439
|
-
|
|
56440
56966
|
&[data-press-effect="scale"][data-pressed] {
|
|
56441
56967
|
& .navi_button_content {
|
|
56442
56968
|
transform: scale(.9);
|
|
@@ -56450,37 +56976,9 @@ a.navi_button {
|
|
|
56450
56976
|
}
|
|
56451
56977
|
|
|
56452
56978
|
&[data-icon] {
|
|
56453
|
-
--button-padding: 0;
|
|
56454
56979
|
justify-content: center;
|
|
56455
56980
|
align-items: center;
|
|
56456
56981
|
}
|
|
56457
|
-
|
|
56458
|
-
&[data-cta] {
|
|
56459
|
-
--button-background-color: var(--button-background, var(--button-cta-background-color));
|
|
56460
|
-
--button-border-color: var(--button-cta-background-color);
|
|
56461
|
-
--button-color: white;
|
|
56462
|
-
--button-background-color-hover: color-mix(in srgb,
|
|
56463
|
-
var(--button-background-color) 85%,
|
|
56464
|
-
white);
|
|
56465
|
-
--button-border-color-hover: color-mix(in srgb,
|
|
56466
|
-
var(--button-border-color) 85%,
|
|
56467
|
-
white);
|
|
56468
|
-
--button-background-color-readonly: color-mix(in srgb,
|
|
56469
|
-
var(--button-background-color) 50%,
|
|
56470
|
-
white);
|
|
56471
|
-
--button-border-color-readonly: color-mix(in srgb,
|
|
56472
|
-
var(--button-border-color) 50%,
|
|
56473
|
-
white);
|
|
56474
|
-
--button-background-color-disabled: color-mix(in srgb,
|
|
56475
|
-
var(--button-background-color) 40%,
|
|
56476
|
-
white);
|
|
56477
|
-
--button-border-color-disabled: color-mix(in srgb,
|
|
56478
|
-
var(--button-border-color) 40%,
|
|
56479
|
-
white);
|
|
56480
|
-
--button-color-disabled: color-mix(in srgb,
|
|
56481
|
-
var(--button-color) 60%,
|
|
56482
|
-
transparent);
|
|
56483
|
-
}
|
|
56484
56982
|
}
|
|
56485
56983
|
`;
|
|
56486
56984
|
const ButtonUI = props => {
|
|
@@ -56609,6 +57107,7 @@ const ButtonUI = props => {
|
|
|
56609
57107
|
"data-press-effect": pressEffect,
|
|
56610
57108
|
"data-icon": icon ? "" : undefined,
|
|
56611
57109
|
"data-cta": cta ? "" : undefined,
|
|
57110
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
56612
57111
|
"data-callout-arrow-x": "center"
|
|
56613
57112
|
// style management
|
|
56614
57113
|
,
|
|
@@ -56921,7 +57420,7 @@ const ButtonWithHref = props => {
|
|
|
56921
57420
|
} = getHrefTargetInfo(href, documentUrlSignal.peek());
|
|
56922
57421
|
return jsx(Next, {
|
|
56923
57422
|
...props,
|
|
56924
|
-
target: target === undefined ?
|
|
57423
|
+
target: target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target,
|
|
56925
57424
|
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
56926
57425
|
// Under whatever the caller already says: a route knows better whether
|
|
56927
57426
|
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
@@ -57075,7 +57574,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57075
57574
|
};
|
|
57076
57575
|
|
|
57077
57576
|
/**
|
|
57078
|
-
* @type {import("
|
|
57577
|
+
* @type {import("preact").FunctionComponent<{
|
|
57079
57578
|
* selfInteractions?: string,
|
|
57080
57579
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
57081
57580
|
* replace?: boolean,
|
|
@@ -57157,7 +57656,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57157
57656
|
* more: the work may already have happened on the other side (see
|
|
57158
57657
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
57159
57658
|
* where the screen can be re-opened on what is actually there.
|
|
57160
|
-
* @param {boolean|import("
|
|
57659
|
+
* @param {boolean|import("preact").ComponentChildren} [confirm] Ask before
|
|
57161
57660
|
* doing it, in the place the button stands: the first press replaces the
|
|
57162
57661
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
57163
57662
|
* does what the button was for. `true` asks navi's default question. The
|
|
@@ -57168,9 +57667,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57168
57667
|
* question up, its message on the button that asked. Use `<Picker
|
|
57169
57668
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
57170
57669
|
* or when the row has no space for it.
|
|
57171
|
-
* @param {import("
|
|
57670
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] Label of the
|
|
57172
57671
|
* second press.
|
|
57173
|
-
* @param {import("
|
|
57672
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Label of the way
|
|
57174
57673
|
* out of the question.
|
|
57175
57674
|
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
57176
57675
|
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
@@ -57269,13 +57768,13 @@ const css$P = /* css */`.navi_control_swap {
|
|
|
57269
57768
|
`;
|
|
57270
57769
|
|
|
57271
57770
|
/**
|
|
57272
|
-
* @type {import("
|
|
57771
|
+
* @type {import("preact").FunctionComponent<{
|
|
57273
57772
|
* value?: string,
|
|
57274
57773
|
* defaultValue?: string,
|
|
57275
57774
|
* signal?: import("@preact/signals").Signal<string>,
|
|
57276
57775
|
* onChange?: (name: string, event: Event) => void,
|
|
57277
57776
|
* animation?: boolean,
|
|
57278
|
-
* children?: import("
|
|
57777
|
+
* children?: import("preact").ComponentChildren,
|
|
57279
57778
|
* }>}
|
|
57280
57779
|
* @param value - The name of the side holding the floor, driven from outside:
|
|
57281
57780
|
* the row swaps to match every change of it, and a press on a cap can still
|
|
@@ -57419,13 +57918,13 @@ const ControlSwap = props => {
|
|
|
57419
57918
|
* reads these and draws the caps at its ends, the controls between them. The
|
|
57420
57919
|
* props below say what the side IS; every other prop describes its cap.
|
|
57421
57920
|
*
|
|
57422
|
-
* @type {import("
|
|
57921
|
+
* @type {import("preact").FunctionComponent<{
|
|
57423
57922
|
* name?: string,
|
|
57424
|
-
* icon: import("
|
|
57923
|
+
* icon: import("preact").ComponentChildren,
|
|
57425
57924
|
* label: string,
|
|
57426
|
-
* badge?: boolean | import("
|
|
57925
|
+
* badge?: boolean | import("preact").ComponentChildren,
|
|
57427
57926
|
* autoFocus?: boolean,
|
|
57428
|
-
* children?: import("
|
|
57927
|
+
* children?: import("preact").ComponentChildren,
|
|
57429
57928
|
* [key: string]: any,
|
|
57430
57929
|
* }>}
|
|
57431
57930
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
@@ -57633,6 +58132,51 @@ const createAvailableConstraint = (
|
|
|
57633
58132
|
};
|
|
57634
58133
|
};
|
|
57635
58134
|
|
|
58135
|
+
/**
|
|
58136
|
+
* ControlGroup — a named sub-group of form controls whose combined state is
|
|
58137
|
+
* aggregated into a single key/value object, exactly like a Form but without
|
|
58138
|
+
* the <form> element semantics.
|
|
58139
|
+
*
|
|
58140
|
+
* Use it when you need a self-contained cluster of inputs that:
|
|
58141
|
+
* - produce one composite value (an object) as their joint state
|
|
58142
|
+
* - can carry their own action / uiAction / command
|
|
58143
|
+
* - may live inside or outside a <Form>
|
|
58144
|
+
*
|
|
58145
|
+
* Props:
|
|
58146
|
+
* name — the key under which the composite value is registered in a
|
|
58147
|
+
* parent Form (optional when used standalone)
|
|
58148
|
+
* action — called with the aggregated object value when any child changes
|
|
58149
|
+
* command — navi command string (e.g. "--navi-send")
|
|
58150
|
+
* disabled — propagates to all children
|
|
58151
|
+
* readOnly — propagates to all children
|
|
58152
|
+
* as — HTML element to render (default "div")
|
|
58153
|
+
* children — the controls that belong to this group
|
|
58154
|
+
*
|
|
58155
|
+
* aggregateChildStates / distributeChildUIState
|
|
58156
|
+
* — what the group is worth, in both directions, when that is not
|
|
58157
|
+
* simply "one key per child": two wheels that add up to a number
|
|
58158
|
+
* of minutes, three fields that make one date. With them the
|
|
58159
|
+
* group takes and hands back a single value, so it can be driven
|
|
58160
|
+
* by one `value`/`signal` like any other control. Same mechanism
|
|
58161
|
+
* InputDuration uses for its own hour/minute/second fields (see
|
|
58162
|
+
* input_duration.jsx). `name` is unaffected: it still says under
|
|
58163
|
+
* which key the group's value — whatever shape it takes —
|
|
58164
|
+
* lands in the form around it.
|
|
58165
|
+
*
|
|
58166
|
+
* distributeChildStates
|
|
58167
|
+
* — the same way down, asked ONCE for all the children at a time:
|
|
58168
|
+
* `(groupValue, children) => Map<child, state>`. The plural of
|
|
58169
|
+
* `distributeChildUIState`, and the mirror of
|
|
58170
|
+
* `aggregateChildStates`, which already sees every child. For a
|
|
58171
|
+
* group whose children cannot be placed one at a time — four
|
|
58172
|
+
* seats where who sits down decides who moves, a row where the
|
|
58173
|
+
* first answer changes what the others may show. A child the Map
|
|
58174
|
+
* does not name is left where it is. Given both, this one wins.
|
|
58175
|
+
* It also closes the loop: the group holds ONE answer its
|
|
58176
|
+
* children are views of, so when one of them speaks the others
|
|
58177
|
+
* are placed again (the one that acted is left where the user
|
|
58178
|
+
* put it).
|
|
58179
|
+
*/
|
|
57636
58180
|
const ControlGroup = props => {
|
|
57637
58181
|
const defaultRef = useRef();
|
|
57638
58182
|
props.ref = props.ref || defaultRef;
|
|
@@ -57825,6 +58369,41 @@ const css$N = /* css */`@layer navi {
|
|
|
57825
58369
|
var(--button-background-color) 95%,
|
|
57826
58370
|
black);
|
|
57827
58371
|
}
|
|
58372
|
+
|
|
58373
|
+
.navi_checkbox[data-variant="switch"] {
|
|
58374
|
+
--margin: var(--switch-margin);
|
|
58375
|
+
--width: var(--switch-outer-width);
|
|
58376
|
+
--height: unset;
|
|
58377
|
+
--border-radius: var(--switch-border-radius);
|
|
58378
|
+
--background-color: var(--switch-background-color);
|
|
58379
|
+
--background-color-hover: var(--switch-background-color-hover);
|
|
58380
|
+
--background-color-readonly: var(--switch-background-color-readonly);
|
|
58381
|
+
--background-color-disabled: var(--switch-background-color-disabled);
|
|
58382
|
+
--background-color-checked: var(--switch-background-color-checked);
|
|
58383
|
+
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
58384
|
+
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
58385
|
+
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
58386
|
+
}
|
|
58387
|
+
|
|
58388
|
+
.navi_checkbox[data-variant="icon"] {
|
|
58389
|
+
--margin: 0;
|
|
58390
|
+
--width: auto;
|
|
58391
|
+
--height: auto;
|
|
58392
|
+
}
|
|
58393
|
+
|
|
58394
|
+
.navi_checkbox[data-variant="button"] {
|
|
58395
|
+
--margin: 0;
|
|
58396
|
+
--width: auto;
|
|
58397
|
+
--height: auto;
|
|
58398
|
+
--border-color: var(--button-border-color);
|
|
58399
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
58400
|
+
--background-color: var(--button-background-color);
|
|
58401
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
58402
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
58403
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
58404
|
+
--border-color-checked: var(--button-border-color);
|
|
58405
|
+
--background-color-checked: var(--button-background-color);
|
|
58406
|
+
}
|
|
57828
58407
|
}
|
|
57829
58408
|
|
|
57830
58409
|
.navi_checkbox {
|
|
@@ -57965,18 +58544,6 @@ const css$N = /* css */`@layer navi {
|
|
|
57965
58544
|
|
|
57966
58545
|
&[data-variant="switch"] {
|
|
57967
58546
|
--switch-outer-width: calc(var(--switch-width) + var(--switch-padding));
|
|
57968
|
-
--margin: var(--switch-margin);
|
|
57969
|
-
--width: var(--switch-outer-width);
|
|
57970
|
-
--height: unset;
|
|
57971
|
-
--border-radius: var(--switch-border-radius);
|
|
57972
|
-
--background-color: var(--switch-background-color);
|
|
57973
|
-
--background-color-hover: var(--switch-background-color-hover);
|
|
57974
|
-
--background-color-readonly: var(--switch-background-color-readonly);
|
|
57975
|
-
--background-color-disabled: var(--switch-background-color-disabled);
|
|
57976
|
-
--background-color-checked: var(--switch-background-color-checked);
|
|
57977
|
-
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
57978
|
-
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
57979
|
-
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
57980
58547
|
min-width: var(--switch-outer-width);
|
|
57981
58548
|
padding: var(--switch-padding);
|
|
57982
58549
|
background-color: var(--x-background-color);
|
|
@@ -57986,25 +58553,11 @@ const css$N = /* css */`@layer navi {
|
|
|
57986
58553
|
}
|
|
57987
58554
|
|
|
57988
58555
|
&[data-variant="icon"] {
|
|
57989
|
-
--margin: 0;
|
|
57990
|
-
--width: auto;
|
|
57991
|
-
--height: auto;
|
|
57992
58556
|
background: none;
|
|
57993
58557
|
border: none;
|
|
57994
58558
|
}
|
|
57995
58559
|
|
|
57996
58560
|
&[data-variant="button"] {
|
|
57997
|
-
--margin: 0;
|
|
57998
|
-
--width: auto;
|
|
57999
|
-
--height: auto;
|
|
58000
|
-
--border-color: var(--button-border-color);
|
|
58001
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
58002
|
-
--background-color: var(--button-background-color);
|
|
58003
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
58004
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
58005
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
58006
|
-
--border-color-checked: var(--button-border-color);
|
|
58007
|
-
--background-color-checked: var(--button-background-color);
|
|
58008
58561
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
58009
58562
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
58010
58563
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58190,7 +58743,7 @@ const css$M = /* css */`@layer navi {
|
|
|
58190
58743
|
[navi-field] {
|
|
58191
58744
|
--spacing-with-control: var(--navi-xs);
|
|
58192
58745
|
|
|
58193
|
-
& .navi_checkbox, & .navi_radio {
|
|
58746
|
+
& .navi_checkbox:not([data-variant="switch"]), & .navi_radio {
|
|
58194
58747
|
--margin: 0;
|
|
58195
58748
|
}
|
|
58196
58749
|
}
|
|
@@ -58545,6 +59098,28 @@ const css$L = /* css */`@layer navi {
|
|
|
58545
59098
|
--button-border-color-disabled: var(--border-color-readonly);
|
|
58546
59099
|
--button-background-color-disabled: var(--background-color-readonly);
|
|
58547
59100
|
}
|
|
59101
|
+
|
|
59102
|
+
.navi_radio[data-variant="icon"] {
|
|
59103
|
+
--width: auto;
|
|
59104
|
+
--height: auto;
|
|
59105
|
+
--outline-offset: 2px;
|
|
59106
|
+
--outline-width: 2px;
|
|
59107
|
+
}
|
|
59108
|
+
|
|
59109
|
+
.navi_radio[data-variant="button"] {
|
|
59110
|
+
--margin: 0;
|
|
59111
|
+
--outline-offset: 0px;
|
|
59112
|
+
--width: auto;
|
|
59113
|
+
--height: auto;
|
|
59114
|
+
--border-color: var(--button-border-color);
|
|
59115
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
59116
|
+
--background-color: var(--button-background-color);
|
|
59117
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
59118
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
59119
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
59120
|
+
--border-color-checked: var(--button-border-color);
|
|
59121
|
+
--background-color-checked: var(--button-background-color);
|
|
59122
|
+
}
|
|
58548
59123
|
}
|
|
58549
59124
|
|
|
58550
59125
|
.navi_radio {
|
|
@@ -58700,26 +59275,7 @@ const css$L = /* css */`@layer navi {
|
|
|
58700
59275
|
}
|
|
58701
59276
|
}
|
|
58702
59277
|
|
|
58703
|
-
&[data-variant="icon"] {
|
|
58704
|
-
--width: auto;
|
|
58705
|
-
--height: auto;
|
|
58706
|
-
--outline-offset: 2px;
|
|
58707
|
-
--outline-width: 2px;
|
|
58708
|
-
}
|
|
58709
|
-
|
|
58710
59278
|
&[data-variant="button"] {
|
|
58711
|
-
--margin: 0;
|
|
58712
|
-
--outline-offset: 0px;
|
|
58713
|
-
--width: auto;
|
|
58714
|
-
--height: auto;
|
|
58715
|
-
--border-color: var(--button-border-color);
|
|
58716
|
-
--border-color-hover: var(--button-border-color-hover);
|
|
58717
|
-
--background-color: var(--button-background-color);
|
|
58718
|
-
--background-color-hover: var(--button-background-color-hover);
|
|
58719
|
-
--background-color-readonly: var(--button-background-color-readonly);
|
|
58720
|
-
--background-color-disabled: var(--button-background-color-disabled);
|
|
58721
|
-
--border-color-checked: var(--button-border-color);
|
|
58722
|
-
--background-color-checked: var(--button-background-color);
|
|
58723
59279
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
58724
59280
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
58725
59281
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58943,6 +59499,7 @@ const css$K = /* css */`@layer navi {
|
|
|
58943
59499
|
--outline-width: var(--navi-focus-outline-width);
|
|
58944
59500
|
--outline-offset: var(--outline-width);
|
|
58945
59501
|
--outline-color: var(--navi-focus-outline-color);
|
|
59502
|
+
--margin: 2px;
|
|
58946
59503
|
--height: 8px;
|
|
58947
59504
|
--thumb-size: 16px;
|
|
58948
59505
|
--thumb-width: var(--thumb-size);
|
|
@@ -59018,6 +59575,7 @@ const css$K = /* css */`@layer navi {
|
|
|
59018
59575
|
box-sizing: border-box;
|
|
59019
59576
|
width: fit-content;
|
|
59020
59577
|
height: var(--height);
|
|
59578
|
+
margin: var(--margin);
|
|
59021
59579
|
font-size: var(--font-size);
|
|
59022
59580
|
font-family: var(--font-family);
|
|
59023
59581
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--outline-border-radius));
|
|
@@ -59030,7 +59588,6 @@ const css$K = /* css */`@layer navi {
|
|
|
59030
59588
|
outline-offset: var(--outline-offset);
|
|
59031
59589
|
flex-direction: row;
|
|
59032
59590
|
align-items: center;
|
|
59033
|
-
margin: 2px;
|
|
59034
59591
|
position: relative;
|
|
59035
59592
|
|
|
59036
59593
|
& .navi_control_input {
|
|
@@ -59327,6 +59884,45 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59327
59884
|
grey);
|
|
59328
59885
|
--color-disabled: var(--color-dimmed);
|
|
59329
59886
|
}
|
|
59887
|
+
|
|
59888
|
+
.navi_input[data-background-transparent] {
|
|
59889
|
+
--background-color-hover: var(--background-color);
|
|
59890
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59891
|
+
}
|
|
59892
|
+
|
|
59893
|
+
.navi_input[data-variant="discrete"], .navi_input[data-variant="discrete-border"] {
|
|
59894
|
+
--background-color: transparent;
|
|
59895
|
+
--background-color-hover: var(--background-color);
|
|
59896
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59897
|
+
|
|
59898
|
+
&[data-background] {
|
|
59899
|
+
--background-color-focus: transparent;
|
|
59900
|
+
}
|
|
59901
|
+
}
|
|
59902
|
+
|
|
59903
|
+
.navi_input[data-variant="discrete"] {
|
|
59904
|
+
--border-color: transparent;
|
|
59905
|
+
}
|
|
59906
|
+
|
|
59907
|
+
.navi_input[data-variant="text"] {
|
|
59908
|
+
--background-color: transparent;
|
|
59909
|
+
--background-color-hover: var(--background-color);
|
|
59910
|
+
--background-color-focus: var(--background-color);
|
|
59911
|
+
--background-color-readonly: var(--background-color);
|
|
59912
|
+
--background-color-disabled: var(--background-color);
|
|
59913
|
+
--border-color: transparent;
|
|
59914
|
+
--border-color-hover: var(--border-color);
|
|
59915
|
+
--border-color-readonly: var(--border-color);
|
|
59916
|
+
--border-color-disabled: var(--border-color);
|
|
59917
|
+
}
|
|
59918
|
+
|
|
59919
|
+
.navi_input[data-variant="underline"] {
|
|
59920
|
+
--background-color: transparent;
|
|
59921
|
+
--background-color-hover: var(--background-color);
|
|
59922
|
+
--background-color-focus: var(--background-color);
|
|
59923
|
+
--background-color-readonly: var(--background-color);
|
|
59924
|
+
--background-color-disabled: var(--background-color);
|
|
59925
|
+
}
|
|
59330
59926
|
}
|
|
59331
59927
|
|
|
59332
59928
|
.navi_input {
|
|
@@ -59478,19 +60074,12 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59478
60074
|
}
|
|
59479
60075
|
|
|
59480
60076
|
&[data-background-transparent] {
|
|
59481
|
-
--background-color-hover: var(--background-color);
|
|
59482
|
-
--background-color-focus: var(--navi-surface-color);
|
|
59483
|
-
|
|
59484
60077
|
&[data-focus] {
|
|
59485
60078
|
--x-background-color: var(--background-color-focus);
|
|
59486
60079
|
}
|
|
59487
60080
|
}
|
|
59488
60081
|
|
|
59489
60082
|
&[data-variant="discrete"], &[data-variant="discrete-border"] {
|
|
59490
|
-
--background-color: transparent;
|
|
59491
|
-
--background-color-hover: var(--background-color);
|
|
59492
|
-
--background-color-focus: var(--navi-surface-color);
|
|
59493
|
-
|
|
59494
60083
|
&[data-focus] {
|
|
59495
60084
|
--x-background-color: var(--background-color-focus);
|
|
59496
60085
|
}
|
|
@@ -59498,15 +60087,9 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59498
60087
|
&[data-readonly], &[data-disabled] {
|
|
59499
60088
|
--x-background-color: var(--background-color);
|
|
59500
60089
|
}
|
|
59501
|
-
|
|
59502
|
-
&[data-background] {
|
|
59503
|
-
--background-color-focus: transparent;
|
|
59504
|
-
}
|
|
59505
60090
|
}
|
|
59506
60091
|
|
|
59507
60092
|
&[data-variant="discrete"] {
|
|
59508
|
-
--border-color: transparent;
|
|
59509
|
-
|
|
59510
60093
|
&[data-focus] {
|
|
59511
60094
|
--x-border-color: color-mix(in srgb,
|
|
59512
60095
|
var(--border-color) 55%,
|
|
@@ -59515,24 +60098,10 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59515
60098
|
}
|
|
59516
60099
|
|
|
59517
60100
|
&[data-variant="text"] {
|
|
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
|
-
--border-color: transparent;
|
|
59524
|
-
--border-color-hover: var(--border-color);
|
|
59525
|
-
--border-color-readonly: var(--border-color);
|
|
59526
|
-
--border-color-disabled: var(--border-color);
|
|
59527
60101
|
cursor: inherit;
|
|
59528
60102
|
}
|
|
59529
60103
|
|
|
59530
60104
|
&[data-variant="underline"] {
|
|
59531
|
-
--background-color: transparent;
|
|
59532
|
-
--background-color-hover: var(--background-color);
|
|
59533
|
-
--background-color-focus: var(--background-color);
|
|
59534
|
-
--background-color-readonly: var(--background-color);
|
|
59535
|
-
--background-color-disabled: var(--background-color);
|
|
59536
60105
|
border: none;
|
|
59537
60106
|
border-radius: 0;
|
|
59538
60107
|
padding-left: 0;
|
|
@@ -60728,6 +61297,38 @@ const InputStyleCSSVars = {
|
|
|
60728
61297
|
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value"];
|
|
60729
61298
|
const InputPseudoElements = ["::-navi-loader"];
|
|
60730
61299
|
|
|
61300
|
+
/**
|
|
61301
|
+
* @type {import("preact").FunctionComponent<{
|
|
61302
|
+
* type?: string,
|
|
61303
|
+
* icon?: import("preact").ComponentChildren | null,
|
|
61304
|
+
* clearable?: boolean,
|
|
61305
|
+
* value?: any,
|
|
61306
|
+
* boolean?: boolean,
|
|
61307
|
+
* selectOnPress?: boolean,
|
|
61308
|
+
* [key: string]: any,
|
|
61309
|
+
* }>}
|
|
61310
|
+
* @param {any} [value] On a checkbox, what it is worth checked (`"on"` when
|
|
61311
|
+
* left out); unchecked it is `undefined`. See control_value.md, "What a
|
|
61312
|
+
* checkbox is worth".
|
|
61313
|
+
* @param {boolean} [boolean] Makes a checkbox a yes/no: `true` checked, `false`
|
|
61314
|
+
* unchecked, everywhere it hands its value. Takes no `value`.
|
|
61315
|
+
* @param {import("preact").ComponentChildren|null} [icon] What the textual
|
|
61316
|
+
* types that draw an icon (`search`, `email`, `tel`) put in their slot: left
|
|
61317
|
+
* out, the type's own glyph; `null`, nothing — for a field an application
|
|
61318
|
+
* draws its own magnifier beside, where a second one two centimetres away
|
|
61319
|
+
* would be one too many; anything else is drawn instead. A `search` field
|
|
61320
|
+
* swaps that slot for the clear cross as soon as it holds a value, whatever
|
|
61321
|
+
* this says.
|
|
61322
|
+
* @param {boolean} [clearable] Gives any other type the same slot: the icon
|
|
61323
|
+
* while empty (none, unless `icon` says otherwise), the clear cross once
|
|
61324
|
+
* filled.
|
|
61325
|
+
* @param {boolean} [selectOnPress] For a textual field whose value is usually
|
|
61326
|
+
* replaced rather than edited, like a browser's address bar: the first press
|
|
61327
|
+
* after the field took the focus selects its whole text once released, with a
|
|
61328
|
+
* mouse or a finger, and a second press places the caret. A focus that came
|
|
61329
|
+
* without a press (a dialog opening, Tab, code) selects nothing; the first key
|
|
61330
|
+
* that types selects first, so it replaces the text.
|
|
61331
|
+
*/
|
|
60731
61332
|
const Input = props => {
|
|
60732
61333
|
resolveInputProps(props);
|
|
60733
61334
|
const {
|
|
@@ -60817,7 +61418,7 @@ const TextareaStyleCSSVars = {
|
|
|
60817
61418
|
};
|
|
60818
61419
|
|
|
60819
61420
|
/**
|
|
60820
|
-
* @type {import("
|
|
61421
|
+
* @type {import("preact").FunctionComponent<{
|
|
60821
61422
|
* value?: string,
|
|
60822
61423
|
* defaultValue?: string,
|
|
60823
61424
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -60924,7 +61525,7 @@ const Textarea = ({
|
|
|
60924
61525
|
* draws: put it under the box, in a form footer, next to a label, and feed it
|
|
60925
61526
|
* the same value or signal as the textarea.
|
|
60926
61527
|
*
|
|
60927
|
-
* @type {import("
|
|
61528
|
+
* @type {import("preact").FunctionComponent<{
|
|
60928
61529
|
* value?: string,
|
|
60929
61530
|
* signal?: import("@preact/signals").Signal<string>,
|
|
60930
61531
|
* maxLength?: number,
|
|
@@ -61157,8 +61758,8 @@ const useEditionController = () => {
|
|
|
61157
61758
|
* it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
|
|
61158
61759
|
* the demo `control/demos/action/8_editable_demo.html` does it with props.
|
|
61159
61760
|
*
|
|
61160
|
-
* @type {import("
|
|
61161
|
-
* children?: import("
|
|
61761
|
+
* @type {import("preact").FunctionComponent<{
|
|
61762
|
+
* children?: import("preact").ComponentChildren,
|
|
61162
61763
|
* action: Function | object,
|
|
61163
61764
|
* editing?: { event?: Event } | null,
|
|
61164
61765
|
* onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
|
|
@@ -61518,6 +62119,77 @@ const commitSubtree = (controller, e) => {
|
|
|
61518
62119
|
* <form> element (FormNested).
|
|
61519
62120
|
*/
|
|
61520
62121
|
|
|
62122
|
+
/**
|
|
62123
|
+
* @param {object} props
|
|
62124
|
+
* @param {boolean} [props.standalone] - Its value is its own: the form does not
|
|
62125
|
+
* register with the control group around it (a Picker, a ControlGroup), so
|
|
62126
|
+
* what is typed in it never becomes part of that group's value, and nothing
|
|
62127
|
+
* that group does — distributing a value, resetting, cascading validation —
|
|
62128
|
+
* reaches it. For a form that lives INSIDE something else while answering a
|
|
62129
|
+
* different question — "create the thing I am about to pick" inside a picker,
|
|
62130
|
+
* say. Every control takes the same prop, for the same reason. A form inside
|
|
62131
|
+
* another form is that by construction and does not need the prop: the group
|
|
62132
|
+
* it would register into is a form, which never owns another form's fields.
|
|
62133
|
+
* @param {boolean} [props.canSendWhileUnchanged] - Send even when nothing changed. By
|
|
62134
|
+
* default a form only acts on an answer that is actually new: submitting a
|
|
62135
|
+
* form nobody touched — one just rendered, one reopened and left alone — runs
|
|
62136
|
+
* no action (a field still showing its `defaultValue` is an answer: confirming
|
|
62137
|
+
* a suggestion), and after the first submission it is that value the next
|
|
62138
|
+
* one is compared against. Everything around the action still happens either
|
|
62139
|
+
* way: the constraints are checked, and what follows the send still follows
|
|
62140
|
+
* it (the slide moves on, the popup closes) — the user is done regardless of
|
|
62141
|
+
* whether there was anything to send. Set this for a form where sending the
|
|
62142
|
+
* same thing twice is the point — a single button that fires off a
|
|
62143
|
+
* notification, an action whose duplicates are fine. See also
|
|
62144
|
+
* `readOnlyWhileFormUnchanged` on `Button`, for a submit that should say it
|
|
62145
|
+
* is waiting rather than accept a press that sends nothing.
|
|
62146
|
+
* @param {boolean} [props.actionAbortable] - The person waiting may give up on
|
|
62147
|
+
* the send: closing the popup this form holds calls the run off and goes
|
|
62148
|
+
* through, instead of being refused. For a send whose answer may never come —
|
|
62149
|
+
* a request over a network that stops answering — where a sheet with no way
|
|
62150
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
62151
|
+
* more: the write may already have landed on the other side (see
|
|
62152
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
62153
|
+
* where the screen can be re-opened on what is actually there. Every control
|
|
62154
|
+
* inside the form inherits it along with the wait itself.
|
|
62155
|
+
* @param {boolean} [props.optimistic] - The send is taken as done on the form's
|
|
62156
|
+
* own word: no busy state, and what follows it (`command="--navi-close"`, a
|
|
62157
|
+
* slide moving on) runs at once, the run going on behind. A failure then has
|
|
62158
|
+
* no sheet to be read in: `resetOnError` puts the form back and the error
|
|
62159
|
+
* callout is drawn on what surrounds the closed sheet. For a write not worth being
|
|
62160
|
+
* waited for — a score, a note — never for one whose refusal changes what the
|
|
62161
|
+
* person does next (see
|
|
62162
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
62163
|
+
* @param {any} [props.pristineKey] - What the form is measured against, taken
|
|
62164
|
+
* again every time this changes. A form knows what it holds as soon as its
|
|
62165
|
+
* fields have registered, which is the right moment for a form whose values
|
|
62166
|
+
* are there on the first render — and never the right one for a screen that
|
|
62167
|
+
* modifies something: the resource arrives a request later and fills the
|
|
62168
|
+
* fields, and a form comparing against what it held BEFORE that opens already
|
|
62169
|
+
* changed. Pass whatever says the filling is done (a boolean, the resource
|
|
62170
|
+
* itself, a count of what has loaded) and what the fields carry at that
|
|
62171
|
+
* moment becomes the reference — including the fields that settle in a render
|
|
62172
|
+
* of their own, so there is no tick to wait for on the caller's side. Change
|
|
62173
|
+
* it once, when the screen is ready: taken again after someone started
|
|
62174
|
+
* typing, it would call what they wrote the reference.
|
|
62175
|
+
* @param {string} [props.command] - What follows a submission that went
|
|
62176
|
+
* through: the form has answered its question, and this says what the screen
|
|
62177
|
+
* does about it. Nothing runs when the submission is refused — the form then
|
|
62178
|
+
* stays in front of the user, showing what it is waiting for.
|
|
62179
|
+
*
|
|
62180
|
+
* `"--navi-close"` dismisses the popup the form is in,
|
|
62181
|
+
* `"--navi-left"`/`"--navi-right"`/`"--navi-up"`/`"--navi-down"` move on the
|
|
62182
|
+
* slide map it is in, `"--navi-nav-to:/the/url"` takes the user to a page,
|
|
62183
|
+
* `"--navi-void"` stays put — the one for a form inside a popup that must
|
|
62184
|
+
* stay open once it is sent (a side panel one keeps editing in).
|
|
62185
|
+
* Any navi command, really: it is triggered from the form, so it finds its
|
|
62186
|
+
* target the way that command always does.
|
|
62187
|
+
*
|
|
62188
|
+
* Left out, the surface the form sits in decides (see resolveAfterSend in
|
|
62189
|
+
* commands.js): a popup closes, a slide goes on to the next one — or back to
|
|
62190
|
+
* the one it came from when there is no next — and a form on a page does
|
|
62191
|
+
* nothing.
|
|
62192
|
+
*/
|
|
61521
62193
|
const Form = props => {
|
|
61522
62194
|
const defaultRef = useRef();
|
|
61523
62195
|
props.ref = props.ref || defaultRef;
|
|
@@ -61834,6 +62506,7 @@ const useRequestSubmitThroughGates = ref => {
|
|
|
61834
62506
|
installImportMetaCssBuild(import.meta);
|
|
61835
62507
|
const css$H = /* css */`.navi_group {
|
|
61836
62508
|
--group-border-width: var(--navi-control-border-width);
|
|
62509
|
+
--border-width: initial;
|
|
61837
62510
|
|
|
61838
62511
|
& > :not([navi-out-of-flow]) {
|
|
61839
62512
|
&:hover, &[data-hover] {
|
|
@@ -65066,6 +65739,122 @@ SlideContainer.Down = SlideDown;
|
|
|
65066
65739
|
SlideContainer.First = SlideFirst;
|
|
65067
65740
|
SlideContainer.Last = SlideLast;
|
|
65068
65741
|
|
|
65742
|
+
/**
|
|
65743
|
+
* Displays a date/time value in a human-readable format using the current locale.
|
|
65744
|
+
*
|
|
65745
|
+
* @param {Date|number|string} children
|
|
65746
|
+
* The date to display. Accepts:
|
|
65747
|
+
* - a `Date` instance
|
|
65748
|
+
* - a Unix timestamp (number, in ms)
|
|
65749
|
+
* - a date string `"YYYY-MM-DD"` or any string parseable by `Date`
|
|
65750
|
+
* If the value cannot be parsed, it is rendered as-is.
|
|
65751
|
+
* If undefined/null, renders a placeholder shaped like the type: `"–"` for
|
|
65752
|
+
* `"relative"`, `"--:--"` for `"time"`, `"--"` for the durations, the
|
|
65753
|
+
* locale's field pattern for `"date"`/`"month"`/`"week"`/`"datetime"`.
|
|
65754
|
+
*
|
|
65755
|
+
* @param {"date"|"month"|"week"|"datetime"|"time"|"hour"|"minute"|"second"|"duration"|"relative"} [type="relative"]
|
|
65756
|
+
* Controls the display format:
|
|
65757
|
+
* - `"date"` → "lundi 11 mai" (long by default), "samedi 1 janvier 2050" outside the current year (see `year`);
|
|
65758
|
+
* `format="short"` → "lun. 11 mai"; `format="numeric"` → "11/05/2026";
|
|
65759
|
+
* `format={{ weekday: "long", month: "short" }}` spells the two apart → "mercredi 2 sept.";
|
|
65760
|
+
* a part set to `false` is dropped — `format={{ day: false, month: false }}` → "mercredi"
|
|
65761
|
+
* - `"month"` → "juin 2026"
|
|
65762
|
+
* - `"week"` → the ISO week as given ("2026-W38"), in a `<time datetime>`
|
|
65763
|
+
* - `"datetime"` → "lun. 11 mai, 14:30" (long); `format="short"` → "11 mai, 14:30"; `format="narrow"` → "11/05, 14:30"
|
|
65764
|
+
* - `"time"` → time-of-day as duration by default (e.g. "14:30" → "14 heures 30");
|
|
65765
|
+
* `format="timestring"` → clock "14 h 30". Midnight (00:xx) is
|
|
65766
|
+
* special-cased regardless of `format` — "0 heures 5 minutes"
|
|
65767
|
+
* would otherwise collapse to "5 minutes", indistinguishable
|
|
65768
|
+
* from an actual 5-minute duration: `format="long"` (default)
|
|
65769
|
+
* says "minuit et 5 minutes" (`format="short"/"narrow"/"compact"`
|
|
65770
|
+
* keep the zero hour instead — "0 h et 5 min"/"0h 5min"/"00h05").
|
|
65771
|
+
* `format="compact"` also zero-pads any single-digit hour, not
|
|
65772
|
+
* just midnight (e.g. "5h30" → "05h30"), closer to "05:30";
|
|
65773
|
+
* `pad={false}` writes it the way one says it instead ("8h30",
|
|
65774
|
+
* "8h"). See `<TimeRange>` for a span between two of them.
|
|
65775
|
+
* - `"hour"` → hours as duration (e.g. 1.5 → "1 heure 30 minutes")
|
|
65776
|
+
* - `"minute"` → minutes as duration (e.g. 90 → "1 heure 30 minutes")
|
|
65777
|
+
* - `"second"` → seconds as duration (e.g. 90 → "1 minute 30 secondes")
|
|
65778
|
+
* These three promote to the largest fitting unit, days included (36 hours
|
|
65779
|
+
* → "1 jour et 12 heures"); see `forceUnit` to stay in the given unit.
|
|
65780
|
+
* - `"duration"` → duration string/object/number (seconds); `format="iso"` → ISO 8601
|
|
65781
|
+
* - `"relative"` → "dans 1 heure 30" / "En cours" / "il y a 2 heures"
|
|
65782
|
+
* Handles past, present, and future.
|
|
65783
|
+
* `format="short"` → "dans 1 h et 30 min"; `format="narrow"`
|
|
65784
|
+
* → "+3 h"; `format="compact"` → "dans 1h30".
|
|
65785
|
+
* `eventDuration` defaults to 0 (instantaneous: no "En cours" window).
|
|
65786
|
+
*
|
|
65787
|
+
* @param {number|string} [eventDuration=0]
|
|
65788
|
+
* Duration of the event in milliseconds, or a duration string (`"PT1H30M"`,
|
|
65789
|
+
* `"1hour30minute"`). Only used with `type="relative"`.
|
|
65790
|
+
* When omitted, the event is instantaneous (point in time, no "En cours" window).
|
|
65791
|
+
* @param {boolean} [bare]
|
|
65792
|
+
* When true, strips the past-tense literal ("il y a", "ago") and returns only integer + unit.
|
|
65793
|
+
* Only applies to the past state of `type="relative"`.
|
|
65794
|
+
* @param {"long"|"short"|"narrow"|"compact"|"numeric"|"timestring"|"iso"|{ weekday?: "long"|"short"|"narrow"|false, day?: boolean, month?: "long"|"short"|"narrow"|"numeric"|false }} [format="long"]
|
|
65795
|
+
* Controls the verbosity of the output. Defaults to `"long"` for all types.
|
|
65796
|
+
* `"long"`, `"short"`, `"narrow"` and `"compact"` are verbosities every `type`
|
|
65797
|
+
* writes. The three under them name a shape only some types have; a `type`
|
|
65798
|
+
* without that shape writes its `"long"` form and names the dropped word in
|
|
65799
|
+
* the console.
|
|
65800
|
+
* - `"short"` → Intl short (e.g. "2 h et 15 min", short month for dates/datetimes, no weekday for datetime)
|
|
65801
|
+
* - `"narrow"` → Intl narrow (e.g. "2h 15min", numeric month for datetime)
|
|
65802
|
+
* - `"compact"` → custom compact notation (e.g. "2h15", no minute symbol
|
|
65803
|
+
* when hours present). A date has nothing tighter than its
|
|
65804
|
+
* digits, so `type="date"`/`"month"`/`"datetime"` write the
|
|
65805
|
+
* all-digit spelling ("17/09/2026", "17/09 14:30")
|
|
65806
|
+
* - `"numeric"` → numeric date, for `type="date"` and `type="month"` (e.g. "11/09/2026", "09/2026")
|
|
65807
|
+
* - `"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)
|
|
65808
|
+
* - `"iso"` → ISO 8601 string, only for `type="duration"` (e.g. "PT2H15M")
|
|
65809
|
+
* - an object → `type="date"` only, one verbosity per part
|
|
65810
|
+
* (`{ weekday: "long", month: "short" }`): a narrow card
|
|
65811
|
+
* keeps the weekday whole, since that is what the eye
|
|
65812
|
+
* lands on, and abbreviates the month, since that is
|
|
65813
|
+
* where the characters are. `false` drops a part:
|
|
65814
|
+
* `{ day: false, month: false }` is the weekday alone
|
|
65815
|
+
* ("mardi"), `{ month: false }` weekday + day-of-month
|
|
65816
|
+
* ("mardi 18").
|
|
65817
|
+
* @param {boolean} [forceUnit=false]
|
|
65818
|
+
* Keeps the value in the unit named by `type` however big it gets, for
|
|
65819
|
+
* `type="hour"`, `"minute"` and `"second"`: 36 hours reads "36 heures"
|
|
65820
|
+
* instead of the default "1 jour et 12 heures". The default promotes to the
|
|
65821
|
+
* largest fitting unit because it reads better; force the unit when the unit
|
|
65822
|
+
* itself is the information (a quota, a counter).
|
|
65823
|
+
* @param {number} [decimals]
|
|
65824
|
+
* `type="duration"` and `type="second"` only — how many decimals a fraction
|
|
65825
|
+
* of a second keeps, rounded: with `decimals={1}`, 1.234 reads "1,2 seconde"
|
|
65826
|
+
* and 1.96 "2 secondes"; a whole value is never padded ("12 secondes").
|
|
65827
|
+
* Without it the fraction is written to the millisecond ("1,234 seconde"). A
|
|
65828
|
+
* duration measured in milliseconds rarely comes out whole, and its last
|
|
65829
|
+
* digits are usually noise.
|
|
65830
|
+
* @param {boolean} [pad=true]
|
|
65831
|
+
* `type="time"` + `format="compact"` only — whether the clock is written at a
|
|
65832
|
+
* fixed width. `true` (default) zero-pads the hour and always writes the
|
|
65833
|
+
* minutes ("08h00", "08h30"), so a column of times lines up. `false` writes
|
|
65834
|
+
* the shape one says out loud instead: bare hour, minutes only when there are
|
|
65835
|
+
* any ("8h", "8h30"). The spelled-out formats put their units in words and
|
|
65836
|
+
* need neither, so they ignore it.
|
|
65837
|
+
* @param {"hour"|"minute"} [precision]
|
|
65838
|
+
* `type="time"` + `format="compact"` only, and rarely set by hand — whether a
|
|
65839
|
+
* zero minute is written ("8h00") or dropped ("8h"). Defaults to whatever
|
|
65840
|
+
* `pad` implies; the one reason to force `"minute"` on an unpadded clock is
|
|
65841
|
+
* to agree with a partner that has minutes of its own, which is what
|
|
65842
|
+
* `<TimeRange>` does for you.
|
|
65843
|
+
* @param {boolean|"auto"} [year]
|
|
65844
|
+
* `type="date"` only — whether the year is written. `"auto"` writes it only
|
|
65845
|
+
* when the date is not in the current year, the way a calendar does
|
|
65846
|
+
* ("lundi 11 mai", but "samedi 1 janvier 2050"); `false` drops it ("30/07",
|
|
65847
|
+
* the day/month order still following the locale). Defaults to `"auto"`
|
|
65848
|
+
* for the spelled formats and to `true` for `format="numeric"`. A format
|
|
65849
|
+
* without the month (`{ month: false }`) never writes it.
|
|
65850
|
+
* @param {boolean} [dayLabel]
|
|
65851
|
+
* When true and `type="date"`, appends the locale-aware relative label
|
|
65852
|
+
* ("hier", "aujourd'hui", "demain") when the date is yesterday, today, or tomorrow.
|
|
65853
|
+
* @param {string} [lang]
|
|
65854
|
+
* BCP 47 locale tag (e.g. `"fr"`, `"en-US"`).
|
|
65855
|
+
* Defaults to `languagesSignal.value`: the browser's languages, as
|
|
65856
|
+
* `setPreferredLanguage`/`setSupportedLanguages` narrow them.
|
|
65857
|
+
*/
|
|
65069
65858
|
const Time = props => {
|
|
65070
65859
|
const {
|
|
65071
65860
|
type
|
|
@@ -65330,6 +66119,20 @@ const TimeMinute = ({
|
|
|
65330
66119
|
children: String(children)
|
|
65331
66120
|
});
|
|
65332
66121
|
}
|
|
66122
|
+
if (!Number.isInteger(minutes)) {
|
|
66123
|
+
// a fraction of a minute is read in seconds, the way formatMinuteDuration
|
|
66124
|
+
// writes it ("1 minute et 30 secondes")
|
|
66125
|
+
const clock = formatClock(Math.round(minutes * 60));
|
|
66126
|
+
return jsx(TimeText, {
|
|
66127
|
+
dateTime: clock,
|
|
66128
|
+
...props,
|
|
66129
|
+
children: format === "timestring" ? clock : formatMinuteDuration(minutes, {
|
|
66130
|
+
lang,
|
|
66131
|
+
format,
|
|
66132
|
+
forceUnit
|
|
66133
|
+
})
|
|
66134
|
+
});
|
|
66135
|
+
}
|
|
65333
66136
|
const totalHours = Math.floor(minutes / 60);
|
|
65334
66137
|
const remainingMinutes = minutes % 60;
|
|
65335
66138
|
const hh = String(totalHours).padStart(2, "0");
|
|
@@ -65359,6 +66162,7 @@ const TimeSecond = ({
|
|
|
65359
66162
|
lang = languagesSignal.value,
|
|
65360
66163
|
format = "long",
|
|
65361
66164
|
forceUnit = false,
|
|
66165
|
+
decimals,
|
|
65362
66166
|
...props
|
|
65363
66167
|
}) => {
|
|
65364
66168
|
if (children === undefined || children === null) {
|
|
@@ -65374,19 +66178,17 @@ const TimeSecond = ({
|
|
|
65374
66178
|
children: String(children)
|
|
65375
66179
|
});
|
|
65376
66180
|
}
|
|
65377
|
-
const
|
|
65378
|
-
const m = Math.floor(seconds % 3600 / 60);
|
|
65379
|
-
const s = seconds % 60;
|
|
65380
|
-
const dateTime = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
66181
|
+
const dateTime = formatClock(seconds);
|
|
65381
66182
|
let text;
|
|
65382
66183
|
if (format === "timestring") {
|
|
65383
66184
|
// Always HH:MM:SS to avoid ambiguity with HH:MM time-of-day format
|
|
65384
|
-
text =
|
|
66185
|
+
text = formatClock(seconds, decimals);
|
|
65385
66186
|
} else {
|
|
65386
66187
|
text = formatSecondDuration(seconds, {
|
|
65387
66188
|
lang,
|
|
65388
66189
|
format,
|
|
65389
|
-
forceUnit
|
|
66190
|
+
forceUnit,
|
|
66191
|
+
decimals
|
|
65390
66192
|
});
|
|
65391
66193
|
}
|
|
65392
66194
|
return jsx(TimeText, {
|
|
@@ -65439,6 +66241,7 @@ const TimeDuration = ({
|
|
|
65439
66241
|
children,
|
|
65440
66242
|
lang = languagesSignal.value,
|
|
65441
66243
|
format = "long",
|
|
66244
|
+
decimals,
|
|
65442
66245
|
...props
|
|
65443
66246
|
}) => {
|
|
65444
66247
|
if (children === undefined || children === null) {
|
|
@@ -65486,7 +66289,8 @@ const TimeDuration = ({
|
|
|
65486
66289
|
...props,
|
|
65487
66290
|
children: formatDuration(duration, {
|
|
65488
66291
|
lang,
|
|
65489
|
-
format
|
|
66292
|
+
format,
|
|
66293
|
+
decimals
|
|
65490
66294
|
})
|
|
65491
66295
|
});
|
|
65492
66296
|
}
|
|
@@ -65498,7 +66302,8 @@ const TimeDuration = ({
|
|
|
65498
66302
|
}
|
|
65499
66303
|
const text = formatDuration(duration, {
|
|
65500
66304
|
lang,
|
|
65501
|
-
format
|
|
66305
|
+
format,
|
|
66306
|
+
decimals
|
|
65502
66307
|
});
|
|
65503
66308
|
return jsx(TimeText, {
|
|
65504
66309
|
dateTime: isoString,
|
|
@@ -65638,6 +66443,24 @@ const TimeRange = ({
|
|
|
65638
66443
|
});
|
|
65639
66444
|
};
|
|
65640
66445
|
|
|
66446
|
+
// "HH:MM:SS", the fraction of a second after a period ("00:00:01.234"): the
|
|
66447
|
+
// shape a `datetime` attribute reads, which takes up to 3 decimals.
|
|
66448
|
+
const formatClock = (seconds, decimals = 3) => {
|
|
66449
|
+
const factor = 10 ** decimals;
|
|
66450
|
+
const total = Math.round(seconds * factor);
|
|
66451
|
+
const fraction = total % factor;
|
|
66452
|
+
const totalSeconds = (total - fraction) / factor;
|
|
66453
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
66454
|
+
const m = Math.floor(totalSeconds % 3600 / 60);
|
|
66455
|
+
const s = totalSeconds % 60;
|
|
66456
|
+
const clock = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
66457
|
+
if (fraction === 0) {
|
|
66458
|
+
return clock;
|
|
66459
|
+
}
|
|
66460
|
+
const digits = String(fraction).padStart(decimals, "0").replace(/0+$/, "");
|
|
66461
|
+
return `${clock}.${digits}`;
|
|
66462
|
+
};
|
|
66463
|
+
|
|
65641
66464
|
/**
|
|
65642
66465
|
* Wraps multiple inputs together and handles keyboard navigation and paste
|
|
65643
66466
|
* distribution between them.
|
|
@@ -65864,6 +66687,11 @@ const renderSafe = (value) => {
|
|
|
65864
66687
|
};
|
|
65865
66688
|
|
|
65866
66689
|
installImportMetaCssBuild(import.meta);
|
|
66690
|
+
/**
|
|
66691
|
+
* The group a popup opens in, or null: what a Popover or a Dialog reads to know
|
|
66692
|
+
* that its wall is the group's. Every popup provides null again to what it
|
|
66693
|
+
* holds, so a popup opened from inside a popup is in front of it, not a peer.
|
|
66694
|
+
*/
|
|
65867
66695
|
const PopupGroupContext = createContext(null);
|
|
65868
66696
|
|
|
65869
66697
|
/**
|
|
@@ -66022,13 +66850,13 @@ const css$F = /* css */`@property --x-popup-group-top {
|
|
|
66022
66850
|
* `navi-out-of-flow` like everything navi adds to a tree. See
|
|
66023
66851
|
* docs/popup_backdrop.md.
|
|
66024
66852
|
*
|
|
66025
|
-
* @type {import("
|
|
66853
|
+
* @type {import("preact").FunctionComponent<{
|
|
66026
66854
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
66027
66855
|
* backdropColor?: string,
|
|
66028
66856
|
* backdropFilter?: string,
|
|
66029
66857
|
* themeColor?: string | false,
|
|
66030
66858
|
* role?: string,
|
|
66031
|
-
* children?: import("
|
|
66859
|
+
* children?: import("preact").ComponentChildren,
|
|
66032
66860
|
* [key: string]: any,
|
|
66033
66861
|
* }>}
|
|
66034
66862
|
* @param {"auto"|"lift"|"discrete"|"invisible"} [props.backdropVariant="auto"] -
|
|
@@ -68075,6 +68903,35 @@ const clipOf = (side, distance) => {
|
|
|
68075
68903
|
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
68076
68904
|
};
|
|
68077
68905
|
|
|
68906
|
+
/**
|
|
68907
|
+
* The cross that closes the popup it sits in — reached as `<Popup.Close />`,
|
|
68908
|
+
* `<Dialog.Close />` or `<Popover.Close />`. A discrete icon button sending
|
|
68909
|
+
* `--navi-close`, which the nearest popup above it answers; the caller only
|
|
68910
|
+
* decides where it goes (the right end of a header, most of the time).
|
|
68911
|
+
*
|
|
68912
|
+
* It is the way out of a popup, so it is the one affordance in there that the
|
|
68913
|
+
* surrounding control's read-only and disabled do not reach: a read-only picker
|
|
68914
|
+
* still opens (reading changes nothing) and hands its popup that read-only, and
|
|
68915
|
+
* a cross wearing it would trap the user in a popup they were invited to read.
|
|
68916
|
+
* A close button written by hand inherits it and refuses the press — which is
|
|
68917
|
+
* the reason to use this one rather than a `<Button command="--navi-close" />`.
|
|
68918
|
+
*
|
|
68919
|
+
* Exempt from the state around it, never from the popup's own answer: the cross
|
|
68920
|
+
* ASKS, and the popup decides — a dialog holding a form mid-send keeps the
|
|
68921
|
+
* close, a Picker holding a value that does not validate keeps it too.
|
|
68922
|
+
*
|
|
68923
|
+
* @type {import("preact").FunctionComponent<{
|
|
68924
|
+
* label?: string,
|
|
68925
|
+
* iconSize?: number | string,
|
|
68926
|
+
* } & Record<string, any>>}
|
|
68927
|
+
* @param {string} [props.label] - What assistive tech reads for the cross
|
|
68928
|
+
* (navi's own "Close" text by default, in the active language).
|
|
68929
|
+
* @param {number|string} [props.iconSize] - How big the cross is drawn, apart
|
|
68930
|
+
* from the box around it. Unset, the glyph follows the button's font size
|
|
68931
|
+
* like a character, and `size` moves glyph and padding together. Set it when
|
|
68932
|
+
* the cross stands in for an affordance the app draws elsewhere and has to
|
|
68933
|
+
* land on the same pixels: `padding="s" iconSize="m"`.
|
|
68934
|
+
*/
|
|
68078
68935
|
const PopupClose = ({
|
|
68079
68936
|
label,
|
|
68080
68937
|
iconSize,
|
|
@@ -68323,6 +69180,9 @@ const TitlePseudoClasses = [":hover"];
|
|
|
68323
69180
|
* step of a form — so only the title knows when it is there.
|
|
68324
69181
|
*/
|
|
68325
69182
|
|
|
69183
|
+
// The popup a title names: the nearest one around it. A surface that is no
|
|
69184
|
+
// popup of this kind but holds content of its own (a picker's callout) sets it
|
|
69185
|
+
// back to null, so a title in there names nothing rather than the popup around.
|
|
68326
69186
|
const PopupTitleContext = createContext(null);
|
|
68327
69187
|
|
|
68328
69188
|
/**
|
|
@@ -68332,10 +69192,10 @@ const PopupTitleContext = createContext(null);
|
|
|
68332
69192
|
* announces the popup by it, and `getByRole("dialog", { name })` finds it.
|
|
68333
69193
|
* An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
|
|
68334
69194
|
*
|
|
68335
|
-
* @type {import("
|
|
69195
|
+
* @type {import("preact").FunctionComponent<{
|
|
68336
69196
|
* as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
68337
69197
|
* id?: string,
|
|
68338
|
-
* children?: import("
|
|
69198
|
+
* children?: import("preact").ComponentChildren,
|
|
68339
69199
|
* } & Record<string, any>>}
|
|
68340
69200
|
*/
|
|
68341
69201
|
const PopupTitle = props => {
|
|
@@ -68396,6 +69256,12 @@ const usePopupName = ({
|
|
|
68396
69256
|
const NO_TITLE = [];
|
|
68397
69257
|
|
|
68398
69258
|
installImportMetaCssBuild(import.meta);
|
|
69259
|
+
// Same need popover.jsx has for its own local renderer (see its
|
|
69260
|
+
// openLocalPopoverCount): a layer="local" dialog is a plain positioned element
|
|
69261
|
+
// in normal flow, so it gets none of the top layer's free "last shown wins" and
|
|
69262
|
+
// would paint under anything declared after it. A live count, not an
|
|
69263
|
+
// ever-growing counter, so the order stays small and resets once nothing is
|
|
69264
|
+
// open.
|
|
68399
69265
|
let openLocalDialogCount = 0;
|
|
68400
69266
|
const css$C = /* css */`
|
|
68401
69267
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -69470,7 +70336,7 @@ const css$C = /* css */`
|
|
|
69470
70336
|
* the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
|
|
69471
70337
|
* nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
|
|
69472
70338
|
* @param {string} [props.openerAriaLabel] - The same, as a string.
|
|
69473
|
-
* @param {import("
|
|
70339
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
69474
70340
|
*/
|
|
69475
70341
|
const Dialog = props => {
|
|
69476
70342
|
import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
@@ -70306,8 +71172,10 @@ const useDialogProps = props => {
|
|
|
70306
71172
|
// gap — the same thing its CSS width/height say (see [data-expand-x],
|
|
70307
71173
|
// [data-expand-y]) — so the placement derives it instead of measuring
|
|
70308
71174
|
// it: a measurement here lays out the dialog's whole content, rows and
|
|
70309
|
-
// all, for a number already known.
|
|
70310
|
-
|
|
71175
|
+
// all, for a number already known. A cap (maxWidth/maxHeight, a Picker's
|
|
71176
|
+
// anchor ceiling) stretches it to less than that, and only a measured
|
|
71177
|
+
// box can be centred.
|
|
71178
|
+
const fill = expandX && expandY && !isSizeCapped(dialogEl);
|
|
70311
71179
|
const pickOptions = {
|
|
70312
71180
|
positionArea,
|
|
70313
71181
|
container: positionedAncestor,
|
|
@@ -70821,6 +71689,14 @@ const switchTransitionsOn = dialogEl => {
|
|
|
70821
71689
|
dialogEl.style.transitionProperty = "";
|
|
70822
71690
|
dialogEl.removeAttribute("navi-transitions-off");
|
|
70823
71691
|
};
|
|
71692
|
+
|
|
71693
|
+
// Reads style, not layout: an unset cap is the guaranteed-invalid value its
|
|
71694
|
+
// @property gives it, and reads back as "". Catches a cap written inline by
|
|
71695
|
+
// the prop as well as one set from a stylesheet.
|
|
71696
|
+
const isSizeCapped = dialogEl => {
|
|
71697
|
+
const computedStyle = getComputedStyle(dialogEl);
|
|
71698
|
+
return computedStyle.getPropertyValue("--dialog-max-width") !== "" || computedStyle.getPropertyValue("--dialog-max-height") !== "";
|
|
71699
|
+
};
|
|
70824
71700
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
70825
71701
|
|
|
70826
71702
|
// Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
|
|
@@ -70846,6 +71722,11 @@ Dialog.Close = PopupClose;
|
|
|
70846
71722
|
Dialog.Title = PopupTitle;
|
|
70847
71723
|
|
|
70848
71724
|
installImportMetaCssBuild(import.meta);
|
|
71725
|
+
// Custom renderer only (layer="local") — count of currently-open instances,
|
|
71726
|
+
// see --popover-stack-order's own CSS comment for why a plain "last shown"
|
|
71727
|
+
// default doesn't happen for free the way it does for the top-layer
|
|
71728
|
+
// renderer. A live count (not an ever-growing counter) so the assigned
|
|
71729
|
+
// order stays small and resets to 0 once nothing is open.
|
|
70849
71730
|
let openLocalPopoverCount = 0;
|
|
70850
71731
|
const css$B = /* css */`
|
|
70851
71732
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -71453,7 +72334,7 @@ const css$B = /* css */`
|
|
|
71453
72334
|
* something names — these, `aria-label`/`aria-labelledby`, a
|
|
71454
72335
|
* `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
|
|
71455
72336
|
* an element with no role is read by nobody.
|
|
71456
|
-
* @param {import("
|
|
72337
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
71457
72338
|
*/
|
|
71458
72339
|
const Popover = props => {
|
|
71459
72340
|
import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
|
|
@@ -72811,7 +73692,7 @@ const css$A = /* css */`@layer navi {
|
|
|
72811
73692
|
* content whose fresh state is its initial state: an edit left unsent at
|
|
72812
73693
|
* the close does not come back at the next opening (an untouched field
|
|
72813
73694
|
* needs none of this to show a new `defaultValue` — it follows one).
|
|
72814
|
-
* @param {import("
|
|
73695
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
72815
73696
|
*/
|
|
72816
73697
|
const Popup = props => {
|
|
72817
73698
|
import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
|
|
@@ -74488,6 +75369,18 @@ const PickerConfirmBody = ({
|
|
|
74488
75369
|
});
|
|
74489
75370
|
};
|
|
74490
75371
|
|
|
75372
|
+
/**
|
|
75373
|
+
* PickerNaviMinute — a custom minute picker that opens a number input.
|
|
75374
|
+
*
|
|
75375
|
+
* Renders a popover containing an `<input type="number">` pre-filled with the
|
|
75376
|
+
* current value, plus Confirm / Clear / Cancel actions. The picker holds a
|
|
75377
|
+
* number of minutes: its own field is a text one told
|
|
75378
|
+
* `navi-input-type="minute"`, so the value is read back as a number and a
|
|
75379
|
+
* min/max/step message speaks of minutes.
|
|
75380
|
+
*
|
|
75381
|
+
* Usage:
|
|
75382
|
+
* <Picker type="navi_minute" min={15} max={300} step={15} value={90} />
|
|
75383
|
+
*/
|
|
74491
75384
|
const PickerNaviMinute = props => {
|
|
74492
75385
|
const Next = useNextResolver();
|
|
74493
75386
|
const {
|
|
@@ -74871,6 +75764,16 @@ const LoadingDotsSvg = () => {
|
|
|
74871
75764
|
});
|
|
74872
75765
|
};
|
|
74873
75766
|
|
|
75767
|
+
/**
|
|
75768
|
+
* A loading indicator sized to match the surrounding text (1em × 1em).
|
|
75769
|
+
* Inherits color from the current text color by default.
|
|
75770
|
+
*
|
|
75771
|
+
* @param {"circle"|"dots"} [props.variant="circle"] - Visual style of the indicator
|
|
75772
|
+
* @param {string} [props.color="currentColor"] - Color of the indicator; inherits from CSS `color` by default
|
|
75773
|
+
*
|
|
75774
|
+
* Every other prop goes to the `Icon` it is drawn in: `size` is a font size
|
|
75775
|
+
* (the indicator being 1em), `width`/`height` size it as a block.
|
|
75776
|
+
*/
|
|
74874
75777
|
const LoadingIndicator = ({
|
|
74875
75778
|
variant = "circle",
|
|
74876
75779
|
...props
|
|
@@ -74933,7 +75836,7 @@ const css$x = /* css */`@layer navi {
|
|
|
74933
75836
|
* Everything about how it looks goes through a CSS var, so it can be set per
|
|
74934
75837
|
* separator as a prop or once for a whole surface in CSS.
|
|
74935
75838
|
*
|
|
74936
|
-
* @type {import("
|
|
75839
|
+
* @type {import("preact").FunctionComponent<{
|
|
74937
75840
|
* vertical?: boolean,
|
|
74938
75841
|
* size?: string|number,
|
|
74939
75842
|
* color?: string,
|
|
@@ -76536,6 +77439,9 @@ const applySearchHighlight = (el, highlight) => {
|
|
|
76536
77439
|
};
|
|
76537
77440
|
|
|
76538
77441
|
installImportMetaCssBuild(import.meta);
|
|
77442
|
+
// Everything the list knows about its items — see list_items.js. Filled in by
|
|
77443
|
+
// the children as they render, read by everything that must reserve room for
|
|
77444
|
+
// what is not rendered, count what is, or know what stands above it.
|
|
76539
77445
|
const ListItemsContext = createContext(null);
|
|
76540
77446
|
// The group an item is declared in, by id (see ListItemGroup): what its count
|
|
76541
77447
|
// and its separator are scoped to.
|
|
@@ -81108,7 +82014,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81108
82014
|
* of it. Props not listed here are forwarded to the rendered <li> (Box layout
|
|
81109
82015
|
* props included).
|
|
81110
82016
|
*
|
|
81111
|
-
* @type {import("
|
|
82017
|
+
* @type {import("preact").FunctionComponent<{
|
|
81112
82018
|
* id?: string,
|
|
81113
82019
|
* selectable?: boolean,
|
|
81114
82020
|
* value?: any,
|
|
@@ -81117,14 +82023,14 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81117
82023
|
* pointed?: boolean,
|
|
81118
82024
|
* selectableArea?: "all" | "manual",
|
|
81119
82025
|
* skeleton?: boolean,
|
|
81120
|
-
* error?: boolean | import("
|
|
82026
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
81121
82027
|
* onErrorDismiss?: (event: Event) => void,
|
|
81122
82028
|
* loading?: boolean | "adding" | "removing" | "updating",
|
|
81123
82029
|
* readOnly?: boolean,
|
|
81124
82030
|
* filtered?: boolean,
|
|
81125
82031
|
* hidden?: boolean,
|
|
81126
82032
|
* matchInfo?: {match: boolean, matchScore?: number, matchRanges?: Array<[number, number]>},
|
|
81127
|
-
* children?: import("
|
|
82033
|
+
* children?: import("preact").ComponentChildren,
|
|
81128
82034
|
* [key: string]: any,
|
|
81129
82035
|
* }>}
|
|
81130
82036
|
* @param {string} [props.id]
|
|
@@ -81159,7 +82065,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81159
82065
|
* real item. This is what a `renderSkeleton` returns for an item on its way;
|
|
81160
82066
|
* Box layout props (padding…) pass through so the placeholder can match the
|
|
81161
82067
|
* real items' metrics.
|
|
81162
|
-
* @param {boolean|import("
|
|
82068
|
+
* @param {boolean|import("preact").ComponentChildren} [props.error]
|
|
81163
82069
|
* What this item stood for failed: the message replaces its content, styled
|
|
81164
82070
|
* like the list's own error. `true` shows a generic sentence. When
|
|
81165
82071
|
* `onErrorDismiss` is given, a dismiss button is drawn next to the message
|
|
@@ -81299,21 +82205,21 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
81299
82205
|
* Several runs can live in one list, next to plain `<List.Item>` children and
|
|
81300
82206
|
* inside `<List.Group>`s; each takes its place in declaration order.
|
|
81301
82207
|
*
|
|
81302
|
-
* @type {import("
|
|
81303
|
-
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("
|
|
82208
|
+
* @type {import("preact").FunctionComponent<{
|
|
82209
|
+
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("preact").ComponentChildren,
|
|
81304
82210
|
* findText?: (item: any, index: number) => string,
|
|
81305
82211
|
* items?: any[],
|
|
81306
82212
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
81307
82213
|
* debounce?: number,
|
|
81308
82214
|
* count?: number,
|
|
81309
82215
|
* groupBy?: (item: any, index: number) => any,
|
|
81310
|
-
* renderGroupLabel?: (item: any, index: number) => import("
|
|
82216
|
+
* renderGroupLabel?: (item: any, index: number) => import("preact").ComponentChildren,
|
|
81311
82217
|
* groupLabelProps?: (item: any, index: number) => object,
|
|
81312
82218
|
* pageSize?: number,
|
|
81313
82219
|
* memoryBudget?: number,
|
|
81314
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82220
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
81315
82221
|
* itemSize?: (index: number) => number,
|
|
81316
|
-
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("
|
|
82222
|
+
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("preact").ComponentChildren,
|
|
81317
82223
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
81318
82224
|
* onCountChange?: (count: number|undefined) => void,
|
|
81319
82225
|
* }>}
|
|
@@ -82908,7 +83814,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82908
83814
|
* List — generic virtualized scroll container.
|
|
82909
83815
|
* Items must use <List.Item> to participate in tracking.
|
|
82910
83816
|
*
|
|
82911
|
-
* @type {import("
|
|
83817
|
+
* @type {import("preact").FunctionComponent<{
|
|
82912
83818
|
* selectable?: boolean,
|
|
82913
83819
|
* multiple?: boolean,
|
|
82914
83820
|
* deselectable?: boolean,
|
|
@@ -82929,16 +83835,16 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82929
83835
|
* scroller?: "self" | "parent" | "document" | Element | {current: Element},
|
|
82930
83836
|
* hoverWhileScrolling?: boolean,
|
|
82931
83837
|
* scrollResetOnNavigation?: boolean,
|
|
82932
|
-
* fallback?: import("
|
|
82933
|
-
* searchFallback?: import("
|
|
83838
|
+
* fallback?: import("preact").ComponentChildren,
|
|
83839
|
+
* searchFallback?: import("preact").ComponentChildren,
|
|
82934
83840
|
* searchText?: string,
|
|
82935
83841
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
|
|
82936
83842
|
* loading?: boolean,
|
|
82937
|
-
* loadingFallback?: "skeleton" | "loader" | import("
|
|
83843
|
+
* loadingFallback?: "skeleton" | "loader" | import("preact").ComponentChildren,
|
|
82938
83844
|
* loadingSkeletonCount?: number,
|
|
82939
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82940
|
-
* error?: boolean | import("
|
|
82941
|
-
* separator?: boolean | import("
|
|
83845
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
83846
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
83847
|
+
* separator?: boolean | import("preact").ComponentChildren,
|
|
82942
83848
|
* itemTransition?: boolean,
|
|
82943
83849
|
* lockSize?: boolean,
|
|
82944
83850
|
* horizontal?: boolean,
|
|
@@ -82952,7 +83858,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82952
83858
|
* expandX?: boolean,
|
|
82953
83859
|
* expandY?: boolean,
|
|
82954
83860
|
* expand?: boolean,
|
|
82955
|
-
* children?: import("
|
|
83861
|
+
* children?: import("preact").ComponentChildren,
|
|
82956
83862
|
* [key: string]: any,
|
|
82957
83863
|
* }>}
|
|
82958
83864
|
* @param {string} [props.columns]
|
|
@@ -83012,7 +83918,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
83012
83918
|
* have, so nothing moves when they arrive. Used for the items a `<List.Items>`
|
|
83013
83919
|
* stands for and does not hold yet, and for the placeholder items drawn while
|
|
83014
83920
|
* the whole list is `loading`. Defaults to a bare `<List.Item skeleton>`.
|
|
83015
|
-
* @param {"skeleton"|"loader"|import("
|
|
83921
|
+
* @param {"skeleton"|"loader"|import("preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
83016
83922
|
* What to display in place of the items while `loading` — that is, while
|
|
83017
83923
|
* there is nothing to show at all: `"skeleton"` renders
|
|
83018
83924
|
* `loadingSkeletonCount` placeholder items (look:
|
|
@@ -83196,6 +84102,17 @@ const List = /*#__PURE__*/Object.assign(ListResolved, {
|
|
|
83196
84102
|
Group: ListItemGroup
|
|
83197
84103
|
});
|
|
83198
84104
|
|
|
84105
|
+
/**
|
|
84106
|
+
* PickerNaviTime — a fully custom time picker driven by min/max/step.
|
|
84107
|
+
*
|
|
84108
|
+
* Unlike the native `<input type="time">`, this picker only exposes the exact
|
|
84109
|
+
* slots generated from min/max/step, so the user can never pick an out-of-range
|
|
84110
|
+
* or out-of-step value. The popover content is a selectable `List` of the
|
|
84111
|
+
* available slots; picking one closes the popover and submits the value.
|
|
84112
|
+
*
|
|
84113
|
+
* Usage:
|
|
84114
|
+
* <Picker type="navi_time" min="08:00" max="18:00" step="00:30" />
|
|
84115
|
+
*/
|
|
83199
84116
|
const PickerNaviTime = props => {
|
|
83200
84117
|
const Next = useNextResolver();
|
|
83201
84118
|
const {
|
|
@@ -83303,18 +84220,20 @@ const createBadgeRegistry = () => {
|
|
|
83303
84220
|
};
|
|
83304
84221
|
|
|
83305
84222
|
installImportMetaCssBuild(import.meta);
|
|
83306
|
-
const css$u = /* css */`@layer navi
|
|
84223
|
+
const css$u = /* css */`@layer navi {
|
|
84224
|
+
.navi_badge {
|
|
84225
|
+
--font-size: .7em;
|
|
84226
|
+
--badge-padding-x-default: .8em;
|
|
84227
|
+
--badge-padding-y-default: .4em;
|
|
84228
|
+
}
|
|
84229
|
+
}
|
|
83307
84230
|
|
|
83308
84231
|
.navi_badge {
|
|
83309
|
-
--font-size: .7em;
|
|
83310
|
-
--badge-padding-x-default: .8em;
|
|
83311
|
-
--badge-padding-y-default: .4em;
|
|
83312
84232
|
--x-badge-padding-top: var(--badge-padding-top, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83313
84233
|
--x-badge-padding-right: var(--badge-padding-right, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83314
84234
|
--x-badge-padding-bottom: var(--badge-padding-bottom, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83315
84235
|
--x-badge-padding-left: var(--badge-padding-left, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83316
84236
|
--x-background: var(--background, light-dark(#e0e0e0, #3a3a3a));
|
|
83317
|
-
--x-background-color: var(--background-color, var(--x-background));
|
|
83318
84237
|
--x-color: var(--color, white);
|
|
83319
84238
|
max-width: 200px;
|
|
83320
84239
|
padding-top: var(--x-badge-padding-top);
|
|
@@ -83325,7 +84244,6 @@ const css$u = /* css */`@layer navi;
|
|
|
83325
84244
|
font-size: var(--font-size);
|
|
83326
84245
|
text-box: trim-both cap alphabetic;
|
|
83327
84246
|
background: var(--x-background);
|
|
83328
|
-
background-color: var(--x-background-color);
|
|
83329
84247
|
border-radius: 1em;
|
|
83330
84248
|
display: inline;
|
|
83331
84249
|
position: relative;
|
|
@@ -83451,7 +84369,6 @@ const BadgeStyleCSSVars = {
|
|
|
83451
84369
|
paddingRight: "--badge-padding-right",
|
|
83452
84370
|
paddingBottom: "--badge-padding-bottom",
|
|
83453
84371
|
paddingLeft: "--badge-padding-left",
|
|
83454
|
-
backgroundColor: "--background-color",
|
|
83455
84372
|
background: "--background",
|
|
83456
84373
|
borderColor: "--border-color",
|
|
83457
84374
|
color: "--color",
|
|
@@ -83625,7 +84542,7 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
83625
84542
|
* shrinkWrap on its own builds the measurement ghost: under maxLines the list
|
|
83626
84543
|
* is measured in place, once the rows are known.
|
|
83627
84544
|
*
|
|
83628
|
-
* @param {import("
|
|
84545
|
+
* @param {import("preact").ComponentChildren} [fallback]
|
|
83629
84546
|
* Rendered in place of the badges when there is none. Without it an empty
|
|
83630
84547
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
83631
84548
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
@@ -84259,10 +85176,10 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
84259
85176
|
* — a row being read still says what was picked, it just no longer offers to
|
|
84260
85177
|
* unpick it.
|
|
84261
85178
|
*
|
|
84262
|
-
* @type {import("
|
|
85179
|
+
* @type {import("preact").FunctionComponent<{
|
|
84263
85180
|
* value: any,
|
|
84264
85181
|
* commandFor?: string,
|
|
84265
|
-
* children?: import("
|
|
85182
|
+
* children?: import("preact").ComponentChildren,
|
|
84266
85183
|
* [key: string]: any,
|
|
84267
85184
|
* }>}
|
|
84268
85185
|
* @param {any} value The value this chip stands for — one entry of what the
|
|
@@ -84528,6 +85445,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84528
85445
|
.navi_picker {
|
|
84529
85446
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
84530
85447
|
--picker-border-width: var(--navi-control-border-width);
|
|
85448
|
+
--border-width: var(--picker-border-width);
|
|
84531
85449
|
--picker-outline-width: var(--navi-focus-outline-width);
|
|
84532
85450
|
--picker-outline-offset: calc(-.5 * var(--picker-outline-width));
|
|
84533
85451
|
--picker-outline-color: var(--navi-focus-outline-color);
|
|
@@ -84567,6 +85485,111 @@ const css$r = /* css */`@layer navi {
|
|
|
84567
85485
|
--picker-align-x-default: flex-start;
|
|
84568
85486
|
--picker-align-y-default: center;
|
|
84569
85487
|
--picker-text-align-default: initial;
|
|
85488
|
+
font-size: var(--picker-font-size);
|
|
85489
|
+
font-family: var(--picker-font-family);
|
|
85490
|
+
line-height: var(--navi-control-line-height);
|
|
85491
|
+
}
|
|
85492
|
+
|
|
85493
|
+
.navi_picker[data-variant="icon"] {
|
|
85494
|
+
--picker-border-width: 0px;
|
|
85495
|
+
--picker-border-color: transparent;
|
|
85496
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85497
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85498
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85499
|
+
--picker-background-color: transparent;
|
|
85500
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85501
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85502
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85503
|
+
--picker-icon-color: currentColor;
|
|
85504
|
+
}
|
|
85505
|
+
|
|
85506
|
+
.navi_picker[data-variant="discrete"] {
|
|
85507
|
+
--picker-border-width: 0px;
|
|
85508
|
+
--picker-border-color: transparent;
|
|
85509
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85510
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85511
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85512
|
+
--picker-background-color: transparent;
|
|
85513
|
+
--picker-background-color-hover: color-mix(in srgb,
|
|
85514
|
+
currentColor 8%,
|
|
85515
|
+
var(--picker-background-color));
|
|
85516
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85517
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85518
|
+
}
|
|
85519
|
+
|
|
85520
|
+
.navi_picker[data-variant="headless"] {
|
|
85521
|
+
--picker-padding-x-default: 0;
|
|
85522
|
+
--picker-padding-y-default: 0;
|
|
85523
|
+
--picker-border-width: 0px;
|
|
85524
|
+
--picker-border-color: transparent;
|
|
85525
|
+
--picker-outline-width: 0px;
|
|
85526
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85527
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85528
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85529
|
+
--picker-background-color: transparent;
|
|
85530
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85531
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85532
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85533
|
+
--picker-icon-color: currentColor;
|
|
85534
|
+
}
|
|
85535
|
+
|
|
85536
|
+
.navi_picker[data-variant="bare"] {
|
|
85537
|
+
--picker-padding-x-default: 0;
|
|
85538
|
+
--picker-padding-y-default: 0;
|
|
85539
|
+
--picker-align-y-default: stretch;
|
|
85540
|
+
--picker-border-width: 0px;
|
|
85541
|
+
--picker-border-color: transparent;
|
|
85542
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85543
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85544
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85545
|
+
--picker-background-color: transparent;
|
|
85546
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85547
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85548
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85549
|
+
--picker-icon-color: currentColor;
|
|
85550
|
+
font-size: inherit;
|
|
85551
|
+
font-family: inherit;
|
|
85552
|
+
line-height: inherit;
|
|
85553
|
+
}
|
|
85554
|
+
|
|
85555
|
+
.navi_picker[data-variant="button"] {
|
|
85556
|
+
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
85557
|
+
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
85558
|
+
--picker-align-x-default: center;
|
|
85559
|
+
--picker-background-color: var(--navi-button-background-color);
|
|
85560
|
+
--picker-border-color-readonly: color-mix(in srgb,
|
|
85561
|
+
var(--picker-border-color) 30%,
|
|
85562
|
+
var(--navi-surface-color));
|
|
85563
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85564
|
+
--picker-color-readonly: color-mix(in srgb,
|
|
85565
|
+
var(--picker-color) 30%,
|
|
85566
|
+
transparent);
|
|
85567
|
+
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
85568
|
+
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
85569
|
+
--picker-color-disabled: var(--picker-color-readonly);
|
|
85570
|
+
--picker-text-align-default: center;
|
|
85571
|
+
}
|
|
85572
|
+
|
|
85573
|
+
.navi_picker[data-variant="text"] {
|
|
85574
|
+
--picker-padding-x-default: 0;
|
|
85575
|
+
--picker-padding-y-default: 0;
|
|
85576
|
+
--picker-border-width: 0px;
|
|
85577
|
+
--picker-border-color: transparent;
|
|
85578
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85579
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85580
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85581
|
+
--picker-background-color: transparent;
|
|
85582
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85583
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85584
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85585
|
+
font-size: inherit;
|
|
85586
|
+
font-family: inherit;
|
|
85587
|
+
}
|
|
85588
|
+
|
|
85589
|
+
.navi_picker[data-icon] {
|
|
85590
|
+
--picker-padding-x-default: 0;
|
|
85591
|
+
--picker-padding-y-default: 0;
|
|
85592
|
+
--picker-align-x-default: center;
|
|
84570
85593
|
}
|
|
84571
85594
|
}
|
|
84572
85595
|
|
|
@@ -84589,10 +85612,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84589
85612
|
box-sizing: border-box;
|
|
84590
85613
|
max-width: 100%;
|
|
84591
85614
|
color: var(--x-picker-color);
|
|
84592
|
-
font-size: var(--picker-font-size);
|
|
84593
|
-
font-family: var(--picker-font-family);
|
|
84594
85615
|
text-align: inherit;
|
|
84595
|
-
line-height: var(--navi-control-line-height);
|
|
84596
85616
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--picker-border-radius));
|
|
84597
85617
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--picker-border-radius));
|
|
84598
85618
|
border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--picker-border-radius));
|
|
@@ -84834,48 +85854,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84834
85854
|
--x-picker-border-color: var(--callout-color);
|
|
84835
85855
|
}
|
|
84836
85856
|
|
|
84837
|
-
&[data-variant="icon"] {
|
|
84838
|
-
--picker-border-width: 0px;
|
|
84839
|
-
--picker-border-color: transparent;
|
|
84840
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84841
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84842
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84843
|
-
--picker-background-color: transparent;
|
|
84844
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84845
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84846
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84847
|
-
--picker-icon-color: currentColor;
|
|
84848
|
-
}
|
|
84849
|
-
|
|
84850
|
-
&[data-variant="discrete"] {
|
|
84851
|
-
--picker-border-width: 0px;
|
|
84852
|
-
--picker-border-color: transparent;
|
|
84853
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84854
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84855
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84856
|
-
--picker-background-color: transparent;
|
|
84857
|
-
--picker-background-color-hover: color-mix(in srgb,
|
|
84858
|
-
currentColor 8%,
|
|
84859
|
-
var(--picker-background-color));
|
|
84860
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84861
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84862
|
-
}
|
|
84863
|
-
|
|
84864
85857
|
&[data-variant="headless"] {
|
|
84865
|
-
--picker-padding-x-default: 0;
|
|
84866
|
-
--picker-padding-y-default: 0;
|
|
84867
|
-
--picker-border-width: 0px;
|
|
84868
|
-
--picker-border-color: transparent;
|
|
84869
|
-
--picker-outline-width: 0px;
|
|
84870
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84871
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84872
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84873
|
-
--picker-background-color: transparent;
|
|
84874
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84875
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84876
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84877
|
-
--picker-icon-color: currentColor;
|
|
84878
|
-
|
|
84879
85858
|
& > .navi_picker_box {
|
|
84880
85859
|
z-index: -1;
|
|
84881
85860
|
position: absolute;
|
|
@@ -84884,23 +85863,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84884
85863
|
}
|
|
84885
85864
|
|
|
84886
85865
|
&[data-variant="bare"] {
|
|
84887
|
-
--picker-padding-x-default: 0;
|
|
84888
|
-
--picker-padding-y-default: 0;
|
|
84889
|
-
--picker-align-y-default: stretch;
|
|
84890
|
-
--picker-border-width: 0px;
|
|
84891
|
-
--picker-border-color: transparent;
|
|
84892
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84893
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84894
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84895
|
-
--picker-background-color: transparent;
|
|
84896
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84897
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84898
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84899
|
-
--picker-icon-color: currentColor;
|
|
84900
|
-
font-size: inherit;
|
|
84901
|
-
font-family: inherit;
|
|
84902
|
-
line-height: inherit;
|
|
84903
|
-
|
|
84904
85866
|
& > .navi_picker_box {
|
|
84905
85867
|
min-height: 0;
|
|
84906
85868
|
}
|
|
@@ -84935,39 +85897,8 @@ const css$r = /* css */`@layer navi {
|
|
|
84935
85897
|
}
|
|
84936
85898
|
}
|
|
84937
85899
|
|
|
84938
|
-
&[data-variant="button"] {
|
|
84939
|
-
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
84940
|
-
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
84941
|
-
--picker-align-x-default: center;
|
|
84942
|
-
--picker-background-color: var(--navi-button-background-color);
|
|
84943
|
-
--picker-border-color-readonly: color-mix(in srgb,
|
|
84944
|
-
var(--picker-border-color) 30%,
|
|
84945
|
-
var(--navi-surface-color));
|
|
84946
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84947
|
-
--picker-color-readonly: color-mix(in srgb,
|
|
84948
|
-
var(--picker-color) 30%,
|
|
84949
|
-
transparent);
|
|
84950
|
-
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
84951
|
-
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
84952
|
-
--picker-color-disabled: var(--picker-color-readonly);
|
|
84953
|
-
--picker-text-align-default: center;
|
|
84954
|
-
}
|
|
84955
|
-
|
|
84956
85900
|
&[data-variant="text"] {
|
|
84957
|
-
--picker-padding-x-default: 0;
|
|
84958
|
-
--picker-padding-y-default: 0;
|
|
84959
|
-
--picker-border-width: 0px;
|
|
84960
|
-
--picker-border-color: transparent;
|
|
84961
|
-
--picker-border-color-hover: var(--picker-border-color);
|
|
84962
|
-
--picker-border-color-readonly: var(--picker-border-color);
|
|
84963
|
-
--picker-border-color-disabled: var(--picker-border-color);
|
|
84964
|
-
--picker-background-color: transparent;
|
|
84965
|
-
--picker-background-color-hover: var(--picker-background-color);
|
|
84966
|
-
--picker-background-color-readonly: var(--picker-background-color);
|
|
84967
|
-
--picker-background-color-disabled: var(--picker-background-color);
|
|
84968
|
-
font-size: inherit;
|
|
84969
85901
|
text-underline-offset: .2em;
|
|
84970
|
-
font-family: inherit;
|
|
84971
85902
|
text-decoration: underline dotted;
|
|
84972
85903
|
|
|
84973
85904
|
&[data-hover] {
|
|
@@ -84976,10 +85907,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84976
85907
|
}
|
|
84977
85908
|
|
|
84978
85909
|
&[data-icon] {
|
|
84979
|
-
--picker-padding-x-default: 0;
|
|
84980
|
-
--picker-padding-y-default: 0;
|
|
84981
|
-
--picker-align-x-default: center;
|
|
84982
|
-
|
|
84983
85910
|
& > .navi_picker_box {
|
|
84984
85911
|
min-height: 0;
|
|
84985
85912
|
}
|
|
@@ -85414,13 +86341,13 @@ const PickerButton = props => {
|
|
|
85414
86341
|
* It goes when there is nothing to take out and when nothing can be changed, so
|
|
85415
86342
|
* a caller places it once and never asks whether to draw it.
|
|
85416
86343
|
*
|
|
85417
|
-
* @type {import("
|
|
86344
|
+
* @type {import("preact").FunctionComponent<{
|
|
85418
86345
|
* size?: number | string,
|
|
85419
|
-
* children?: import("
|
|
86346
|
+
* children?: import("preact").ComponentChildren,
|
|
85420
86347
|
* } & Record<string, any>>}
|
|
85421
86348
|
* @param {number|string} [size="inherit"] How big the cross is drawn, as
|
|
85422
86349
|
* `<Icon size>` reads it — "inherit" takes the size of the text around it.
|
|
85423
|
-
* @param {import("
|
|
86350
|
+
* @param {import("preact").ComponentChildren} [children] The icon, in place of
|
|
85424
86351
|
* the cross. Anything else goes to the `<Button>` underneath (`variant`,
|
|
85425
86352
|
* `paddingX`, `aria-label`, …).
|
|
85426
86353
|
*/
|
|
@@ -85751,6 +86678,580 @@ const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
|
85751
86678
|
ColorSvg
|
|
85752
86679
|
});
|
|
85753
86680
|
|
|
86681
|
+
/**
|
|
86682
|
+
* Button-like trigger that opens a picker (native or custom popup) when clicked.
|
|
86683
|
+
*
|
|
86684
|
+
* Without `children`, opens the browser-native picker for the given `type`.
|
|
86685
|
+
* With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
|
|
86686
|
+
* Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
|
|
86687
|
+
* Either way the popup is a `dialog`, named by a `<Popup.Title>` in the
|
|
86688
|
+
* children (see `popupAriaLabelledBy`), and the trigger answers to `combobox` —
|
|
86689
|
+
* `button` under `picksNothing` — with `aria-haspopup="dialog"` and
|
|
86690
|
+
* `aria-expanded` (see docs/testid.md).
|
|
86691
|
+
*
|
|
86692
|
+
* `type="confirm"` is the picker that asks "are you sure?" before doing what it
|
|
86693
|
+
* stands for: its popup is the question, `ui` its label, and yes runs its
|
|
86694
|
+
* `action` and triggers its `command` once the popup has closed (the command
|
|
86695
|
+
* follows the action, and only if it succeeded) — see
|
|
86696
|
+
* picker_confirm.jsx, and `message`, `confirmLabel`, `cancelLabel`,
|
|
86697
|
+
* `focusOnOpen` below.
|
|
86698
|
+
*
|
|
86699
|
+
* @type {import("preact").FunctionComponent<{
|
|
86700
|
+
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
86701
|
+
* value?: any,
|
|
86702
|
+
* defaultValue?: any,
|
|
86703
|
+
* name?: string,
|
|
86704
|
+
* placeholder?: import("preact").ComponentChildren,
|
|
86705
|
+
* ui?: import("preact").ComponentChildren | "default" | import("preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
86706
|
+
* required?: boolean,
|
|
86707
|
+
* min?: Date | string | number,
|
|
86708
|
+
* max?: Date | string | number,
|
|
86709
|
+
* step?: string | number,
|
|
86710
|
+
* disabled?: boolean,
|
|
86711
|
+
* readOnly?: boolean,
|
|
86712
|
+
* openWhileReadOnly?: boolean,
|
|
86713
|
+
* error?: boolean | string,
|
|
86714
|
+
* uiAction?: (value: any, event: Event) => void,
|
|
86715
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void,
|
|
86716
|
+
* children?: import("preact").ComponentChildren,
|
|
86717
|
+
* mode?: "popover" | "dialog" | "callout",
|
|
86718
|
+
* openOn?: "press" | "longpress" | "contextmenu" | string | string[],
|
|
86719
|
+
* animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
|
|
86720
|
+
* lift?: "box" | "scene",
|
|
86721
|
+
* liftAnchor?: Element | { current: Element } | string,
|
|
86722
|
+
* animationDuration?: string,
|
|
86723
|
+
* calloutStatus?: "info" | "warning" | "error" | "success" | "none",
|
|
86724
|
+
* calloutIcon?: boolean,
|
|
86725
|
+
* calloutCloseButton?: boolean,
|
|
86726
|
+
* calloutCloseByScroll?: boolean | number,
|
|
86727
|
+
* popoverMode?: "nearby" | "overlay",
|
|
86728
|
+
* positionArea?: string,
|
|
86729
|
+
* popupWidthFitContent?: boolean,
|
|
86730
|
+
* loadingOutline?: "custom",
|
|
86731
|
+
* loadingOutlineInset?: number,
|
|
86732
|
+
* variant?: "icon" | "circle" | "headless" | "discrete" | "button" | "text" | "bare" | "picker",
|
|
86733
|
+
* icon?: boolean,
|
|
86734
|
+
* alignX?: "start" | "center" | "end",
|
|
86735
|
+
* alignY?: "start" | "center" | "end" | "stretch",
|
|
86736
|
+
* rightSlotIcon?: import("preact").ComponentChildren,
|
|
86737
|
+
* rightSlotIconSize?: number | string,
|
|
86738
|
+
* rightSlot?: import("preact").ComponentChildren,
|
|
86739
|
+
* chevron?: "end" | "start" | "hang-end",
|
|
86740
|
+
* clearConfirm?: string | import("preact").ComponentChildren,
|
|
86741
|
+
* message?: import("preact").ComponentChildren,
|
|
86742
|
+
* confirmLabel?: import("preact").ComponentChildren,
|
|
86743
|
+
* cancelLabel?: import("preact").ComponentChildren,
|
|
86744
|
+
* focusOnOpen?: "confirm" | "cancel" | "none",
|
|
86745
|
+
* onConfirm?: (confirmEvent: CustomEvent) => void,
|
|
86746
|
+
* onOpen?: (openEvent: CustomEvent) => void,
|
|
86747
|
+
* onClose?: (closeEvent: CustomEvent) => void,
|
|
86748
|
+
* maxLines?: number,
|
|
86749
|
+
* slotSpacing?: number | string,
|
|
86750
|
+
* popoverMaxHeight?: number | string,
|
|
86751
|
+
* dialogMinWidth?: number | string,
|
|
86752
|
+
* dialogMinHeight?: number | string,
|
|
86753
|
+
* dialogMaxWidth?: number | string,
|
|
86754
|
+
* dialogMaxHeight?: number | string,
|
|
86755
|
+
* popupBackgroundColor?: string,
|
|
86756
|
+
* popupBorderRadius?: number | string,
|
|
86757
|
+
* popupBoxShadow?: string,
|
|
86758
|
+
* dialogBorderWidth?: number | string,
|
|
86759
|
+
* clearable?: boolean,
|
|
86760
|
+
* picksNothing?: boolean,
|
|
86761
|
+
* standalone?: boolean,
|
|
86762
|
+
* popupLayer?: "top" | "local",
|
|
86763
|
+
* popupTestId?: string,
|
|
86764
|
+
* popupAriaLabel?: string,
|
|
86765
|
+
* popupAriaLabelledBy?: string,
|
|
86766
|
+
* popupAriaDescribedBy?: string,
|
|
86767
|
+
* confirmTestId?: string,
|
|
86768
|
+
* cancelTestId?: string,
|
|
86769
|
+
* mount?: "always" | "idle" | "from-first-open" | "while-opened",
|
|
86770
|
+
* dialogExpand?: boolean,
|
|
86771
|
+
* dialogExpandX?: boolean,
|
|
86772
|
+
* dialogExpandY?: boolean,
|
|
86773
|
+
* dialogSizeFromAnchor?: boolean,
|
|
86774
|
+
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
86775
|
+
* marginWithContainer?: number | string,
|
|
86776
|
+
* anchor?: import("preact").RefObject<HTMLElement> | HTMLElement,
|
|
86777
|
+
* escapeEffect?: "cancel" | "close",
|
|
86778
|
+
* pressOutside?: "close" | "cancel" | "capture",
|
|
86779
|
+
* backdrop?: boolean,
|
|
86780
|
+
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
86781
|
+
* backdropColor?: string,
|
|
86782
|
+
* backdropFilter?: string,
|
|
86783
|
+
* ref?: import("preact").RefObject<HTMLElement>,
|
|
86784
|
+
* [key: string]: any,
|
|
86785
|
+
* }>}
|
|
86786
|
+
* @param {string} [popupBackgroundColor] The popup's surface. Left out it is
|
|
86787
|
+
* the one every navi popup uses (`--navi-popup-background-color`, which
|
|
86788
|
+
* follows the theme) — the trigger's own `backgroundColor` is not it, so a
|
|
86789
|
+
* variant that paints the trigger transparent leaves the popup a real sheet.
|
|
86790
|
+
* @param {number|string} [popupBorderRadius] The popup's corners. Left out they
|
|
86791
|
+
* echo the trigger's `borderRadius`.
|
|
86792
|
+
* @param {string} [popupBoxShadow] The popup's shadow, popover or dialog. Left
|
|
86793
|
+
* out it is the one every navi popup casts. `"none"` for a popup that is
|
|
86794
|
+
* not a sheet — a card lifted out of a page draws its own edges, and the
|
|
86795
|
+
* popup only carries it (see `dialogSizeFromAnchor`).
|
|
86796
|
+
* @param {"press"|"longpress"|"contextmenu"|string|string[]} [openOn="press"]
|
|
86797
|
+
* What opens the popup. The press, by default — the way a native select
|
|
86798
|
+
* opens. Any other name is an interaction navi detects (see
|
|
86799
|
+
* docs/interactions.md: `"longpress"`, `"contextmenu"`, `"double_click"`…), or
|
|
86800
|
+
* a list of them — `["longpress", "contextmenu"]` is the hold on a phone and
|
|
86801
|
+
* the right click at a desk. The picker declares it on itself, so it is
|
|
86802
|
+
* arbitrated like any other gesture: a swipe on the same card takes the
|
|
86803
|
+
* press a hold would have taken. A tap then opens nothing — the press stays
|
|
86804
|
+
* free for what the `ui` holds: a link in the card navigates, a button in it
|
|
86805
|
+
* presses, a picker in it opens on its own click, with nothing to declare
|
|
86806
|
+
* (`selfInteractions` is for a drawing that DOES open on the press). The
|
|
86807
|
+
* trigger says so under the mouse too: it drops the hand cursor, which is
|
|
86808
|
+
* the press's promise, and every pixel of the drawing keeps the cursor it
|
|
86809
|
+
* would have on its own. That is what lets a whole card be a picker without
|
|
86810
|
+
* every touch on it opening the sheet — and the keyboard keeps its ways in
|
|
86811
|
+
* (Enter, Space, the arrows). A hold declared INSIDE the card (`interactions={{ longpress }}`
|
|
86812
|
+
* on something it holds) answers before this one: the nearer hold takes the
|
|
86813
|
+
* press, the way a click is the innermost target's. Being a gesture, the open goes through
|
|
86814
|
+
* the interaction gate as one: a `readOnly` picker refuses it and says so
|
|
86815
|
+
* where the finger is, whatever `openWhileReadOnly` says.
|
|
86816
|
+
* @param {number} [maxLines=1] How many lines the value may take before it is
|
|
86817
|
+
* cut with an ellipsis — `Text`'s `maxLines`, put on the value box, and
|
|
86818
|
+
* granted to a `BadgeList` in the `ui`, which caps its rows to it (see
|
|
86819
|
+
* max_lines_context.js). `variant="text"` and `variant="bare"` are not
|
|
86820
|
+
* clamped: their drawing is the caller's own, a `Text` in the `ui` cuts
|
|
86821
|
+
* itself with its own `maxLines`. See docs/typography.md.
|
|
86822
|
+
* @param {import("preact").ComponentChildren | "default" | import("preact").ComponentType} [ui] What the
|
|
86823
|
+
* trigger draws in place of the value's default rendering (a date, a list
|
|
86824
|
+
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
86825
|
+
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
86826
|
+
* this draws when the picker holds nothing may well be an answer ("no
|
|
86827
|
+
* filter", "anywhere"), so its empty look is the caller's. A `ui` must
|
|
86828
|
+
* therefore say on its own that the picker is empty, and take the same room
|
|
86829
|
+
* empty as it does filled. A <BadgeList> gets both from its `fallback` — the
|
|
86830
|
+
* placeholder text, as plain text, see docs/typography.md#badgelist.
|
|
86831
|
+
* `"default"` draws nothing in the slot at all.
|
|
86832
|
+
*
|
|
86833
|
+
* Under `variant="icon"` there is no value to draw, so this IS the trigger:
|
|
86834
|
+
* left out it is the icon the right slot would have shown (`rightSlotIcon`,
|
|
86835
|
+
* or the chevron), and given it is drawn instead — one icon of the caller's
|
|
86836
|
+
* own, and nothing else.
|
|
86837
|
+
*
|
|
86838
|
+
* Under `variant="bare"` it is the trigger as well, and the picker adds
|
|
86839
|
+
* nothing around it: whatever this draws is what the picker measures. Its
|
|
86840
|
+
* own padding, its own lines, its own corners — a `<Box>` here gives a
|
|
86841
|
+
* picker the exact box that `<Box>` has on its own. Anything interactive
|
|
86842
|
+
* inside it takes its clicks back with `data-self-interactions`, the press
|
|
86843
|
+
* everywhere else opening the popup. There is no right slot beside it either,
|
|
86844
|
+
* so what a field would have got from navi this drawing places itself: a
|
|
86845
|
+
* `<Picker.Clear />` where the cross belongs, laid out like anything else it
|
|
86846
|
+
* holds. What the picker catches the press over is that box and no more, so
|
|
86847
|
+
* a drawing that paints outside it on purpose — an affordance in the margin
|
|
86848
|
+
* beside it, out of the flow so the rest keeps its width — is drawn and
|
|
86849
|
+
* opens nothing; `pressPadding` is how far the press area follows it out
|
|
86850
|
+
* there. The chevron itself is the `chevron` prop's to draw and place.
|
|
86851
|
+
*
|
|
86852
|
+
* A drawing that follows what the picker HOLDS — the value the popup just
|
|
86853
|
+
* closed on, before the server has answered, the same state navi's own
|
|
86854
|
+
* default `ui` reads, rolled back if the action fails (`resetOnError`) —
|
|
86855
|
+
* reads it one of two ways. As a component, `ui={MyCard}`: the picker
|
|
86856
|
+
* renders it and hands it `value`, `loading` and `interactive` as props.
|
|
86857
|
+
* As an element, `ui={<MyCard game={game} />}`, with `usePickerState()`
|
|
86858
|
+
* inside it returning the same three — for a drawing that needs props of
|
|
86859
|
+
* its own as well. Either way a card shows the score the moment the sheet
|
|
86860
|
+
* leaves and takes it back on a refusal, with nothing held by the caller.
|
|
86861
|
+
* `loading` is the wait it wears meanwhile: navi draws its loading outline
|
|
86862
|
+
* on every trigger, two pixels out of the box. On a card-sized
|
|
86863
|
+
* `variant="bare"` that run sits on the card's own border and reads as a
|
|
86864
|
+
* border blinking; `loadingOutlineInset` moves it clear of it. And when the
|
|
86865
|
+
* picker's box is not the shape of what waits, `loadingOutline="custom"`
|
|
86866
|
+
* lets the drawing draw the wait itself, from that flag or from
|
|
86867
|
+
* `[data-loading]` on the picker root in css.
|
|
86868
|
+
* @param {boolean} [readOnly] Nothing in this picker can be changed — and it
|
|
86869
|
+
* still opens, so what is in the popup can be read: everything in there is
|
|
86870
|
+
* held read-only in turn, each control greying out and saying why on its own.
|
|
86871
|
+
* Which is the point of opening it at all: a picker's answer often exists
|
|
86872
|
+
* only in the shape the popup draws (a plan with one tile ringed, a wheel
|
|
86873
|
+
* stopped on a time), and the trigger's one line is a summary of it, not the
|
|
86874
|
+
* whole of it. The clear cross goes, closing commits nothing, and a `value`
|
|
86875
|
+
* the popup would otherwise have confirmed stays a suggestion.
|
|
86876
|
+
* A picker with no `children` opens the browser's own picker instead, and
|
|
86877
|
+
* that one cannot be held read-only — so it goes on refusing (see
|
|
86878
|
+
* PickerNative).
|
|
86879
|
+
* @param {boolean} [openWhileReadOnly=true] Pass false to make `readOnly`
|
|
86880
|
+
* refuse the open as well, for a popup with nothing to read: a form of
|
|
86881
|
+
* controls to fill in, a menu of gestures. Opening then only shows what is
|
|
86882
|
+
* refused, so the picker says why on the trigger instead — where the
|
|
86883
|
+
* interaction happened. A busy picker follows the same rule: it still
|
|
86884
|
+
* opens to be read while its action runs, and with `false` the press is
|
|
86885
|
+
* refused with the wait as its reason — what a card that is syncing wants
|
|
86886
|
+
* for the sheet that would write it again.
|
|
86887
|
+
* @param {boolean|string} [error] Something went wrong around this picker (its
|
|
86888
|
+
* content failed to load, its value could not be resolved…). Shown as a
|
|
86889
|
+
* callout on the trigger, open or closed — the caller has nothing to place.
|
|
86890
|
+
* Dismissing it discards that error; a new `error` value raises another one.
|
|
86891
|
+
* @param {string} [id] What the popup's open state is kept under in the
|
|
86892
|
+
* history entry: a screen left and come back to finds the picker open, and
|
|
86893
|
+
* in dialog mode the opening is an entry of its own, closed by the back
|
|
86894
|
+
* button before the screen is left. Left out, the key is a generated id,
|
|
86895
|
+
* which names one mount: the state survives neither leaving the screen nor a
|
|
86896
|
+
* reload. A picker whose popup leads somewhere (a link inside it) has one.
|
|
86897
|
+
* @param {"popover"|"dialog"|"callout"} [mode] Which popup the children open
|
|
86898
|
+
* in. Left out, a popover on a large screen and a dialog on a narrow one.
|
|
86899
|
+
* `"callout"` shows them in the picker's own callout — the speech bubble its
|
|
86900
|
+
* constraints speak in, with its status icon, its cross and its arrow on
|
|
86901
|
+
* the trigger — for a tooltip that opens on a press. It closes the way a
|
|
86902
|
+
* callout does: its cross, Escape, a click outside.
|
|
86903
|
+
* @param {"info"|"warning"|"error"|"success"|"none"} [calloutStatus]
|
|
86904
|
+
* `mode="callout"`: what the callout says about what it holds — its border,
|
|
86905
|
+
* its icon, and whether a click outside closes it (an "error" stays).
|
|
86906
|
+
* `"none"` is a plain tooltip: no icon, a neutral frame. "info" by default,
|
|
86907
|
+
* "none" under `variant="text"`. The trigger's default icon is the
|
|
86908
|
+
* callout's own status icon in that status (see `CalloutStatusIcon`) — left
|
|
86909
|
+
* out, the trigger is that icon; `variant="circle"` draws it round. The callout's arrow points at
|
|
86910
|
+
* the middle of the trigger (`data-callout-arrow-x="center"` unless the
|
|
86911
|
+
* picker says otherwise).
|
|
86912
|
+
* @param {boolean} [calloutIcon] `mode="callout"`: whether the callout shows
|
|
86913
|
+
* its status icon beside what it holds. On by default, off under
|
|
86914
|
+
* `variant="text"` — a word asks for a plain bubble — and never shown
|
|
86915
|
+
* without a status, whatever this says.
|
|
86916
|
+
* @param {boolean} [calloutCloseButton=true] `mode="callout"`: pass false to
|
|
86917
|
+
* take the cross off the callout. It still closes on Escape, a click
|
|
86918
|
+
* outside, and a `--navi-close` from what it holds — for a tooltip that is
|
|
86919
|
+
* read rather than dismissed. Off by nobody's default: a text trigger
|
|
86920
|
+
* keeps the cross unless told not to.
|
|
86921
|
+
* @param {boolean|number} [calloutCloseByScroll=false] `mode="callout"`: close
|
|
86922
|
+
* the callout once the page has scrolled under it — `true` for a short
|
|
86923
|
+
* distance, a number for that many pixels. For a tooltip read in place,
|
|
86924
|
+
* which scrolling away is done with.
|
|
86925
|
+
* @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
|
|
86926
|
+
* over the trigger, "nearby" leaves a small gap below it.
|
|
86927
|
+
* @param {string} [positionArea] Where the popup goes — relative to the trigger
|
|
86928
|
+
* in popover mode, relative to the viewport in dialog mode. Same grammar as
|
|
86929
|
+
* Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
|
|
86930
|
+
* …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
|
|
86931
|
+
* popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
|
|
86932
|
+
* popover still flips to the opposite side on its own when there isn't
|
|
86933
|
+
* enough room.
|
|
86934
|
+
* @param {boolean} [popupWidthFitContent] By default the popover is at least as
|
|
86935
|
+
* wide as the trigger. Set this to let the content size it instead, so a
|
|
86936
|
+
* popover narrower than the trigger stays narrow. Popover mode only — a
|
|
86937
|
+
* dialog never takes the trigger's width to begin with (see
|
|
86938
|
+
* `dialogMinWidth`).
|
|
86939
|
+
* @param {import("preact").ComponentChildren} [rightSlotIcon] What the right
|
|
86940
|
+
* slot draws in place of the chevron. It is the whole slot, not an addition
|
|
86941
|
+
* to it: the picker then no longer says on its own that it opens, so pass
|
|
86942
|
+
* something that does. Decoration only — it is wrapped in an `<Icon>`, which
|
|
86943
|
+
* is `aria-hidden`, so anything focusable in there would be reachable by tab
|
|
86944
|
+
* while invisible to assistive tech. Use `rightSlot` for that.
|
|
86945
|
+
*
|
|
86946
|
+
* The slot only exists beside a value. `variant="icon"` has none, so there is
|
|
86947
|
+
* no slot either: the icon becomes the trigger itself and is drawn as the
|
|
86948
|
+
* `ui` — which is also what a caller overrides to draw their own. Nothing
|
|
86949
|
+
* else is drawn there, the clear cross included: one icon has room for one
|
|
86950
|
+
* thing.
|
|
86951
|
+
|
|
86952
|
+
* `variant="bare"` has no slot either, for the same reason turned around: the
|
|
86953
|
+
* picker is the drawing's box to the pixel, so nothing may be added beside it
|
|
86954
|
+
* — the drawing holds what it needs (`<Picker.Clear />`) and places it. Its
|
|
86955
|
+
* `chevron` prop is the exception: the slot, put where the drawing is not.
|
|
86956
|
+
* @param {"end"|"start"|"hang-end"} [chevron] `variant="bare"` only: the
|
|
86957
|
+
* picker draws no chevron beside the drawing, so nothing says it opens; this
|
|
86958
|
+
* draws one — the icon the right slot would hold, `rightSlotIcon` or the
|
|
86959
|
+
* chevron, at `rightSlotIconSize`, `slotSpacing` away from the drawing (xxs
|
|
86960
|
+
* by default), taking the clear cross's place under `clearable` like any
|
|
86961
|
+
* slot — and says where, so the drawing keeps the box it has when nothing
|
|
86962
|
+
* opens. Which side depends on where the drawing is anchored in its line.
|
|
86963
|
+
* `"end"`: after it, in the flow — a label with room after it. `"start"`:
|
|
86964
|
+
* before it — a label pushed against the right edge, which the chevron
|
|
86965
|
+
* would otherwise push left by its width. `"hang-end"`: after it and OUT of
|
|
86966
|
+
* the flow, in the margin beside the box — a drawing filling its whole
|
|
86967
|
+
* line; the press follows it out there by its size plus its gap, on top of
|
|
86968
|
+
* any `pressPaddingRight`. The chevron is drawn in `--picker-icon-color`
|
|
86969
|
+
* (`currentColor` here) and `--picker-icon-color-hover`; a dimmed one is
|
|
86970
|
+
* `--picker-icon-color: color-mix(in srgb, currentColor 55%, transparent)`.
|
|
86971
|
+
* @param {import("preact").ComponentChildren} [rightSlot] Same place, rendered
|
|
86972
|
+
* as-is: no `<Icon>` around it, nothing `aria-hidden`. This is where an
|
|
86973
|
+
* interactive right slot goes. `null` takes the slot away entirely — the
|
|
86974
|
+
* gutter it carries with it, so the trigger measures its drawing and centers
|
|
86975
|
+
* on it.
|
|
86976
|
+
* @param {boolean} [picksNothing] The popup asks something rather than holding
|
|
86977
|
+
* an answer, so the trigger draws a label and never a value: no chevron, no
|
|
86978
|
+
* clear cross, in any variant — and the controls in the popup are their own
|
|
86979
|
+
* (a checkbox gating the yes), never the picker's value, so a bound `action`
|
|
86980
|
+
* keeps the params it was bound with. `type="confirm"` says it for itself; say it
|
|
86981
|
+
* for a popup of your own that is a menu of actions rather than a choice —
|
|
86982
|
+
* or a DOOR: a drawing that grows to be looked at (a weather scene, a plan),
|
|
86983
|
+
* with no `action` at all. The picker is then a trigger and a popup, and
|
|
86984
|
+
* nothing more: its close does nothing, and what it keeps from being a
|
|
86985
|
+
* control is what a door still needs — the role of a button, the keyboard
|
|
86986
|
+
* opening it, the anchor being itself, and one popup per instance without
|
|
86987
|
+
* an id to wire. See "The trigger wears the wait" in docs/popup_open.md for
|
|
86988
|
+
* when that beats a `Button` opening a shared `Dialog`.
|
|
86989
|
+
* @param {string|import("preact").ComponentChildren} [clearConfirm] The
|
|
86990
|
+
* question asked before the clear cross clears anything — the `message` of
|
|
86991
|
+
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
86992
|
+
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
86993
|
+
* as it was; answering yes clears and sends, and the picker's own action
|
|
86994
|
+
* receives the cleared value like any other choice. A cut (Ctrl+X) on such a
|
|
86995
|
+
* picker copies the value and leaves it in place: no clear without the
|
|
86996
|
+
* question.
|
|
86997
|
+
* @param {"icon"|"headless"|"discrete"|"button"|"text"|"bare"|"picker"} [variant]
|
|
86998
|
+
* How the trigger is drawn. `"button"` is a Button's drawing — surface,
|
|
86999
|
+
* padding, a centered label, no chevron — and what a `type="confirm"` picker
|
|
87000
|
+
* draws unless told otherwise: nothing is picked, the popup is a question.
|
|
87001
|
+
* `"text"` is a word in a sentence, underlined with dots, no box and no
|
|
87002
|
+
* slot: what `mode="callout"` uses for a callout on a term rather than on an
|
|
87003
|
+
* icon (`"icon"` being its default, with an info icon in place of the
|
|
87004
|
+
* chevron). `"bare"` draws no trigger of its own at all: the `ui` is it, and
|
|
87005
|
+
* the picker is exactly that drawing's box — no padding, no frame, no
|
|
87006
|
+
* control line, no clamp — for a picker that is a whole piece of a layout
|
|
87007
|
+
* rather than a field, and which must measure the same as the same drawing
|
|
87008
|
+
* placed anywhere else. No right slot comes with it: a cross belongs inside
|
|
87009
|
+
* that drawing, at the place and the size the drawing decides, so it is
|
|
87010
|
+
* `<Picker.Clear />` the `ui` holds rather than `clearable` the picker
|
|
87011
|
+
* takes. What navi still draws on that box — the focus ring, the loading
|
|
87012
|
+
* outline — follows the picker's `borderRadius`, which stays the control's
|
|
87013
|
+
* unless told: a drawing with corners of its own states them on the picker
|
|
87014
|
+
* as well. `"headless"` draws nothing either, and is not the `ui`'s box but
|
|
87015
|
+
* its parent's: it stretches to whatever positioned element holds it, for a
|
|
87016
|
+
* picker put inside the thing that opens it (a button, a row) so that the
|
|
87017
|
+
* popup hangs off that whole element. A picker opened from something it does
|
|
87018
|
+
* not sit in — an object on a map saying
|
|
87019
|
+
* `triggerNaviCommand(pickerEl, "--navi-open", event, { anchor, value })` —
|
|
87020
|
+
* therefore wants an element of its own to stretch into rather than the
|
|
87021
|
+
* nearest positioned ancestor it happens to find. `"picker"` (or an explicit
|
|
87022
|
+
* `variant={undefined}`) asks a confirm or callout picker for the field-like
|
|
87023
|
+
* drawing every other picker has.
|
|
87024
|
+
* @param {"custom"} [loadingOutline] Who draws the loading outline while the
|
|
87025
|
+
* action runs. Left unsaid, the picker does, around its own box. `"custom"`
|
|
87026
|
+
* says someone else does — what it means on a `Button` too — for a picker
|
|
87027
|
+
* that carries a wait drawn elsewhere: two nested pickers sharing one run
|
|
87028
|
+
* (a score picker drawn inside the card an edit picker wraps, see
|
|
87029
|
+
* `12_picker_card_demo.html`) are one wait, and one outline; and a
|
|
87030
|
+
* `variant="bare"` whose box is not
|
|
87031
|
+
* the shape of what waits (a card with a note pinned under it) leaves the
|
|
87032
|
+
* outline to the card, which knows its own box. The wait itself is still
|
|
87033
|
+
* worn: `aria-busy`, the busy refusal, the error callout, `[data-loading]`.
|
|
87034
|
+
* @param {number} [loadingOutlineInset=-2] Where the loading outline runs, in
|
|
87035
|
+
* px from the picker's box — negative is outside. A field wants it right on
|
|
87036
|
+
* its border; a card-sized trigger wants it a few pixels out, clear of the
|
|
87037
|
+
* card's own frame, so it reads as a run around the card rather than as its
|
|
87038
|
+
* border blinking.
|
|
87039
|
+
* @param {boolean} [icon] The trigger is one icon — the `ui`, or the chevron
|
|
87040
|
+
* when there is none. Its box is that icon's box plus the padding asked for
|
|
87041
|
+
* and nothing else: no right slot, and no control line to be as tall as, so
|
|
87042
|
+
* it measures the same as `<Button icon>` and stands in a row of icon
|
|
87043
|
+
* buttons. A shape, not a paint: the variant still decides what is drawn
|
|
87044
|
+
* around it, so `variant="discrete" icon` is the discrete hover wash on an
|
|
87045
|
+
* icon-sized square. `variant="icon"` is this shape with no surface at all.
|
|
87046
|
+
* @param {import("preact").ComponentChildren} [message] `type="confirm"`: the
|
|
87047
|
+
* question, in the popup's default body — text, or JSX when it needs an
|
|
87048
|
+
* emphasis, a link. Left out, a generic "are you sure?" in the current
|
|
87049
|
+
* language. To draw the whole popup instead, pass `children`: yes is then
|
|
87050
|
+
* whatever carries `--navi-confirm`, no whatever closes (`--navi-cancel`).
|
|
87051
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] `type="confirm"`,
|
|
87052
|
+
* default body: what the yes button reads. Defaults to the translated
|
|
87053
|
+
* "Confirm".
|
|
87054
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Same, for the no
|
|
87055
|
+
* button. Defaults to the translated "Cancel".
|
|
87056
|
+
* @param {"confirm"|"cancel"|"none"} [focusOnOpen="confirm"] `type="confirm"`,
|
|
87057
|
+
* default body: which button the keyboard lands on when the popup opens.
|
|
87058
|
+
* `"cancel"` is the careful choice before something that cannot be undone —
|
|
87059
|
+
* a stray Enter then answers no.
|
|
87060
|
+
* @param {(confirmEvent: CustomEvent) => void} [onConfirm] Called once the
|
|
87061
|
+
* popup has closed on a `--navi-confirm` said inside it. What a confirm
|
|
87062
|
+
* picker uses to run its press; a picker of any type may listen too.
|
|
87063
|
+
* @param {boolean} [canSendWhileUnchanged] Run `action` when a choice pressed
|
|
87064
|
+
* inside the popup — a row's `--navi-send`, a `--navi-confirm` — is the
|
|
87065
|
+
* value the picker already held. By default that close runs nothing: the
|
|
87066
|
+
* answer has not moved, so there is nothing new to say, same as `Form`.
|
|
87067
|
+
* Set this for a picker whose value is a setting that can be valid while
|
|
87068
|
+
* what it points to is not — a channel whose account came unlinked — so
|
|
87069
|
+
* picking it again is how the person asks for it to be put right, and the
|
|
87070
|
+
* action sees the same value and re-asks what there is to re-ask. A press
|
|
87071
|
+
* outside, Escape and the cross still send nothing: nothing was pressed.
|
|
87072
|
+
* @param {(openEvent: CustomEvent) => void} [onOpen] Called when the popup
|
|
87073
|
+
* opens, whatever asked for it (a press, a command, history bringing it
|
|
87074
|
+
* back). You most likely do NOT need this pair: "the user validated" is
|
|
87075
|
+
* already `action` — it fires on every close that keeps (click outside, the
|
|
87076
|
+
* cross, Enter, a `--navi-send` inside the popup) and never on a cancel.
|
|
87077
|
+
* Reach for `onOpen`/`onClose` only for the one thing `action` cannot say:
|
|
87078
|
+
* the popup being up at all, independent of the value — e.g. a derived
|
|
87079
|
+
* computation held while the sheet is open, released on close.
|
|
87080
|
+
* @param {(closeEvent: CustomEvent) => void} [onClose] Called when the popup
|
|
87081
|
+
* actually closes — not preventable, and (see `onOpen`) not the way to know
|
|
87082
|
+
* the user validated: that is `action`. `closeEvent.detail.isCancel` tells a
|
|
87083
|
+
* cancel (Escape, the back gesture) from a close that keeps. Fires after the
|
|
87084
|
+
* picker's value has settled — committed, or restored on a cancel — so the
|
|
87085
|
+
* value read in here is the one the picker ends up holding, and before the
|
|
87086
|
+
* popup leaves the history entry it pushed, so a route param cleared in here
|
|
87087
|
+
* is part of the url a close that keeps carries back.
|
|
87088
|
+
* @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
|
|
87089
|
+
* right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
|
|
87090
|
+
* cross. "inherit" takes the picker's own font size.
|
|
87091
|
+
* @param {number|string} [slotSpacing] Gap kept between what sits in the right
|
|
87092
|
+
* slot (the chevron, or the clear button) and the picker's own edge — same
|
|
87093
|
+
* prop name Input uses for its own slots. Accepts a spacing token ("s",
|
|
87094
|
+
* "m"…) like any other spacing prop, or a length. Defaults to half the
|
|
87095
|
+
* horizontal padding.
|
|
87096
|
+
* @param {number|string} [pressPadding] How far a press counts as the trigger
|
|
87097
|
+
* past the picker's own box — a spacing token ("s", "m"…) or a length, zero
|
|
87098
|
+
* by default. What the picker draws is transparent to the pointer: the press
|
|
87099
|
+
* lands on an invisible input covering the box, so the box is the whole
|
|
87100
|
+
* answer to "does this open". That is right while what is drawn stays inside
|
|
87101
|
+
* it, which is what `variant="bare"` measures. A `ui` that paints OUTSIDE
|
|
87102
|
+
* its box on purpose — a chevron pushed into the margin beside it, so the
|
|
87103
|
+
* label under it keeps the width it has when nothing opens — draws something
|
|
87104
|
+
* nobody can press, and this is how far the press area goes to reach it. The
|
|
87105
|
+
* press area only; nothing moves and nothing is drawn. Font-relative lengths
|
|
87106
|
+
* are read against the picker's own text, so "0.75em" is the drawing's em.
|
|
87107
|
+
* Only the sides that overhang: the area grows over whatever is next to the
|
|
87108
|
+
* picker, and takes the presses aimed at it.
|
|
87109
|
+
* @param {number|string} [pressPaddingX] Same, left and right only;
|
|
87110
|
+
* `pressPaddingY` top and bottom, `pressPaddingTop`/`Right`/`Bottom`/`Left`
|
|
87111
|
+
* one side each.
|
|
87112
|
+
* @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
|
|
87113
|
+
* (default 300px). The popover shrinks below it when space is tight.
|
|
87114
|
+
* @param {number|string} [dialogMinWidth] Floor on the dialog's width. A dialog
|
|
87115
|
+
* picker is not attached to its trigger and does not take its width (unlike
|
|
87116
|
+
* the popover, which uses it as a floor) — this is how a dialog too narrow
|
|
87117
|
+
* for what it holds is widened. Clamped by `dialogMaxWidth`/the container.
|
|
87118
|
+
* @param {number|string} [dialogMinHeight] Same, on the height.
|
|
87119
|
+
* @param {number|string} [dialogMaxWidth] Ceiling on the dialog's width, the
|
|
87120
|
+
* one `dialogExpand`/`dialogExpandX` grows up to — what makes an expanded
|
|
87121
|
+
* dialog a large sheet rather than a full screen. Capped in turn by the
|
|
87122
|
+
* container minus `marginWithContainer`. Describes the centered shape only:
|
|
87123
|
+
* a dialog docked by `dockedOnSmallTouchScreen` withdraws it and stays
|
|
87124
|
+
* container-wide, so both can be stated at once.
|
|
87125
|
+
* @param {number|string} [dialogMaxHeight] Same, on the height.
|
|
87126
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"lifting"|`slide-from-${string}`} [animation]
|
|
87127
|
+
* The popup's own (see Popover/Dialog). `"lifting"` is the one a picker
|
|
87128
|
+
* whose `ui` is a whole piece of the page wants: the popup is the browser
|
|
87129
|
+
* morphing the trigger's box into what carries `data-lift` inside the popup
|
|
87130
|
+
* (the popup itself when it carries it), and back on close. The trigger IS
|
|
87131
|
+
* the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
|
|
87132
|
+
* for a card that must keep its width on the way, left out for a drawing
|
|
87133
|
+
* that opens precisely to get bigger. It brings an opaque, blurred backdrop
|
|
87134
|
+
* with it (`--navi-backdrop-lift-*`); `backdropVariant="discrete"` asks for
|
|
87135
|
+
* the light wash back.
|
|
87136
|
+
* @param {Element|{current: Element}|string} [liftAnchor] Dialog mode,
|
|
87137
|
+
* Dialog's own: where a lifting close lands when that is not the trigger
|
|
87138
|
+
* (`animation="lifting"`, or `animation={{ open, close: "lifting" }}` for a
|
|
87139
|
+
* popup that opens the plain way and only lands on close),
|
|
87140
|
+
* read once the close is made (after `onClose`).
|
|
87141
|
+
* @param {"box"|"scene"} [lift="box"] Dialog's own, under `animation="lifting"`:
|
|
87142
|
+
* `"box"` for a card that extends (its top stays, the box uncovers the rest),
|
|
87143
|
+
* `"scene"` for a thumbnail that is a band cut from the middle of the bigger
|
|
87144
|
+
* view, as wide as it (each picture at the box's width, centred in it).
|
|
87145
|
+
* @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
|
|
87146
|
+
* @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
|
|
87147
|
+
* as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and
|
|
87148
|
+
* as a ceiling, unless a `dialogMaxWidth` says otherwise. What keeps a card
|
|
87149
|
+
* its own width once lifted out of the page (`animation="lifting"`, with
|
|
87150
|
+
* `data-lift` on the card inside the popup): it moves, and nothing else
|
|
87151
|
+
* about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
|
|
87152
|
+
* ceiling by hand: the popup props are written on the dialog element, where
|
|
87153
|
+
* that variable lives.
|
|
87154
|
+
* @param {boolean|"top"|"bottom"} [dockedOnSmallTouchScreen] Dialog mode,
|
|
87155
|
+
* Dialog's own: on a small touch screen the dialog becomes a sheet docked
|
|
87156
|
+
* full width to one edge — `true` or `"top"` the top edge, out of the
|
|
87157
|
+
* virtual keyboard's way; `"bottom"` the bottom one, for a sheet that is
|
|
87158
|
+
* read and tapped. Anywhere else it stays the centered box. Ignored in
|
|
87159
|
+
* popover mode.
|
|
87160
|
+
* @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
|
|
87161
|
+
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
87162
|
+
* picker with an explicit `id` (its opening being an entry of its own)
|
|
87163
|
+
* also goes back in history — so anything written to the url while it was
|
|
87164
|
+
* open (a route `stateSignal`, a search param) goes back with it. "close"
|
|
87165
|
+
* makes Escape say what clicking outside says: keep what was chosen,
|
|
87166
|
+
* close — a last resort, see
|
|
87167
|
+
* docs/popup_open.md#escape-cancels-the-other-gestures-keep for why Escape
|
|
87168
|
+
* should go on meaning cancel, and
|
|
87169
|
+
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
87170
|
+
* back.
|
|
87171
|
+
* @param {import("preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
87172
|
+
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
87173
|
+
* trigger is one piece of a bigger control (the chevron half of a split
|
|
87174
|
+
* button) points at the whole control, so the popup lines up with it and is
|
|
87175
|
+
* at least as wide as it.
|
|
87176
|
+
* @param {boolean} [standalone] - This picker answers for itself: it does not
|
|
87177
|
+
* register with the control group around it, so what it holds never becomes
|
|
87178
|
+
* part of that group's value and nothing that group does — distributing,
|
|
87179
|
+
* resetting, validating — reaches it. For a door that only opens something,
|
|
87180
|
+
* and for a picker that commits on its own inside a form.
|
|
87181
|
+
* @param {"close"|"cancel"|"capture"} [pressOutside="close"]
|
|
87182
|
+
* What a press outside the popup does: close and keep ("close"), close and
|
|
87183
|
+
* put back the value at open ("cancel"), or nothing at all ("capture"). The
|
|
87184
|
+
* default is what gives a popup with no confirm button its way out that
|
|
87185
|
+
* keeps — see the same section.
|
|
87186
|
+
* @param {boolean} [backdrop=true] Whether anything is laid between the popup
|
|
87187
|
+
* and the page at all. `false` lets a press outside both close the popup and
|
|
87188
|
+
* reach whatever it landed on, in one gesture — for a picker opened over a
|
|
87189
|
+
* page that stays as pressable as it looks (a plan, a map, a canvas), where a
|
|
87190
|
+
* wall would spend the first press on dismissing. Honoured in every mode: a
|
|
87191
|
+
* dialog with no wall is shown through the Popover API rather than
|
|
87192
|
+
* `showModal()`, so a picker docked as a bottom sheet on a phone still
|
|
87193
|
+
* leaves the plan behind it live. `false` by default under `mode="callout"`,
|
|
87194
|
+
* a bubble that dims nothing; `true` there spends the press outside on
|
|
87195
|
+
* closing it — the link under it not followed, the card under it not held —
|
|
87196
|
+
* without a wall: the scroll a finger starts beside the bubble still
|
|
87197
|
+
* scrolls. Not read inside a `<PopupGroup>`, whose wall it is.
|
|
87198
|
+
* @param {"auto"|"lift"|"discrete"|"invisible"} [backdropVariant="auto"] How
|
|
87199
|
+
* visible the popup's backdrop is, independently of what a click outside
|
|
87200
|
+
* does: `"auto"` is the paint `pressOutside` implies,
|
|
87201
|
+
* `"lift"` the opaque blurred wall `animation="lifting"` brings, asked for
|
|
87202
|
+
* without the movement, `"discrete"` a barely-there dim, `"invisible"` fully
|
|
87203
|
+
* transparent — a wall that is not seen is still a wall, which is what
|
|
87204
|
+
* `backdrop` above answers. For a picker that closes on an outside click
|
|
87205
|
+
* without wanting to dim the page for it.
|
|
87206
|
+
* @param {string} [backdropColor] The wash the popup paints over what is
|
|
87207
|
+
* behind, for this picker alone. See Dialog's own doc.
|
|
87208
|
+
* @param {string} [backdropFilter] What that wash does to the picture
|
|
87209
|
+
* underneath (`"blur(4px)"`), independently of what a click outside does.
|
|
87210
|
+
* See Dialog's own doc.
|
|
87211
|
+
* @param {number|string} [marginWithContainer] Minimum gap kept between the
|
|
87212
|
+
* popup and the edges of what contains it (the viewport, or the picker's own
|
|
87213
|
+
* positioned ancestor for `popupLayer="local"`). Caps the popup's size as
|
|
87214
|
+
* well as its placement, so what an expanded dialog leaves visible around
|
|
87215
|
+
* itself is set here. Defaults to `popoverSpacing` in popover mode, and to
|
|
87216
|
+
* Dialog's own in dialog mode: `3appw` in the top layer, `3cqw` under
|
|
87217
|
+
* `popupLayer="local"`, 0 for a dialog docked by
|
|
87218
|
+
* `dockedOnSmallTouchScreen`.
|
|
87219
|
+
* @param {string} [popupTestId] The `data-testid` of the popup element — the
|
|
87220
|
+
* popover or dialog the children open in. A `data-testid` on the picker
|
|
87221
|
+
* itself names the trigger (see docs/testid.md); this one is for a test that
|
|
87222
|
+
* needs the popup's own box — its surface, its position, its backdrop — and
|
|
87223
|
+
* not only what it holds, which a testid inside the children already names.
|
|
87224
|
+
* Under `mode="callout"` it names the callout element — the control's own,
|
|
87225
|
+
* shared with its constraint messages — while the children are shown in it.
|
|
87226
|
+
* @param {string} [popupAriaLabelledBy] The id(s) of what names the popup:
|
|
87227
|
+
* `aria-labelledby` on the popup element, which is a dialog in either mode
|
|
87228
|
+
* (the trigger says `aria-haspopup="dialog"`). `aria-label`/`aria-labelledby`
|
|
87229
|
+
* on the picker itself name the trigger. Rarely needed: a `<Popup.Title>`
|
|
87230
|
+
* in the children names the popup with nothing to wire, and without one
|
|
87231
|
+
* the popup takes the trigger's own name — the picker's `aria-labelledby`,
|
|
87232
|
+
* the label a picker that picks nothing draws, its `aria-label`, or the
|
|
87233
|
+
* `<Label>`/`<label for>` pointing at it (one that has an id, and does not
|
|
87234
|
+
* wrap the picker). A `mode="callout"` popup is read as its message.
|
|
87235
|
+
* @param {string} [popupAriaLabel] The same, as a string, for a popup whose
|
|
87236
|
+
* title is not an element.
|
|
87237
|
+
* @param {string} [popupAriaDescribedBy] The id(s) of what describes the
|
|
87238
|
+
* popup: `aria-describedby` on the popup element.
|
|
87239
|
+
* @param {string} [confirmTestId] `type="confirm"`, default body: the
|
|
87240
|
+
* `data-testid` of the yes button; `cancelTestId` that of the no button.
|
|
87241
|
+
* Both are elements navi builds, which a test may not name by class (see
|
|
87242
|
+
* docs/testid.md).
|
|
87243
|
+
* @param {string} [cancelTestId]
|
|
87244
|
+
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [mount] The popup's own
|
|
87245
|
+
* (see Popup): when the children are built and thrown away. Left out, a
|
|
87246
|
+
* picker told no value holds what the control they hold holds, and builds
|
|
87247
|
+
* them as soon as that value is read: right away (`"always"`) when navi draws
|
|
87248
|
+
* it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
|
|
87249
|
+
* cross, or when a form or group around the picker collects it; when an
|
|
87250
|
+
* element `ui` calls `usePickerState()`. Read by nothing, it waits for the
|
|
87251
|
+
* first open, like a picker told a value. A `picksNothing` picker (and so
|
|
87252
|
+
* `type="confirm"`) never builds them early: its popup holds no value, there
|
|
87253
|
+
* is nothing to read.
|
|
87254
|
+
*/
|
|
85754
87255
|
// Assembled in pure expressions rather than written onto Picker afterwards
|
|
85755
87256
|
// (`Picker.Chip = PickerChip`): what createComponentResolver returns is an object
|
|
85756
87257
|
// a bundler cannot see through, so a property assigned to it later is a side
|
|
@@ -86007,7 +87508,7 @@ const css$q = /* css */`@layer navi {
|
|
|
86007
87508
|
const TRAVELS_NONE = [];
|
|
86008
87509
|
|
|
86009
87510
|
/**
|
|
86010
|
-
* @type {import("
|
|
87511
|
+
* @type {import("preact").FunctionComponent<{
|
|
86011
87512
|
* value?: any,
|
|
86012
87513
|
* defaultValue?: any,
|
|
86013
87514
|
* signal?: import("@preact/signals").Signal<any>,
|
|
@@ -86023,7 +87524,7 @@ const TRAVELS_NONE = [];
|
|
|
86023
87524
|
* fallbackValue?: any,
|
|
86024
87525
|
* valueAtStep: (value: any, count: number) => any,
|
|
86025
87526
|
* compareValues?: (a: any, b: any) => number,
|
|
86026
|
-
* renderValue?: (value: any, context: { maxLines?: number }) => import("
|
|
87527
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("preact").ComponentChildren,
|
|
86027
87528
|
* controlProps?: object,
|
|
86028
87529
|
* duration?: number,
|
|
86029
87530
|
* vertical?: boolean,
|
|
@@ -86810,14 +88311,14 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
86810
88311
|
* single `value`/`signal` like any other control. `TimeSpin` is that, for a
|
|
86811
88312
|
* time of day.
|
|
86812
88313
|
*
|
|
86813
|
-
* @type {import("
|
|
88314
|
+
* @type {import("preact").FunctionComponent<{
|
|
86814
88315
|
* name?: string,
|
|
86815
88316
|
* value?: any,
|
|
86816
88317
|
* defaultValue?: any,
|
|
86817
88318
|
* signal?: import("@preact/signals").Signal<any>,
|
|
86818
88319
|
* aggregateChildStates?: (childUIStateControllers: any[]) => any,
|
|
86819
88320
|
* distributeChildUIState?: (groupState: any, childUIStateController: any) => any,
|
|
86820
|
-
* children?: import("
|
|
88321
|
+
* children?: import("preact").ComponentChildren,
|
|
86821
88322
|
* [key: string]: any,
|
|
86822
88323
|
* }>}
|
|
86823
88324
|
* Everything a box takes is taken here too — `width`, `borderWidth`,
|
|
@@ -86920,7 +88421,7 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
86920
88421
|
* above and below it by default — sideways they would be where the caret
|
|
86921
88422
|
* moves — with the bigger number up top.
|
|
86922
88423
|
*
|
|
86923
|
-
* @type {import("
|
|
88424
|
+
* @type {import("preact").FunctionComponent<{
|
|
86924
88425
|
* value?: number|string,
|
|
86925
88426
|
* defaultValue?: number|string,
|
|
86926
88427
|
* min?: number,
|
|
@@ -87025,7 +88526,7 @@ const numberAtStep = (value, count, {
|
|
|
87025
88526
|
* A day one steps through: the date plus what it is to today when there is a
|
|
87026
88527
|
* word for it, and a calendar behind it for the day that is far from here.
|
|
87027
88528
|
*
|
|
87028
|
-
* @type {import("
|
|
88529
|
+
* @type {import("preact").FunctionComponent<{
|
|
87029
88530
|
* value?: string,
|
|
87030
88531
|
* defaultValue?: string,
|
|
87031
88532
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -87036,7 +88537,7 @@ const numberAtStep = (value, count, {
|
|
|
87036
88537
|
* maxMessage?: any,
|
|
87037
88538
|
* step?: number,
|
|
87038
88539
|
* lang?: string,
|
|
87039
|
-
* renderDay?: (day: string) => import("
|
|
88540
|
+
* renderDay?: (day: string) => import("preact").ComponentChildren,
|
|
87040
88541
|
* [key: string]: any,
|
|
87041
88542
|
* }>}
|
|
87042
88543
|
* @param {string} [value] The day shown, as "YYYY-MM-DD".
|
|
@@ -87047,7 +88548,7 @@ const numberAtStep = (value, count, {
|
|
|
87047
88548
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
87048
88549
|
* Long by default, since this is a control one reads rather than a column
|
|
87049
88550
|
* one scans; `short` where the room is not there.
|
|
87050
|
-
* @param {(day: string) => import("
|
|
88551
|
+
* @param {(day: string) => import("preact").ComponentChildren} [renderDay] What
|
|
87051
88552
|
* to write for a day. Defaults to the date plus what it is to today when
|
|
87052
88553
|
* there is a word for it ("samedi 8 août (demain)"), since a day near now is
|
|
87053
88554
|
* read as a distance from now before it is read as a date.
|
|
@@ -87164,7 +88665,7 @@ const HOUR_MAX = 23;
|
|
|
87164
88665
|
const MINUTE_MAX = 59;
|
|
87165
88666
|
|
|
87166
88667
|
/**
|
|
87167
|
-
* @type {import("
|
|
88668
|
+
* @type {import("preact").FunctionComponent<{
|
|
87168
88669
|
* name?: string,
|
|
87169
88670
|
* value?: string,
|
|
87170
88671
|
* defaultValue?: string,
|
|
@@ -87172,7 +88673,7 @@ const MINUTE_MAX = 59;
|
|
|
87172
88673
|
* minuteStep?: number,
|
|
87173
88674
|
* pad?: number,
|
|
87174
88675
|
* loop?: boolean,
|
|
87175
|
-
* separator?: import("
|
|
88676
|
+
* separator?: import("preact").ComponentChildren,
|
|
87176
88677
|
* hourLabel?: string,
|
|
87177
88678
|
* minuteLabel?: string,
|
|
87178
88679
|
* [key: string]: any,
|
|
@@ -87185,7 +88686,7 @@ const MINUTE_MAX = 59;
|
|
|
87185
88686
|
* of a day to stop at. Say `loop={false}` for two ends one cannot step past.
|
|
87186
88687
|
* @param {number} [pad=2] How many digits an hour and a minute are written on:
|
|
87187
88688
|
* a clock says "07:00", never "7:0". Say `pad={0}` for the bare numbers.
|
|
87188
|
-
* @param {import("
|
|
88689
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
87189
88690
|
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
87190
88691
|
* Everything a box takes is taken here too — `width`, `borderRadius`, `size`.
|
|
87191
88692
|
*/
|
|
@@ -87251,7 +88752,7 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87251
88752
|
};
|
|
87252
88753
|
|
|
87253
88754
|
/**
|
|
87254
|
-
* @type {import("
|
|
88755
|
+
* @type {import("preact").FunctionComponent<{
|
|
87255
88756
|
* name?: string,
|
|
87256
88757
|
* value?: { start?: string, end?: string },
|
|
87257
88758
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -87261,13 +88762,13 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87261
88762
|
* timeProps?: object,
|
|
87262
88763
|
* loop?: boolean,
|
|
87263
88764
|
* size?: string,
|
|
87264
|
-
* startLabel?: import("
|
|
87265
|
-
* endLabel?: import("
|
|
88765
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
88766
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
87266
88767
|
* [key: string]: any,
|
|
87267
88768
|
* }>}
|
|
87268
88769
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
87269
88770
|
* "HH:MM".
|
|
87270
|
-
* @param {import("
|
|
88771
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
87271
88772
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
87272
88773
|
* `null` for neither.
|
|
87273
88774
|
* @param {number} [minuteStep=1] How many minutes a press on a minute chevron
|
|
@@ -87364,7 +88865,7 @@ const css$p = /* css */`.navi_checkbox_group {
|
|
|
87364
88865
|
*
|
|
87365
88866
|
* See `<RadioGroup>` for the same shape holding a single value.
|
|
87366
88867
|
*
|
|
87367
|
-
* @type {import("
|
|
88868
|
+
* @type {import("preact").FunctionComponent<{
|
|
87368
88869
|
* maxLength?: number,
|
|
87369
88870
|
* maxLengthGuard?: number,
|
|
87370
88871
|
* [key: string]: any,
|
|
@@ -87425,6 +88926,19 @@ const CheckboxGroupInterface = props => {
|
|
|
87425
88926
|
});
|
|
87426
88927
|
};
|
|
87427
88928
|
|
|
88929
|
+
/**
|
|
88930
|
+
* @type {import("preact").FunctionComponent<{
|
|
88931
|
+
* unit: string,
|
|
88932
|
+
* plural?: boolean,
|
|
88933
|
+
* format?: "long" | "short" | "narrow",
|
|
88934
|
+
* lang?: string,
|
|
88935
|
+
* label?: string,
|
|
88936
|
+
* size?: string,
|
|
88937
|
+
* sizeRatio?: number,
|
|
88938
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
88939
|
+
* [key: string]: any,
|
|
88940
|
+
* }>}
|
|
88941
|
+
*/
|
|
87428
88942
|
const Unit = ({
|
|
87429
88943
|
unit,
|
|
87430
88944
|
plural,
|
|
@@ -88182,7 +89696,7 @@ const css$m = /* css */`.navi_input.navi_select {
|
|
|
88182
89696
|
`;
|
|
88183
89697
|
|
|
88184
89698
|
/**
|
|
88185
|
-
* @type {import("
|
|
89699
|
+
* @type {import("preact").FunctionComponent<{
|
|
88186
89700
|
* value?: string,
|
|
88187
89701
|
* defaultValue?: string,
|
|
88188
89702
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -88261,30 +89775,40 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88261
89775
|
|
|
88262
89776
|
& > .navi_group {
|
|
88263
89777
|
flex: auto;
|
|
89778
|
+
|
|
89779
|
+
& > .navi_button {
|
|
89780
|
+
flex: auto;
|
|
89781
|
+
}
|
|
88264
89782
|
}
|
|
88265
89783
|
}
|
|
88266
89784
|
|
|
88267
89785
|
.navi_split_button_menu {
|
|
89786
|
+
--border-width: 0px;
|
|
88268
89787
|
display: flex;
|
|
88269
89788
|
position: relative;
|
|
89789
|
+
|
|
89790
|
+
& > .navi_button {
|
|
89791
|
+
margin-left: calc(var(--border-width) * -1);
|
|
89792
|
+
}
|
|
88270
89793
|
}
|
|
88271
89794
|
`;
|
|
88272
89795
|
|
|
88273
89796
|
/**
|
|
88274
|
-
* @type {import("
|
|
88275
|
-
* options: Array<{value: any, label: import("
|
|
89797
|
+
* @type {import("preact").FunctionComponent<{
|
|
89798
|
+
* options: Array<{value: any, label: import("preact").ComponentChildren, [key: string]: any}>,
|
|
88276
89799
|
* value?: any,
|
|
88277
89800
|
* defaultValue?: any,
|
|
88278
|
-
* label?: import("
|
|
88279
|
-
* action?: (value: any, event: Event) => void | Promise<void>,
|
|
89801
|
+
* label?: import("preact").ComponentChildren,
|
|
89802
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void | Promise<void>,
|
|
88280
89803
|
* command?: string,
|
|
88281
89804
|
* commandFor?: string,
|
|
88282
89805
|
* prefetch?: boolean,
|
|
88283
|
-
* onValueChange?: (value: any, event: Event) => void,
|
|
89806
|
+
* onValueChange?: (value: any, { event }: { event: Event }) => void,
|
|
88284
89807
|
* chooseEffect?: "select" | "run",
|
|
88285
89808
|
* menuLabel?: string,
|
|
88286
|
-
* menuIcon?: import("
|
|
89809
|
+
* menuIcon?: import("preact").ComponentChildren,
|
|
88287
89810
|
* menuIconSize?: number | string,
|
|
89811
|
+
* menuTestId?: string,
|
|
88288
89812
|
* loading?: boolean,
|
|
88289
89813
|
* readOnly?: boolean,
|
|
88290
89814
|
* disabled?: boolean,
|
|
@@ -88320,19 +89844,21 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88320
89844
|
* popupTestId?: string,
|
|
88321
89845
|
* [key: string]: any,
|
|
88322
89846
|
* }>}
|
|
88323
|
-
* @param {Array<{value: any, label: import("
|
|
89847
|
+
* @param {Array<{value: any, label: import("preact").ComponentChildren}>} options
|
|
88324
89848
|
* The ways of doing the thing, in the order the menu lists them. Anything
|
|
88325
89849
|
* else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
|
|
88326
89850
|
* @param {any} [value] Which entry the button stands for — the one it runs, and
|
|
88327
89851
|
* the one it is named after. `defaultValue` is the uncontrolled form; without
|
|
88328
89852
|
* either, the first entry.
|
|
88329
|
-
* @param {import("
|
|
89853
|
+
* @param {import("preact").ComponentChildren} [label] What the button says, when
|
|
88330
89854
|
* that is not the name of the entry it stands for — a button that keeps one
|
|
88331
89855
|
* name whatever the menu does.
|
|
88332
|
-
* @param {(value: any, event
|
|
88333
|
-
* entry given. Pressed on the button it is the entry the button stands
|
|
88334
|
-
* with `chooseEffect="run"` it is also the entry just chosen.
|
|
88335
|
-
*
|
|
89856
|
+
* @param {(value: any, { event, signal }) => void} [action] Do the thing, for
|
|
89857
|
+
* the entry given. Pressed on the button it is the entry the button stands
|
|
89858
|
+
* for; with `chooseEffect="run"` it is also the entry just chosen. Run as a
|
|
89859
|
+
* navi action: the second argument is the run's (see docs/actions.md), the
|
|
89860
|
+
* press being its `event`. Awaited: the whole split button is busy until it
|
|
89861
|
+
* settles.
|
|
88336
89862
|
* @param {string} [command] What a press on the button does once its `action`
|
|
88337
89863
|
* is done, as on a `<Button>` (with `commandFor`, and `command-value` when
|
|
88338
89864
|
* what the command is about is not the entry the button stands for). A
|
|
@@ -88346,12 +89872,14 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88346
89872
|
* menu of one-offs around a primary action.
|
|
88347
89873
|
* @param {string} [menuLabel] What the chevron half is called, for a screen
|
|
88348
89874
|
* reader and for the tooltip a long press raises.
|
|
88349
|
-
* @param {import("
|
|
89875
|
+
* @param {import("preact").ComponentChildren} [menuIcon] What the chevron half
|
|
88350
89876
|
* draws, in place of the chevron. It is the whole of what that half says, so
|
|
88351
89877
|
* pass something that still reads as "there is more here" — and say what,
|
|
88352
89878
|
* through `menuLabel`.
|
|
88353
89879
|
* @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
|
|
88354
89880
|
* the button's own font size.
|
|
89881
|
+
* @param {string} [menuTestId] The `data-testid` of the chevron half, which
|
|
89882
|
+
* navi builds and the caller has no other way to name.
|
|
88355
89883
|
* @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
|
|
88356
89884
|
* picker left to itself turns into a dialog on a small screen; a split
|
|
88357
89885
|
* button's menu stays hung off the button there, so this says popover unless
|
|
@@ -88372,8 +89900,12 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88372
89900
|
* `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
|
|
88373
89901
|
* An `aria-*` attribute describes the button and reaches the main half —
|
|
88374
89902
|
* `aria-pressed` for a split button whose main half toggles a mode;
|
|
88375
|
-
* `aria-hidden` excepted, which hides the whole control.
|
|
88376
|
-
*
|
|
89903
|
+
* `aria-hidden` excepted, which hides the whole control. How the words are
|
|
89904
|
+
* written (`size`, `bold`, `lineHeight`, `noWrap`…) reaches both halves, and so
|
|
89905
|
+
* does vertical padding (`paddingY`, `paddingTop`, `paddingBottom`); the rest
|
|
89906
|
+
* of the padding is the main half's — the chevron half keeps its own, which
|
|
89907
|
+
* gives it its width. Anything else (`className`, `width`, margins, `data-*`)
|
|
89908
|
+
* lands on the split button's own box.
|
|
88377
89909
|
*/
|
|
88378
89910
|
const SplitButton = props => {
|
|
88379
89911
|
import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
@@ -88392,6 +89924,7 @@ const SplitButton = props => {
|
|
|
88392
89924
|
menuLabel = buttonI18n("button.more_actions"),
|
|
88393
89925
|
menuIcon = jsx(ChevronDownSvg$1, {}),
|
|
88394
89926
|
menuIconSize,
|
|
89927
|
+
menuTestId,
|
|
88395
89928
|
loading,
|
|
88396
89929
|
readOnly,
|
|
88397
89930
|
disabled,
|
|
@@ -88411,11 +89944,13 @@ const SplitButton = props => {
|
|
|
88411
89944
|
...rest
|
|
88412
89945
|
} = props;
|
|
88413
89946
|
// Everything the popup answers to travels to the Picker; an aria-* describes
|
|
88414
|
-
// the button a screen reader meets, which is the main half;
|
|
88415
|
-
//
|
|
88416
|
-
//
|
|
88417
|
-
//
|
|
88418
|
-
|
|
89947
|
+
// the button a screen reader meets, which is the main half; how the words are
|
|
89948
|
+
// written and how far they sit from the edge belong to the halves, since a
|
|
89949
|
+
// <button> inherits neither from the box around it; everything else is the
|
|
89950
|
+
// split button's own box (margins, width, data-*). Sorted by name rather
|
|
89951
|
+
// than named one by one so a Picker popup prop is forwarded by adding it to
|
|
89952
|
+
// that list, not by threading it through here.
|
|
89953
|
+
const [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps] = sortRestProps(rest);
|
|
88419
89954
|
// A split button is a control on the page, not a place one goes: its menu
|
|
88420
89955
|
// hangs off it even on a phone, where a picker left to itself would decide a
|
|
88421
89956
|
// small screen means a dialog. Passing mode="dialog" asks for that back.
|
|
@@ -88460,6 +89995,7 @@ const SplitButton = props => {
|
|
|
88460
89995
|
borderColor,
|
|
88461
89996
|
borderWidth,
|
|
88462
89997
|
borderRadius,
|
|
89998
|
+
...halfStyleProps,
|
|
88463
89999
|
pressEffect,
|
|
88464
90000
|
loading: loadingResolved,
|
|
88465
90001
|
readOnly,
|
|
@@ -88469,7 +90005,7 @@ const SplitButton = props => {
|
|
|
88469
90005
|
};
|
|
88470
90006
|
return jsxs(Box, {
|
|
88471
90007
|
ref: rootRef,
|
|
88472
|
-
|
|
90008
|
+
baseClassName: "navi_split_button",
|
|
88473
90009
|
borderRadius: borderRadius,
|
|
88474
90010
|
...boxProps,
|
|
88475
90011
|
children: [jsx(LoadingOutline, {
|
|
@@ -88486,6 +90022,7 @@ const SplitButton = props => {
|
|
|
88486
90022
|
"command-value": commandValue,
|
|
88487
90023
|
prefetch: prefetch,
|
|
88488
90024
|
...buttonAriaProps,
|
|
90025
|
+
...mainPaddingProps,
|
|
88489
90026
|
...halfProps,
|
|
88490
90027
|
children: label === undefined ? optionShown?.label : label
|
|
88491
90028
|
}), jsxs("div", {
|
|
@@ -88495,6 +90032,7 @@ const SplitButton = props => {
|
|
|
88495
90032
|
paddingX: "s",
|
|
88496
90033
|
expandY: true,
|
|
88497
90034
|
"aria-label": menuLabel,
|
|
90035
|
+
"data-testid": menuTestId,
|
|
88498
90036
|
command: "--navi-open",
|
|
88499
90037
|
commandFor: menuId,
|
|
88500
90038
|
...halfProps,
|
|
@@ -88574,9 +90112,16 @@ const SplitButton = props => {
|
|
|
88574
90112
|
// a caller reaches all of them through the split button (see picker.jsx's JSDoc
|
|
88575
90113
|
// for what each one says).
|
|
88576
90114
|
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"]);
|
|
90115
|
+
// Vertical padding is said about both halves so they stay the same height; the
|
|
90116
|
+
// horizontal one is the main half's alone, the chevron half's own (paddingX="s")
|
|
90117
|
+
// being what gives it its width.
|
|
90118
|
+
const HALF_PADDING_PROP_SET = new Set(["paddingY", "paddingTop", "paddingBottom"]);
|
|
90119
|
+
const MAIN_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingLeft", "paddingRight"]);
|
|
88577
90120
|
const sortRestProps = props => {
|
|
88578
90121
|
const popupProps = {};
|
|
88579
90122
|
const buttonAriaProps = {};
|
|
90123
|
+
const halfStyleProps = {};
|
|
90124
|
+
const mainPaddingProps = {};
|
|
88580
90125
|
const boxProps = {};
|
|
88581
90126
|
for (const key of Object.keys(props)) {
|
|
88582
90127
|
if (POPUP_PROP_SET.has(key)) {
|
|
@@ -88585,11 +90130,15 @@ const sortRestProps = props => {
|
|
|
88585
90130
|
// aria-hidden hides a subtree rather than describing a button: on the
|
|
88586
90131
|
// main half alone it would leave the chevron half exposed.
|
|
88587
90132
|
buttonAriaProps[key] = props[key];
|
|
90133
|
+
} else if (isTypoProp(key) || HALF_PADDING_PROP_SET.has(key)) {
|
|
90134
|
+
halfStyleProps[key] = props[key];
|
|
90135
|
+
} else if (MAIN_PADDING_PROP_SET.has(key)) {
|
|
90136
|
+
mainPaddingProps[key] = props[key];
|
|
88588
90137
|
} else {
|
|
88589
90138
|
boxProps[key] = props[key];
|
|
88590
90139
|
}
|
|
88591
90140
|
}
|
|
88592
|
-
return [popupProps, buttonAriaProps, boxProps];
|
|
90141
|
+
return [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps];
|
|
88593
90142
|
};
|
|
88594
90143
|
|
|
88595
90144
|
/**
|
|
@@ -89791,7 +91340,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89791
91340
|
* value, the value lives in one hidden input for the form. The visible rows are
|
|
89792
91341
|
* presentational.
|
|
89793
91342
|
*
|
|
89794
|
-
* @type {import("
|
|
91343
|
+
* @type {import("preact").FunctionComponent<{
|
|
89795
91344
|
* value?: any,
|
|
89796
91345
|
* defaultValue?: any,
|
|
89797
91346
|
* action?: (value: any) => void,
|
|
@@ -89809,7 +91358,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89809
91358
|
* readOnly?: boolean,
|
|
89810
91359
|
* disabled?: boolean,
|
|
89811
91360
|
* loading?: boolean,
|
|
89812
|
-
* children?: import("
|
|
91361
|
+
* children?: import("preact").ComponentChildren,
|
|
89813
91362
|
* [key: string]: any,
|
|
89814
91363
|
* }>}
|
|
89815
91364
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
@@ -90437,8 +91986,9 @@ function WheelUI(props) {
|
|
|
90437
91986
|
}, {
|
|
90438
91987
|
controlType: "input",
|
|
90439
91988
|
// The host is the hidden input carrying the value: the container is
|
|
90440
|
-
// what is focused and spun.
|
|
90441
|
-
listenersOn: "root"
|
|
91989
|
+
// what is focused and spun, and the spinbutton its name has to name.
|
|
91990
|
+
listenersOn: "root",
|
|
91991
|
+
nameOn: "root"
|
|
90442
91992
|
});
|
|
90443
91993
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
90444
91994
|
const {
|
|
@@ -91288,9 +92838,9 @@ const WheelWindow = ({
|
|
|
91288
92838
|
* of rows and fills each from this tracked data — so the value list is the source
|
|
91289
92839
|
* of truth, not a per-item DOM node. Selection lives on the wheel's value.
|
|
91290
92840
|
*
|
|
91291
|
-
* @type {import("
|
|
92841
|
+
* @type {import("preact").FunctionComponent<{
|
|
91292
92842
|
* value: any,
|
|
91293
|
-
* children?: import("
|
|
92843
|
+
* children?: import("preact").ComponentChildren,
|
|
91294
92844
|
* [key: string]: any,
|
|
91295
92845
|
* }>}
|
|
91296
92846
|
*/
|
|
@@ -91325,9 +92875,9 @@ Wheel.Item = WheelItem;
|
|
|
91325
92875
|
* each Wheel.Item (it's a Box: paddingX="0.5ch") so the gap stays scrollable.
|
|
91326
92876
|
* `spacing` is just the Box flex gap between children (a layout gap, not padding).
|
|
91327
92877
|
*
|
|
91328
|
-
* @type {import("
|
|
92878
|
+
* @type {import("preact").FunctionComponent<{
|
|
91329
92879
|
* horizontal?: boolean,
|
|
91330
|
-
* children?: import("
|
|
92880
|
+
* children?: import("preact").ComponentChildren,
|
|
91331
92881
|
* [key: string]: any,
|
|
91332
92882
|
* }>}
|
|
91333
92883
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
@@ -91524,14 +93074,14 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
91524
93074
|
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
91525
93075
|
|
|
91526
93076
|
/**
|
|
91527
|
-
* @type {import("
|
|
93077
|
+
* @type {import("preact").FunctionComponent<{
|
|
91528
93078
|
* name?: string,
|
|
91529
93079
|
* value?: string,
|
|
91530
93080
|
* defaultValue?: string,
|
|
91531
93081
|
* signal?: import("@preact/signals").Signal<string>,
|
|
91532
93082
|
* minuteStep?: number,
|
|
91533
93083
|
* loop?: boolean,
|
|
91534
|
-
* separator?: import("
|
|
93084
|
+
* separator?: import("preact").ComponentChildren,
|
|
91535
93085
|
* hourLabel?: string,
|
|
91536
93086
|
* minuteLabel?: string,
|
|
91537
93087
|
* size?: string,
|
|
@@ -91552,7 +93102,7 @@ const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
|
91552
93102
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
91553
93103
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
91554
93104
|
* past.
|
|
91555
|
-
* @param {import("
|
|
93105
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
91556
93106
|
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
91557
93107
|
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
91558
93108
|
* is written by default outside French.
|
|
@@ -91671,7 +93221,7 @@ const TimeWheel = ({
|
|
|
91671
93221
|
};
|
|
91672
93222
|
|
|
91673
93223
|
/**
|
|
91674
|
-
* @type {import("
|
|
93224
|
+
* @type {import("preact").FunctionComponent<{
|
|
91675
93225
|
* name?: string,
|
|
91676
93226
|
* value?: { start?: string, end?: string },
|
|
91677
93227
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -91680,15 +93230,15 @@ const TimeWheel = ({
|
|
|
91680
93230
|
* minDuration?: number,
|
|
91681
93231
|
* loop?: boolean,
|
|
91682
93232
|
* size?: string,
|
|
91683
|
-
* startLabel?: import("
|
|
91684
|
-
* endLabel?: import("
|
|
93233
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
93234
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
91685
93235
|
* labelPosition?: "before" | "above",
|
|
91686
93236
|
* timeProps?: object,
|
|
91687
93237
|
* [key: string]: any,
|
|
91688
93238
|
* }>}
|
|
91689
93239
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
91690
93240
|
* "HH:MM".
|
|
91691
|
-
* @param {import("
|
|
93241
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
91692
93242
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
91693
93243
|
* `null` for neither.
|
|
91694
93244
|
* @param {"before"|"above"} [labelPosition="before"] Where each label stands.
|
|
@@ -95673,17 +97223,25 @@ const Address = ({
|
|
|
95673
97223
|
};
|
|
95674
97224
|
|
|
95675
97225
|
installImportMetaCssBuild(import.meta);
|
|
95676
|
-
const css$a = /* css */`@layer navi
|
|
97226
|
+
const css$a = /* css */`@layer navi {
|
|
97227
|
+
.navi_text.navi_badge_count {
|
|
97228
|
+
--font-size: .7em;
|
|
97229
|
+
--badge-count-padding-x-default: .5em;
|
|
97230
|
+
--badge-count-padding-y-default: .3em;
|
|
97231
|
+
|
|
97232
|
+
&[data-ellipse][data-single-char], &[data-circle] {
|
|
97233
|
+
--badge-count-padding-x-default: 0;
|
|
97234
|
+
--badge-count-padding-y-default: 0;
|
|
97235
|
+
}
|
|
97236
|
+
}
|
|
97237
|
+
}
|
|
95677
97238
|
|
|
95678
97239
|
.navi_text.navi_badge_count {
|
|
95679
97240
|
white-space: nowrap;
|
|
95680
|
-
--font-size: .7em;
|
|
95681
97241
|
--x-background: var(--background);
|
|
95682
|
-
--x-background-color: var(--background-color
|
|
97242
|
+
--x-background-color: var(--background-color);
|
|
95683
97243
|
--x-color-contrasting: var(--navi-color-white);
|
|
95684
97244
|
--x-color: var(--color, var(--x-color-contrasting));
|
|
95685
|
-
--badge-count-padding-x-default: .5em;
|
|
95686
|
-
--badge-count-padding-y-default: .3em;
|
|
95687
97245
|
--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))));
|
|
95688
97246
|
--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))));
|
|
95689
97247
|
--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))));
|
|
@@ -95715,12 +97273,9 @@ const css$a = /* css */`@layer navi;
|
|
|
95715
97273
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95716
97274
|
padding-left: var(--x-badge-count-padding-left);
|
|
95717
97275
|
background: var(--x-background);
|
|
95718
|
-
background-color: var(--x-background-color);
|
|
95719
97276
|
border-radius: 1em;
|
|
95720
97277
|
|
|
95721
97278
|
&[data-single-char] {
|
|
95722
|
-
--badge-count-padding-x-default: 0;
|
|
95723
|
-
--badge-count-padding-y-default: 0;
|
|
95724
97279
|
box-sizing: content-box;
|
|
95725
97280
|
text-align: center;
|
|
95726
97281
|
border-radius: 100em;
|
|
@@ -95733,8 +97288,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95733
97288
|
|
|
95734
97289
|
&[data-circle] {
|
|
95735
97290
|
--x-number-font-size: var(--font-size);
|
|
95736
|
-
--badge-count-padding-x-default: 0;
|
|
95737
|
-
--badge-count-padding-y-default: 0;
|
|
95738
97291
|
box-sizing: content-box;
|
|
95739
97292
|
aspect-ratio: 1;
|
|
95740
97293
|
width: var(--x-radius);
|
|
@@ -95744,7 +97297,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95744
97297
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95745
97298
|
padding-left: var(--x-badge-count-padding-left);
|
|
95746
97299
|
background: var(--x-background);
|
|
95747
|
-
background-color: var(--x-background-color);
|
|
95748
97300
|
border-radius: 100em;
|
|
95749
97301
|
justify-content: center;
|
|
95750
97302
|
align-items: center;
|
|
@@ -95769,6 +97321,10 @@ const css$a = /* css */`@layer navi;
|
|
|
95769
97321
|
font-size: var(--x-number-font-size);
|
|
95770
97322
|
}
|
|
95771
97323
|
}
|
|
97324
|
+
|
|
97325
|
+
&[data-background-color] {
|
|
97326
|
+
background-color: var(--x-background-color);
|
|
97327
|
+
}
|
|
95772
97328
|
}
|
|
95773
97329
|
`;
|
|
95774
97330
|
const BadgeCountOverflow = () => jsx("span", {
|
|
@@ -95905,6 +97461,7 @@ const BadgeCountEllipse = ({
|
|
|
95905
97461
|
"data-ellipse": "",
|
|
95906
97462
|
"data-value-overflow": hasOverflow ? "" : undefined,
|
|
95907
97463
|
"data-loading": loading ? "" : undefined,
|
|
97464
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95908
97465
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95909
97466
|
...props,
|
|
95910
97467
|
styleCSSVars: BadgeCountStyleCSSVars,
|
|
@@ -95927,6 +97484,7 @@ const BadgeCountCircle = ({
|
|
|
95927
97484
|
"data-circle": "",
|
|
95928
97485
|
bold: true,
|
|
95929
97486
|
"data-loading": loading ? "" : undefined,
|
|
97487
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95930
97488
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95931
97489
|
"data-two-chars": charCount === 2 ? "" : undefined,
|
|
95932
97490
|
"data-three-chars": charCount === 3 ? "" : undefined,
|
|
@@ -96419,7 +97977,7 @@ const css$8 = /* css */`@layer navi;
|
|
|
96419
97977
|
`;
|
|
96420
97978
|
|
|
96421
97979
|
/**
|
|
96422
|
-
* @type {import("
|
|
97980
|
+
* @type {import("preact").FunctionComponent<{
|
|
96423
97981
|
* children?: number | string,
|
|
96424
97982
|
* unit?: string | [string, string],
|
|
96425
97983
|
* unitPosition?: "right" | "bottom",
|
|
@@ -97011,8 +98569,8 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
97011
98569
|
*
|
|
97012
98570
|
* @param {Object} props - Component properties
|
|
97013
98571
|
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
97014
|
-
* @param {import("
|
|
97015
|
-
* @returns {import("
|
|
98572
|
+
* @param {import("preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
98573
|
+
* @returns {import("preact").VNode} A composed SVG with all elements properly masked
|
|
97016
98574
|
*/
|
|
97017
98575
|
const SVGMaskOverlay = ({
|
|
97018
98576
|
viewBox,
|
|
@@ -97102,7 +98660,6 @@ const css$3 = /* css */`@layer navi {
|
|
|
97102
98660
|
padding-bottom: var(--layout-padding-bottom, var(--layout-padding-y, var(--layout-padding)));
|
|
97103
98661
|
padding-left: var(--layout-padding-left, var(--layout-padding-x, var(--layout-padding)));
|
|
97104
98662
|
background: var(--layout-background);
|
|
97105
|
-
background-color: var(--layout-background-color, var(--layout-background));
|
|
97106
98663
|
border-width: var(--layout-border-width);
|
|
97107
98664
|
border-style: solid;
|
|
97108
98665
|
border-color: var(--layout-border-color);
|
|
@@ -97119,7 +98676,6 @@ const CardLayoutStyleCSSVars = {
|
|
|
97119
98676
|
borderWidth: "--layout-border-width",
|
|
97120
98677
|
borderColor: "--layout-border-color",
|
|
97121
98678
|
background: "--layout-background",
|
|
97122
|
-
backgroundColor: "--layout-background-color",
|
|
97123
98679
|
padding: "--layout-padding",
|
|
97124
98680
|
paddingTop: "--layout-padding-top",
|
|
97125
98681
|
paddingBottom: "--layout-padding-bottom",
|
|
@@ -97134,7 +98690,7 @@ const CardLayoutStyleCSSVars = {
|
|
|
97134
98690
|
* what holds it, so how tall that area is is up to the page around it, as for
|
|
97135
98691
|
* `ViewportLayout`.
|
|
97136
98692
|
*
|
|
97137
|
-
* @type {import("
|
|
98693
|
+
* @type {import("preact").FunctionComponent<{
|
|
97138
98694
|
* alignX?: string,
|
|
97139
98695
|
* alignY?: string,
|
|
97140
98696
|
* margin?: string|number,
|
|
@@ -97159,6 +98715,7 @@ const CardLayout = ({
|
|
|
97159
98715
|
children,
|
|
97160
98716
|
alignX = "center",
|
|
97161
98717
|
alignY = "center",
|
|
98718
|
+
backgroundColor,
|
|
97162
98719
|
...props
|
|
97163
98720
|
}) => {
|
|
97164
98721
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
@@ -97177,6 +98734,7 @@ const CardLayout = ({
|
|
|
97177
98734
|
className: "navi_card",
|
|
97178
98735
|
alignX: alignX,
|
|
97179
98736
|
alignY: alignY,
|
|
98737
|
+
backgroundColor: backgroundColor,
|
|
97180
98738
|
children: children
|
|
97181
98739
|
})
|
|
97182
98740
|
});
|
|
@@ -97350,7 +98908,7 @@ const LINE_GAP = 5;
|
|
|
97350
98908
|
const StepListContext = createContext(null);
|
|
97351
98909
|
|
|
97352
98910
|
/**
|
|
97353
|
-
* @type {import("
|
|
98911
|
+
* @type {import("preact").FunctionComponent<{
|
|
97354
98912
|
* current?: string,
|
|
97355
98913
|
* slideContainer?: string,
|
|
97356
98914
|
* travelByClick?: boolean,
|
|
@@ -97707,7 +99265,7 @@ const sameShallow = (previousObject, nextObject) => {
|
|
|
97707
99265
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
97708
99266
|
* SlideContainer.
|
|
97709
99267
|
*
|
|
97710
|
-
* @type {import("
|
|
99268
|
+
* @type {import("preact").FunctionComponent<{
|
|
97711
99269
|
* value: string,
|
|
97712
99270
|
* done?: boolean,
|
|
97713
99271
|
* [key: string]: any,
|
|
@@ -97769,7 +99327,7 @@ const ViewportLayoutStyleCSSVars = {
|
|
|
97769
99327
|
* same inside a route, a preview pane or a demo box, and it is up to the page
|
|
97770
99328
|
* around it to be full-height.
|
|
97771
99329
|
*
|
|
97772
|
-
* @type {import("
|
|
99330
|
+
* @type {import("preact").FunctionComponent<{
|
|
97773
99331
|
* padding?: string|number,
|
|
97774
99332
|
* paddingTop?: string|number,
|
|
97775
99333
|
* paddingBottom?: string|number,
|
|
@@ -97994,7 +99552,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
97994
99552
|
* `closeByPressOutside`/`pressOutside` only say what a
|
|
97995
99553
|
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
97996
99554
|
* doc).
|
|
97997
|
-
* @param {import("
|
|
99555
|
+
* @param {import("preact").ComponentChildren} props.children - No built-in
|
|
97998
99556
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
97999
99557
|
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
98000
99558
|
* `closeByPressOutside`/Escape instead.
|
|
@@ -98093,7 +99651,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
98093
99651
|
* @param {object} props
|
|
98094
99652
|
* @param {string} [props.className] - Merged with the shared
|
|
98095
99653
|
* `"navi_side_panel_head"` class this file's own CSS targets.
|
|
98096
|
-
* @param {import("
|
|
99654
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98097
99655
|
*/
|
|
98098
99656
|
const SidePanelHead = props => jsx(Box, {
|
|
98099
99657
|
baseClassName: "navi_side_panel_head",
|
|
@@ -98106,7 +99664,7 @@ const SidePanelHead = props => jsx(Box, {
|
|
|
98106
99664
|
* @param {object} props
|
|
98107
99665
|
* @param {string} [props.className] - Merged with the shared
|
|
98108
99666
|
* `"navi_side_panel_foot"` class this file's own CSS targets.
|
|
98109
|
-
* @param {import("
|
|
99667
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98110
99668
|
*/
|
|
98111
99669
|
const SidePanelFoot = props => jsx(Box, {
|
|
98112
99670
|
className: "navi_side_panel_foot",
|
|
@@ -98115,6 +99673,58 @@ const SidePanelFoot = props => jsx(Box, {
|
|
|
98115
99673
|
SidePanel.Head = SidePanelHead;
|
|
98116
99674
|
SidePanel.Foot = SidePanelFoot;
|
|
98117
99675
|
|
|
99676
|
+
/**
|
|
99677
|
+
* Creates a linked `[Slot, SlotFill]` pair so content rendered anywhere in
|
|
99678
|
+
* the tree (`SlotFill`) appears at a fixed location elsewhere (`Slot`) — a
|
|
99679
|
+
* lightweight, signal-based alternative to a DOM portal: `SlotFill` itself
|
|
99680
|
+
* renders nothing (no extra DOM node at its own call site), it just writes
|
|
99681
|
+
* its props into a shared signal that `Slot` reads reactively.
|
|
99682
|
+
*
|
|
99683
|
+
* Holds at most one filler at a time — there is no stacking/queueing. The
|
|
99684
|
+
* filler is the last `SlotFill` (from this same `createSlot()` call) to
|
|
99685
|
+
* mount, or the first to render into an empty slot; later renders of a
|
|
99686
|
+
* `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
|
|
99687
|
+
* unmounting empties the slot only while it is still that one, and so does one
|
|
99688
|
+
* in a page a route transition keeps while leaving (see keepLeavingPages in
|
|
99689
|
+
* nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
|
|
99690
|
+
* take it back from the page arriving.
|
|
99691
|
+
*
|
|
99692
|
+
* **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
|
|
99693
|
+
* the fills as they mount and unmount, in the order the tree walks them. A
|
|
99694
|
+
* slot fed from several places — a `SlotFill` in each card of a board, each
|
|
99695
|
+
* card deciding on its own whether it is the open one — reads as empty
|
|
99696
|
+
* between the leaver and the newcomer whenever they render separately, and
|
|
99697
|
+
* anything acting on `isFilled` acts on that empty frame (a `SidePanel` with
|
|
99698
|
+
* `open={isFilled}` closes for good: its `onClose` clears the state the
|
|
99699
|
+
* newcomer was waiting for). The slot cannot tell that gap from a real
|
|
99700
|
+
* emptying, and does not try to: render one `SlotFill` at the level that
|
|
99701
|
+
* holds the choice, with the chosen content as its children, and bind a
|
|
99702
|
+
* state that must not blink (a panel's `open`) to the app's own signal
|
|
99703
|
+
* rather than to `isFilled` — see docs/popup_open.md, "One panel, fed by a
|
|
99704
|
+
* slot".
|
|
99705
|
+
*
|
|
99706
|
+
* `Slot` keeps `SlotRenderer` mounted permanently — even while unfilled it
|
|
99707
|
+
* still renders it, with no props and `isFilled={false}` — instead of
|
|
99708
|
+
* unmounting it. This lets `SlotRenderer` be a persistent wrapper (a
|
|
99709
|
+
* `SidePanel` that opens and closes with an animation instead of being
|
|
99710
|
+
* mounted/unmounted alongside the filler — see 7_slot_demo.html's "Two
|
|
99711
|
+
* side-panel slots" section). A `SlotRenderer` that wants the
|
|
99712
|
+
* render-nothing-when-unfilled behavior can opt in with its own
|
|
99713
|
+
* `if (!isFilled) return null;` (the default `Box` renderer doesn't do
|
|
99714
|
+
* this — an unfilled default slot just renders an empty `<Box/>`).
|
|
99715
|
+
*
|
|
99716
|
+
* @param {import("preact").ComponentType} [SlotRenderer=Box] - Rendered by
|
|
99717
|
+
* `Slot`, always, with whatever props `SlotFill` last set (spread
|
|
99718
|
+
* directly) plus `isFilled`. Swap this out to reuse the same slot
|
|
99719
|
+
* mechanism for something other than a plain `Box` (e.g. a specific
|
|
99720
|
+
* component expecting its own particular props, or a persistent wrapper
|
|
99721
|
+
* like `SidePanel`).
|
|
99722
|
+
* @returns {[import("preact").ComponentType, import("preact").ComponentType<object>]}
|
|
99723
|
+
* `[Slot, SlotFill]` — `Slot` takes no props, render it once wherever the
|
|
99724
|
+
* content should appear. `SlotFill` takes whatever props `SlotRenderer`
|
|
99725
|
+
* expects, render it anywhere else in the tree to supply/update that
|
|
99726
|
+
* content.
|
|
99727
|
+
*/
|
|
98118
99728
|
const createSlot = (SlotRenderer = Box) => {
|
|
98119
99729
|
const slotPropsSignal = signal();
|
|
98120
99730
|
let filler = null;
|