@jsenv/navi 0.29.478 → 0.29.479
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +1729 -680
- package/dist/dev/jsenv_navi.js.map +38 -36
- package/dist/jsenv_navi.js +2158 -550
- package/dist/jsenv_navi.js.map +38 -36
- package/docs/AI_INSTRUCTIONS.md +3 -1
- package/docs/actions.md +23 -0
- package/docs/drag_to_travel.md +9 -3
- package/docs/form_changed.md +4 -3
- package/docs/interactions.md +8 -4
- package/docs/navigation.md +31 -2
- package/docs/pan_zoom.md +6 -4
- package/docs/popup_backdrop.md +5 -0
- package/docs/popup_lift.md +4 -3
- package/docs/route_transitions.md +7 -3
- package/docs/scroll.md +5 -5
- package/docs/testid.md +8 -5
- package/docs/view_transitions.md +3 -2
- package/package.json +3 -3
package/dist/jsenv_navi.js
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* using @jsenv/navi as intended.
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, coarsePointerSignal, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
|
-
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, findEvent, mergeOneStyle, findFocusable, getElementSignature, chainEvent, isTouchDrivenEvent, getKeyboardEventDefaultAction, isPressDrivenClick, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, createValueEffect, findFocusDelegateTarget, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, getScrollContainerSet, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, keepTouchRefusable, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, activeElementSignal, normalizeStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, getScrollContainer, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, isScrollable, resolveOklchLightness, contrastColor, overflowGoesToViewport, parsePositionArea, snapToPixel, trapFocusInside, onAncestorReopen,
|
|
6
|
+
import { createPubSub, elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, findEvent, mergeOneStyle, findFocusable, getElementSignature, chainEvent, isTouchDrivenEvent, getKeyboardEventDefaultAction, isPressDrivenClick, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, createValueEffect, findFocusDelegateTarget, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, getScrollContainerSet, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, keepTouchRefusable, waitForTap, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, dragSourceThatStoodDown, markDragSource, refuseDragTo, startDragTo, installPanZoom, createInternalCustomEvent, activeElementSignal, normalizeStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, getScrollContainer, watchWheelTravel, scrollRoomTowards, trapScrollInside, scrollIntoViewScoped, isPressDisputedByDrag, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, initFocusGroup, stringifyStyle as stringifyStyle$1, isScrollable, resolveOklchLightness, contrastColor, overflowGoesToViewport, parsePositionArea, snapToPixel, trapFocusInside, onAncestorReopen, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement, setVirtualKeyboardOverlaysContent, subscribeVirtualKeyboardGeometryChange, getVirtualKeyboardOverlayHeight } from "@jsenv/dom";
|
|
7
7
|
export { chainEvent, clickIsSuppressed, contrastColor, createDragGestureController, dragAfterIntent, findEvent, markDragSource, startDragTo } from "@jsenv/dom";
|
|
8
8
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
9
9
|
import { options, isValidElement, createContext, render as render$1, h, toChildArray, createRef, cloneElement, createElement, Fragment as Fragment$1 } from "preact";
|
|
10
10
|
import { useErrorBoundary, useLayoutEffect, useContext, useState, useMemo, useRef, useReducer, useEffect, useCallback, useId } from "preact/hooks";
|
|
11
|
-
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay,
|
|
11
|
+
import { humanizeI18n, setRuntimeLangSource, formatDuration, formatMonth, formatDay, resolveTimeRangePrecision, formatDatePlaceholder, toDate, getRelativeDay, formatDayRelative, formatMonthPlaceholder, formatWeekPlaceholder, formatDatetimePlaceholder, formatDatetime, toTimeOfDay, formatTimeOfDay, formatMinuteDuration, formatTime, formatSecondDuration, formatHourDuration, formatTimeRelative, formatNumber, interpolateText, installInterpolateJsx } from "@jsenv/humanize";
|
|
12
12
|
export { createI18n, formatDatetime, formatDay, formatDayRelative, formatDuration, formatHourDuration, formatMinuteDuration, formatMonth, formatNumber, formatSecondDuration, formatTime, formatTimeOfDay, formatTimeRange, formatTimeRelative, interpolateText } from "@jsenv/humanize";
|
|
13
13
|
import { jsxs, jsx, Fragment } from "preact/jsx-runtime";
|
|
14
14
|
import { CHAR_CLASS_PRESETS, createValidity, durationContainsNaN, compareTwoDurations, TYPE_RULE, durationToSeconds, resolveCharClass, getCharClassMessageKey, compileCharClassAnchored, compileCharClass, DISPLAYABLE_RULE, MAX_LINE_BREAKS_RULE, NO_EMOJI_RULE, SINGLE_SPACE_RULE, parseDuration, durationToISOString } from "@jsenv/validity";
|
|
@@ -3873,6 +3873,11 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
3873
3873
|
* asks nothing.
|
|
3874
3874
|
*/
|
|
3875
3875
|
const bindParams = (newParamsOrSignal, options = {}) => {
|
|
3876
|
+
if (newParamsOrSignal === null) {
|
|
3877
|
+
// A signal's `null` becomes `undefined` in mergeActionParams; a static
|
|
3878
|
+
// one never goes through it, so it is said the same way here.
|
|
3879
|
+
newParamsOrSignal = undefined;
|
|
3880
|
+
}
|
|
3876
3881
|
const { debounce, ...optionsWithoutDebounce } = options;
|
|
3877
3882
|
if (debounce) {
|
|
3878
3883
|
if (!isSignal(newParamsOrSignal)) {
|
|
@@ -4460,7 +4465,9 @@ const createActionProxyFromSignal = (
|
|
|
4460
4465
|
const inheritsAnswer =
|
|
4461
4466
|
inheritData && getActionPrivateProperties(action).inheritData;
|
|
4462
4467
|
const createTarget = (params) => {
|
|
4463
|
-
|
|
4468
|
+
// `undefined` is "nothing to ask": no answer is coming to replace a stand-in,
|
|
4469
|
+
// and that one instance is shared by every binding of the action.
|
|
4470
|
+
if (inheritsAnswer && params !== undefined) {
|
|
4464
4471
|
const previousActionTarget = actionTargetPreviousWeakRef?.deref();
|
|
4465
4472
|
const previousTarget = previousActionTarget || action;
|
|
4466
4473
|
return action.bindParams(params, {
|
|
@@ -6274,9 +6281,11 @@ const CONTROL_ATTRIBUTE_SET = new Set([
|
|
|
6274
6281
|
"spellcheck",
|
|
6275
6282
|
"autoCorrect",
|
|
6276
6283
|
"aria-controls",
|
|
6277
|
-
// The name goes where the role is: the root box has none,
|
|
6278
|
-
// left on it names nothing at all, while the host is what
|
|
6279
|
-
// and what a screen reader announces (and what
|
|
6284
|
+
// The name goes where the role is: for most controls the root box has none,
|
|
6285
|
+
// so an aria-label left on it names nothing at all, while the host is what
|
|
6286
|
+
// the user focuses and what a screen reader announces (and what
|
|
6287
|
+
// getByRole({ name }) reads). A control whose role is on its root says so
|
|
6288
|
+
// with `nameOn: "root"` (see useControlProps).
|
|
6280
6289
|
"aria-label",
|
|
6281
6290
|
"aria-labelledby",
|
|
6282
6291
|
"tabIndex",
|
|
@@ -6803,7 +6812,7 @@ const useDebugUIState = useDebugNoop;
|
|
|
6803
6812
|
* @param {boolean|Function} [props.debugPopup] - Popover/dialog open/close/positioning.
|
|
6804
6813
|
* @param {boolean|Function} [props.debugAction] - Action lifecycle.
|
|
6805
6814
|
* @param {boolean|Function} [props.debugUIState] - UI-state transitions and validation.
|
|
6806
|
-
* @param {import("
|
|
6815
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
6807
6816
|
*
|
|
6808
6817
|
* @example
|
|
6809
6818
|
* // Log everything under this subtree
|
|
@@ -7020,9 +7029,11 @@ const generateSignalId = () => {
|
|
|
7020
7029
|
* @param {Array} [options.oneOf] - Array of valid values for validation. Signal will be marked invalid if value is not in this array
|
|
7021
7030
|
* @param {boolean} [options.autoFix=false] - Hold the repaired value instead of the refused one: a number
|
|
7022
7031
|
* clamped to `min`/`max` and rounded to `step`, a value outside `oneOf` replaced by the signal's
|
|
7023
|
-
* default value
|
|
7024
|
-
*
|
|
7025
|
-
*
|
|
7032
|
+
* default value, or made absent when the signal has no default (an enumeration has no nearest
|
|
7033
|
+
* value, and its order says nothing). The default must therefore pass the rules itself: one of
|
|
7034
|
+
* `oneOf`. Without it the refused value is held as-is, marked invalid, and `validSignal` reads
|
|
7035
|
+
* `undefined`. When the value came from the address, the address is rewritten with the repaired
|
|
7036
|
+
* one (a replace).
|
|
7026
7037
|
* @param {boolean} [options.weak=false] - The param qualifies one visit, not the screen: it is written into a
|
|
7027
7038
|
* url only when explicitly named (`routeParams={{ edit: id }}`), never inherited from the signal's current
|
|
7028
7039
|
* value, and it goes back to the default value when the route stops matching. Use it for params like an
|
|
@@ -7164,7 +7175,7 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7164
7175
|
|
|
7165
7176
|
// Determine localStorage key: use id if persists=true, or legacy localStorage option
|
|
7166
7177
|
const localStorageKey = signalIdString;
|
|
7167
|
-
const [validity, updateValidity] = createValidity({
|
|
7178
|
+
const [validity, updateValidity, clearValidity] = createValidity({
|
|
7168
7179
|
type,
|
|
7169
7180
|
min,
|
|
7170
7181
|
max,
|
|
@@ -7280,9 +7291,19 @@ const stateSignal = (defaultValue, options = {}) => {
|
|
|
7280
7291
|
// Create signal with initial value: use stored value, or undefined to indicate no explicit value
|
|
7281
7292
|
const processValue = (value) => {
|
|
7282
7293
|
if (value === undefined) {
|
|
7294
|
+
// Absence is never validated, and the verdict on the previous value
|
|
7295
|
+
// (validSignal, validity.valid) must not outlive it.
|
|
7296
|
+
clearValidity();
|
|
7283
7297
|
return undefined;
|
|
7284
7298
|
}
|
|
7285
7299
|
updateValidity(value);
|
|
7300
|
+
// A state without a default is repaired to absent. validity cannot suggest
|
|
7301
|
+
// it: `undefined` is "no suggestion" there and its rules refuse it.
|
|
7302
|
+
if (autoFix && !validity.valid && getDefaultValue(false) === undefined) {
|
|
7303
|
+
clearValidity();
|
|
7304
|
+
validity.autoFixed = true;
|
|
7305
|
+
return undefined;
|
|
7306
|
+
}
|
|
7286
7307
|
// Always return the coerced value (type coercion applies), even if invalid.
|
|
7287
7308
|
// Invalid values are preserved as-is so the UI can display them and the URL
|
|
7288
7309
|
// can reflect the current input state without silently correcting it.
|
|
@@ -7573,6 +7594,18 @@ const readSignalForUrlBuild = (connection) => {
|
|
|
7573
7594
|
return connection.signal.value;
|
|
7574
7595
|
};
|
|
7575
7596
|
|
|
7597
|
+
/**
|
|
7598
|
+
* Whether a pattern catches the addresses below its own: "/map/", "/map/*" and
|
|
7599
|
+
* the root "/" do, "/places" does not. Only such a route lends its search
|
|
7600
|
+
* params to the routes below it: it is on screen wherever they are, so its
|
|
7601
|
+
* state holds there too. Declared on "/places", a search param belongs to
|
|
7602
|
+
* that address alone — "/places/:section" beside it also matches
|
|
7603
|
+
* "/places/clubs", and borrowing the list's search would hand it to the clubs.
|
|
7604
|
+
*/
|
|
7605
|
+
const coversAddressesBelow = (patternObj) => {
|
|
7606
|
+
return patternObj.pattern.trailingSlash || patternObj.pattern.wildcard;
|
|
7607
|
+
};
|
|
7608
|
+
|
|
7576
7609
|
/**
|
|
7577
7610
|
* Creates a custom route pattern matcher
|
|
7578
7611
|
*/
|
|
@@ -7684,11 +7717,13 @@ const createRoutePattern = (
|
|
|
7684
7717
|
}
|
|
7685
7718
|
}
|
|
7686
7719
|
|
|
7687
|
-
//
|
|
7688
|
-
// Search params are global and should be inherited from any matching ancestor
|
|
7689
|
-
// regardless of path segment relationships
|
|
7720
|
+
// Search params lent by the ancestors covering this route
|
|
7690
7721
|
let ancestorPatternObj = patternObject.parent;
|
|
7691
7722
|
while (ancestorPatternObj) {
|
|
7723
|
+
if (!coversAddressesBelow(ancestorPatternObj)) {
|
|
7724
|
+
ancestorPatternObj = ancestorPatternObj.parent;
|
|
7725
|
+
continue;
|
|
7726
|
+
}
|
|
7692
7727
|
for (const [
|
|
7693
7728
|
paramName,
|
|
7694
7729
|
ancestorConnection,
|
|
@@ -7773,7 +7808,12 @@ const createRoutePattern = (
|
|
|
7773
7808
|
childParam,
|
|
7774
7809
|
childConnection,
|
|
7775
7810
|
] of childPatternObj.queryConnectionMap) {
|
|
7776
|
-
|
|
7811
|
+
// a name this route declares is its own state's, whatever a child
|
|
7812
|
+
// declares under the same name
|
|
7813
|
+
if (
|
|
7814
|
+
childParam in resolvedParams ||
|
|
7815
|
+
queryConnectionMap.has(childParam)
|
|
7816
|
+
) {
|
|
7777
7817
|
continue;
|
|
7778
7818
|
}
|
|
7779
7819
|
const childSignalValue = readSignalForUrlBuild(childConnection);
|
|
@@ -7819,7 +7859,9 @@ const createRoutePattern = (
|
|
|
7819
7859
|
* - own connection signals (path + query, inherited query included)
|
|
7820
7860
|
* - ancestor path params pinned by this pattern's own literals: being on
|
|
7821
7861
|
* "/admin/settings" MEANS section=settings, whatever the signal says
|
|
7822
|
-
* -
|
|
7862
|
+
* - custom values of the search params lent by the ancestors covering this
|
|
7863
|
+
* route (coversAddressesBelow). Another ancestor's search params ride
|
|
7864
|
+
* only on that ancestor's own url (see buildMostPreciseUrl)
|
|
7823
7865
|
* - reachable descendants' connection signals. A descendant is reachable
|
|
7824
7866
|
* when its literals agree with the state collected so far (a tab value
|
|
7825
7867
|
* is unreachable while section conflicts with the tab route's literals)
|
|
@@ -7945,6 +7987,10 @@ const createRoutePattern = (
|
|
|
7945
7987
|
setEntry(seg.name, selfSeg.value, conn, seg.index, "ancestor", false);
|
|
7946
7988
|
}
|
|
7947
7989
|
}
|
|
7990
|
+
if (!coversAddressesBelow(ancestor)) {
|
|
7991
|
+
ancestor = ancestor.parent;
|
|
7992
|
+
continue;
|
|
7993
|
+
}
|
|
7948
7994
|
for (const [name, conn] of ancestor.queryConnectionMap) {
|
|
7949
7995
|
if (intended.has(name)) {
|
|
7950
7996
|
continue;
|
|
@@ -8091,6 +8137,10 @@ const createRoutePattern = (
|
|
|
8091
8137
|
if (!queryReachable || value === undefined) {
|
|
8092
8138
|
continue;
|
|
8093
8139
|
}
|
|
8140
|
+
// a name this route declares is its own state's (see resolveParams)
|
|
8141
|
+
if (queryConnectionMap.has(paramName)) {
|
|
8142
|
+
continue;
|
|
8143
|
+
}
|
|
8094
8144
|
if (conn.isCustomValue(value)) {
|
|
8095
8145
|
setEntry(paramName, value, conn, undefined, "descendant", false);
|
|
8096
8146
|
}
|
|
@@ -8350,20 +8400,50 @@ const createRoutePattern = (
|
|
|
8350
8400
|
);
|
|
8351
8401
|
};
|
|
8352
8402
|
|
|
8403
|
+
// An ancestor's url is that ancestor's address: it carries the search params
|
|
8404
|
+
// declared there, including those it lends to nobody (coversAddressesBelow).
|
|
8405
|
+
// Each ancestor climbed past adds its own, so a higher one must keep them too.
|
|
8406
|
+
const withOwnSearchParams = (ancestorPattern, intended) => {
|
|
8407
|
+
let intendedWithOwn = intended;
|
|
8408
|
+
for (const [name, conn] of ancestorPattern.queryConnectionMap) {
|
|
8409
|
+
if (intendedWithOwn.has(name)) {
|
|
8410
|
+
continue;
|
|
8411
|
+
}
|
|
8412
|
+
const value = readSignalForUrlBuild(conn);
|
|
8413
|
+
if (value === undefined || !conn.isCustomValue(value)) {
|
|
8414
|
+
continue;
|
|
8415
|
+
}
|
|
8416
|
+
if (intendedWithOwn === intended) {
|
|
8417
|
+
intendedWithOwn = new Map(intended);
|
|
8418
|
+
}
|
|
8419
|
+
intendedWithOwn.set(name, {
|
|
8420
|
+
value,
|
|
8421
|
+
connection: conn,
|
|
8422
|
+
segmentIndex: undefined,
|
|
8423
|
+
owner: "ancestor",
|
|
8424
|
+
explicit: false,
|
|
8425
|
+
pageNaming: false,
|
|
8426
|
+
});
|
|
8427
|
+
}
|
|
8428
|
+
return intendedWithOwn;
|
|
8429
|
+
};
|
|
8430
|
+
|
|
8353
8431
|
const buildMostPreciseUrl = (params = {}) => {
|
|
8354
8432
|
const { intended, reachableDescendants } = buildIntendedState(params);
|
|
8355
8433
|
// ancestors: start at the immediate parent, climb while urls keep round-
|
|
8356
8434
|
// tripping, keep the highest one ("/" is never a target)
|
|
8357
8435
|
let ancestorUrl = null;
|
|
8358
8436
|
let ancestor = patternObject.parent;
|
|
8437
|
+
let ancestorIntended = intended;
|
|
8359
8438
|
while (ancestor && ancestor.originalPattern !== "/") {
|
|
8360
8439
|
if (!isAncestorCollapseAllowed(ancestor, intended)) {
|
|
8361
8440
|
break;
|
|
8362
8441
|
}
|
|
8363
|
-
|
|
8442
|
+
ancestorIntended = withOwnSearchParams(ancestor, ancestorIntended);
|
|
8443
|
+
const url = buildCandidateUrl(ancestor, ancestorIntended, {
|
|
8364
8444
|
dropMissing: true,
|
|
8365
8445
|
});
|
|
8366
|
-
if (!url || !verifyUrl(url,
|
|
8446
|
+
if (!url || !verifyUrl(url, ancestorIntended)) {
|
|
8367
8447
|
break;
|
|
8368
8448
|
}
|
|
8369
8449
|
debug$3(
|
|
@@ -9527,11 +9607,15 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9527
9607
|
}
|
|
9528
9608
|
routePattern.familyRoot = root;
|
|
9529
9609
|
}
|
|
9530
|
-
// Phase 3: Inherit search
|
|
9531
|
-
//
|
|
9610
|
+
// Phase 3: Inherit the search param connections of the ancestors covering
|
|
9611
|
+
// the addresses below them (see coversAddressesBelow)
|
|
9532
9612
|
for (const routePattern of routePatternSet) {
|
|
9533
9613
|
let ancestorRoutePattern = routePattern.parent;
|
|
9534
9614
|
while (ancestorRoutePattern) {
|
|
9615
|
+
if (!coversAddressesBelow(ancestorRoutePattern)) {
|
|
9616
|
+
ancestorRoutePattern = ancestorRoutePattern.parent;
|
|
9617
|
+
continue;
|
|
9618
|
+
}
|
|
9535
9619
|
// For each ancestor's query connection, check if it should be inherited
|
|
9536
9620
|
for (const [
|
|
9537
9621
|
paramName,
|
|
@@ -9641,23 +9725,6 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9641
9725
|
continue;
|
|
9642
9726
|
}
|
|
9643
9727
|
const { index } = paramSegment;
|
|
9644
|
-
const sharesPathUpTo = (otherSegments) => {
|
|
9645
|
-
for (let i = 0; i < index; i++) {
|
|
9646
|
-
const seg = routePattern.pattern.segments[i];
|
|
9647
|
-
const otherSeg = otherSegments[i];
|
|
9648
|
-
if (!otherSeg) {
|
|
9649
|
-
return false;
|
|
9650
|
-
}
|
|
9651
|
-
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
9652
|
-
if (seg.value !== otherSeg.value) {
|
|
9653
|
-
return false;
|
|
9654
|
-
}
|
|
9655
|
-
} else if (seg.type !== otherSeg.type) {
|
|
9656
|
-
return false;
|
|
9657
|
-
}
|
|
9658
|
-
}
|
|
9659
|
-
return true;
|
|
9660
|
-
};
|
|
9661
9728
|
for (const otherPattern of routePatternSet) {
|
|
9662
9729
|
if (otherPattern === routePattern) {
|
|
9663
9730
|
continue;
|
|
@@ -9667,7 +9734,9 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9667
9734
|
if (!otherSegment || otherSegment.type !== "literal") {
|
|
9668
9735
|
continue;
|
|
9669
9736
|
}
|
|
9670
|
-
if (
|
|
9737
|
+
if (
|
|
9738
|
+
!sharesPathUpTo(routePattern.pattern.segments, otherSegments, index)
|
|
9739
|
+
) {
|
|
9671
9740
|
continue;
|
|
9672
9741
|
}
|
|
9673
9742
|
connection.namedByLiteralRoutes = true;
|
|
@@ -9675,11 +9744,90 @@ const setupRoutePatterns = (routePatterns) => {
|
|
|
9675
9744
|
}
|
|
9676
9745
|
}
|
|
9677
9746
|
}
|
|
9747
|
+
// Phase 4c: A default with an address of its own. "/me/:section" defaulting
|
|
9748
|
+
// to "players", beside "/me/players" and "/me": the default section has a
|
|
9749
|
+
// segment, and the bare address is another page. The param is then required,
|
|
9750
|
+
// and the address writes every value, the default included. Both routes must
|
|
9751
|
+
// be declared: with none at the bare address, that address is the default's
|
|
9752
|
+
// ("/admin" opens on "/admin/settings/:tab"); with no literal for the default,
|
|
9753
|
+
// the bare address IS the default section ("/games/me").
|
|
9754
|
+
// The state keeps its default (getDefaultValue): what the signal holds while
|
|
9755
|
+
// another page of the family says nothing about it (see route.js).
|
|
9756
|
+
for (const routePattern of routePatternSet) {
|
|
9757
|
+
const { segments } = routePattern.pattern;
|
|
9758
|
+
for (const connection of routePattern.pathConnectionMap.values()) {
|
|
9759
|
+
const defaultValue = connection.getDefaultValue();
|
|
9760
|
+
if (defaultValue === undefined) {
|
|
9761
|
+
continue;
|
|
9762
|
+
}
|
|
9763
|
+
const paramSegment = segments.find(
|
|
9764
|
+
(seg) => seg.type === "param" && seg.name === connection.paramName,
|
|
9765
|
+
);
|
|
9766
|
+
if (!paramSegment) {
|
|
9767
|
+
continue;
|
|
9768
|
+
}
|
|
9769
|
+
const { index } = paramSegment;
|
|
9770
|
+
let defaultHasLiteral = false;
|
|
9771
|
+
let bareAddressTaken = false;
|
|
9772
|
+
for (const otherPattern of routePatternSet) {
|
|
9773
|
+
if (otherPattern === routePattern) {
|
|
9774
|
+
continue;
|
|
9775
|
+
}
|
|
9776
|
+
const otherSegments = otherPattern.pattern.segments;
|
|
9777
|
+
if (!sharesPathUpTo(segments, otherSegments, index)) {
|
|
9778
|
+
continue;
|
|
9779
|
+
}
|
|
9780
|
+
const otherSegment = otherSegments[index];
|
|
9781
|
+
if (otherSegment) {
|
|
9782
|
+
if (
|
|
9783
|
+
otherSegment.type === "literal" &&
|
|
9784
|
+
otherSegment.value === String(defaultValue)
|
|
9785
|
+
) {
|
|
9786
|
+
defaultHasLiteral = true;
|
|
9787
|
+
}
|
|
9788
|
+
continue;
|
|
9789
|
+
}
|
|
9790
|
+
// a route covering the addresses below it is a frame around the
|
|
9791
|
+
// sections, not a page standing at the bare address
|
|
9792
|
+
if (!coversAddressesBelow(otherPattern)) {
|
|
9793
|
+
bareAddressTaken = true;
|
|
9794
|
+
}
|
|
9795
|
+
}
|
|
9796
|
+
if (!defaultHasLiteral || !bareAddressTaken) {
|
|
9797
|
+
continue;
|
|
9798
|
+
}
|
|
9799
|
+
paramSegment.optional = false;
|
|
9800
|
+
// Both answer for the address: the url build, the signal -> url sync,
|
|
9801
|
+
// and checkIfLiteralCanBeOptionalWithPatternObj ("/me" does not stand for
|
|
9802
|
+
// "/me/players" either)
|
|
9803
|
+
connection.isDefaultValue = (value) => value === undefined;
|
|
9804
|
+
connection.isCustomValue = (value) => value !== undefined;
|
|
9805
|
+
}
|
|
9806
|
+
}
|
|
9678
9807
|
// Phase 5: Calculate depths for all patterns
|
|
9679
9808
|
for (const routePattern of routePatternSet) {
|
|
9680
9809
|
calculatePatternDepth(routePattern);
|
|
9681
9810
|
}
|
|
9682
9811
|
};
|
|
9812
|
+
// Whether two patterns spell the same path before `index`: the same literals,
|
|
9813
|
+
// and a param wherever the other has one
|
|
9814
|
+
const sharesPathUpTo = (segments, otherSegments, index) => {
|
|
9815
|
+
for (let i = 0; i < index; i++) {
|
|
9816
|
+
const seg = segments[i];
|
|
9817
|
+
const otherSeg = otherSegments[i];
|
|
9818
|
+
if (!otherSeg) {
|
|
9819
|
+
return false;
|
|
9820
|
+
}
|
|
9821
|
+
if (seg.type === "literal" && otherSeg.type === "literal") {
|
|
9822
|
+
if (seg.value !== otherSeg.value) {
|
|
9823
|
+
return false;
|
|
9824
|
+
}
|
|
9825
|
+
} else if (seg.type !== otherSeg.type) {
|
|
9826
|
+
return false;
|
|
9827
|
+
}
|
|
9828
|
+
}
|
|
9829
|
+
return true;
|
|
9830
|
+
};
|
|
9683
9831
|
// Store the most specific parent (closest parent in hierarchy)
|
|
9684
9832
|
const getPathSegmentCount = (pattern) => {
|
|
9685
9833
|
// Only count path segments, not query parameters
|
|
@@ -9741,6 +9889,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9741
9889
|
* are the same state: writing the signal rewrites the address, an address
|
|
9742
9890
|
* arriving from outside writes the signal, and the param disappears from the
|
|
9743
9891
|
* url while the signal holds its default.
|
|
9892
|
+
* They exist on this route's address only, unless the pattern catches the
|
|
9893
|
+
* addresses below it (`/map/`, `/map/*`, the root): then they hold on every
|
|
9894
|
+
* route below as well. `/places` does not lend its `?q=` to `/places/clubs`.
|
|
9744
9895
|
* `{ signal, default }` says what that state is worth ON THIS ROUTE: the same
|
|
9745
9896
|
* step opens a creation wizard on its first question and an edition one on
|
|
9746
9897
|
* its summary, each address staying clean on the step its page opens on. One
|
|
@@ -9759,7 +9910,9 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
9759
9910
|
* whose param declines the segment does not match at all: no signal is
|
|
9760
9911
|
* written, `<Route fallback>` is reachable, and `/:gameId` can sit at the root
|
|
9761
9912
|
* without swallowing `/cgu`. A constrained param is also required — no segment
|
|
9762
|
-
* is not one of the values it accepts — so `/:gameId` does not match
|
|
9913
|
+
* is not one of the values it accepts — so `/:gameId` does not match `/`,
|
|
9914
|
+
* unless its default stands for the bare address (see docs/navigation.md,
|
|
9915
|
+
* "Declaring the sections is what makes them places").
|
|
9763
9916
|
*
|
|
9764
9917
|
* Constrain the shape, not the existence: whether that game exists is for the
|
|
9765
9918
|
* route action and the page to answer, on a route that did match.
|
|
@@ -10942,14 +11095,17 @@ const urlWithoutSearchAndHash = (url) => {
|
|
|
10942
11095
|
*
|
|
10943
11096
|
* - `isShown()` — whether the container would still show the page. False as
|
|
10944
11097
|
* soon as the address leads elsewhere: the page is about to be taken down, or
|
|
10945
|
-
* hidden until the route transition leaving it is over,
|
|
10946
|
-
*
|
|
10947
|
-
*
|
|
10948
|
-
*
|
|
11098
|
+
* hidden until the route transition leaving it is over, nothing it reads is
|
|
11099
|
+
* worth a render any more, and nothing it holds speaks for the page shown (a
|
|
11100
|
+
* list's hold on the document, see holdDocumentScroll). False for good once
|
|
11101
|
+
* it is kept while leaving: a return to its route mounts the page anew. Asked
|
|
11102
|
+
* outside any render — by a subscription deciding whether to re-render, by
|
|
11103
|
+
* the scroll restoration when a position is due — and it reads the routes
|
|
10949
11104
|
* untracked, so asking subscribes nobody to them.
|
|
10950
11105
|
* - `leavingSignal` — true once the page is hidden and kept while its movement
|
|
10951
|
-
* plays: still mounted, so what it fills outside its own nodes (a
|
|
10952
|
-
* bar) steps out rather than showing
|
|
11106
|
+
* plays: still mounted, so what it fills or moves outside its own nodes (a
|
|
11107
|
+
* slot in a bar, the document a list scrolls) steps out rather than showing
|
|
11108
|
+
* with, or moving, the page arriving. Every box of it measures 0×0 by then.
|
|
10953
11109
|
*/
|
|
10954
11110
|
const RoutePageContext = createContext(null);
|
|
10955
11111
|
|
|
@@ -12443,6 +12599,25 @@ const getHrefTargetInfo = (
|
|
|
12443
12599
|
};
|
|
12444
12600
|
};
|
|
12445
12601
|
|
|
12602
|
+
// The target of a link whose caller names none: a page of another site opens
|
|
12603
|
+
// in a new tab. Only a page: a `tel:`, `mailto:` or `sms:` address is handed to
|
|
12604
|
+
// another app, a `blob:` or `data:` one is this page's own, and a tab opened
|
|
12605
|
+
// for them would be left behind empty.
|
|
12606
|
+
const getDefaultHrefTarget = (
|
|
12607
|
+
href,
|
|
12608
|
+
currentUrlString = window.location.href,
|
|
12609
|
+
) => {
|
|
12610
|
+
const { isEmpty, isSameSite } = getHrefTargetInfo(href, currentUrlString);
|
|
12611
|
+
if (isEmpty || isSameSite) {
|
|
12612
|
+
return undefined;
|
|
12613
|
+
}
|
|
12614
|
+
const { protocol } = new URL(href, currentUrlString);
|
|
12615
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
12616
|
+
return undefined;
|
|
12617
|
+
}
|
|
12618
|
+
return "_blank";
|
|
12619
|
+
};
|
|
12620
|
+
|
|
12446
12621
|
// A field of getHrefTargetInfo that depends on the page one is on (isCurrent,
|
|
12447
12622
|
// isAnchor), read through a computed of that one boolean: the caller re-renders
|
|
12448
12623
|
// when it flips, not at every address write — a search param bound to a wheel
|
|
@@ -12483,6 +12658,27 @@ const linkAsksForDocument = (linkElement) => {
|
|
|
12483
12658
|
return linkElement.hasAttribute(LINK_DOCUMENT_ATTRIBUTE);
|
|
12484
12659
|
};
|
|
12485
12660
|
|
|
12661
|
+
/*
|
|
12662
|
+
* A link does not always move the tab it is in. A `target` naming another
|
|
12663
|
+
* browsing context (a new tab, a named window) loads the address there, in a
|
|
12664
|
+
* document of its own; a `download` saves it. The browser answers both
|
|
12665
|
+
* without touching this page, and that is the point of writing them: reading
|
|
12666
|
+
* the terms beside a half-filled form costs nothing. Routing such a link
|
|
12667
|
+
* would take this tab instead, and give no tab and no file.
|
|
12668
|
+
*
|
|
12669
|
+
* `_parent` and `_top` count as elsewhere too. Inside a frame they are; at the
|
|
12670
|
+
* top level the browser treats them as `_self` and loads a document, which
|
|
12671
|
+
* costs a load and nothing else.
|
|
12672
|
+
*/
|
|
12673
|
+
|
|
12674
|
+
const linkOpensElsewhere = (linkElement) => {
|
|
12675
|
+
const target = linkElement.getAttribute("target");
|
|
12676
|
+
if (target && target !== "_self") {
|
|
12677
|
+
return true;
|
|
12678
|
+
}
|
|
12679
|
+
return linkElement.hasAttribute("download");
|
|
12680
|
+
};
|
|
12681
|
+
|
|
12486
12682
|
/*
|
|
12487
12683
|
* A press aims at a place; it does not always go one step deeper. A row of tabs
|
|
12488
12684
|
* is a lateral move — the neighbour is one finger away — so the whole row should
|
|
@@ -12812,8 +13008,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12812
13008
|
// Ignore non-left clicks
|
|
12813
13009
|
return;
|
|
12814
13010
|
}
|
|
12815
|
-
if (e.metaKey) {
|
|
12816
|
-
//
|
|
13011
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
13012
|
+
// A modifier asks the browser for something other than this tab: a new
|
|
13013
|
+
// tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
|
|
13014
|
+
// (alt).
|
|
12817
13015
|
return;
|
|
12818
13016
|
}
|
|
12819
13017
|
if (e.defaultPrevented) {
|
|
@@ -12824,6 +13022,10 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
12824
13022
|
if (linkElement.hasAttribute("data-readonly")) {
|
|
12825
13023
|
return;
|
|
12826
13024
|
}
|
|
13025
|
+
if (linkOpensElsewhere(linkElement)) {
|
|
13026
|
+
// Another tab or window, or a download (see link_elsewhere.js).
|
|
13027
|
+
return;
|
|
13028
|
+
}
|
|
12827
13029
|
const href = linkElement.href;
|
|
12828
13030
|
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
12829
13031
|
getHrefTargetInfo(href);
|
|
@@ -16925,6 +17127,9 @@ const surfaceTextCss = /* css */ `
|
|
|
16925
17127
|
}
|
|
16926
17128
|
`;
|
|
16927
17129
|
|
|
17130
|
+
// What a callout's own JSX may know about the callout around it: how to close
|
|
17131
|
+
// it, and the element — whose id is what `commandFor` needs when the button
|
|
17132
|
+
// closing it is not inside it (inside, `--navi-close` finds it on its own).
|
|
16928
17133
|
const CalloutContext = createContext();
|
|
16929
17134
|
const useCalloutRequestClose = () => {
|
|
16930
17135
|
return useContext(CalloutContext)?.requestClose;
|
|
@@ -17058,7 +17263,7 @@ const CALLOUT_STATUS_GLYPH_VIEWBOX = "0 0 540 540";
|
|
|
17058
17263
|
const CALLOUT_STATUS_GLYPH_PATH = "m232.5,121 8,196h59l8-196zm36.5,224a37,37 0 1,0 2,0z";
|
|
17059
17264
|
|
|
17060
17265
|
/**
|
|
17061
|
-
* @type {import("
|
|
17266
|
+
* @type {import("preact").FunctionComponent<{
|
|
17062
17267
|
* status?: "info" | "warning" | "error" | "success" | "none",
|
|
17063
17268
|
* shape?: "square" | "circle",
|
|
17064
17269
|
* }>}
|
|
@@ -21877,23 +22082,12 @@ defineInteractionDetector({
|
|
|
21877
22082
|
// A link and an image are draggable without anyone asking, and a native
|
|
21878
22083
|
// drag IS press-and-move: the browser claims the gesture, takes the pointer
|
|
21879
22084
|
// 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.
|
|
22085
|
+
// `draggable` refuses it on the element itself, before it starts; the drag
|
|
22086
|
+
// of a link or a thumbnail INSIDE it is refused by the travel reading the
|
|
22087
|
+
// press (startDragToTravel). The cost is stated rather than worked around:
|
|
22088
|
+
// an element that takes a swipe cannot also be dragged out of the page,
|
|
22089
|
+
// because there is one gesture and it cannot mean both.
|
|
21889
22090
|
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
22091
|
|
|
21898
22092
|
// A touch this element may take has to be refusable before the finger
|
|
21899
22093
|
// lands, or the browser can cancel an established swipe mid-gesture by
|
|
@@ -22928,9 +23122,10 @@ const isMac = detectMac();
|
|
|
22928
23122
|
* place rather than about this box.
|
|
22929
23123
|
*
|
|
22930
23124
|
* `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.
|
|
23125
|
+
* pan: the surface a finger has given the hand keeps it — panning on contact,
|
|
23126
|
+
* like the same plan opened full screen — and asks again after a press has
|
|
23127
|
+
* landed away from it, which is the hand saying it has moved on. A mouse never
|
|
23128
|
+
* owed the wait, so it neither spends it nor marks it. What answers "away" is the same
|
|
22934
23129
|
* thing that closes a popup, and the surface is the one that watches for it, so
|
|
22935
23130
|
* an application never keeps a `pointerdown` listener on the window to know.
|
|
22936
23131
|
* `[data-hand-kept]` is on the element for as long as it holds the hand that
|
|
@@ -24052,7 +24247,8 @@ const isSameKey = (browserEventKey, key) => {
|
|
|
24052
24247
|
* and that field is the control the declaration speaks for: an ArrowUp declared
|
|
24053
24248
|
* on a text field is the field saying what ArrowUp does there, not a container
|
|
24054
24249
|
* 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
|
|
24250
|
+
* keys for something else (a row, a list) keeps every key it has a use for,
|
|
24251
|
+
* even when it is the only control in that box.
|
|
24056
24252
|
*
|
|
24057
24253
|
* A declared key that matches takes the key: its default does not happen as
|
|
24058
24254
|
* well, whether the gate lets the interaction through or refuses it and says
|
|
@@ -24078,7 +24274,10 @@ defineInteractionDetector({
|
|
|
24078
24274
|
};
|
|
24079
24275
|
}
|
|
24080
24276
|
const onKeyDown = createOnKeyDownForShortcuts(shortcuts, {
|
|
24081
|
-
|
|
24277
|
+
// Only the control's own root speaks for its host: a box that wraps a
|
|
24278
|
+
// single control is reached by that control's gate, not handed its keys.
|
|
24279
|
+
ownsTarget: (target) =>
|
|
24280
|
+
isControlRoot(element) && target === findNearestControlHost(element),
|
|
24082
24281
|
});
|
|
24083
24282
|
element.addEventListener("keydown", onKeyDown);
|
|
24084
24283
|
return () => {
|
|
@@ -28146,6 +28345,127 @@ const useForceRender = () => {
|
|
|
28146
28345
|
|
|
28147
28346
|
// https://github.com/preactjs/preact/issues/4756
|
|
28148
28347
|
|
|
28348
|
+
/**
|
|
28349
|
+
* Reads the current state of an action and returns `[data, loading, error, dismissError]`.
|
|
28350
|
+
*
|
|
28351
|
+
* By default (`loading` and `error` not set) the component suspends on load
|
|
28352
|
+
* and throws on failure, delegating both states to the nearest `<Loading>` and
|
|
28353
|
+
* `<ErrorBoundary>` ancestors. Pass `loading: true` or `error: true` to handle
|
|
28354
|
+
* either state directly inside the component instead.
|
|
28355
|
+
*
|
|
28356
|
+
* Return value: `[data, loading, error, dismissError]`
|
|
28357
|
+
* - `data` — the action's last successful data, or `undefined` if it has never completed
|
|
28358
|
+
* - `loading` — `true` while the action is running (only when `loading: true` is passed)
|
|
28359
|
+
* - `error` — the Error thrown by the action (only when `error: true` is passed)
|
|
28360
|
+
* - `dismissError` — takes that error out of what this hook returns, nothing else (only when `error: true` is passed)
|
|
28361
|
+
*
|
|
28362
|
+
* Stale data is always returned: the previous `data` remains available while
|
|
28363
|
+
* the action re-runs and after a run has failed, so a screen that had something
|
|
28364
|
+
* to show never has it taken away. What is drawn over it — a loading indicator,
|
|
28365
|
+
* the error, nothing — is entirely up to the component.
|
|
28366
|
+
*
|
|
28367
|
+
* `loading` covers the delay of a debounced binding
|
|
28368
|
+
* (`bindParams(paramsSignal, { debounce })`, `actionRunEffect({ debounce })`)
|
|
28369
|
+
* as well as the run itself: from the moment the params move, what is on screen
|
|
28370
|
+
* is the previous answer, and a screen showing "searching…" while someone types
|
|
28371
|
+
* needs to know it. Suspending has nothing to wait on during that delay, so
|
|
28372
|
+
* only `loading: true` reads it — the default keeps showing the previous answer
|
|
28373
|
+
* until the run really starts.
|
|
28374
|
+
*
|
|
28375
|
+
* When `loading` is not set (default), the component suspends until data is
|
|
28376
|
+
* ready, so `data` is always defined when the component renders and `loading`
|
|
28377
|
+
* is always `false`. With `loading: true` the component never suspends: every
|
|
28378
|
+
* state — running, settling, params nobody has started yet, a failure whose
|
|
28379
|
+
* message was dismissed — comes back as a value, so the subtree it draws is
|
|
28380
|
+
* never swapped for the boundary above and remounted.
|
|
28381
|
+
*
|
|
28382
|
+
* When `error` is not set (default), any action failure causes the component
|
|
28383
|
+
* to throw, so `error` is always `undefined` when the component renders. With
|
|
28384
|
+
* `error: true` the failure arrives beside the data the action already had — a
|
|
28385
|
+
* message on the content, not one in place of it. `dismissError()` removes the
|
|
28386
|
+
* message and nothing else: nothing is asked again, and what comes back is the
|
|
28387
|
+
* same state minus the error — the same data, or none at all when nothing ever
|
|
28388
|
+
* came back.
|
|
28389
|
+
*
|
|
28390
|
+
* `data === undefined` says there is nothing to show and `loading` says whether
|
|
28391
|
+
* anything is on its way: a skeleton reads both, or it claims a load nobody
|
|
28392
|
+
* started (see docs/data_states.md).
|
|
28393
|
+
*
|
|
28394
|
+
* @param {object | (() => Promise<any>) | Promise<any>} promiseOrAction -
|
|
28395
|
+
* an action, or the request this component owns written as a function:
|
|
28396
|
+
* made into an action once, on the first render, and kept for the life of
|
|
28397
|
+
* the instance — so it may be written inline — with what it resolves to as
|
|
28398
|
+
* the data and `run` implied. Code loaded on demand reads this way
|
|
28399
|
+
* (`() => import("./plan.jsx").then((m) => m.Plan)`, see
|
|
28400
|
+
* docs/dynamic_import.md), as does anything else asked for once per mount.
|
|
28401
|
+
* @param {{ loading?: true, error?: true, run?: true, prerun?: true, onLoad?: (data: any, context: {params: any}) => void }} [options]
|
|
28402
|
+
* @param {true} [options.run] - The fallback for data nothing else can ask for:
|
|
28403
|
+
* this component owns the request and starts it, from the render that reads
|
|
28404
|
+
* it. `loading` and `error` keep their meaning — delegated by default,
|
|
28405
|
+
* handled inline when asked for — so a run started here suspends into
|
|
28406
|
+
* `<Loading>` like any other.
|
|
28407
|
+
*
|
|
28408
|
+
* A failure is shown, not asked again: a render that retried it would retry
|
|
28409
|
+
* it in a loop while the network is down — that is `rerun()`'s job. Except a
|
|
28410
|
+
* failure a prerun left (a `prerun: true` head start, a press that preran
|
|
28411
|
+
* it): nothing on screen asked for that run, so this asks again, once.
|
|
28412
|
+
*
|
|
28413
|
+
* Prefer a `routeAction` wherever the parameter is one the address holds (a
|
|
28414
|
+
* path param, a search param bound to a `stateSignal`), popup included: it is
|
|
28415
|
+
* asked for when the address changes, with everything else that address
|
|
28416
|
+
* needs, whereas this one cannot start before the component that draws it
|
|
28417
|
+
* exists — one render late, and behind whatever gesture mounted it. What is
|
|
28418
|
+
* left for `run` is the parameter chosen inside the component and dying with
|
|
28419
|
+
* it — and any run that WRITES, whatever its parameter: an address asks again
|
|
28420
|
+
* on every arrival, which is right for a read and wrong for a write (see
|
|
28421
|
+
* docs/actions.md and docs/popup_open.md).
|
|
28422
|
+
* @param {true} [options.prerun] - A head start: this component starts a
|
|
28423
|
+
* read that another one, mounted later, owns with `run: true` — the read
|
|
28424
|
+
* leaves as soon as its params are known instead of once the owner exists.
|
|
28425
|
+
* Started from the render like `run`, as a prerun: it draws none of it, so
|
|
28426
|
+
* nothing suspends (no `<Loading>` needed) and a failure is neither thrown
|
|
28427
|
+
* nor reported — the owner asks again. A head start still running when the
|
|
28428
|
+
* owner reads it is the owner's wait.
|
|
28429
|
+
* @param {(data: any, context: {params: any}) => void} [options.onLoad] - what
|
|
28430
|
+
* this screen does with the data ONCE, when it becomes known: seed the fields
|
|
28431
|
+
* someone is about to edit, remember where a list was, focus something.
|
|
28432
|
+
*
|
|
28433
|
+
* Fired once per set of params — never again for a rerun that brings the same
|
|
28434
|
+
* thing back. That is the whole point of it: a save, a refresh, a poll all
|
|
28435
|
+
* hand the data over again, and copying it a second time would overwrite what
|
|
28436
|
+
* the person is in the middle of writing. The action knows what it ran with,
|
|
28437
|
+
* so nobody has to guess the dependency.
|
|
28438
|
+
*
|
|
28439
|
+
* Called from a layout effect, so what it writes belongs to the same tick as
|
|
28440
|
+
* the render that got the data — a `<Form pristineKey>` taking its reference
|
|
28441
|
+
* then sees the filled fields rather than the empty ones.
|
|
28442
|
+
* @returns {[data: unknown, loading: boolean, error: Error | undefined, dismissError: (() => void) | undefined]}
|
|
28443
|
+
*
|
|
28444
|
+
* @example <caption>Default — delegate both states (least code)</caption>
|
|
28445
|
+
* const [user] = useAsyncData(userAction);
|
|
28446
|
+
* // user is always defined here — never loading, never error
|
|
28447
|
+
*
|
|
28448
|
+
* @example <caption>Handle loading inline while keeping stale data</caption>
|
|
28449
|
+
* const [user, loading] = useAsyncData(userAction, { loading: true });
|
|
28450
|
+
* // First load: user === undefined, loading === true → show skeleton
|
|
28451
|
+
* // While refreshing: user === <stale>, loading === true → show stale + spinner
|
|
28452
|
+
* // Done: user === <fresh>, loading === false
|
|
28453
|
+
*
|
|
28454
|
+
* @example <caption>Handle error inline</caption>
|
|
28455
|
+
* const [user, , error] = useAsyncData(userAction, { error: true });
|
|
28456
|
+
* if (error) return <ErrorCard message={error.message} />;
|
|
28457
|
+
*
|
|
28458
|
+
* @example <caption>Say the failure over the rows already on screen</caption>
|
|
28459
|
+
* const [items, , error, dismissError] = useAsyncData(listAction, { error: true });
|
|
28460
|
+
* // First failure: items === undefined → only the message
|
|
28461
|
+
* // Failed refresh: items === <stale> → the message above the rows
|
|
28462
|
+
* return (
|
|
28463
|
+
* <>
|
|
28464
|
+
* {error && <ErrorStrip error={error} onClose={dismissError} />}
|
|
28465
|
+
* {items && <List items={items} />}
|
|
28466
|
+
* </>
|
|
28467
|
+
* );
|
|
28468
|
+
*/
|
|
28149
28469
|
const useAsyncData = (promiseOrAction, {
|
|
28150
28470
|
loading = "delegate",
|
|
28151
28471
|
error = "delegate",
|
|
@@ -28588,7 +28908,7 @@ const LoadingFallback = ({
|
|
|
28588
28908
|
* ones that would render fine.
|
|
28589
28909
|
*
|
|
28590
28910
|
* @param {object} props
|
|
28591
|
-
* @param {Function|import("
|
|
28911
|
+
* @param {Function|import("preact").VNode} [props.fallback] - what is displayed
|
|
28592
28912
|
* instead of the children: an element, or a component receiving
|
|
28593
28913
|
* `{ error, resetError }`. Without it the boundary is transparent.
|
|
28594
28914
|
* @param {() => void} [props.onReset] - called when the fallback dismisses the
|
|
@@ -28947,6 +29267,12 @@ const hasWidthHeight = (context) => {
|
|
|
28947
29267
|
(context.styles.height || context.remainingProps.height)
|
|
28948
29268
|
);
|
|
28949
29269
|
};
|
|
29270
|
+
// The `stretch` sizing keyword: the containing block's width minus the box's
|
|
29271
|
+
// own margins, so a box with a margin fills its row without leaving it (100%
|
|
29272
|
+
// adds the margins on top). Spelled with the prefix because an inline style
|
|
29273
|
+
// holds one value, and this is the one name Chromium, WebKit and Firefox all
|
|
29274
|
+
// read (Firefox 155 has no unprefixed `stretch`).
|
|
29275
|
+
const FILL_AVAILABLE_WIDTH = "-webkit-fill-available";
|
|
28950
29276
|
const DIMENSION_PROPS = {
|
|
28951
29277
|
boxSizing: PASS_THROUGH,
|
|
28952
29278
|
width: PASS_THROUGH,
|
|
@@ -28999,16 +29325,16 @@ const DIMENSION_PROPS = {
|
|
|
28999
29325
|
return { flexGrow: expandWeight(value), flexBasis: "0%" };
|
|
29000
29326
|
}
|
|
29001
29327
|
if (parentBoxFlow === "flex-y" || parentBoxFlow === "inline-flex-y") {
|
|
29002
|
-
// width
|
|
29328
|
+
// A width is what fills the cross axis; align-self stretch would do
|
|
29003
29329
|
// the same but takes the parent's alignX away — a maxWidth-capped item
|
|
29004
29330
|
// in an alignX="center" parent would then sit at the start instead of
|
|
29005
29331
|
// in the middle.
|
|
29006
29332
|
return {
|
|
29007
|
-
width:
|
|
29333
|
+
width: FILL_AVAILABLE_WIDTH,
|
|
29008
29334
|
};
|
|
29009
29335
|
}
|
|
29010
29336
|
// Can't use flexGrow — parent is not flex-x
|
|
29011
|
-
return { width:
|
|
29337
|
+
return { width: FILL_AVAILABLE_WIDTH };
|
|
29012
29338
|
},
|
|
29013
29339
|
expandY: (value, { parentBoxFlow }) => {
|
|
29014
29340
|
if (!value) {
|
|
@@ -29569,6 +29895,7 @@ const getVisualChildStylePropStrategy = (name) => {
|
|
|
29569
29895
|
};
|
|
29570
29896
|
|
|
29571
29897
|
const isStyleProp = (name) => STYLE_PROP_NAME_SET.has(name);
|
|
29898
|
+
const isTypoProp = (name) => TYPO_PROP_NAME_SET.has(name);
|
|
29572
29899
|
|
|
29573
29900
|
const getStringifier = (key) => {
|
|
29574
29901
|
if (SPACING_PROP_SET.has(key)) {
|
|
@@ -30210,10 +30537,10 @@ const readCSSVarName = entry => Array.isArray(entry) ? entry[0] : entry;
|
|
|
30210
30537
|
const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
30211
30538
|
|
|
30212
30539
|
/**
|
|
30213
|
-
* @type {import("
|
|
30540
|
+
* @type {import("preact").FunctionComponent<{
|
|
30214
30541
|
* as?: string,
|
|
30215
30542
|
* className?: string,
|
|
30216
|
-
* style?: import("
|
|
30543
|
+
* style?: import("preact").JSX.CSSProperties & { [pseudo: string]: import("preact").JSX.CSSProperties },
|
|
30217
30544
|
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
30218
30545
|
* inline?: boolean,
|
|
30219
30546
|
* block?: boolean,
|
|
@@ -30228,7 +30555,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30228
30555
|
* hasChildUsingForwardedProps?: boolean,
|
|
30229
30556
|
* childPropSet?: Set<string>,
|
|
30230
30557
|
* preventInitialTransition?: boolean,
|
|
30231
|
-
* separator?: import("
|
|
30558
|
+
* separator?: import("preact").ComponentChildren | ((index: number) => import("preact").ComponentChildren),
|
|
30232
30559
|
* selfInteractions?: string,
|
|
30233
30560
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
30234
30561
|
* alignX?: string,
|
|
@@ -30240,7 +30567,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
30240
30567
|
* body?: boolean,
|
|
30241
30568
|
* sticky?: boolean,
|
|
30242
30569
|
* mount?: "always" | "after-paint",
|
|
30243
|
-
* children?: import("
|
|
30570
|
+
* children?: import("preact").ComponentChildren,
|
|
30244
30571
|
* [key: string]: any,
|
|
30245
30572
|
* }>}
|
|
30246
30573
|
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
@@ -32795,7 +33122,7 @@ const debug$1 = (...args) => {
|
|
|
32795
33122
|
* matches. `routeFallback()` gives that page a name, which is what lets a
|
|
32796
33123
|
* movement be written about it and its `matchingSignal` be read from outside
|
|
32797
33124
|
* the tree (see route_fallback.js).
|
|
32798
|
-
* @param {Function|import("
|
|
33125
|
+
* @param {Function|import("preact").VNode} [props.element] - what the branch renders
|
|
32799
33126
|
* @param {object} [props.elementProps] - props given to `element`
|
|
32800
33127
|
*
|
|
32801
33128
|
* A branch says what it renders, not what happens when it cannot: loading and
|
|
@@ -35144,7 +35471,7 @@ const css$13 = /* css */`@property --navi-route-transition-leave {
|
|
|
35144
35471
|
* that. An application that already has an element holding its pages can mark
|
|
35145
35472
|
* that one with `data-navi-route-transition-area` rather than nesting another.
|
|
35146
35473
|
*
|
|
35147
|
-
* @type {import("
|
|
35474
|
+
* @type {import("preact").FunctionComponent<{ children?: any, [key: string]: any }>}
|
|
35148
35475
|
*/
|
|
35149
35476
|
const RouteTransitionArea = ({
|
|
35150
35477
|
children,
|
|
@@ -35934,7 +36261,7 @@ const beginTransition = ({
|
|
|
35934
36261
|
warnAboutNamesEscapingArea(areaElement, capturedNames);
|
|
35935
36262
|
return;
|
|
35936
36263
|
}
|
|
35937
|
-
for (const name of capturedNames) {
|
|
36264
|
+
for (const name of capturedNames.keys()) {
|
|
35938
36265
|
if (name === "root") {
|
|
35939
36266
|
continue;
|
|
35940
36267
|
}
|
|
@@ -36137,9 +36464,10 @@ const ignoreSkipped$1 = () => {};
|
|
|
36137
36464
|
|
|
36138
36465
|
// The names the browser captured, read off the pictures themselves: what was
|
|
36139
36466
|
// asked for in CSS and what was taken are not the same question (see
|
|
36140
|
-
// viewTransitionReady).
|
|
36467
|
+
// viewTransitionReady). Each with the states it was taken in: a name taken in
|
|
36468
|
+
// only one of them pairs with nothing.
|
|
36141
36469
|
const capturedViewTransitionNames = () => {
|
|
36142
|
-
const names = new
|
|
36470
|
+
const names = new Map();
|
|
36143
36471
|
for (const animation of document.getAnimations()) {
|
|
36144
36472
|
const pseudoElement = animation.effect?.pseudoElement;
|
|
36145
36473
|
if (!pseudoElement || !pseudoElement.startsWith("::view-transition")) {
|
|
@@ -36149,43 +36477,59 @@ const capturedViewTransitionNames = () => {
|
|
|
36149
36477
|
if (nameStart === -1) {
|
|
36150
36478
|
continue;
|
|
36151
36479
|
}
|
|
36152
|
-
|
|
36480
|
+
const name = pseudoElement.slice(nameStart + 1, -1);
|
|
36481
|
+
let states = names.get(name);
|
|
36482
|
+
if (!states) {
|
|
36483
|
+
states = {
|
|
36484
|
+
old: false,
|
|
36485
|
+
new: false
|
|
36486
|
+
};
|
|
36487
|
+
names.set(name, states);
|
|
36488
|
+
}
|
|
36489
|
+
if (pseudoElement.startsWith("::view-transition-old(")) {
|
|
36490
|
+
states.old = true;
|
|
36491
|
+
} else if (pseudoElement.startsWith("::view-transition-new(")) {
|
|
36492
|
+
states.new = true;
|
|
36493
|
+
}
|
|
36153
36494
|
}
|
|
36154
36495
|
return names;
|
|
36155
36496
|
};
|
|
36156
36497
|
|
|
36157
|
-
//
|
|
36158
|
-
//
|
|
36159
|
-
//
|
|
36160
|
-
//
|
|
36498
|
+
// A name written inside the area is a group of its own: it stands where it was
|
|
36499
|
+
// captured while the area's two pictures carry the movement, in every browser.
|
|
36500
|
+
// Nested groups only keep it cut at the pages' edge (see the @supports block in
|
|
36501
|
+
// the CSS above). Paired with the same name on the other page it is a morph;
|
|
36502
|
+
// taken in one state only it pairs with nothing, and is a piece of its page
|
|
36503
|
+
// left behind, fading, while that page moves.
|
|
36161
36504
|
const NESTED_GROUPS_SUPPORTED = window.CSS.supports("view-transition-group", "contain");
|
|
36162
36505
|
const warnAboutNamesEscapingArea = (areaElement, capturedNames) => {
|
|
36163
|
-
|
|
36164
|
-
|
|
36165
|
-
}
|
|
36166
|
-
let escapedName = null;
|
|
36167
|
-
for (const name of capturedNames) {
|
|
36506
|
+
const unpairedNames = new Set();
|
|
36507
|
+
for (const [name, states] of capturedNames) {
|
|
36168
36508
|
if (name === "root" || name === AREA_NAME || name.startsWith(FURNITURE_NAME_PREFIX)) {
|
|
36169
36509
|
continue;
|
|
36170
36510
|
}
|
|
36171
|
-
|
|
36172
|
-
|
|
36511
|
+
if (states.old && states.new) {
|
|
36512
|
+
continue;
|
|
36513
|
+
}
|
|
36514
|
+
unpairedNames.add(name);
|
|
36173
36515
|
}
|
|
36174
36516
|
// A name captured next to the area is not necessarily inside it — a bar the
|
|
36175
36517
|
// application animates on the same clock is named on purpose. The subtree is
|
|
36176
36518
|
// walked only once something is there to find, so the common case reads
|
|
36177
|
-
// nothing.
|
|
36178
|
-
|
|
36519
|
+
// nothing. The page being left is still in it, hidden (keepLeavingPages in
|
|
36520
|
+
// route.jsx), so a name taken only in the old state is found too.
|
|
36521
|
+
if (unpairedNames.size === 0) {
|
|
36179
36522
|
return;
|
|
36180
36523
|
}
|
|
36181
36524
|
for (const descendant of areaElement.querySelectorAll("*")) {
|
|
36182
36525
|
const {
|
|
36183
36526
|
viewTransitionName
|
|
36184
36527
|
} = getComputedStyle(descendant);
|
|
36185
|
-
if (!viewTransitionName
|
|
36528
|
+
if (!unpairedNames.has(viewTransitionName)) {
|
|
36186
36529
|
continue;
|
|
36187
36530
|
}
|
|
36188
|
-
|
|
36531
|
+
const unclipped = NESTED_GROUPS_SUPPORTED ? "" : " This browser has no nested groups (view-transition-group: contain), so it is not even cut at the pages' edge.";
|
|
36532
|
+
warnOnce("names-escaping-area", `"${viewTransitionName}" is a view-transition-name written inside the element marked ${TRANSITION_AREA_ATTRIBUTE}, and the page on the other side of the navigation has nothing of that name. A name lifts its element out of the pages' pictures, in every browser: with nothing to morph into, it stands where it was captured and fades on its own while the pages move.${unclipped} Give that name only for the length of the gesture it serves, or drop it while the pages move: :root:is([${TRANSITION_ATTRIBUTE}], [${ROUTE_TRAVEL_ATTRIBUTE}]) <that element> { view-transition-name: none } (with !important over an inline name).`);
|
|
36189
36533
|
return;
|
|
36190
36534
|
}
|
|
36191
36535
|
};
|
|
@@ -36330,6 +36674,8 @@ const installRouteTransitionCss = () => {
|
|
|
36330
36674
|
};
|
|
36331
36675
|
|
|
36332
36676
|
installImportMetaCssBuild(import.meta);
|
|
36677
|
+
// The pictures carrying a travel, among everything else the movement takes
|
|
36678
|
+
// along (see findLeadAnimation).
|
|
36333
36679
|
const TRAVEL_LEAD_NAMES = ["navi-route-travel"];
|
|
36334
36680
|
|
|
36335
36681
|
// A browser with no view transitions of its own has no picture of the page
|
|
@@ -36597,7 +36943,7 @@ const css$12 = /* css */`.navi_route_travel {
|
|
|
36597
36943
|
`;
|
|
36598
36944
|
|
|
36599
36945
|
/**
|
|
36600
|
-
* @type {import("
|
|
36946
|
+
* @type {import("preact").FunctionComponent<{
|
|
36601
36947
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
36602
36948
|
* axis?: "x"|"y",
|
|
36603
36949
|
* travelByDrag?: boolean,
|
|
@@ -39374,7 +39720,7 @@ const resetCommand = (source, event) => {
|
|
|
39374
39720
|
// answers: "save" stays, "delete" goes back to the list. Same shape as the
|
|
39375
39721
|
// browser's own formaction/formmethod, and the same shape the action already
|
|
39376
39722
|
// has (it is told which button requested it).
|
|
39377
|
-
const resolveAfterSend = (target, requester) => {
|
|
39723
|
+
const resolveAfterSend = (target, requester, surface) => {
|
|
39378
39724
|
const askedForByRequester = requester?.getAttribute?.("data-after-send");
|
|
39379
39725
|
if (askedForByRequester) {
|
|
39380
39726
|
return askedForByRequester;
|
|
@@ -39383,11 +39729,6 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39383
39729
|
if (askedFor) {
|
|
39384
39730
|
return askedFor;
|
|
39385
39731
|
}
|
|
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
39732
|
if (!surface) {
|
|
39392
39733
|
return undefined;
|
|
39393
39734
|
}
|
|
@@ -39399,6 +39740,11 @@ const resolveAfterSend = (target, requester) => {
|
|
|
39399
39740
|
}
|
|
39400
39741
|
return undefined;
|
|
39401
39742
|
};
|
|
39743
|
+
// The surface a send answers when the control says nothing. Found from above
|
|
39744
|
+
// the target: a popup that IS the send target is handled on its own (see the
|
|
39745
|
+
// aria-expanded branch in sendCommand), and must not answer twice.
|
|
39746
|
+
const resolveSendSurface = (target) =>
|
|
39747
|
+
target.parentElement?.closest(`[data-slide], [aria-expanded]`);
|
|
39402
39748
|
// What follows a send is caused by the send, not by the gesture: the send
|
|
39403
39749
|
// answered the gesture and may have prevented it (an Enter must not also click
|
|
39404
39750
|
// the submit button, nor activate the trigger of the popup it closed), and a
|
|
@@ -39480,20 +39826,30 @@ const sendCommand = (source, event, { requester }) => {
|
|
|
39480
39826
|
// and the popup must stay open — with the form still in front of the
|
|
39481
39827
|
// user, showing what it is waiting for.
|
|
39482
39828
|
let invalid = false;
|
|
39483
|
-
// What follows the send is
|
|
39484
|
-
//
|
|
39485
|
-
//
|
|
39486
|
-
//
|
|
39487
|
-
//
|
|
39488
|
-
//
|
|
39829
|
+
// What follows the send is aimed at the press, like a button's command
|
|
39830
|
+
// waiting for its action (see resolveNaviCommand): the send may take the
|
|
39831
|
+
// form out of the document (a form keyed on what it edits, a row that
|
|
39832
|
+
// moves once saved), and resolved from there it finds no popup to close.
|
|
39833
|
+
// Which command it is gets read again once the send went through: an
|
|
39834
|
+
// action that learned where to go from the response writes it on the
|
|
39835
|
+
// form while it runs (data-after-send). That one can only be aimed when
|
|
39836
|
+
// it is read.
|
|
39837
|
+
const surface = resolveSendSurface(target);
|
|
39838
|
+
const sentEvent = createSentEvent(event);
|
|
39839
|
+
const afterSendAtPress = resolveAfterSend(target, sendRequester, surface);
|
|
39840
|
+
const runAimedAtPress = afterSendAtPress
|
|
39841
|
+
? resolveNaviCommand(source, afterSendAtPress, sentEvent)
|
|
39842
|
+
: undefined;
|
|
39489
39843
|
const runAfterSend = () => {
|
|
39490
|
-
const afterSend = resolveAfterSend(target, sendRequester);
|
|
39844
|
+
const afterSend = resolveAfterSend(target, sendRequester, surface);
|
|
39491
39845
|
if (!afterSend) {
|
|
39492
39846
|
return;
|
|
39493
39847
|
}
|
|
39494
|
-
|
|
39495
|
-
|
|
39496
|
-
|
|
39848
|
+
if (afterSend === afterSendAtPress) {
|
|
39849
|
+
runAimedAtPress?.();
|
|
39850
|
+
return;
|
|
39851
|
+
}
|
|
39852
|
+
triggerNaviCommand(source, afterSend, sentEvent);
|
|
39497
39853
|
};
|
|
39498
39854
|
const {
|
|
39499
39855
|
result: sent,
|
|
@@ -40432,6 +40788,12 @@ const isDeclaredInsidePopup = (target, popupId) =>
|
|
|
40432
40788
|
* Lives on the popup's own content element rather than in the backdrop's
|
|
40433
40789
|
* handler, for both renderers: the backdrop is a sibling behind the popup
|
|
40434
40790
|
* (never an ancestor), so a press inside the popup's box never reaches it.
|
|
40791
|
+
*
|
|
40792
|
+
* Inside a box that travels by drag — a row of slides walked by a swipe, a
|
|
40793
|
+
* sheet pushed back towards its edge — the region is also where that travel
|
|
40794
|
+
* starts, and at the mousedown nobody knows yet which of the two the press is.
|
|
40795
|
+
* The dismissal then waits for the click, which only comes if the press stayed
|
|
40796
|
+
* a press: a travel swallows the one it leaves behind.
|
|
40435
40797
|
*/
|
|
40436
40798
|
const handlePressOnOutsideRegion = (
|
|
40437
40799
|
mouseDownEvent,
|
|
@@ -40462,11 +40824,36 @@ const handlePressOnOutsideRegion = (
|
|
|
40462
40824
|
mouseDownEvent.preventDefault();
|
|
40463
40825
|
return;
|
|
40464
40826
|
}
|
|
40465
|
-
if (pressOutside
|
|
40466
|
-
|
|
40467
|
-
|
|
40827
|
+
if (pressOutside !== "close" && pressOutside !== "cancel") {
|
|
40828
|
+
return;
|
|
40829
|
+
}
|
|
40830
|
+
const isCancel = pressOutside === "cancel";
|
|
40831
|
+
if (isPressDisputedByDrag(target)) {
|
|
40832
|
+
onClickOfThisPress(target, (clickEvent) => {
|
|
40833
|
+
openController.requestClose(clickEvent, { isCancel });
|
|
40468
40834
|
});
|
|
40835
|
+
return;
|
|
40469
40836
|
}
|
|
40837
|
+
openController.requestClose(mouseDownEvent, { isCancel });
|
|
40838
|
+
};
|
|
40839
|
+
|
|
40840
|
+
// The click ending the press whose mousedown landed on `element`. A click goes
|
|
40841
|
+
// to what the press and the release have in common, so it reaches `element`
|
|
40842
|
+
// only when the release stayed on it or inside it. A press that never clicks
|
|
40843
|
+
// leaves it armed until the next one, and no longer — a click always comes
|
|
40844
|
+
// after a press, so it cannot reach the click of another (the rule
|
|
40845
|
+
// click_suppression.js in @jsenv/dom goes by).
|
|
40846
|
+
const onClickOfThisPress = (element, callback) => {
|
|
40847
|
+
const onClick = (clickEvent) => {
|
|
40848
|
+
disarm();
|
|
40849
|
+
callback(clickEvent);
|
|
40850
|
+
};
|
|
40851
|
+
const disarm = () => {
|
|
40852
|
+
element.removeEventListener("click", onClick);
|
|
40853
|
+
window.removeEventListener("pointerdown", disarm, { capture: true });
|
|
40854
|
+
};
|
|
40855
|
+
element.addEventListener("click", onClick);
|
|
40856
|
+
window.addEventListener("pointerdown", disarm, { capture: true });
|
|
40470
40857
|
};
|
|
40471
40858
|
|
|
40472
40859
|
/**
|
|
@@ -45674,6 +46061,9 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
45674
46061
|
* handles the timing centrally rather than each component having to manage its
|
|
45675
46062
|
* own debounce logic.
|
|
45676
46063
|
*/
|
|
46064
|
+
// Sentinel used as the initial value of lastActionValueRef.
|
|
46065
|
+
// Distinct from undefined so that undefined (e.g. unchecked radio) can itself
|
|
46066
|
+
// be stored as a valid "last action value" and trigger the dedup logic.
|
|
45677
46067
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
45678
46068
|
|
|
45679
46069
|
// The control types rendered as an element `<label for>` can name: HTML's
|
|
@@ -45773,13 +46163,18 @@ const LISTENERS_CALLED_BY_REACTIONS = new Set(["onMouseDown", "onClick", "onKeyD
|
|
|
45773
46163
|
* the control opens, so that a listener hears the control as a whole.
|
|
45774
46164
|
* `onMouseDown`, `onClick`, `onKeyDown`, `onPaste` and `onInput` stay the
|
|
45775
46165
|
* host's either way: the control's own reactions to them call them.
|
|
46166
|
+
* @param {"host"|"root"} [options.nameOn] Where `aria-label` and
|
|
46167
|
+
* `aria-labelledby` land: where the role is. `"host"`, the default, for every
|
|
46168
|
+
* control whose host is what is announced; `"root"` for a control whose role
|
|
46169
|
+
* is on its root and whose host is an `aria-hidden` carrier (a wheel).
|
|
45776
46170
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
45777
46171
|
*/
|
|
45778
46172
|
const useControlProps = (props, {
|
|
45779
46173
|
controlType,
|
|
45780
46174
|
persists,
|
|
45781
46175
|
uiActionInternal,
|
|
45782
|
-
listenersOn = "host"
|
|
46176
|
+
listenersOn = "host",
|
|
46177
|
+
nameOn = "host"
|
|
45783
46178
|
}) => {
|
|
45784
46179
|
const debugUIState = useDebugUIState();
|
|
45785
46180
|
|
|
@@ -45867,6 +46262,7 @@ const useControlProps = (props, {
|
|
|
45867
46262
|
boundAction,
|
|
45868
46263
|
controlInfo,
|
|
45869
46264
|
listenersOn,
|
|
46265
|
+
nameOn,
|
|
45870
46266
|
listenersCalledByReactions: LISTENERS_CALLED_BY_REACTIONS
|
|
45871
46267
|
});
|
|
45872
46268
|
{
|
|
@@ -47095,6 +47491,7 @@ const useInteractiveProps = (props, {
|
|
|
47095
47491
|
boundAction,
|
|
47096
47492
|
controlInfo,
|
|
47097
47493
|
listenersOn,
|
|
47494
|
+
nameOn,
|
|
47098
47495
|
listenersCalledByReactions
|
|
47099
47496
|
}) => {
|
|
47100
47497
|
const {
|
|
@@ -47102,6 +47499,7 @@ const useInteractiveProps = (props, {
|
|
|
47102
47499
|
} = props;
|
|
47103
47500
|
const [controlRootProps, controlHostProps] = splitControlProps(props, {
|
|
47104
47501
|
listenersOn,
|
|
47502
|
+
nameOn,
|
|
47105
47503
|
listenersCalledByReactions
|
|
47106
47504
|
});
|
|
47107
47505
|
controlRootProps["navi-control"] = controlInfo.controlType;
|
|
@@ -47713,8 +48111,10 @@ const useInteractiveProps = (props, {
|
|
|
47713
48111
|
}
|
|
47714
48112
|
return [controlRootProps, controlHostProps];
|
|
47715
48113
|
};
|
|
48114
|
+
const NAME_ATTRIBUTE_SET = new Set(["aria-label", "aria-labelledby"]);
|
|
47716
48115
|
const splitControlProps = (props, {
|
|
47717
48116
|
listenersOn,
|
|
48117
|
+
nameOn,
|
|
47718
48118
|
listenersCalledByReactions
|
|
47719
48119
|
}) => {
|
|
47720
48120
|
const {
|
|
@@ -47725,6 +48125,10 @@ const splitControlProps = (props, {
|
|
|
47725
48125
|
};
|
|
47726
48126
|
const controlRootProps = {};
|
|
47727
48127
|
for (const key of Object.keys(props)) {
|
|
48128
|
+
if (nameOn === "root" && NAME_ATTRIBUTE_SET.has(key)) {
|
|
48129
|
+
controlRootProps[key] = props[key];
|
|
48130
|
+
continue;
|
|
48131
|
+
}
|
|
47728
48132
|
const attributeName = controlAttributeFromProp(key);
|
|
47729
48133
|
if (attributeName) {
|
|
47730
48134
|
controlHostProps[attributeName] = props[key];
|
|
@@ -49854,7 +50258,7 @@ const css$$ = /* css */`.navi_text_anchor {
|
|
|
49854
50258
|
* @param {{ size?: number, verticalAlign?: string }} [lineLayout]
|
|
49855
50259
|
* Describes the surrounding line context. Used as layout-effect dependencies so the correction
|
|
49856
50260
|
* reruns when the surrounding text's font-size or vertical-align changes.
|
|
49857
|
-
* @param {import("
|
|
50261
|
+
* @param {import("preact").RefObject} childRef — ref on the child element to reposition
|
|
49858
50262
|
*/
|
|
49859
50263
|
const TextAnchor = ({
|
|
49860
50264
|
childRef,
|
|
@@ -50516,25 +50920,25 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50516
50920
|
* See `docs/typography.md` for the decisions behind it — truncating, rows made
|
|
50517
50921
|
* of an icon, a text and an icon, and where a line may break.
|
|
50518
50922
|
*
|
|
50519
|
-
* @type {import("
|
|
50520
|
-
* children?: import("
|
|
50923
|
+
* @type {import("preact").FunctionComponent<{
|
|
50924
|
+
* children?: import("preact").ComponentChildren,
|
|
50521
50925
|
* as?: string,
|
|
50522
50926
|
* className?: string,
|
|
50523
|
-
* style?: import("
|
|
50927
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
50524
50928
|
* bold?: boolean,
|
|
50525
50929
|
* noWrap?: boolean,
|
|
50526
50930
|
* maxLines?: number,
|
|
50527
|
-
* spacing?: string | number | import("
|
|
50931
|
+
* spacing?: string | number | import("preact").ComponentChildren,
|
|
50528
50932
|
* loading?: boolean,
|
|
50529
50933
|
* skeleton?: boolean,
|
|
50530
50934
|
* attachLastChild?: boolean,
|
|
50531
50935
|
* preventSpaceUnderlines?: boolean,
|
|
50532
|
-
* holdSpaceForStyle?: import("
|
|
50936
|
+
* holdSpaceForStyle?: import("preact").JSX.CSSProperties,
|
|
50533
50937
|
* boldStable?: boolean,
|
|
50534
50938
|
* shrinkWrap?: boolean,
|
|
50535
50939
|
* capitalize?: boolean,
|
|
50536
50940
|
* selectRange?: string | [number, number] | [string, string],
|
|
50537
|
-
* childrenOutsideFlow?: import("
|
|
50941
|
+
* childrenOutsideFlow?: import("preact").ComponentChildren,
|
|
50538
50942
|
* [key: string]: any,
|
|
50539
50943
|
* }>}
|
|
50540
50944
|
*
|
|
@@ -50575,7 +50979,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50575
50979
|
* Replaces real space characters between children with padding-based spaces.
|
|
50576
50980
|
* Useful inside `<a>` elements where browsers draw an underline under spaces.
|
|
50577
50981
|
*
|
|
50578
|
-
* @param {import("
|
|
50982
|
+
* @param {import("preact").JSX.CSSProperties} [holdSpaceForStyle]
|
|
50579
50983
|
* Prevents layout shifts when text styles change (e.g. font-weight, font-size).
|
|
50580
50984
|
* Pass the CSS properties representing the "largest" visual state of the text.
|
|
50581
50985
|
* An invisible placeholder rendered with those styles reserves the space; the
|
|
@@ -50608,7 +51012,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
50608
51012
|
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
50609
51013
|
* substrings selecting from the first to the end of the second.
|
|
50610
51014
|
*
|
|
50611
|
-
* @param {import("
|
|
51015
|
+
* @param {import("preact").ComponentChildren} [childrenOutsideFlow]
|
|
50612
51016
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
50613
51017
|
* internally for overlays such as the skeleton container.
|
|
50614
51018
|
*/
|
|
@@ -50896,7 +51300,7 @@ const TextWithSelectRange = ({
|
|
|
50896
51300
|
* @param {object} props
|
|
50897
51301
|
* @param {string} [props.href] - URL/id of an external SVG symbol, rendered via
|
|
50898
51302
|
* `<svg><use href></svg>`. Mutually exclusive with meaningful `children`.
|
|
50899
|
-
* @param {import("
|
|
51303
|
+
* @param {import("preact").ComponentChildren} [props.children] - Inline icon
|
|
50900
51304
|
* content: an `<svg>` element, or a string (renders as a text/emoji icon).
|
|
50901
51305
|
* @param {boolean} [props.decorative] - Marks the icon `aria-hidden`. Defaults
|
|
50902
51306
|
* to `true` for an unsized, non-interactive icon; pass `false` for a
|
|
@@ -51071,6 +51475,11 @@ const usePreloadOnIntent = (ref, href, prefetch = true) => {
|
|
|
51071
51475
|
return undefined;
|
|
51072
51476
|
}
|
|
51073
51477
|
const preload = () => {
|
|
51478
|
+
// The tab or the window it opens loads its own code: what would be
|
|
51479
|
+
// fetched here is not what runs there.
|
|
51480
|
+
if (linkOpensElsewhere(element)) {
|
|
51481
|
+
return;
|
|
51482
|
+
}
|
|
51074
51483
|
preloadUrl(href);
|
|
51075
51484
|
};
|
|
51076
51485
|
element.addEventListener("pointerenter", preload);
|
|
@@ -51279,6 +51688,15 @@ const useDimColorWhen = (elementRef, shouldDim) => {
|
|
|
51279
51688
|
};
|
|
51280
51689
|
|
|
51281
51690
|
installImportMetaCssBuild(import.meta);
|
|
51691
|
+
/*
|
|
51692
|
+
* Apply opacity to child content, not the link element itself.
|
|
51693
|
+
*
|
|
51694
|
+
* Why not apply opacity directly to .navi_link?
|
|
51695
|
+
* - Would make focus outlines semi-transparent too (accessibility issue)
|
|
51696
|
+
* - We want dimmed text but full-opacity focus indicators for visibility
|
|
51697
|
+
*
|
|
51698
|
+
* This approach dims the content while preserving focus outline visibility.
|
|
51699
|
+
*/
|
|
51282
51700
|
const css$Z = /* css */`@layer navi {
|
|
51283
51701
|
.navi_link {
|
|
51284
51702
|
--link-border-radius: unset;
|
|
@@ -51299,6 +51717,19 @@ const css$Z = /* css */`@layer navi {
|
|
|
51299
51717
|
--link-current-indicator-spacing: 0;
|
|
51300
51718
|
--link-current-indicator-color: var(--navi-link-current-indicator-color);
|
|
51301
51719
|
}
|
|
51720
|
+
|
|
51721
|
+
.navi_link[data-variant="text"], .navi_link[data-variant="icon"] {
|
|
51722
|
+
--link-color: unset;
|
|
51723
|
+
--link-text-decoration: none;
|
|
51724
|
+
}
|
|
51725
|
+
|
|
51726
|
+
.navi_link[data-variant="tab"] {
|
|
51727
|
+
--link-background-hover: color-mix(in srgb,
|
|
51728
|
+
var(--link-background, transparent),
|
|
51729
|
+
var(--x-link-contrasting-color) 15%);
|
|
51730
|
+
--link-color: unset;
|
|
51731
|
+
--link-text-decoration: none;
|
|
51732
|
+
}
|
|
51302
51733
|
}
|
|
51303
51734
|
|
|
51304
51735
|
.navi_link {
|
|
@@ -51319,7 +51750,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
51319
51750
|
--x-link-padding-right: var(--link-padding-right, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51320
51751
|
--x-link-padding-bottom: var(--link-padding-bottom, var(--link-padding-y, var(--link-padding, 0px)));
|
|
51321
51752
|
--x-link-padding-left: var(--link-padding-left, var(--link-padding-x, var(--link-padding, 0px)));
|
|
51322
|
-
aspect-ratio: inherit;
|
|
51323
51753
|
padding-top: var(--x-link-padding-top);
|
|
51324
51754
|
padding-right: var(--x-link-padding-right);
|
|
51325
51755
|
padding-bottom: var(--x-link-padding-bottom);
|
|
@@ -51508,17 +51938,7 @@ const css$Z = /* css */`@layer navi {
|
|
|
51508
51938
|
margin-left: -.1em;
|
|
51509
51939
|
}
|
|
51510
51940
|
|
|
51511
|
-
&[data-variant="text"], &[data-variant="icon"] {
|
|
51512
|
-
--link-color: unset;
|
|
51513
|
-
--link-text-decoration: none;
|
|
51514
|
-
}
|
|
51515
|
-
|
|
51516
51941
|
&[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
51942
|
white-space: nowrap;
|
|
51523
51943
|
user-select: none;
|
|
51524
51944
|
|
|
@@ -51605,8 +52025,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51605
52025
|
*
|
|
51606
52026
|
* Href-derived behavior (all computed from `href`, recomputed on navigation):
|
|
51607
52027
|
* - internal vs external target (`data-href-internal`/`data-href-external`),
|
|
51608
|
-
* with `target`/`rel` auto-defaulted
|
|
51609
|
-
* `noopener noreferrer`
|
|
52028
|
+
* with `target`/`rel` auto-defaulted unless explicitly set: `_blank` for a
|
|
52029
|
+
* page of another site (`http:`/`https:`), `noopener noreferrer` for any
|
|
52030
|
+
* external address, nothing otherwise.
|
|
51610
52031
|
* - "current page" detection (`data-href-current`, `aria-current="page"`).
|
|
51611
52032
|
* - anchor links (`href` starting with `#`) get `data-href-anchor`.
|
|
51612
52033
|
* - an automatic end icon for `tel:`/`sms:`/`mailto:`/`github.com`, external
|
|
@@ -51668,14 +52089,14 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51668
52089
|
* text reads (see `holdSpaceForStyle` in `text.jsx`).
|
|
51669
52090
|
* @param {boolean} [props.currentEffectShadow] - Inset-shadow effect while
|
|
51670
52091
|
* current (used with `variant="tab"`).
|
|
51671
|
-
* @param {boolean|import("
|
|
52092
|
+
* @param {boolean|import("preact").ComponentChild} [props.startIcon] - Icon
|
|
51672
52093
|
* placed before the text.
|
|
51673
|
-
* @param {boolean|import("
|
|
52094
|
+
* @param {boolean|import("preact").ComponentChild} [props.endIcon] - Icon
|
|
51674
52095
|
* placed after the text; when omitted, an icon may be auto-chosen from the
|
|
51675
52096
|
* href (see above).
|
|
51676
|
-
* @param {boolean|import("
|
|
52097
|
+
* @param {boolean|import("preact").ComponentChild} [props.blankTargetIcon] -
|
|
51677
52098
|
* Override/suppress the auto external-link icon.
|
|
51678
|
-
* @param {boolean|import("
|
|
52099
|
+
* @param {boolean|import("preact").ComponentChild} [props.anchorIcon] -
|
|
51679
52100
|
* Controls the auto anchor icon shown for a childless anchor link:
|
|
51680
52101
|
* `true`/`undefined` uses the default chain SVG, `false` suppresses it, any
|
|
51681
52102
|
* other node replaces it. To render a plain `#` instead, prefer writing it as
|
|
@@ -51746,7 +52167,7 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
|
|
|
51746
52167
|
* @param {boolean} [props.preventDefault] - Call `event.preventDefault()` on
|
|
51747
52168
|
* click (navigation suppressed; `onClick` still runs).
|
|
51748
52169
|
* @param {(event: MouseEvent) => void} [props.onClick]
|
|
51749
|
-
* @param {import("
|
|
52170
|
+
* @param {import("preact").ComponentChildren} [props.children] - Link content;
|
|
51750
52171
|
* falls back to the route's relative URL / `href` per the rules above.
|
|
51751
52172
|
* @param {string} [props.name] - Control facade name (links may be nameless).
|
|
51752
52173
|
* @param {boolean} [props.disabled]
|
|
@@ -51875,7 +52296,7 @@ const LinkPlain = props => {
|
|
|
51875
52296
|
":-navi-href-current": innerCurrent,
|
|
51876
52297
|
":-navi-selected": selected
|
|
51877
52298
|
};
|
|
51878
|
-
const innerTarget = target === undefined ?
|
|
52299
|
+
const innerTarget = target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target;
|
|
51879
52300
|
const innerRel = rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel;
|
|
51880
52301
|
let innerEndIcon;
|
|
51881
52302
|
if (endIcon === undefined) {
|
|
@@ -52042,7 +52463,7 @@ const LinkPlain = props => {
|
|
|
52042
52463
|
},
|
|
52043
52464
|
href: href,
|
|
52044
52465
|
rel: innerRel,
|
|
52045
|
-
target: innerTarget === "_self" ? undefined :
|
|
52466
|
+
target: innerTarget === "_self" ? undefined : innerTarget
|
|
52046
52467
|
// Which slide this tab is, and which box to say it to: read by the <Nav>
|
|
52047
52468
|
// around it to place the row's own bar, and by the command above to find
|
|
52048
52469
|
// the container across the document.
|
|
@@ -52294,6 +52715,9 @@ const useSlideContainer = (target) => {
|
|
|
52294
52715
|
};
|
|
52295
52716
|
|
|
52296
52717
|
installImportMetaCssBuild(import.meta);
|
|
52718
|
+
// A name of its own for the bar under the current tab, one per nav on the page:
|
|
52719
|
+
// two bars answering to the same name are two elements claiming one picture,
|
|
52720
|
+
// and the browser refuses the whole transition rather than pick.
|
|
52297
52721
|
let navCount = 0;
|
|
52298
52722
|
// Worn by a nav of routes while a route movement between two of its tabs is
|
|
52299
52723
|
// pictured (see markIndicatorTakesPart, and the CSS below for what it decides).
|
|
@@ -52318,6 +52742,7 @@ const rememberProgressAnimation = (navElement, travel) => {
|
|
|
52318
52742
|
};
|
|
52319
52743
|
const css$Y = /* css */`@layer navi {
|
|
52320
52744
|
.navi_nav {
|
|
52745
|
+
--nav-width: fit-content;
|
|
52321
52746
|
--nav-border: none;
|
|
52322
52747
|
--nav-padding: 0px;
|
|
52323
52748
|
--nav-border-radius: 0px;
|
|
@@ -52341,7 +52766,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52341
52766
|
var(--nav-indicator-length-delta));
|
|
52342
52767
|
--x-nav-indicator-color: var(--nav-indicator-color, var(--nav-current-indicator-color));
|
|
52343
52768
|
--x-nav-indicator-color-toward: var(--nav-indicator-color-toward, var(--x-nav-indicator-color));
|
|
52344
|
-
z-index:
|
|
52769
|
+
z-index: 2;
|
|
52345
52770
|
background: color-mix(in srgb,
|
|
52346
52771
|
var(--x-nav-indicator-color-toward)
|
|
52347
52772
|
calc(var(--slide-travel-progress) * var(--nav-indicator-toward-sign, 0) *
|
|
@@ -52398,7 +52823,7 @@ const css$Y = /* css */`@layer navi {
|
|
|
52398
52823
|
}
|
|
52399
52824
|
|
|
52400
52825
|
.navi_nav {
|
|
52401
|
-
width:
|
|
52826
|
+
width: var(--nav-width);
|
|
52402
52827
|
padding-top: var(--nav-padding-top, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
52403
52828
|
padding-right: var(--nav-padding-right, var(--nav-padding-x, var(--nav-padding, unset)));
|
|
52404
52829
|
padding-bottom: var(--nav-padding-bottom, var(--nav-padding-y, var(--nav-padding, unset)));
|
|
@@ -52427,12 +52852,12 @@ const css$Y = /* css */`@layer navi {
|
|
|
52427
52852
|
--x-nav-child-border-radius: calc(var(--nav-border-radius) - var(--nav-padding));
|
|
52428
52853
|
--x-nav-link-border-radius: var(--link-border-radius, var(--x-nav-child-border-radius));
|
|
52429
52854
|
|
|
52430
|
-
&:
|
|
52855
|
+
&:nth-child(1 of .navi_link) {
|
|
52431
52856
|
border-top-left-radius: var(--x-nav-link-border-radius);
|
|
52432
52857
|
border-bottom-left-radius: var(--x-nav-link-border-radius);
|
|
52433
52858
|
}
|
|
52434
52859
|
|
|
52435
|
-
&:last-child {
|
|
52860
|
+
&:nth-last-child(1 of .navi_link) {
|
|
52436
52861
|
border-top-right-radius: var(--x-nav-link-border-radius);
|
|
52437
52862
|
border-bottom-right-radius: var(--x-nav-link-border-radius);
|
|
52438
52863
|
}
|
|
@@ -52535,7 +52960,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
52535
52960
|
};
|
|
52536
52961
|
|
|
52537
52962
|
/**
|
|
52538
|
-
* @type {import("
|
|
52963
|
+
* @type {import("preact").FunctionComponent<{
|
|
52539
52964
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
52540
52965
|
* currentIndicatorSlides?: boolean,
|
|
52541
52966
|
* slideContainer?: string,
|
|
@@ -53685,7 +54110,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53685
54110
|
};
|
|
53686
54111
|
|
|
53687
54112
|
/**
|
|
53688
|
-
* @type {import("
|
|
54113
|
+
* @type {import("preact").FunctionComponent<{
|
|
53689
54114
|
* value?: any,
|
|
53690
54115
|
* defaultValue?: any,
|
|
53691
54116
|
* onChange?: (value: any) => void,
|
|
@@ -53729,7 +54154,7 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
53729
54154
|
* grows the binder instead of closing in on the text.
|
|
53730
54155
|
* @param {string} [props.tabBorderColor] - The inactive tabs alone; the rest of
|
|
53731
54156
|
* the outline follows `borderColor`.
|
|
53732
|
-
* @param {import("
|
|
54157
|
+
* @param {import("preact").ComponentChildren} props.children - `Binder.Item`s.
|
|
53733
54158
|
*/
|
|
53734
54159
|
const Binder = ({
|
|
53735
54160
|
children,
|
|
@@ -53870,13 +54295,13 @@ const Binder = ({
|
|
|
53870
54295
|
/**
|
|
53871
54296
|
* @param {any} [props.value] - What identifies this item; defaults to its
|
|
53872
54297
|
* position among its siblings.
|
|
53873
|
-
* @param {import("
|
|
54298
|
+
* @param {import("preact").ComponentChildren} [props.label] - What the tab
|
|
53874
54299
|
* shows.
|
|
53875
54300
|
* @param {object} [props.route] - Makes the tab a `Link`
|
|
53876
54301
|
* to that route, and the binder follows the url: the open item is the one
|
|
53877
54302
|
* whose route matches, no `value`/`onChange` needed.
|
|
53878
54303
|
* @param {string} [props.href] - Same, for a plain url.
|
|
53879
|
-
* @param {import("
|
|
54304
|
+
* @param {import("preact").ComponentChildren} [props.children] - The page.
|
|
53880
54305
|
*
|
|
53881
54306
|
* Every other prop goes to the tab (a `Box`, or a `Link` when routed): an item
|
|
53882
54307
|
* styles and configures the control that opens it.
|
|
@@ -54286,7 +54711,7 @@ const FixedBarStyleCSSVars = {
|
|
|
54286
54711
|
};
|
|
54287
54712
|
|
|
54288
54713
|
/**
|
|
54289
|
-
* @type {import("
|
|
54714
|
+
* @type {import("preact").FunctionComponent<{
|
|
54290
54715
|
* area?: "top"|"bottom"|"left"|"right",
|
|
54291
54716
|
* width?: string|number,
|
|
54292
54717
|
* height?: string|number,
|
|
@@ -54677,7 +55102,7 @@ const css$U = /* css */`.navi_summary_marker {
|
|
|
54677
55102
|
`;
|
|
54678
55103
|
|
|
54679
55104
|
/**
|
|
54680
|
-
* @type {import("
|
|
55105
|
+
* @type {import("preact").FunctionComponent<{
|
|
54681
55106
|
* open?: boolean,
|
|
54682
55107
|
* loading?: boolean,
|
|
54683
55108
|
* openDirection?: "down" | "up" | "left",
|
|
@@ -55097,8 +55522,8 @@ const useExpandableContext = partName => {
|
|
|
55097
55522
|
};
|
|
55098
55523
|
|
|
55099
55524
|
/**
|
|
55100
|
-
* @type {import("
|
|
55101
|
-
* ui?: import("
|
|
55525
|
+
* @type {import("preact").FunctionComponent<{
|
|
55526
|
+
* ui?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55102
55527
|
* open?: boolean,
|
|
55103
55528
|
* defaultOpen?: boolean,
|
|
55104
55529
|
* signal?: import("@preact/signals").Signal,
|
|
@@ -55738,9 +56163,9 @@ const Expandable = props => {
|
|
|
55738
56163
|
* Controls inside it keep their own behavior and do not toggle. A Box, so it
|
|
55739
56164
|
* takes the layout and style props every Box takes.
|
|
55740
56165
|
*
|
|
55741
|
-
* @type {import("
|
|
55742
|
-
* marker?: false | import("
|
|
55743
|
-
* children?: import("
|
|
56166
|
+
* @type {import("preact").FunctionComponent<{
|
|
56167
|
+
* marker?: false | import("preact").ComponentChildren,
|
|
56168
|
+
* children?: import("preact").ComponentChildren | ((state: { open: boolean }) => import("preact").ComponentChildren),
|
|
55744
56169
|
* }>}
|
|
55745
56170
|
* @param marker - The chevron drawn before the label. `false` removes it (a UI
|
|
55746
56171
|
* part that is already an icon has no room for one); any node replaces it.
|
|
@@ -55815,7 +56240,7 @@ const ExpandableUI = ({
|
|
|
55815
56240
|
* Expandable, children may be a function `(data) => ui` or a branches object —
|
|
55816
56241
|
* see ActionRenderer.
|
|
55817
56242
|
*
|
|
55818
|
-
* @type {import("
|
|
56243
|
+
* @type {import("preact").FunctionComponent<{}>}
|
|
55819
56244
|
*/
|
|
55820
56245
|
const ExpandableContent = ({
|
|
55821
56246
|
children,
|
|
@@ -55971,6 +56396,40 @@ const createToggleEvent = open => {
|
|
|
55971
56396
|
return toggleEvent;
|
|
55972
56397
|
};
|
|
55973
56398
|
|
|
56399
|
+
/**
|
|
56400
|
+
* Creates a renderComponent function that passes props through a chain of resolvers.
|
|
56401
|
+
* Each resolver is a component function rendered in sequence (hooks are allowed).
|
|
56402
|
+
* To pass through to the next resolver, call useNextResolver() and render the
|
|
56403
|
+
* returned Next component with the desired props.
|
|
56404
|
+
* To terminate the chain early (e.g. render a specialized component), render
|
|
56405
|
+
* directly without calling Next.
|
|
56406
|
+
*
|
|
56407
|
+
* The last entry in the array is the final/target component — it receives null
|
|
56408
|
+
* from useNextResolver() indicating it is terminal.
|
|
56409
|
+
*
|
|
56410
|
+
* Usage:
|
|
56411
|
+
* const renderButton = createComponentResolver([ResolverA, ResolverB, ButtonTarget]);
|
|
56412
|
+
* // Then inside a component render:
|
|
56413
|
+
* renderButton(props)
|
|
56414
|
+
*
|
|
56415
|
+
* A chain step is one component: a runner, defined once per position, whose
|
|
56416
|
+
* render CALLS its resolver's body rather than rendering it as a child. The
|
|
56417
|
+
* body runs synchronously inside the runner's render — its hooks are the
|
|
56418
|
+
* runner's, at the same tree position on every render — and for exactly that
|
|
56419
|
+
* long, `useNextResolver()` answers with the runner after this one (null for
|
|
56420
|
+
* the last). Nothing is handed down through a context: a chain rendered by the
|
|
56421
|
+
* hundred (a list's rows) pays one component per step, not a provider and a
|
|
56422
|
+
* wrapper around each.
|
|
56423
|
+
*
|
|
56424
|
+
* A resolver that renders ANOTHER resolver — a type resolver picking the
|
|
56425
|
+
* component for `type="date"` — cannot render it as a JSX child: that child
|
|
56426
|
+
* would render later, outside any body, with no Next to read. It renders it
|
|
56427
|
+
* with `renderResolver(Resolver, props)`, which captures the Next of the body
|
|
56428
|
+
* it is called from and hands it to a runner made for that resolver — the
|
|
56429
|
+
* child continues the chain where its parent stood. Called anywhere else, or
|
|
56430
|
+
* a `useNextResolver()` reached outside a body, throws: the chain has no
|
|
56431
|
+
* silent answer to give there.
|
|
56432
|
+
*/
|
|
55974
56433
|
const createComponentResolver = (resolvers, {
|
|
55975
56434
|
pure
|
|
55976
56435
|
} = {}) => {
|
|
@@ -56180,10 +56639,21 @@ const useAccentColorAttributes = (
|
|
|
56180
56639
|
};
|
|
56181
56640
|
|
|
56182
56641
|
installImportMetaCssBuild(import.meta);
|
|
56642
|
+
/**
|
|
56643
|
+
* We need the content to visually shrink (scale down) but the button interactive area MUST remain intact
|
|
56644
|
+
* Otherwise a click on the edges of the button cannot not trigger the click event (mouseup occurs outside the button)
|
|
56645
|
+
**/
|
|
56646
|
+
|
|
56647
|
+
/**
|
|
56648
|
+
* We have to re-define the CSS of button because getComputedStyle(button).borderColor returns
|
|
56649
|
+
* rgb(0, 0, 0) while being visually grey in chrome
|
|
56650
|
+
* So we redefine chrome styles so that loader can keep up with the actual color visible to the user
|
|
56651
|
+
*/
|
|
56183
56652
|
const css$R = /* css */`@layer navi {
|
|
56184
56653
|
.navi_button {
|
|
56185
56654
|
--button-border-radius: var(--navi-control-border-radius);
|
|
56186
56655
|
--button-border-width: var(--navi-control-border-width);
|
|
56656
|
+
--border-width: var(--button-border-width);
|
|
56187
56657
|
--button-cta-background-color: var(--navi-accent-color);
|
|
56188
56658
|
--button-outline-width: var(--navi-focus-outline-width);
|
|
56189
56659
|
--button-outline-offset: calc(-.5 * var(--button-outline-width));
|
|
@@ -56192,7 +56662,7 @@ const css$R = /* css */`@layer navi {
|
|
|
56192
56662
|
--button-padding-y-default: var(--navi-button-padding-y-default);
|
|
56193
56663
|
--button-loader-color: var(--navi-loader-color);
|
|
56194
56664
|
--button-border-color: var(--navi-control-border-color);
|
|
56195
|
-
--button-background-color: var(--
|
|
56665
|
+
--button-background-color: var(--navi-button-background-color);
|
|
56196
56666
|
--button-color: currentColor;
|
|
56197
56667
|
--button-cursor: pointer;
|
|
56198
56668
|
--button-font-size: var(--navi-control-font-size);
|
|
@@ -56225,6 +56695,92 @@ const css$R = /* css */`@layer navi {
|
|
|
56225
56695
|
line-height: var(--navi-control-line-height);
|
|
56226
56696
|
display: inline-block;
|
|
56227
56697
|
}
|
|
56698
|
+
|
|
56699
|
+
.navi_button[data-variant="discrete"] {
|
|
56700
|
+
--button-border-width: 0;
|
|
56701
|
+
--button-border-color: transparent;
|
|
56702
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56703
|
+
--button-border-color-current: var(--button-border-color);
|
|
56704
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56705
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56706
|
+
--button-background-color: transparent;
|
|
56707
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56708
|
+
currentColor 8%,
|
|
56709
|
+
var(--button-background-color));
|
|
56710
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56711
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56712
|
+
}
|
|
56713
|
+
|
|
56714
|
+
.navi_button[data-variant="bare"] {
|
|
56715
|
+
--button-padding-x-default: 0;
|
|
56716
|
+
--button-padding-y-default: 0;
|
|
56717
|
+
--button-border-width: 0;
|
|
56718
|
+
--button-border-color: transparent;
|
|
56719
|
+
--button-border-color-hover: var(--button-border-color);
|
|
56720
|
+
--button-border-color-current: var(--button-border-color);
|
|
56721
|
+
--button-border-color-readonly: var(--button-border-color);
|
|
56722
|
+
--button-border-color-disabled: var(--button-border-color);
|
|
56723
|
+
--button-background-color: transparent;
|
|
56724
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56725
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56726
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56727
|
+
font-size: inherit;
|
|
56728
|
+
font-family: inherit;
|
|
56729
|
+
line-height: inherit;
|
|
56730
|
+
}
|
|
56731
|
+
|
|
56732
|
+
.navi_button[data-variant="discrete-border"] {
|
|
56733
|
+
--button-background-color: transparent;
|
|
56734
|
+
--button-background-color-hover: var(--button-background-color);
|
|
56735
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56736
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56737
|
+
}
|
|
56738
|
+
|
|
56739
|
+
.navi_button[data-variant="border"] {
|
|
56740
|
+
--button-background-color: transparent;
|
|
56741
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56742
|
+
currentColor 8%,
|
|
56743
|
+
var(--button-background-color));
|
|
56744
|
+
--button-background-color-readonly: var(--button-background-color);
|
|
56745
|
+
--button-background-color-disabled: var(--button-background-color);
|
|
56746
|
+
}
|
|
56747
|
+
|
|
56748
|
+
.navi_button[data-icon] {
|
|
56749
|
+
--button-padding: 0;
|
|
56750
|
+
}
|
|
56751
|
+
|
|
56752
|
+
.navi_button[data-cta] {
|
|
56753
|
+
--button-background-color: var(--button-cta-background-color);
|
|
56754
|
+
--button-border-color: var(--button-cta-background-color);
|
|
56755
|
+
--button-color: white;
|
|
56756
|
+
--button-background-color-hover: color-mix(in srgb,
|
|
56757
|
+
var(--button-background-color) 85%,
|
|
56758
|
+
white);
|
|
56759
|
+
--button-border-color-hover: color-mix(in srgb,
|
|
56760
|
+
var(--button-border-color) 85%,
|
|
56761
|
+
white);
|
|
56762
|
+
--button-background-color-readonly: color-mix(in srgb,
|
|
56763
|
+
var(--button-background-color) 50%,
|
|
56764
|
+
white);
|
|
56765
|
+
--button-border-color-readonly: color-mix(in srgb,
|
|
56766
|
+
var(--button-border-color) 50%,
|
|
56767
|
+
white);
|
|
56768
|
+
--button-background-color-disabled: color-mix(in srgb,
|
|
56769
|
+
var(--button-background-color) 40%,
|
|
56770
|
+
white);
|
|
56771
|
+
--button-border-color-disabled: color-mix(in srgb,
|
|
56772
|
+
var(--button-border-color) 40%,
|
|
56773
|
+
white);
|
|
56774
|
+
--button-color-disabled: color-mix(in srgb,
|
|
56775
|
+
var(--button-color) 60%,
|
|
56776
|
+
transparent);
|
|
56777
|
+
}
|
|
56778
|
+
|
|
56779
|
+
.navi_button[data-content-display="contents"] {
|
|
56780
|
+
background: none;
|
|
56781
|
+
border: none;
|
|
56782
|
+
padding: 0;
|
|
56783
|
+
}
|
|
56228
56784
|
}
|
|
56229
56785
|
|
|
56230
56786
|
a.navi_button {
|
|
@@ -56241,7 +56797,6 @@ a.navi_button {
|
|
|
56241
56797
|
--x-button-color: var(--button-color);
|
|
56242
56798
|
--x-button-cursor: var(--button-cursor);
|
|
56243
56799
|
box-sizing: border-box;
|
|
56244
|
-
aspect-ratio: inherit;
|
|
56245
56800
|
color: var(--x-button-color);
|
|
56246
56801
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--button-border-radius));
|
|
56247
56802
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--button-border-radius));
|
|
@@ -56250,12 +56805,15 @@ a.navi_button {
|
|
|
56250
56805
|
cursor: var(--x-button-cursor);
|
|
56251
56806
|
touch-action: manipulation;
|
|
56252
56807
|
user-select: none;
|
|
56253
|
-
background: none;
|
|
56254
|
-
border: none;
|
|
56255
56808
|
outline: none;
|
|
56256
|
-
padding: 0;
|
|
56257
56809
|
position: relative;
|
|
56258
56810
|
|
|
56811
|
+
&:where(:not([data-content-display="contents"])) {
|
|
56812
|
+
background: none;
|
|
56813
|
+
border: none;
|
|
56814
|
+
padding: 0;
|
|
56815
|
+
}
|
|
56816
|
+
|
|
56259
56817
|
& .navi_button_content {
|
|
56260
56818
|
--x-corner-top-left-radius: initial;
|
|
56261
56819
|
--x-corner-top-right-radius: initial;
|
|
@@ -56277,8 +56835,7 @@ a.navi_button {
|
|
|
56277
56835
|
gap: inherit;
|
|
56278
56836
|
color: inherit;
|
|
56279
56837
|
vertical-align: inherit;
|
|
56280
|
-
background: var(--x-button-background);
|
|
56281
|
-
background-color: var(--x-button-background-color, var(--x-button-background));
|
|
56838
|
+
background: var(--x-button-background, var(--x-button-background-color));
|
|
56282
56839
|
border: var(--x-button-border);
|
|
56283
56840
|
border-radius: inherit;
|
|
56284
56841
|
outline-width: var(--button-outline-width);
|
|
@@ -56301,6 +56858,10 @@ a.navi_button {
|
|
|
56301
56858
|
}
|
|
56302
56859
|
}
|
|
56303
56860
|
|
|
56861
|
+
&[data-background-color] .navi_button_content {
|
|
56862
|
+
background-color: var(--x-button-background-color);
|
|
56863
|
+
}
|
|
56864
|
+
|
|
56304
56865
|
&[data-href-current] {
|
|
56305
56866
|
--x-button-border-color: var(--button-border-color-current);
|
|
56306
56867
|
--x-button-background-color: var(--button-background-color-current);
|
|
@@ -56345,7 +56906,7 @@ a.navi_button {
|
|
|
56345
56906
|
}
|
|
56346
56907
|
|
|
56347
56908
|
&[data-content-display="contents"] {
|
|
56348
|
-
&
|
|
56909
|
+
&[data-focus-visible] {
|
|
56349
56910
|
outline: var(--button-outline-width) solid var(--button-outline-color);
|
|
56350
56911
|
outline-offset: var(--button-outline-offset);
|
|
56351
56912
|
}
|
|
@@ -56371,19 +56932,6 @@ a.navi_button {
|
|
|
56371
56932
|
}
|
|
56372
56933
|
|
|
56373
56934
|
&[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
56935
|
@media (hover: hover) {
|
|
56388
56936
|
&[data-pressed] {
|
|
56389
56937
|
& .navi_button_content {
|
|
@@ -56394,17 +56942,6 @@ a.navi_button {
|
|
|
56394
56942
|
}
|
|
56395
56943
|
|
|
56396
56944
|
&[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
56945
|
&[data-pressed] {
|
|
56409
56946
|
& .navi_button_content {
|
|
56410
56947
|
transform: none;
|
|
@@ -56413,10 +56950,6 @@ a.navi_button {
|
|
|
56413
56950
|
}
|
|
56414
56951
|
|
|
56415
56952
|
&[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
56953
|
--x-button-border-color: transparent;
|
|
56421
56954
|
|
|
56422
56955
|
&[data-hover] {
|
|
@@ -56428,15 +56961,6 @@ a.navi_button {
|
|
|
56428
56961
|
}
|
|
56429
56962
|
}
|
|
56430
56963
|
|
|
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
56964
|
&[data-press-effect="scale"][data-pressed] {
|
|
56441
56965
|
& .navi_button_content {
|
|
56442
56966
|
transform: scale(.9);
|
|
@@ -56450,37 +56974,9 @@ a.navi_button {
|
|
|
56450
56974
|
}
|
|
56451
56975
|
|
|
56452
56976
|
&[data-icon] {
|
|
56453
|
-
--button-padding: 0;
|
|
56454
56977
|
justify-content: center;
|
|
56455
56978
|
align-items: center;
|
|
56456
56979
|
}
|
|
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
56980
|
}
|
|
56485
56981
|
`;
|
|
56486
56982
|
const ButtonUI = props => {
|
|
@@ -56609,6 +57105,7 @@ const ButtonUI = props => {
|
|
|
56609
57105
|
"data-press-effect": pressEffect,
|
|
56610
57106
|
"data-icon": icon ? "" : undefined,
|
|
56611
57107
|
"data-cta": cta ? "" : undefined,
|
|
57108
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
56612
57109
|
"data-callout-arrow-x": "center"
|
|
56613
57110
|
// style management
|
|
56614
57111
|
,
|
|
@@ -56921,7 +57418,7 @@ const ButtonWithHref = props => {
|
|
|
56921
57418
|
} = getHrefTargetInfo(href, documentUrlSignal.peek());
|
|
56922
57419
|
return jsx(Next, {
|
|
56923
57420
|
...props,
|
|
56924
|
-
target: target === undefined ?
|
|
57421
|
+
target: target === undefined ? getDefaultHrefTarget(href, documentUrlSignal.peek()) : target,
|
|
56925
57422
|
rel: rel === undefined ? isSameSite ? undefined : "noopener noreferrer" : rel
|
|
56926
57423
|
// Under whatever the caller already says: a route knows better whether
|
|
56927
57424
|
// it is the page one is on (see button_route.jsx), and a demo can force
|
|
@@ -57075,7 +57572,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57075
57572
|
};
|
|
57076
57573
|
|
|
57077
57574
|
/**
|
|
57078
|
-
* @type {import("
|
|
57575
|
+
* @type {import("preact").FunctionComponent<{
|
|
57079
57576
|
* selfInteractions?: string,
|
|
57080
57577
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
57081
57578
|
* replace?: boolean,
|
|
@@ -57157,7 +57654,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57157
57654
|
* more: the work may already have happened on the other side (see
|
|
57158
57655
|
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
57159
57656
|
* where the screen can be re-opened on what is actually there.
|
|
57160
|
-
* @param {boolean|import("
|
|
57657
|
+
* @param {boolean|import("preact").ComponentChildren} [confirm] Ask before
|
|
57161
57658
|
* doing it, in the place the button stands: the first press replaces the
|
|
57162
57659
|
* button with this question and a "Confirmer"/"Annuler" pair, the second one
|
|
57163
57660
|
* does what the button was for. `true` asks navi's default question. The
|
|
@@ -57168,9 +57665,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
57168
57665
|
* question up, its message on the button that asked. Use `<Picker
|
|
57169
57666
|
* type="confirm">` instead when the question is long enough to want a popup,
|
|
57170
57667
|
* or when the row has no space for it.
|
|
57171
|
-
* @param {import("
|
|
57668
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] Label of the
|
|
57172
57669
|
* second press.
|
|
57173
|
-
* @param {import("
|
|
57670
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Label of the way
|
|
57174
57671
|
* out of the question.
|
|
57175
57672
|
* @param {string} [confirmTestId] `data-testid` of the second press.
|
|
57176
57673
|
* @param {string} [cancelTestId] `data-testid` of the way out.
|
|
@@ -57269,13 +57766,13 @@ const css$P = /* css */`.navi_control_swap {
|
|
|
57269
57766
|
`;
|
|
57270
57767
|
|
|
57271
57768
|
/**
|
|
57272
|
-
* @type {import("
|
|
57769
|
+
* @type {import("preact").FunctionComponent<{
|
|
57273
57770
|
* value?: string,
|
|
57274
57771
|
* defaultValue?: string,
|
|
57275
57772
|
* signal?: import("@preact/signals").Signal<string>,
|
|
57276
57773
|
* onChange?: (name: string, event: Event) => void,
|
|
57277
57774
|
* animation?: boolean,
|
|
57278
|
-
* children?: import("
|
|
57775
|
+
* children?: import("preact").ComponentChildren,
|
|
57279
57776
|
* }>}
|
|
57280
57777
|
* @param value - The name of the side holding the floor, driven from outside:
|
|
57281
57778
|
* the row swaps to match every change of it, and a press on a cap can still
|
|
@@ -57419,13 +57916,13 @@ const ControlSwap = props => {
|
|
|
57419
57916
|
* reads these and draws the caps at its ends, the controls between them. The
|
|
57420
57917
|
* props below say what the side IS; every other prop describes its cap.
|
|
57421
57918
|
*
|
|
57422
|
-
* @type {import("
|
|
57919
|
+
* @type {import("preact").FunctionComponent<{
|
|
57423
57920
|
* name?: string,
|
|
57424
|
-
* icon: import("
|
|
57921
|
+
* icon: import("preact").ComponentChildren,
|
|
57425
57922
|
* label: string,
|
|
57426
|
-
* badge?: boolean | import("
|
|
57923
|
+
* badge?: boolean | import("preact").ComponentChildren,
|
|
57427
57924
|
* autoFocus?: boolean,
|
|
57428
|
-
* children?: import("
|
|
57925
|
+
* children?: import("preact").ComponentChildren,
|
|
57429
57926
|
* [key: string]: any,
|
|
57430
57927
|
* }>}
|
|
57431
57928
|
* @param name - How `value`/`signal`/`onChange` name this side. Its position
|
|
@@ -57633,6 +58130,51 @@ const createAvailableConstraint = (
|
|
|
57633
58130
|
};
|
|
57634
58131
|
};
|
|
57635
58132
|
|
|
58133
|
+
/**
|
|
58134
|
+
* ControlGroup — a named sub-group of form controls whose combined state is
|
|
58135
|
+
* aggregated into a single key/value object, exactly like a Form but without
|
|
58136
|
+
* the <form> element semantics.
|
|
58137
|
+
*
|
|
58138
|
+
* Use it when you need a self-contained cluster of inputs that:
|
|
58139
|
+
* - produce one composite value (an object) as their joint state
|
|
58140
|
+
* - can carry their own action / uiAction / command
|
|
58141
|
+
* - may live inside or outside a <Form>
|
|
58142
|
+
*
|
|
58143
|
+
* Props:
|
|
58144
|
+
* name — the key under which the composite value is registered in a
|
|
58145
|
+
* parent Form (optional when used standalone)
|
|
58146
|
+
* action — called with the aggregated object value when any child changes
|
|
58147
|
+
* command — navi command string (e.g. "--navi-send")
|
|
58148
|
+
* disabled — propagates to all children
|
|
58149
|
+
* readOnly — propagates to all children
|
|
58150
|
+
* as — HTML element to render (default "div")
|
|
58151
|
+
* children — the controls that belong to this group
|
|
58152
|
+
*
|
|
58153
|
+
* aggregateChildStates / distributeChildUIState
|
|
58154
|
+
* — what the group is worth, in both directions, when that is not
|
|
58155
|
+
* simply "one key per child": two wheels that add up to a number
|
|
58156
|
+
* of minutes, three fields that make one date. With them the
|
|
58157
|
+
* group takes and hands back a single value, so it can be driven
|
|
58158
|
+
* by one `value`/`signal` like any other control. Same mechanism
|
|
58159
|
+
* InputDuration uses for its own hour/minute/second fields (see
|
|
58160
|
+
* input_duration.jsx). `name` is unaffected: it still says under
|
|
58161
|
+
* which key the group's value — whatever shape it takes —
|
|
58162
|
+
* lands in the form around it.
|
|
58163
|
+
*
|
|
58164
|
+
* distributeChildStates
|
|
58165
|
+
* — the same way down, asked ONCE for all the children at a time:
|
|
58166
|
+
* `(groupValue, children) => Map<child, state>`. The plural of
|
|
58167
|
+
* `distributeChildUIState`, and the mirror of
|
|
58168
|
+
* `aggregateChildStates`, which already sees every child. For a
|
|
58169
|
+
* group whose children cannot be placed one at a time — four
|
|
58170
|
+
* seats where who sits down decides who moves, a row where the
|
|
58171
|
+
* first answer changes what the others may show. A child the Map
|
|
58172
|
+
* does not name is left where it is. Given both, this one wins.
|
|
58173
|
+
* It also closes the loop: the group holds ONE answer its
|
|
58174
|
+
* children are views of, so when one of them speaks the others
|
|
58175
|
+
* are placed again (the one that acted is left where the user
|
|
58176
|
+
* put it).
|
|
58177
|
+
*/
|
|
57636
58178
|
const ControlGroup = props => {
|
|
57637
58179
|
const defaultRef = useRef();
|
|
57638
58180
|
props.ref = props.ref || defaultRef;
|
|
@@ -57825,6 +58367,41 @@ const css$N = /* css */`@layer navi {
|
|
|
57825
58367
|
var(--button-background-color) 95%,
|
|
57826
58368
|
black);
|
|
57827
58369
|
}
|
|
58370
|
+
|
|
58371
|
+
.navi_checkbox[data-variant="switch"] {
|
|
58372
|
+
--margin: var(--switch-margin);
|
|
58373
|
+
--width: var(--switch-outer-width);
|
|
58374
|
+
--height: unset;
|
|
58375
|
+
--border-radius: var(--switch-border-radius);
|
|
58376
|
+
--background-color: var(--switch-background-color);
|
|
58377
|
+
--background-color-hover: var(--switch-background-color-hover);
|
|
58378
|
+
--background-color-readonly: var(--switch-background-color-readonly);
|
|
58379
|
+
--background-color-disabled: var(--switch-background-color-disabled);
|
|
58380
|
+
--background-color-checked: var(--switch-background-color-checked);
|
|
58381
|
+
--background-color-hover-checked: var(--switch-background-color-hover-checked);
|
|
58382
|
+
--background-color-readonly-checked: var(--switch-background-color-readonly-checked);
|
|
58383
|
+
--background-color-disabled-checked: var(--switch-background-color-disabled-checked);
|
|
58384
|
+
}
|
|
58385
|
+
|
|
58386
|
+
.navi_checkbox[data-variant="icon"] {
|
|
58387
|
+
--margin: 0;
|
|
58388
|
+
--width: auto;
|
|
58389
|
+
--height: auto;
|
|
58390
|
+
}
|
|
58391
|
+
|
|
58392
|
+
.navi_checkbox[data-variant="button"] {
|
|
58393
|
+
--margin: 0;
|
|
58394
|
+
--width: auto;
|
|
58395
|
+
--height: auto;
|
|
58396
|
+
--border-color: var(--button-border-color);
|
|
58397
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
58398
|
+
--background-color: var(--button-background-color);
|
|
58399
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
58400
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
58401
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
58402
|
+
--border-color-checked: var(--button-border-color);
|
|
58403
|
+
--background-color-checked: var(--button-background-color);
|
|
58404
|
+
}
|
|
57828
58405
|
}
|
|
57829
58406
|
|
|
57830
58407
|
.navi_checkbox {
|
|
@@ -57965,18 +58542,6 @@ const css$N = /* css */`@layer navi {
|
|
|
57965
58542
|
|
|
57966
58543
|
&[data-variant="switch"] {
|
|
57967
58544
|
--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
58545
|
min-width: var(--switch-outer-width);
|
|
57981
58546
|
padding: var(--switch-padding);
|
|
57982
58547
|
background-color: var(--x-background-color);
|
|
@@ -57986,25 +58551,11 @@ const css$N = /* css */`@layer navi {
|
|
|
57986
58551
|
}
|
|
57987
58552
|
|
|
57988
58553
|
&[data-variant="icon"] {
|
|
57989
|
-
--margin: 0;
|
|
57990
|
-
--width: auto;
|
|
57991
|
-
--height: auto;
|
|
57992
58554
|
background: none;
|
|
57993
58555
|
border: none;
|
|
57994
58556
|
}
|
|
57995
58557
|
|
|
57996
58558
|
&[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
58559
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
58009
58560
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
58010
58561
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58190,7 +58741,7 @@ const css$M = /* css */`@layer navi {
|
|
|
58190
58741
|
[navi-field] {
|
|
58191
58742
|
--spacing-with-control: var(--navi-xs);
|
|
58192
58743
|
|
|
58193
|
-
& .navi_checkbox, & .navi_radio {
|
|
58744
|
+
& .navi_checkbox:not([data-variant="switch"]), & .navi_radio {
|
|
58194
58745
|
--margin: 0;
|
|
58195
58746
|
}
|
|
58196
58747
|
}
|
|
@@ -58545,6 +59096,28 @@ const css$L = /* css */`@layer navi {
|
|
|
58545
59096
|
--button-border-color-disabled: var(--border-color-readonly);
|
|
58546
59097
|
--button-background-color-disabled: var(--background-color-readonly);
|
|
58547
59098
|
}
|
|
59099
|
+
|
|
59100
|
+
.navi_radio[data-variant="icon"] {
|
|
59101
|
+
--width: auto;
|
|
59102
|
+
--height: auto;
|
|
59103
|
+
--outline-offset: 2px;
|
|
59104
|
+
--outline-width: 2px;
|
|
59105
|
+
}
|
|
59106
|
+
|
|
59107
|
+
.navi_radio[data-variant="button"] {
|
|
59108
|
+
--margin: 0;
|
|
59109
|
+
--outline-offset: 0px;
|
|
59110
|
+
--width: auto;
|
|
59111
|
+
--height: auto;
|
|
59112
|
+
--border-color: var(--button-border-color);
|
|
59113
|
+
--border-color-hover: var(--button-border-color-hover);
|
|
59114
|
+
--background-color: var(--button-background-color);
|
|
59115
|
+
--background-color-hover: var(--button-background-color-hover);
|
|
59116
|
+
--background-color-readonly: var(--button-background-color-readonly);
|
|
59117
|
+
--background-color-disabled: var(--button-background-color-disabled);
|
|
59118
|
+
--border-color-checked: var(--button-border-color);
|
|
59119
|
+
--background-color-checked: var(--button-background-color);
|
|
59120
|
+
}
|
|
58548
59121
|
}
|
|
58549
59122
|
|
|
58550
59123
|
.navi_radio {
|
|
@@ -58700,26 +59273,7 @@ const css$L = /* css */`@layer navi {
|
|
|
58700
59273
|
}
|
|
58701
59274
|
}
|
|
58702
59275
|
|
|
58703
|
-
&[data-variant="icon"] {
|
|
58704
|
-
--width: auto;
|
|
58705
|
-
--height: auto;
|
|
58706
|
-
--outline-offset: 2px;
|
|
58707
|
-
--outline-width: 2px;
|
|
58708
|
-
}
|
|
58709
|
-
|
|
58710
59276
|
&[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
59277
|
padding-top: var(--button-padding-top, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
58724
59278
|
padding-right: var(--button-padding-right, var(--button-padding-x, var(--button-padding, var(--button-padding-x-default))));
|
|
58725
59279
|
padding-bottom: var(--button-padding-bottom, var(--button-padding-y, var(--button-padding, var(--button-padding-y-default))));
|
|
@@ -58943,6 +59497,7 @@ const css$K = /* css */`@layer navi {
|
|
|
58943
59497
|
--outline-width: var(--navi-focus-outline-width);
|
|
58944
59498
|
--outline-offset: var(--outline-width);
|
|
58945
59499
|
--outline-color: var(--navi-focus-outline-color);
|
|
59500
|
+
--margin: 2px;
|
|
58946
59501
|
--height: 8px;
|
|
58947
59502
|
--thumb-size: 16px;
|
|
58948
59503
|
--thumb-width: var(--thumb-size);
|
|
@@ -59018,6 +59573,7 @@ const css$K = /* css */`@layer navi {
|
|
|
59018
59573
|
box-sizing: border-box;
|
|
59019
59574
|
width: fit-content;
|
|
59020
59575
|
height: var(--height);
|
|
59576
|
+
margin: var(--margin);
|
|
59021
59577
|
font-size: var(--font-size);
|
|
59022
59578
|
font-family: var(--font-family);
|
|
59023
59579
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--outline-border-radius));
|
|
@@ -59030,7 +59586,6 @@ const css$K = /* css */`@layer navi {
|
|
|
59030
59586
|
outline-offset: var(--outline-offset);
|
|
59031
59587
|
flex-direction: row;
|
|
59032
59588
|
align-items: center;
|
|
59033
|
-
margin: 2px;
|
|
59034
59589
|
position: relative;
|
|
59035
59590
|
|
|
59036
59591
|
& .navi_control_input {
|
|
@@ -59327,6 +59882,45 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59327
59882
|
grey);
|
|
59328
59883
|
--color-disabled: var(--color-dimmed);
|
|
59329
59884
|
}
|
|
59885
|
+
|
|
59886
|
+
.navi_input[data-background-transparent] {
|
|
59887
|
+
--background-color-hover: var(--background-color);
|
|
59888
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59889
|
+
}
|
|
59890
|
+
|
|
59891
|
+
.navi_input[data-variant="discrete"], .navi_input[data-variant="discrete-border"] {
|
|
59892
|
+
--background-color: transparent;
|
|
59893
|
+
--background-color-hover: var(--background-color);
|
|
59894
|
+
--background-color-focus: var(--navi-surface-color);
|
|
59895
|
+
|
|
59896
|
+
&[data-background] {
|
|
59897
|
+
--background-color-focus: transparent;
|
|
59898
|
+
}
|
|
59899
|
+
}
|
|
59900
|
+
|
|
59901
|
+
.navi_input[data-variant="discrete"] {
|
|
59902
|
+
--border-color: transparent;
|
|
59903
|
+
}
|
|
59904
|
+
|
|
59905
|
+
.navi_input[data-variant="text"] {
|
|
59906
|
+
--background-color: transparent;
|
|
59907
|
+
--background-color-hover: var(--background-color);
|
|
59908
|
+
--background-color-focus: var(--background-color);
|
|
59909
|
+
--background-color-readonly: var(--background-color);
|
|
59910
|
+
--background-color-disabled: var(--background-color);
|
|
59911
|
+
--border-color: transparent;
|
|
59912
|
+
--border-color-hover: var(--border-color);
|
|
59913
|
+
--border-color-readonly: var(--border-color);
|
|
59914
|
+
--border-color-disabled: var(--border-color);
|
|
59915
|
+
}
|
|
59916
|
+
|
|
59917
|
+
.navi_input[data-variant="underline"] {
|
|
59918
|
+
--background-color: transparent;
|
|
59919
|
+
--background-color-hover: var(--background-color);
|
|
59920
|
+
--background-color-focus: var(--background-color);
|
|
59921
|
+
--background-color-readonly: var(--background-color);
|
|
59922
|
+
--background-color-disabled: var(--background-color);
|
|
59923
|
+
}
|
|
59330
59924
|
}
|
|
59331
59925
|
|
|
59332
59926
|
.navi_input {
|
|
@@ -59478,19 +60072,12 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59478
60072
|
}
|
|
59479
60073
|
|
|
59480
60074
|
&[data-background-transparent] {
|
|
59481
|
-
--background-color-hover: var(--background-color);
|
|
59482
|
-
--background-color-focus: var(--navi-surface-color);
|
|
59483
|
-
|
|
59484
60075
|
&[data-focus] {
|
|
59485
60076
|
--x-background-color: var(--background-color-focus);
|
|
59486
60077
|
}
|
|
59487
60078
|
}
|
|
59488
60079
|
|
|
59489
60080
|
&[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
60081
|
&[data-focus] {
|
|
59495
60082
|
--x-background-color: var(--background-color-focus);
|
|
59496
60083
|
}
|
|
@@ -59498,15 +60085,9 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59498
60085
|
&[data-readonly], &[data-disabled] {
|
|
59499
60086
|
--x-background-color: var(--background-color);
|
|
59500
60087
|
}
|
|
59501
|
-
|
|
59502
|
-
&[data-background] {
|
|
59503
|
-
--background-color-focus: transparent;
|
|
59504
|
-
}
|
|
59505
60088
|
}
|
|
59506
60089
|
|
|
59507
60090
|
&[data-variant="discrete"] {
|
|
59508
|
-
--border-color: transparent;
|
|
59509
|
-
|
|
59510
60091
|
&[data-focus] {
|
|
59511
60092
|
--x-border-color: color-mix(in srgb,
|
|
59512
60093
|
var(--border-color) 55%,
|
|
@@ -59515,24 +60096,10 @@ const inputCss = /* css */ `@layer navi {
|
|
|
59515
60096
|
}
|
|
59516
60097
|
|
|
59517
60098
|
&[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
60099
|
cursor: inherit;
|
|
59528
60100
|
}
|
|
59529
60101
|
|
|
59530
60102
|
&[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
60103
|
border: none;
|
|
59537
60104
|
border-radius: 0;
|
|
59538
60105
|
padding-left: 0;
|
|
@@ -60728,6 +61295,38 @@ const InputStyleCSSVars = {
|
|
|
60728
61295
|
const InputPseudoClasses = [":hover", ":active", ":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value"];
|
|
60729
61296
|
const InputPseudoElements = ["::-navi-loader"];
|
|
60730
61297
|
|
|
61298
|
+
/**
|
|
61299
|
+
* @type {import("preact").FunctionComponent<{
|
|
61300
|
+
* type?: string,
|
|
61301
|
+
* icon?: import("preact").ComponentChildren | null,
|
|
61302
|
+
* clearable?: boolean,
|
|
61303
|
+
* value?: any,
|
|
61304
|
+
* boolean?: boolean,
|
|
61305
|
+
* selectOnPress?: boolean,
|
|
61306
|
+
* [key: string]: any,
|
|
61307
|
+
* }>}
|
|
61308
|
+
* @param {any} [value] On a checkbox, what it is worth checked (`"on"` when
|
|
61309
|
+
* left out); unchecked it is `undefined`. See control_value.md, "What a
|
|
61310
|
+
* checkbox is worth".
|
|
61311
|
+
* @param {boolean} [boolean] Makes a checkbox a yes/no: `true` checked, `false`
|
|
61312
|
+
* unchecked, everywhere it hands its value. Takes no `value`.
|
|
61313
|
+
* @param {import("preact").ComponentChildren|null} [icon] What the textual
|
|
61314
|
+
* types that draw an icon (`search`, `email`, `tel`) put in their slot: left
|
|
61315
|
+
* out, the type's own glyph; `null`, nothing — for a field an application
|
|
61316
|
+
* draws its own magnifier beside, where a second one two centimetres away
|
|
61317
|
+
* would be one too many; anything else is drawn instead. A `search` field
|
|
61318
|
+
* swaps that slot for the clear cross as soon as it holds a value, whatever
|
|
61319
|
+
* this says.
|
|
61320
|
+
* @param {boolean} [clearable] Gives any other type the same slot: the icon
|
|
61321
|
+
* while empty (none, unless `icon` says otherwise), the clear cross once
|
|
61322
|
+
* filled.
|
|
61323
|
+
* @param {boolean} [selectOnPress] For a textual field whose value is usually
|
|
61324
|
+
* replaced rather than edited, like a browser's address bar: the first press
|
|
61325
|
+
* after the field took the focus selects its whole text once released, with a
|
|
61326
|
+
* mouse or a finger, and a second press places the caret. A focus that came
|
|
61327
|
+
* without a press (a dialog opening, Tab, code) selects nothing; the first key
|
|
61328
|
+
* that types selects first, so it replaces the text.
|
|
61329
|
+
*/
|
|
60731
61330
|
const Input = props => {
|
|
60732
61331
|
resolveInputProps(props);
|
|
60733
61332
|
const {
|
|
@@ -60817,7 +61416,7 @@ const TextareaStyleCSSVars = {
|
|
|
60817
61416
|
};
|
|
60818
61417
|
|
|
60819
61418
|
/**
|
|
60820
|
-
* @type {import("
|
|
61419
|
+
* @type {import("preact").FunctionComponent<{
|
|
60821
61420
|
* value?: string,
|
|
60822
61421
|
* defaultValue?: string,
|
|
60823
61422
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -60924,7 +61523,7 @@ const Textarea = ({
|
|
|
60924
61523
|
* draws: put it under the box, in a form footer, next to a label, and feed it
|
|
60925
61524
|
* the same value or signal as the textarea.
|
|
60926
61525
|
*
|
|
60927
|
-
* @type {import("
|
|
61526
|
+
* @type {import("preact").FunctionComponent<{
|
|
60928
61527
|
* value?: string,
|
|
60929
61528
|
* signal?: import("@preact/signals").Signal<string>,
|
|
60930
61529
|
* maxLength?: number,
|
|
@@ -61157,8 +61756,8 @@ const useEditionController = () => {
|
|
|
61157
61756
|
* it in CSS (the `[data-editing] input` rules in `control/table/table_css.js`),
|
|
61158
61757
|
* the demo `control/demos/action/8_editable_demo.html` does it with props.
|
|
61159
61758
|
*
|
|
61160
|
-
* @type {import("
|
|
61161
|
-
* children?: import("
|
|
61759
|
+
* @type {import("preact").FunctionComponent<{
|
|
61760
|
+
* children?: import("preact").ComponentChildren,
|
|
61162
61761
|
* action: Function | object,
|
|
61163
61762
|
* editing?: { event?: Event } | null,
|
|
61164
61763
|
* onEditEnd?: (detail: { success?: boolean, cancelled?: boolean, event: Event }) => void,
|
|
@@ -61518,6 +62117,77 @@ const commitSubtree = (controller, e) => {
|
|
|
61518
62117
|
* <form> element (FormNested).
|
|
61519
62118
|
*/
|
|
61520
62119
|
|
|
62120
|
+
/**
|
|
62121
|
+
* @param {object} props
|
|
62122
|
+
* @param {boolean} [props.standalone] - Its value is its own: the form does not
|
|
62123
|
+
* register with the control group around it (a Picker, a ControlGroup), so
|
|
62124
|
+
* what is typed in it never becomes part of that group's value, and nothing
|
|
62125
|
+
* that group does — distributing a value, resetting, cascading validation —
|
|
62126
|
+
* reaches it. For a form that lives INSIDE something else while answering a
|
|
62127
|
+
* different question — "create the thing I am about to pick" inside a picker,
|
|
62128
|
+
* say. Every control takes the same prop, for the same reason. A form inside
|
|
62129
|
+
* another form is that by construction and does not need the prop: the group
|
|
62130
|
+
* it would register into is a form, which never owns another form's fields.
|
|
62131
|
+
* @param {boolean} [props.canSendWhileUnchanged] - Send even when nothing changed. By
|
|
62132
|
+
* default a form only acts on an answer that is actually new: submitting a
|
|
62133
|
+
* form nobody touched — one just rendered, one reopened and left alone — runs
|
|
62134
|
+
* no action (a field still showing its `defaultValue` is an answer: confirming
|
|
62135
|
+
* a suggestion), and after the first submission it is that value the next
|
|
62136
|
+
* one is compared against. Everything around the action still happens either
|
|
62137
|
+
* way: the constraints are checked, and what follows the send still follows
|
|
62138
|
+
* it (the slide moves on, the popup closes) — the user is done regardless of
|
|
62139
|
+
* whether there was anything to send. Set this for a form where sending the
|
|
62140
|
+
* same thing twice is the point — a single button that fires off a
|
|
62141
|
+
* notification, an action whose duplicates are fine. See also
|
|
62142
|
+
* `readOnlyWhileFormUnchanged` on `Button`, for a submit that should say it
|
|
62143
|
+
* is waiting rather than accept a press that sends nothing.
|
|
62144
|
+
* @param {boolean} [props.actionAbortable] - The person waiting may give up on
|
|
62145
|
+
* the send: closing the popup this form holds calls the run off and goes
|
|
62146
|
+
* through, instead of being refused. For a send whose answer may never come —
|
|
62147
|
+
* a request over a network that stops answering — where a sheet with no way
|
|
62148
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
62149
|
+
* more: the write may already have landed on the other side (see
|
|
62150
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
62151
|
+
* where the screen can be re-opened on what is actually there. Every control
|
|
62152
|
+
* inside the form inherits it along with the wait itself.
|
|
62153
|
+
* @param {boolean} [props.optimistic] - The send is taken as done on the form's
|
|
62154
|
+
* own word: no busy state, and what follows it (`command="--navi-close"`, a
|
|
62155
|
+
* slide moving on) runs at once, the run going on behind. A failure then has
|
|
62156
|
+
* no sheet to be read in: `resetOnError` puts the form back and the error
|
|
62157
|
+
* callout is drawn on what surrounds the closed sheet. For a write not worth being
|
|
62158
|
+
* waited for — a score, a note — never for one whose refusal changes what the
|
|
62159
|
+
* person does next (see
|
|
62160
|
+
* docs/popup_open.md#closing-when-a-button-also-runs-an-action).
|
|
62161
|
+
* @param {any} [props.pristineKey] - What the form is measured against, taken
|
|
62162
|
+
* again every time this changes. A form knows what it holds as soon as its
|
|
62163
|
+
* fields have registered, which is the right moment for a form whose values
|
|
62164
|
+
* are there on the first render — and never the right one for a screen that
|
|
62165
|
+
* modifies something: the resource arrives a request later and fills the
|
|
62166
|
+
* fields, and a form comparing against what it held BEFORE that opens already
|
|
62167
|
+
* changed. Pass whatever says the filling is done (a boolean, the resource
|
|
62168
|
+
* itself, a count of what has loaded) and what the fields carry at that
|
|
62169
|
+
* moment becomes the reference — including the fields that settle in a render
|
|
62170
|
+
* of their own, so there is no tick to wait for on the caller's side. Change
|
|
62171
|
+
* it once, when the screen is ready: taken again after someone started
|
|
62172
|
+
* typing, it would call what they wrote the reference.
|
|
62173
|
+
* @param {string} [props.command] - What follows a submission that went
|
|
62174
|
+
* through: the form has answered its question, and this says what the screen
|
|
62175
|
+
* does about it. Nothing runs when the submission is refused — the form then
|
|
62176
|
+
* stays in front of the user, showing what it is waiting for.
|
|
62177
|
+
*
|
|
62178
|
+
* `"--navi-close"` dismisses the popup the form is in,
|
|
62179
|
+
* `"--navi-left"`/`"--navi-right"`/`"--navi-up"`/`"--navi-down"` move on the
|
|
62180
|
+
* slide map it is in, `"--navi-nav-to:/the/url"` takes the user to a page,
|
|
62181
|
+
* `"--navi-void"` stays put — the one for a form inside a popup that must
|
|
62182
|
+
* stay open once it is sent (a side panel one keeps editing in).
|
|
62183
|
+
* Any navi command, really: it is triggered from the form, so it finds its
|
|
62184
|
+
* target the way that command always does.
|
|
62185
|
+
*
|
|
62186
|
+
* Left out, the surface the form sits in decides (see resolveAfterSend in
|
|
62187
|
+
* commands.js): a popup closes, a slide goes on to the next one — or back to
|
|
62188
|
+
* the one it came from when there is no next — and a form on a page does
|
|
62189
|
+
* nothing.
|
|
62190
|
+
*/
|
|
61521
62191
|
const Form = props => {
|
|
61522
62192
|
const defaultRef = useRef();
|
|
61523
62193
|
props.ref = props.ref || defaultRef;
|
|
@@ -61834,6 +62504,7 @@ const useRequestSubmitThroughGates = ref => {
|
|
|
61834
62504
|
installImportMetaCssBuild(import.meta);
|
|
61835
62505
|
const css$H = /* css */`.navi_group {
|
|
61836
62506
|
--group-border-width: var(--navi-control-border-width);
|
|
62507
|
+
--border-width: initial;
|
|
61837
62508
|
|
|
61838
62509
|
& > :not([navi-out-of-flow]) {
|
|
61839
62510
|
&:hover, &[data-hover] {
|
|
@@ -65066,6 +65737,122 @@ SlideContainer.Down = SlideDown;
|
|
|
65066
65737
|
SlideContainer.First = SlideFirst;
|
|
65067
65738
|
SlideContainer.Last = SlideLast;
|
|
65068
65739
|
|
|
65740
|
+
/**
|
|
65741
|
+
* Displays a date/time value in a human-readable format using the current locale.
|
|
65742
|
+
*
|
|
65743
|
+
* @param {Date|number|string} children
|
|
65744
|
+
* The date to display. Accepts:
|
|
65745
|
+
* - a `Date` instance
|
|
65746
|
+
* - a Unix timestamp (number, in ms)
|
|
65747
|
+
* - a date string `"YYYY-MM-DD"` or any string parseable by `Date`
|
|
65748
|
+
* If the value cannot be parsed, it is rendered as-is.
|
|
65749
|
+
* If undefined/null, renders a placeholder shaped like the type: `"–"` for
|
|
65750
|
+
* `"relative"`, `"--:--"` for `"time"`, `"--"` for the durations, the
|
|
65751
|
+
* locale's field pattern for `"date"`/`"month"`/`"week"`/`"datetime"`.
|
|
65752
|
+
*
|
|
65753
|
+
* @param {"date"|"month"|"week"|"datetime"|"time"|"hour"|"minute"|"second"|"duration"|"relative"} [type="relative"]
|
|
65754
|
+
* Controls the display format:
|
|
65755
|
+
* - `"date"` → "lundi 11 mai" (long by default), "samedi 1 janvier 2050" outside the current year (see `year`);
|
|
65756
|
+
* `format="short"` → "lun. 11 mai"; `format="numeric"` → "11/05/2026";
|
|
65757
|
+
* `format={{ weekday: "long", month: "short" }}` spells the two apart → "mercredi 2 sept.";
|
|
65758
|
+
* a part set to `false` is dropped — `format={{ day: false, month: false }}` → "mercredi"
|
|
65759
|
+
* - `"month"` → "juin 2026"
|
|
65760
|
+
* - `"week"` → the ISO week as given ("2026-W38"), in a `<time datetime>`
|
|
65761
|
+
* - `"datetime"` → "lun. 11 mai, 14:30" (long); `format="short"` → "11 mai, 14:30"; `format="narrow"` → "11/05, 14:30"
|
|
65762
|
+
* - `"time"` → time-of-day as duration by default (e.g. "14:30" → "14 heures 30");
|
|
65763
|
+
* `format="timestring"` → clock "14 h 30". Midnight (00:xx) is
|
|
65764
|
+
* special-cased regardless of `format` — "0 heures 5 minutes"
|
|
65765
|
+
* would otherwise collapse to "5 minutes", indistinguishable
|
|
65766
|
+
* from an actual 5-minute duration: `format="long"` (default)
|
|
65767
|
+
* says "minuit et 5 minutes" (`format="short"/"narrow"/"compact"`
|
|
65768
|
+
* keep the zero hour instead — "0 h et 5 min"/"0h 5min"/"00h05").
|
|
65769
|
+
* `format="compact"` also zero-pads any single-digit hour, not
|
|
65770
|
+
* just midnight (e.g. "5h30" → "05h30"), closer to "05:30";
|
|
65771
|
+
* `pad={false}` writes it the way one says it instead ("8h30",
|
|
65772
|
+
* "8h"). See `<TimeRange>` for a span between two of them.
|
|
65773
|
+
* - `"hour"` → hours as duration (e.g. 1.5 → "1 heure 30 minutes")
|
|
65774
|
+
* - `"minute"` → minutes as duration (e.g. 90 → "1 heure 30 minutes")
|
|
65775
|
+
* - `"second"` → seconds as duration (e.g. 90 → "1 minute 30 secondes")
|
|
65776
|
+
* These three promote to the largest fitting unit, days included (36 hours
|
|
65777
|
+
* → "1 jour et 12 heures"); see `forceUnit` to stay in the given unit.
|
|
65778
|
+
* - `"duration"` → duration string/object/number (seconds); `format="iso"` → ISO 8601
|
|
65779
|
+
* - `"relative"` → "dans 1 heure 30" / "En cours" / "il y a 2 heures"
|
|
65780
|
+
* Handles past, present, and future.
|
|
65781
|
+
* `format="short"` → "dans 1 h et 30 min"; `format="narrow"`
|
|
65782
|
+
* → "+3 h"; `format="compact"` → "dans 1h30".
|
|
65783
|
+
* `eventDuration` defaults to 0 (instantaneous: no "En cours" window).
|
|
65784
|
+
*
|
|
65785
|
+
* @param {number|string} [eventDuration=0]
|
|
65786
|
+
* Duration of the event in milliseconds, or a duration string (`"PT1H30M"`,
|
|
65787
|
+
* `"1hour30minute"`). Only used with `type="relative"`.
|
|
65788
|
+
* When omitted, the event is instantaneous (point in time, no "En cours" window).
|
|
65789
|
+
* @param {boolean} [bare]
|
|
65790
|
+
* When true, strips the past-tense literal ("il y a", "ago") and returns only integer + unit.
|
|
65791
|
+
* Only applies to the past state of `type="relative"`.
|
|
65792
|
+
* @param {"long"|"short"|"narrow"|"compact"|"numeric"|"timestring"|"iso"|{ weekday?: "long"|"short"|"narrow"|false, day?: boolean, month?: "long"|"short"|"narrow"|"numeric"|false }} [format="long"]
|
|
65793
|
+
* Controls the verbosity of the output. Defaults to `"long"` for all types.
|
|
65794
|
+
* `"long"`, `"short"`, `"narrow"` and `"compact"` are verbosities every `type`
|
|
65795
|
+
* writes. The three under them name a shape only some types have; a `type`
|
|
65796
|
+
* without that shape writes its `"long"` form and names the dropped word in
|
|
65797
|
+
* the console.
|
|
65798
|
+
* - `"short"` → Intl short (e.g. "2 h et 15 min", short month for dates/datetimes, no weekday for datetime)
|
|
65799
|
+
* - `"narrow"` → Intl narrow (e.g. "2h 15min", numeric month for datetime)
|
|
65800
|
+
* - `"compact"` → custom compact notation (e.g. "2h15", no minute symbol
|
|
65801
|
+
* when hours present). A date has nothing tighter than its
|
|
65802
|
+
* digits, so `type="date"`/`"month"`/`"datetime"` write the
|
|
65803
|
+
* all-digit spelling ("17/09/2026", "17/09 14:30")
|
|
65804
|
+
* - `"numeric"` → numeric date, for `type="date"` and `type="month"` (e.g. "11/09/2026", "09/2026")
|
|
65805
|
+
* - `"timestring"` → clock display for `type="time"`, `type="minute"`, `type="hour"`, and `type="second"` (e.g. "14:30"; `type="second"` always writes the hours, "00:01:30" for 90)
|
|
65806
|
+
* - `"iso"` → ISO 8601 string, only for `type="duration"` (e.g. "PT2H15M")
|
|
65807
|
+
* - an object → `type="date"` only, one verbosity per part
|
|
65808
|
+
* (`{ weekday: "long", month: "short" }`): a narrow card
|
|
65809
|
+
* keeps the weekday whole, since that is what the eye
|
|
65810
|
+
* lands on, and abbreviates the month, since that is
|
|
65811
|
+
* where the characters are. `false` drops a part:
|
|
65812
|
+
* `{ day: false, month: false }` is the weekday alone
|
|
65813
|
+
* ("mardi"), `{ month: false }` weekday + day-of-month
|
|
65814
|
+
* ("mardi 18").
|
|
65815
|
+
* @param {boolean} [forceUnit=false]
|
|
65816
|
+
* Keeps the value in the unit named by `type` however big it gets, for
|
|
65817
|
+
* `type="hour"`, `"minute"` and `"second"`: 36 hours reads "36 heures"
|
|
65818
|
+
* instead of the default "1 jour et 12 heures". The default promotes to the
|
|
65819
|
+
* largest fitting unit because it reads better; force the unit when the unit
|
|
65820
|
+
* itself is the information (a quota, a counter).
|
|
65821
|
+
* @param {number} [decimals]
|
|
65822
|
+
* `type="duration"` and `type="second"` only — how many decimals a fraction
|
|
65823
|
+
* of a second keeps, rounded: with `decimals={1}`, 1.234 reads "1,2 seconde"
|
|
65824
|
+
* and 1.96 "2 secondes"; a whole value is never padded ("12 secondes").
|
|
65825
|
+
* Without it the fraction is written to the millisecond ("1,234 seconde"). A
|
|
65826
|
+
* duration measured in milliseconds rarely comes out whole, and its last
|
|
65827
|
+
* digits are usually noise.
|
|
65828
|
+
* @param {boolean} [pad=true]
|
|
65829
|
+
* `type="time"` + `format="compact"` only — whether the clock is written at a
|
|
65830
|
+
* fixed width. `true` (default) zero-pads the hour and always writes the
|
|
65831
|
+
* minutes ("08h00", "08h30"), so a column of times lines up. `false` writes
|
|
65832
|
+
* the shape one says out loud instead: bare hour, minutes only when there are
|
|
65833
|
+
* any ("8h", "8h30"). The spelled-out formats put their units in words and
|
|
65834
|
+
* need neither, so they ignore it.
|
|
65835
|
+
* @param {"hour"|"minute"} [precision]
|
|
65836
|
+
* `type="time"` + `format="compact"` only, and rarely set by hand — whether a
|
|
65837
|
+
* zero minute is written ("8h00") or dropped ("8h"). Defaults to whatever
|
|
65838
|
+
* `pad` implies; the one reason to force `"minute"` on an unpadded clock is
|
|
65839
|
+
* to agree with a partner that has minutes of its own, which is what
|
|
65840
|
+
* `<TimeRange>` does for you.
|
|
65841
|
+
* @param {boolean|"auto"} [year]
|
|
65842
|
+
* `type="date"` only — whether the year is written. `"auto"` writes it only
|
|
65843
|
+
* when the date is not in the current year, the way a calendar does
|
|
65844
|
+
* ("lundi 11 mai", but "samedi 1 janvier 2050"); `false` drops it ("30/07",
|
|
65845
|
+
* the day/month order still following the locale). Defaults to `"auto"`
|
|
65846
|
+
* for the spelled formats and to `true` for `format="numeric"`. A format
|
|
65847
|
+
* without the month (`{ month: false }`) never writes it.
|
|
65848
|
+
* @param {boolean} [dayLabel]
|
|
65849
|
+
* When true and `type="date"`, appends the locale-aware relative label
|
|
65850
|
+
* ("hier", "aujourd'hui", "demain") when the date is yesterday, today, or tomorrow.
|
|
65851
|
+
* @param {string} [lang]
|
|
65852
|
+
* BCP 47 locale tag (e.g. `"fr"`, `"en-US"`).
|
|
65853
|
+
* Defaults to `languagesSignal.value`: the browser's languages, as
|
|
65854
|
+
* `setPreferredLanguage`/`setSupportedLanguages` narrow them.
|
|
65855
|
+
*/
|
|
65069
65856
|
const Time = props => {
|
|
65070
65857
|
const {
|
|
65071
65858
|
type
|
|
@@ -65330,6 +66117,20 @@ const TimeMinute = ({
|
|
|
65330
66117
|
children: String(children)
|
|
65331
66118
|
});
|
|
65332
66119
|
}
|
|
66120
|
+
if (!Number.isInteger(minutes)) {
|
|
66121
|
+
// a fraction of a minute is read in seconds, the way formatMinuteDuration
|
|
66122
|
+
// writes it ("1 minute et 30 secondes")
|
|
66123
|
+
const clock = formatClock(Math.round(minutes * 60));
|
|
66124
|
+
return jsx(TimeText, {
|
|
66125
|
+
dateTime: clock,
|
|
66126
|
+
...props,
|
|
66127
|
+
children: format === "timestring" ? clock : formatMinuteDuration(minutes, {
|
|
66128
|
+
lang,
|
|
66129
|
+
format,
|
|
66130
|
+
forceUnit
|
|
66131
|
+
})
|
|
66132
|
+
});
|
|
66133
|
+
}
|
|
65333
66134
|
const totalHours = Math.floor(minutes / 60);
|
|
65334
66135
|
const remainingMinutes = minutes % 60;
|
|
65335
66136
|
const hh = String(totalHours).padStart(2, "0");
|
|
@@ -65359,6 +66160,7 @@ const TimeSecond = ({
|
|
|
65359
66160
|
lang = languagesSignal.value,
|
|
65360
66161
|
format = "long",
|
|
65361
66162
|
forceUnit = false,
|
|
66163
|
+
decimals,
|
|
65362
66164
|
...props
|
|
65363
66165
|
}) => {
|
|
65364
66166
|
if (children === undefined || children === null) {
|
|
@@ -65374,19 +66176,17 @@ const TimeSecond = ({
|
|
|
65374
66176
|
children: String(children)
|
|
65375
66177
|
});
|
|
65376
66178
|
}
|
|
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")}`;
|
|
66179
|
+
const dateTime = formatClock(seconds);
|
|
65381
66180
|
let text;
|
|
65382
66181
|
if (format === "timestring") {
|
|
65383
66182
|
// Always HH:MM:SS to avoid ambiguity with HH:MM time-of-day format
|
|
65384
|
-
text =
|
|
66183
|
+
text = formatClock(seconds, decimals);
|
|
65385
66184
|
} else {
|
|
65386
66185
|
text = formatSecondDuration(seconds, {
|
|
65387
66186
|
lang,
|
|
65388
66187
|
format,
|
|
65389
|
-
forceUnit
|
|
66188
|
+
forceUnit,
|
|
66189
|
+
decimals
|
|
65390
66190
|
});
|
|
65391
66191
|
}
|
|
65392
66192
|
return jsx(TimeText, {
|
|
@@ -65439,6 +66239,7 @@ const TimeDuration = ({
|
|
|
65439
66239
|
children,
|
|
65440
66240
|
lang = languagesSignal.value,
|
|
65441
66241
|
format = "long",
|
|
66242
|
+
decimals,
|
|
65442
66243
|
...props
|
|
65443
66244
|
}) => {
|
|
65444
66245
|
if (children === undefined || children === null) {
|
|
@@ -65486,7 +66287,8 @@ const TimeDuration = ({
|
|
|
65486
66287
|
...props,
|
|
65487
66288
|
children: formatDuration(duration, {
|
|
65488
66289
|
lang,
|
|
65489
|
-
format
|
|
66290
|
+
format,
|
|
66291
|
+
decimals
|
|
65490
66292
|
})
|
|
65491
66293
|
});
|
|
65492
66294
|
}
|
|
@@ -65498,7 +66300,8 @@ const TimeDuration = ({
|
|
|
65498
66300
|
}
|
|
65499
66301
|
const text = formatDuration(duration, {
|
|
65500
66302
|
lang,
|
|
65501
|
-
format
|
|
66303
|
+
format,
|
|
66304
|
+
decimals
|
|
65502
66305
|
});
|
|
65503
66306
|
return jsx(TimeText, {
|
|
65504
66307
|
dateTime: isoString,
|
|
@@ -65638,6 +66441,24 @@ const TimeRange = ({
|
|
|
65638
66441
|
});
|
|
65639
66442
|
};
|
|
65640
66443
|
|
|
66444
|
+
// "HH:MM:SS", the fraction of a second after a period ("00:00:01.234"): the
|
|
66445
|
+
// shape a `datetime` attribute reads, which takes up to 3 decimals.
|
|
66446
|
+
const formatClock = (seconds, decimals = 3) => {
|
|
66447
|
+
const factor = 10 ** decimals;
|
|
66448
|
+
const total = Math.round(seconds * factor);
|
|
66449
|
+
const fraction = total % factor;
|
|
66450
|
+
const totalSeconds = (total - fraction) / factor;
|
|
66451
|
+
const h = Math.floor(totalSeconds / 3600);
|
|
66452
|
+
const m = Math.floor(totalSeconds % 3600 / 60);
|
|
66453
|
+
const s = totalSeconds % 60;
|
|
66454
|
+
const clock = `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
|
|
66455
|
+
if (fraction === 0) {
|
|
66456
|
+
return clock;
|
|
66457
|
+
}
|
|
66458
|
+
const digits = String(fraction).padStart(decimals, "0").replace(/0+$/, "");
|
|
66459
|
+
return `${clock}.${digits}`;
|
|
66460
|
+
};
|
|
66461
|
+
|
|
65641
66462
|
/**
|
|
65642
66463
|
* Wraps multiple inputs together and handles keyboard navigation and paste
|
|
65643
66464
|
* distribution between them.
|
|
@@ -65864,6 +66685,11 @@ const renderSafe = (value) => {
|
|
|
65864
66685
|
};
|
|
65865
66686
|
|
|
65866
66687
|
installImportMetaCssBuild(import.meta);
|
|
66688
|
+
/**
|
|
66689
|
+
* The group a popup opens in, or null: what a Popover or a Dialog reads to know
|
|
66690
|
+
* that its wall is the group's. Every popup provides null again to what it
|
|
66691
|
+
* holds, so a popup opened from inside a popup is in front of it, not a peer.
|
|
66692
|
+
*/
|
|
65867
66693
|
const PopupGroupContext = createContext(null);
|
|
65868
66694
|
|
|
65869
66695
|
/**
|
|
@@ -66022,13 +66848,13 @@ const css$F = /* css */`@property --x-popup-group-top {
|
|
|
66022
66848
|
* `navi-out-of-flow` like everything navi adds to a tree. See
|
|
66023
66849
|
* docs/popup_backdrop.md.
|
|
66024
66850
|
*
|
|
66025
|
-
* @type {import("
|
|
66851
|
+
* @type {import("preact").FunctionComponent<{
|
|
66026
66852
|
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
66027
66853
|
* backdropColor?: string,
|
|
66028
66854
|
* backdropFilter?: string,
|
|
66029
66855
|
* themeColor?: string | false,
|
|
66030
66856
|
* role?: string,
|
|
66031
|
-
* children?: import("
|
|
66857
|
+
* children?: import("preact").ComponentChildren,
|
|
66032
66858
|
* [key: string]: any,
|
|
66033
66859
|
* }>}
|
|
66034
66860
|
* @param {"auto"|"lift"|"discrete"|"invisible"} [props.backdropVariant="auto"] -
|
|
@@ -68075,6 +68901,35 @@ const clipOf = (side, distance) => {
|
|
|
68075
68901
|
return `inset(${UNCUT} ${UNCUT} ${UNCUT} ${cut})`;
|
|
68076
68902
|
};
|
|
68077
68903
|
|
|
68904
|
+
/**
|
|
68905
|
+
* The cross that closes the popup it sits in — reached as `<Popup.Close />`,
|
|
68906
|
+
* `<Dialog.Close />` or `<Popover.Close />`. A discrete icon button sending
|
|
68907
|
+
* `--navi-close`, which the nearest popup above it answers; the caller only
|
|
68908
|
+
* decides where it goes (the right end of a header, most of the time).
|
|
68909
|
+
*
|
|
68910
|
+
* It is the way out of a popup, so it is the one affordance in there that the
|
|
68911
|
+
* surrounding control's read-only and disabled do not reach: a read-only picker
|
|
68912
|
+
* still opens (reading changes nothing) and hands its popup that read-only, and
|
|
68913
|
+
* a cross wearing it would trap the user in a popup they were invited to read.
|
|
68914
|
+
* A close button written by hand inherits it and refuses the press — which is
|
|
68915
|
+
* the reason to use this one rather than a `<Button command="--navi-close" />`.
|
|
68916
|
+
*
|
|
68917
|
+
* Exempt from the state around it, never from the popup's own answer: the cross
|
|
68918
|
+
* ASKS, and the popup decides — a dialog holding a form mid-send keeps the
|
|
68919
|
+
* close, a Picker holding a value that does not validate keeps it too.
|
|
68920
|
+
*
|
|
68921
|
+
* @type {import("preact").FunctionComponent<{
|
|
68922
|
+
* label?: string,
|
|
68923
|
+
* iconSize?: number | string,
|
|
68924
|
+
* } & Record<string, any>>}
|
|
68925
|
+
* @param {string} [props.label] - What assistive tech reads for the cross
|
|
68926
|
+
* (navi's own "Close" text by default, in the active language).
|
|
68927
|
+
* @param {number|string} [props.iconSize] - How big the cross is drawn, apart
|
|
68928
|
+
* from the box around it. Unset, the glyph follows the button's font size
|
|
68929
|
+
* like a character, and `size` moves glyph and padding together. Set it when
|
|
68930
|
+
* the cross stands in for an affordance the app draws elsewhere and has to
|
|
68931
|
+
* land on the same pixels: `padding="s" iconSize="m"`.
|
|
68932
|
+
*/
|
|
68078
68933
|
const PopupClose = ({
|
|
68079
68934
|
label,
|
|
68080
68935
|
iconSize,
|
|
@@ -68323,6 +69178,9 @@ const TitlePseudoClasses = [":hover"];
|
|
|
68323
69178
|
* step of a form — so only the title knows when it is there.
|
|
68324
69179
|
*/
|
|
68325
69180
|
|
|
69181
|
+
// The popup a title names: the nearest one around it. A surface that is no
|
|
69182
|
+
// popup of this kind but holds content of its own (a picker's callout) sets it
|
|
69183
|
+
// back to null, so a title in there names nothing rather than the popup around.
|
|
68326
69184
|
const PopupTitleContext = createContext(null);
|
|
68327
69185
|
|
|
68328
69186
|
/**
|
|
@@ -68332,10 +69190,10 @@ const PopupTitleContext = createContext(null);
|
|
|
68332
69190
|
* announces the popup by it, and `getByRole("dialog", { name })` finds it.
|
|
68333
69191
|
* An `aria-label`/`aria-labelledby` written on the popup itself wins over it.
|
|
68334
69192
|
*
|
|
68335
|
-
* @type {import("
|
|
69193
|
+
* @type {import("preact").FunctionComponent<{
|
|
68336
69194
|
* as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6",
|
|
68337
69195
|
* id?: string,
|
|
68338
|
-
* children?: import("
|
|
69196
|
+
* children?: import("preact").ComponentChildren,
|
|
68339
69197
|
* } & Record<string, any>>}
|
|
68340
69198
|
*/
|
|
68341
69199
|
const PopupTitle = props => {
|
|
@@ -68396,6 +69254,12 @@ const usePopupName = ({
|
|
|
68396
69254
|
const NO_TITLE = [];
|
|
68397
69255
|
|
|
68398
69256
|
installImportMetaCssBuild(import.meta);
|
|
69257
|
+
// Same need popover.jsx has for its own local renderer (see its
|
|
69258
|
+
// openLocalPopoverCount): a layer="local" dialog is a plain positioned element
|
|
69259
|
+
// in normal flow, so it gets none of the top layer's free "last shown wins" and
|
|
69260
|
+
// would paint under anything declared after it. A live count, not an
|
|
69261
|
+
// ever-growing counter, so the order stays small and resets once nothing is
|
|
69262
|
+
// open.
|
|
68399
69263
|
let openLocalDialogCount = 0;
|
|
68400
69264
|
const css$C = /* css */`
|
|
68401
69265
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -69470,7 +70334,7 @@ const css$C = /* css */`
|
|
|
69470
70334
|
* the dialog: its accessible name when neither `aria-label`/`aria-labelledby`
|
|
69471
70335
|
* nor a `<Dialog.Title>` names it. A Picker passes its trigger's.
|
|
69472
70336
|
* @param {string} [props.openerAriaLabel] - The same, as a string.
|
|
69473
|
-
* @param {import("
|
|
70337
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
69474
70338
|
*/
|
|
69475
70339
|
const Dialog = props => {
|
|
69476
70340
|
import.meta.css = [css$C, "@jsenv/navi/src/layout/dialog.jsx"];
|
|
@@ -70306,8 +71170,10 @@ const useDialogProps = props => {
|
|
|
70306
71170
|
// gap — the same thing its CSS width/height say (see [data-expand-x],
|
|
70307
71171
|
// [data-expand-y]) — so the placement derives it instead of measuring
|
|
70308
71172
|
// it: a measurement here lays out the dialog's whole content, rows and
|
|
70309
|
-
// all, for a number already known.
|
|
70310
|
-
|
|
71173
|
+
// all, for a number already known. A cap (maxWidth/maxHeight, a Picker's
|
|
71174
|
+
// anchor ceiling) stretches it to less than that, and only a measured
|
|
71175
|
+
// box can be centred.
|
|
71176
|
+
const fill = expandX && expandY && !isSizeCapped(dialogEl);
|
|
70311
71177
|
const pickOptions = {
|
|
70312
71178
|
positionArea,
|
|
70313
71179
|
container: positionedAncestor,
|
|
@@ -70821,6 +71687,14 @@ const switchTransitionsOn = dialogEl => {
|
|
|
70821
71687
|
dialogEl.style.transitionProperty = "";
|
|
70822
71688
|
dialogEl.removeAttribute("navi-transitions-off");
|
|
70823
71689
|
};
|
|
71690
|
+
|
|
71691
|
+
// Reads style, not layout: an unset cap is the guaranteed-invalid value its
|
|
71692
|
+
// @property gives it, and reads back as "". Catches a cap written inline by
|
|
71693
|
+
// the prop as well as one set from a stylesheet.
|
|
71694
|
+
const isSizeCapped = dialogEl => {
|
|
71695
|
+
const computedStyle = getComputedStyle(dialogEl);
|
|
71696
|
+
return computedStyle.getPropertyValue("--dialog-max-width") !== "" || computedStyle.getPropertyValue("--dialog-max-height") !== "";
|
|
71697
|
+
};
|
|
70824
71698
|
const DIALOG_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-visible", ":focus-within"];
|
|
70825
71699
|
|
|
70826
71700
|
// Lets consumers pass animationDuration="0.5s" as a regular prop; Box maps
|
|
@@ -70846,6 +71720,11 @@ Dialog.Close = PopupClose;
|
|
|
70846
71720
|
Dialog.Title = PopupTitle;
|
|
70847
71721
|
|
|
70848
71722
|
installImportMetaCssBuild(import.meta);
|
|
71723
|
+
// Custom renderer only (layer="local") — count of currently-open instances,
|
|
71724
|
+
// see --popover-stack-order's own CSS comment for why a plain "last shown"
|
|
71725
|
+
// default doesn't happen for free the way it does for the top-layer
|
|
71726
|
+
// renderer. A live count (not an ever-growing counter) so the assigned
|
|
71727
|
+
// order stays small and resets to 0 once nothing is open.
|
|
70849
71728
|
let openLocalPopoverCount = 0;
|
|
70850
71729
|
const css$B = /* css */`
|
|
70851
71730
|
/* jsenv-css-opaque: popupCss overlaps these rules, own sheet = cascade
|
|
@@ -71453,7 +72332,7 @@ const css$B = /* css */`
|
|
|
71453
72332
|
* something names — these, `aria-label`/`aria-labelledby`, a
|
|
71454
72333
|
* `<Popover.Title>` — is a `dialog`, unless given another `role`: a name on
|
|
71455
72334
|
* an element with no role is read by nobody.
|
|
71456
|
-
* @param {import("
|
|
72335
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
71457
72336
|
*/
|
|
71458
72337
|
const Popover = props => {
|
|
71459
72338
|
import.meta.css = [css$B, "@jsenv/navi/src/layout/popover.jsx"];
|
|
@@ -72811,7 +73690,7 @@ const css$A = /* css */`@layer navi {
|
|
|
72811
73690
|
* content whose fresh state is its initial state: an edit left unsent at
|
|
72812
73691
|
* the close does not come back at the next opening (an untouched field
|
|
72813
73692
|
* needs none of this to show a new `defaultValue` — it follows one).
|
|
72814
|
-
* @param {import("
|
|
73693
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
72815
73694
|
*/
|
|
72816
73695
|
const Popup = props => {
|
|
72817
73696
|
import.meta.css = [css$A, "@jsenv/navi/src/layout/popup.jsx"];
|
|
@@ -74488,6 +75367,18 @@ const PickerConfirmBody = ({
|
|
|
74488
75367
|
});
|
|
74489
75368
|
};
|
|
74490
75369
|
|
|
75370
|
+
/**
|
|
75371
|
+
* PickerNaviMinute — a custom minute picker that opens a number input.
|
|
75372
|
+
*
|
|
75373
|
+
* Renders a popover containing an `<input type="number">` pre-filled with the
|
|
75374
|
+
* current value, plus Confirm / Clear / Cancel actions. The picker holds a
|
|
75375
|
+
* number of minutes: its own field is a text one told
|
|
75376
|
+
* `navi-input-type="minute"`, so the value is read back as a number and a
|
|
75377
|
+
* min/max/step message speaks of minutes.
|
|
75378
|
+
*
|
|
75379
|
+
* Usage:
|
|
75380
|
+
* <Picker type="navi_minute" min={15} max={300} step={15} value={90} />
|
|
75381
|
+
*/
|
|
74491
75382
|
const PickerNaviMinute = props => {
|
|
74492
75383
|
const Next = useNextResolver();
|
|
74493
75384
|
const {
|
|
@@ -74871,6 +75762,16 @@ const LoadingDotsSvg = () => {
|
|
|
74871
75762
|
});
|
|
74872
75763
|
};
|
|
74873
75764
|
|
|
75765
|
+
/**
|
|
75766
|
+
* A loading indicator sized to match the surrounding text (1em × 1em).
|
|
75767
|
+
* Inherits color from the current text color by default.
|
|
75768
|
+
*
|
|
75769
|
+
* @param {"circle"|"dots"} [props.variant="circle"] - Visual style of the indicator
|
|
75770
|
+
* @param {string} [props.color="currentColor"] - Color of the indicator; inherits from CSS `color` by default
|
|
75771
|
+
*
|
|
75772
|
+
* Every other prop goes to the `Icon` it is drawn in: `size` is a font size
|
|
75773
|
+
* (the indicator being 1em), `width`/`height` size it as a block.
|
|
75774
|
+
*/
|
|
74874
75775
|
const LoadingIndicator = ({
|
|
74875
75776
|
variant = "circle",
|
|
74876
75777
|
...props
|
|
@@ -74933,7 +75834,7 @@ const css$x = /* css */`@layer navi {
|
|
|
74933
75834
|
* Everything about how it looks goes through a CSS var, so it can be set per
|
|
74934
75835
|
* separator as a prop or once for a whole surface in CSS.
|
|
74935
75836
|
*
|
|
74936
|
-
* @type {import("
|
|
75837
|
+
* @type {import("preact").FunctionComponent<{
|
|
74937
75838
|
* vertical?: boolean,
|
|
74938
75839
|
* size?: string|number,
|
|
74939
75840
|
* color?: string,
|
|
@@ -76536,6 +77437,9 @@ const applySearchHighlight = (el, highlight) => {
|
|
|
76536
77437
|
};
|
|
76537
77438
|
|
|
76538
77439
|
installImportMetaCssBuild(import.meta);
|
|
77440
|
+
// Everything the list knows about its items — see list_items.js. Filled in by
|
|
77441
|
+
// the children as they render, read by everything that must reserve room for
|
|
77442
|
+
// what is not rendered, count what is, or know what stands above it.
|
|
76539
77443
|
const ListItemsContext = createContext(null);
|
|
76540
77444
|
// The group an item is declared in, by id (see ListItemGroup): what its count
|
|
76541
77445
|
// and its separator are scoped to.
|
|
@@ -81108,7 +82012,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81108
82012
|
* of it. Props not listed here are forwarded to the rendered <li> (Box layout
|
|
81109
82013
|
* props included).
|
|
81110
82014
|
*
|
|
81111
|
-
* @type {import("
|
|
82015
|
+
* @type {import("preact").FunctionComponent<{
|
|
81112
82016
|
* id?: string,
|
|
81113
82017
|
* selectable?: boolean,
|
|
81114
82018
|
* value?: any,
|
|
@@ -81117,14 +82021,14 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81117
82021
|
* pointed?: boolean,
|
|
81118
82022
|
* selectableArea?: "all" | "manual",
|
|
81119
82023
|
* skeleton?: boolean,
|
|
81120
|
-
* error?: boolean | import("
|
|
82024
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
81121
82025
|
* onErrorDismiss?: (event: Event) => void,
|
|
81122
82026
|
* loading?: boolean | "adding" | "removing" | "updating",
|
|
81123
82027
|
* readOnly?: boolean,
|
|
81124
82028
|
* filtered?: boolean,
|
|
81125
82029
|
* hidden?: boolean,
|
|
81126
82030
|
* matchInfo?: {match: boolean, matchScore?: number, matchRanges?: Array<[number, number]>},
|
|
81127
|
-
* children?: import("
|
|
82031
|
+
* children?: import("preact").ComponentChildren,
|
|
81128
82032
|
* [key: string]: any,
|
|
81129
82033
|
* }>}
|
|
81130
82034
|
* @param {string} [props.id]
|
|
@@ -81159,7 +82063,7 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
81159
82063
|
* real item. This is what a `renderSkeleton` returns for an item on its way;
|
|
81160
82064
|
* Box layout props (padding…) pass through so the placeholder can match the
|
|
81161
82065
|
* real items' metrics.
|
|
81162
|
-
* @param {boolean|import("
|
|
82066
|
+
* @param {boolean|import("preact").ComponentChildren} [props.error]
|
|
81163
82067
|
* What this item stood for failed: the message replaces its content, styled
|
|
81164
82068
|
* like the list's own error. `true` shows a generic sentence. When
|
|
81165
82069
|
* `onErrorDismiss` is given, a dismiss button is drawn next to the message
|
|
@@ -81299,21 +82203,21 @@ const VISIBILITY_HIDDEN_STYLE = {
|
|
|
81299
82203
|
* Several runs can live in one list, next to plain `<List.Item>` children and
|
|
81300
82204
|
* inside `<List.Group>`s; each takes its place in declaration order.
|
|
81301
82205
|
*
|
|
81302
|
-
* @type {import("
|
|
81303
|
-
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("
|
|
82206
|
+
* @type {import("preact").FunctionComponent<{
|
|
82207
|
+
* renderItem: (item: any, index: number, state: {refreshing: boolean}) => import("preact").ComponentChildren,
|
|
81304
82208
|
* findText?: (item: any, index: number) => string,
|
|
81305
82209
|
* items?: any[],
|
|
81306
82210
|
* itemsAction?: (range: {start: number, end: number, limit: number, before?: string, after?: string, around?: string, count?: number, signal: AbortSignal}) => any,
|
|
81307
82211
|
* debounce?: number,
|
|
81308
82212
|
* count?: number,
|
|
81309
82213
|
* groupBy?: (item: any, index: number) => any,
|
|
81310
|
-
* renderGroupLabel?: (item: any, index: number) => import("
|
|
82214
|
+
* renderGroupLabel?: (item: any, index: number) => import("preact").ComponentChildren,
|
|
81311
82215
|
* groupLabelProps?: (item: any, index: number) => object,
|
|
81312
82216
|
* pageSize?: number,
|
|
81313
82217
|
* memoryBudget?: number,
|
|
81314
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82218
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
81315
82219
|
* itemSize?: (index: number) => number,
|
|
81316
|
-
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("
|
|
82220
|
+
* renderError?: (failure: {error: any, retry: () => void, start: number, end: number}) => import("preact").ComponentChildren,
|
|
81317
82221
|
* onRequestStateChange?: (state: {busy: boolean, refreshing: boolean, range: {start: number, end: number}|null}) => void,
|
|
81318
82222
|
* onCountChange?: (count: number|undefined) => void,
|
|
81319
82223
|
* }>}
|
|
@@ -82908,7 +83812,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82908
83812
|
* List — generic virtualized scroll container.
|
|
82909
83813
|
* Items must use <List.Item> to participate in tracking.
|
|
82910
83814
|
*
|
|
82911
|
-
* @type {import("
|
|
83815
|
+
* @type {import("preact").FunctionComponent<{
|
|
82912
83816
|
* selectable?: boolean,
|
|
82913
83817
|
* multiple?: boolean,
|
|
82914
83818
|
* deselectable?: boolean,
|
|
@@ -82929,16 +83833,16 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82929
83833
|
* scroller?: "self" | "parent" | "document" | Element | {current: Element},
|
|
82930
83834
|
* hoverWhileScrolling?: boolean,
|
|
82931
83835
|
* scrollResetOnNavigation?: boolean,
|
|
82932
|
-
* fallback?: import("
|
|
82933
|
-
* searchFallback?: import("
|
|
83836
|
+
* fallback?: import("preact").ComponentChildren,
|
|
83837
|
+
* searchFallback?: import("preact").ComponentChildren,
|
|
82934
83838
|
* searchText?: string,
|
|
82935
83839
|
* searchNoMatchMode?: "remove" | "invisible_and_inert" | "muted",
|
|
82936
83840
|
* loading?: boolean,
|
|
82937
|
-
* loadingFallback?: "skeleton" | "loader" | import("
|
|
83841
|
+
* loadingFallback?: "skeleton" | "loader" | import("preact").ComponentChildren,
|
|
82938
83842
|
* loadingSkeletonCount?: number,
|
|
82939
|
-
* renderSkeleton?: false | ((index: number) => import("
|
|
82940
|
-
* error?: boolean | import("
|
|
82941
|
-
* separator?: boolean | import("
|
|
83843
|
+
* renderSkeleton?: false | ((index: number) => import("preact").ComponentChildren),
|
|
83844
|
+
* error?: boolean | import("preact").ComponentChildren,
|
|
83845
|
+
* separator?: boolean | import("preact").ComponentChildren,
|
|
82942
83846
|
* itemTransition?: boolean,
|
|
82943
83847
|
* lockSize?: boolean,
|
|
82944
83848
|
* horizontal?: boolean,
|
|
@@ -82952,7 +83856,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
82952
83856
|
* expandX?: boolean,
|
|
82953
83857
|
* expandY?: boolean,
|
|
82954
83858
|
* expand?: boolean,
|
|
82955
|
-
* children?: import("
|
|
83859
|
+
* children?: import("preact").ComponentChildren,
|
|
82956
83860
|
* [key: string]: any,
|
|
82957
83861
|
* }>}
|
|
82958
83862
|
* @param {string} [props.columns]
|
|
@@ -83012,7 +83916,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
83012
83916
|
* have, so nothing moves when they arrive. Used for the items a `<List.Items>`
|
|
83013
83917
|
* stands for and does not hold yet, and for the placeholder items drawn while
|
|
83014
83918
|
* the whole list is `loading`. Defaults to a bare `<List.Item skeleton>`.
|
|
83015
|
-
* @param {"skeleton"|"loader"|import("
|
|
83919
|
+
* @param {"skeleton"|"loader"|import("preact").ComponentChildren} [props.loadingFallback="skeleton"]
|
|
83016
83920
|
* What to display in place of the items while `loading` — that is, while
|
|
83017
83921
|
* there is nothing to show at all: `"skeleton"` renders
|
|
83018
83922
|
* `loadingSkeletonCount` placeholder items (look:
|
|
@@ -83196,6 +84100,17 @@ const List = /*#__PURE__*/Object.assign(ListResolved, {
|
|
|
83196
84100
|
Group: ListItemGroup
|
|
83197
84101
|
});
|
|
83198
84102
|
|
|
84103
|
+
/**
|
|
84104
|
+
* PickerNaviTime — a fully custom time picker driven by min/max/step.
|
|
84105
|
+
*
|
|
84106
|
+
* Unlike the native `<input type="time">`, this picker only exposes the exact
|
|
84107
|
+
* slots generated from min/max/step, so the user can never pick an out-of-range
|
|
84108
|
+
* or out-of-step value. The popover content is a selectable `List` of the
|
|
84109
|
+
* available slots; picking one closes the popover and submits the value.
|
|
84110
|
+
*
|
|
84111
|
+
* Usage:
|
|
84112
|
+
* <Picker type="navi_time" min="08:00" max="18:00" step="00:30" />
|
|
84113
|
+
*/
|
|
83199
84114
|
const PickerNaviTime = props => {
|
|
83200
84115
|
const Next = useNextResolver();
|
|
83201
84116
|
const {
|
|
@@ -83303,18 +84218,20 @@ const createBadgeRegistry = () => {
|
|
|
83303
84218
|
};
|
|
83304
84219
|
|
|
83305
84220
|
installImportMetaCssBuild(import.meta);
|
|
83306
|
-
const css$u = /* css */`@layer navi
|
|
84221
|
+
const css$u = /* css */`@layer navi {
|
|
84222
|
+
.navi_badge {
|
|
84223
|
+
--font-size: .7em;
|
|
84224
|
+
--badge-padding-x-default: .8em;
|
|
84225
|
+
--badge-padding-y-default: .4em;
|
|
84226
|
+
}
|
|
84227
|
+
}
|
|
83307
84228
|
|
|
83308
84229
|
.navi_badge {
|
|
83309
|
-
--font-size: .7em;
|
|
83310
|
-
--badge-padding-x-default: .8em;
|
|
83311
|
-
--badge-padding-y-default: .4em;
|
|
83312
84230
|
--x-badge-padding-top: var(--badge-padding-top, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83313
84231
|
--x-badge-padding-right: var(--badge-padding-right, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83314
84232
|
--x-badge-padding-bottom: var(--badge-padding-bottom, var(--badge-padding-y, var(--badge-padding, var(--badge-padding-y-default))));
|
|
83315
84233
|
--x-badge-padding-left: var(--badge-padding-left, var(--badge-padding-x, var(--badge-padding, var(--badge-padding-x-default))));
|
|
83316
84234
|
--x-background: var(--background, light-dark(#e0e0e0, #3a3a3a));
|
|
83317
|
-
--x-background-color: var(--background-color, var(--x-background));
|
|
83318
84235
|
--x-color: var(--color, white);
|
|
83319
84236
|
max-width: 200px;
|
|
83320
84237
|
padding-top: var(--x-badge-padding-top);
|
|
@@ -83325,7 +84242,6 @@ const css$u = /* css */`@layer navi;
|
|
|
83325
84242
|
font-size: var(--font-size);
|
|
83326
84243
|
text-box: trim-both cap alphabetic;
|
|
83327
84244
|
background: var(--x-background);
|
|
83328
|
-
background-color: var(--x-background-color);
|
|
83329
84245
|
border-radius: 1em;
|
|
83330
84246
|
display: inline;
|
|
83331
84247
|
position: relative;
|
|
@@ -83451,7 +84367,6 @@ const BadgeStyleCSSVars = {
|
|
|
83451
84367
|
paddingRight: "--badge-padding-right",
|
|
83452
84368
|
paddingBottom: "--badge-padding-bottom",
|
|
83453
84369
|
paddingLeft: "--badge-padding-left",
|
|
83454
|
-
backgroundColor: "--background-color",
|
|
83455
84370
|
background: "--background",
|
|
83456
84371
|
borderColor: "--border-color",
|
|
83457
84372
|
color: "--color",
|
|
@@ -83625,7 +84540,7 @@ const useBadgeRegistry = (children, enabled) => {
|
|
|
83625
84540
|
* shrinkWrap on its own builds the measurement ghost: under maxLines the list
|
|
83626
84541
|
* is measured in place, once the rows are known.
|
|
83627
84542
|
*
|
|
83628
|
-
* @param {import("
|
|
84543
|
+
* @param {import("preact").ComponentChildren} [fallback]
|
|
83629
84544
|
* Rendered in place of the badges when there is none. Without it an empty
|
|
83630
84545
|
* list renders nothing at all. In a <Picker> this is the only placeholder
|
|
83631
84546
|
* the user gets — a picker given a `ui` does not draw its own — so pass the
|
|
@@ -84259,10 +85174,10 @@ const PickerArrayUI = /*#__PURE__*/asPickerOwnUI(() => {
|
|
|
84259
85174
|
* — a row being read still says what was picked, it just no longer offers to
|
|
84260
85175
|
* unpick it.
|
|
84261
85176
|
*
|
|
84262
|
-
* @type {import("
|
|
85177
|
+
* @type {import("preact").FunctionComponent<{
|
|
84263
85178
|
* value: any,
|
|
84264
85179
|
* commandFor?: string,
|
|
84265
|
-
* children?: import("
|
|
85180
|
+
* children?: import("preact").ComponentChildren,
|
|
84266
85181
|
* [key: string]: any,
|
|
84267
85182
|
* }>}
|
|
84268
85183
|
* @param {any} value The value this chip stands for — one entry of what the
|
|
@@ -84528,6 +85443,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84528
85443
|
.navi_picker {
|
|
84529
85444
|
--picker-border-radius: var(--navi-control-border-radius);
|
|
84530
85445
|
--picker-border-width: var(--navi-control-border-width);
|
|
85446
|
+
--border-width: var(--picker-border-width);
|
|
84531
85447
|
--picker-outline-width: var(--navi-focus-outline-width);
|
|
84532
85448
|
--picker-outline-offset: calc(-.5 * var(--picker-outline-width));
|
|
84533
85449
|
--picker-outline-color: var(--navi-focus-outline-color);
|
|
@@ -84567,6 +85483,111 @@ const css$r = /* css */`@layer navi {
|
|
|
84567
85483
|
--picker-align-x-default: flex-start;
|
|
84568
85484
|
--picker-align-y-default: center;
|
|
84569
85485
|
--picker-text-align-default: initial;
|
|
85486
|
+
font-size: var(--picker-font-size);
|
|
85487
|
+
font-family: var(--picker-font-family);
|
|
85488
|
+
line-height: var(--navi-control-line-height);
|
|
85489
|
+
}
|
|
85490
|
+
|
|
85491
|
+
.navi_picker[data-variant="icon"] {
|
|
85492
|
+
--picker-border-width: 0px;
|
|
85493
|
+
--picker-border-color: transparent;
|
|
85494
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85495
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85496
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85497
|
+
--picker-background-color: transparent;
|
|
85498
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85499
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85500
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85501
|
+
--picker-icon-color: currentColor;
|
|
85502
|
+
}
|
|
85503
|
+
|
|
85504
|
+
.navi_picker[data-variant="discrete"] {
|
|
85505
|
+
--picker-border-width: 0px;
|
|
85506
|
+
--picker-border-color: transparent;
|
|
85507
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85508
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85509
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85510
|
+
--picker-background-color: transparent;
|
|
85511
|
+
--picker-background-color-hover: color-mix(in srgb,
|
|
85512
|
+
currentColor 8%,
|
|
85513
|
+
var(--picker-background-color));
|
|
85514
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85515
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85516
|
+
}
|
|
85517
|
+
|
|
85518
|
+
.navi_picker[data-variant="headless"] {
|
|
85519
|
+
--picker-padding-x-default: 0;
|
|
85520
|
+
--picker-padding-y-default: 0;
|
|
85521
|
+
--picker-border-width: 0px;
|
|
85522
|
+
--picker-border-color: transparent;
|
|
85523
|
+
--picker-outline-width: 0px;
|
|
85524
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85525
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85526
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85527
|
+
--picker-background-color: transparent;
|
|
85528
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85529
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85530
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85531
|
+
--picker-icon-color: currentColor;
|
|
85532
|
+
}
|
|
85533
|
+
|
|
85534
|
+
.navi_picker[data-variant="bare"] {
|
|
85535
|
+
--picker-padding-x-default: 0;
|
|
85536
|
+
--picker-padding-y-default: 0;
|
|
85537
|
+
--picker-align-y-default: stretch;
|
|
85538
|
+
--picker-border-width: 0px;
|
|
85539
|
+
--picker-border-color: transparent;
|
|
85540
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85541
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85542
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85543
|
+
--picker-background-color: transparent;
|
|
85544
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85545
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85546
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85547
|
+
--picker-icon-color: currentColor;
|
|
85548
|
+
font-size: inherit;
|
|
85549
|
+
font-family: inherit;
|
|
85550
|
+
line-height: inherit;
|
|
85551
|
+
}
|
|
85552
|
+
|
|
85553
|
+
.navi_picker[data-variant="button"] {
|
|
85554
|
+
--picker-padding-x-default: var(--navi-button-padding-x-default);
|
|
85555
|
+
--picker-padding-y-default: var(--navi-button-padding-y-default);
|
|
85556
|
+
--picker-align-x-default: center;
|
|
85557
|
+
--picker-background-color: var(--navi-button-background-color);
|
|
85558
|
+
--picker-border-color-readonly: color-mix(in srgb,
|
|
85559
|
+
var(--picker-border-color) 30%,
|
|
85560
|
+
var(--navi-surface-color));
|
|
85561
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85562
|
+
--picker-color-readonly: color-mix(in srgb,
|
|
85563
|
+
var(--picker-color) 30%,
|
|
85564
|
+
transparent);
|
|
85565
|
+
--picker-border-color-disabled: var(--picker-border-color-readonly);
|
|
85566
|
+
--picker-background-color-disabled: var(--picker-background-color-readonly);
|
|
85567
|
+
--picker-color-disabled: var(--picker-color-readonly);
|
|
85568
|
+
--picker-text-align-default: center;
|
|
85569
|
+
}
|
|
85570
|
+
|
|
85571
|
+
.navi_picker[data-variant="text"] {
|
|
85572
|
+
--picker-padding-x-default: 0;
|
|
85573
|
+
--picker-padding-y-default: 0;
|
|
85574
|
+
--picker-border-width: 0px;
|
|
85575
|
+
--picker-border-color: transparent;
|
|
85576
|
+
--picker-border-color-hover: var(--picker-border-color);
|
|
85577
|
+
--picker-border-color-readonly: var(--picker-border-color);
|
|
85578
|
+
--picker-border-color-disabled: var(--picker-border-color);
|
|
85579
|
+
--picker-background-color: transparent;
|
|
85580
|
+
--picker-background-color-hover: var(--picker-background-color);
|
|
85581
|
+
--picker-background-color-readonly: var(--picker-background-color);
|
|
85582
|
+
--picker-background-color-disabled: var(--picker-background-color);
|
|
85583
|
+
font-size: inherit;
|
|
85584
|
+
font-family: inherit;
|
|
85585
|
+
}
|
|
85586
|
+
|
|
85587
|
+
.navi_picker[data-icon] {
|
|
85588
|
+
--picker-padding-x-default: 0;
|
|
85589
|
+
--picker-padding-y-default: 0;
|
|
85590
|
+
--picker-align-x-default: center;
|
|
84570
85591
|
}
|
|
84571
85592
|
}
|
|
84572
85593
|
|
|
@@ -84589,10 +85610,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84589
85610
|
box-sizing: border-box;
|
|
84590
85611
|
max-width: 100%;
|
|
84591
85612
|
color: var(--x-picker-color);
|
|
84592
|
-
font-size: var(--picker-font-size);
|
|
84593
|
-
font-family: var(--picker-font-family);
|
|
84594
85613
|
text-align: inherit;
|
|
84595
|
-
line-height: var(--navi-control-line-height);
|
|
84596
85614
|
border-top-left-radius: var(--x-corner-top-left-radius, var(--picker-border-radius));
|
|
84597
85615
|
border-top-right-radius: var(--x-corner-top-right-radius, var(--picker-border-radius));
|
|
84598
85616
|
border-bottom-right-radius: var(--x-corner-bottom-right-radius, var(--picker-border-radius));
|
|
@@ -84834,48 +85852,7 @@ const css$r = /* css */`@layer navi {
|
|
|
84834
85852
|
--x-picker-border-color: var(--callout-color);
|
|
84835
85853
|
}
|
|
84836
85854
|
|
|
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
85855
|
&[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
85856
|
& > .navi_picker_box {
|
|
84880
85857
|
z-index: -1;
|
|
84881
85858
|
position: absolute;
|
|
@@ -84884,23 +85861,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84884
85861
|
}
|
|
84885
85862
|
|
|
84886
85863
|
&[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
85864
|
& > .navi_picker_box {
|
|
84905
85865
|
min-height: 0;
|
|
84906
85866
|
}
|
|
@@ -84935,39 +85895,8 @@ const css$r = /* css */`@layer navi {
|
|
|
84935
85895
|
}
|
|
84936
85896
|
}
|
|
84937
85897
|
|
|
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
85898
|
&[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
85899
|
text-underline-offset: .2em;
|
|
84970
|
-
font-family: inherit;
|
|
84971
85900
|
text-decoration: underline dotted;
|
|
84972
85901
|
|
|
84973
85902
|
&[data-hover] {
|
|
@@ -84976,10 +85905,6 @@ const css$r = /* css */`@layer navi {
|
|
|
84976
85905
|
}
|
|
84977
85906
|
|
|
84978
85907
|
&[data-icon] {
|
|
84979
|
-
--picker-padding-x-default: 0;
|
|
84980
|
-
--picker-padding-y-default: 0;
|
|
84981
|
-
--picker-align-x-default: center;
|
|
84982
|
-
|
|
84983
85908
|
& > .navi_picker_box {
|
|
84984
85909
|
min-height: 0;
|
|
84985
85910
|
}
|
|
@@ -85414,13 +86339,13 @@ const PickerButton = props => {
|
|
|
85414
86339
|
* It goes when there is nothing to take out and when nothing can be changed, so
|
|
85415
86340
|
* a caller places it once and never asks whether to draw it.
|
|
85416
86341
|
*
|
|
85417
|
-
* @type {import("
|
|
86342
|
+
* @type {import("preact").FunctionComponent<{
|
|
85418
86343
|
* size?: number | string,
|
|
85419
|
-
* children?: import("
|
|
86344
|
+
* children?: import("preact").ComponentChildren,
|
|
85420
86345
|
* } & Record<string, any>>}
|
|
85421
86346
|
* @param {number|string} [size="inherit"] How big the cross is drawn, as
|
|
85422
86347
|
* `<Icon size>` reads it — "inherit" takes the size of the text around it.
|
|
85423
|
-
* @param {import("
|
|
86348
|
+
* @param {import("preact").ComponentChildren} [children] The icon, in place of
|
|
85424
86349
|
* the cross. Anything else goes to the `<Button>` underneath (`variant`,
|
|
85425
86350
|
* `paddingX`, `aria-label`, …).
|
|
85426
86351
|
*/
|
|
@@ -85751,6 +86676,580 @@ const PickerUI = /*#__PURE__*/Object.assign(PickerDefaultUI, {
|
|
|
85751
86676
|
ColorSvg
|
|
85752
86677
|
});
|
|
85753
86678
|
|
|
86679
|
+
/**
|
|
86680
|
+
* Button-like trigger that opens a picker (native or custom popup) when clicked.
|
|
86681
|
+
*
|
|
86682
|
+
* Without `children`, opens the browser-native picker for the given `type`.
|
|
86683
|
+
* With `children`, opens a popover (desktop) or dialog (mobile) containing the children.
|
|
86684
|
+
* Pass `mode="popover"` or `mode="dialog"` to override the automatic choice.
|
|
86685
|
+
* Either way the popup is a `dialog`, named by a `<Popup.Title>` in the
|
|
86686
|
+
* children (see `popupAriaLabelledBy`), and the trigger answers to `combobox` —
|
|
86687
|
+
* `button` under `picksNothing` — with `aria-haspopup="dialog"` and
|
|
86688
|
+
* `aria-expanded` (see docs/testid.md).
|
|
86689
|
+
*
|
|
86690
|
+
* `type="confirm"` is the picker that asks "are you sure?" before doing what it
|
|
86691
|
+
* stands for: its popup is the question, `ui` its label, and yes runs its
|
|
86692
|
+
* `action` and triggers its `command` once the popup has closed (the command
|
|
86693
|
+
* follows the action, and only if it succeeded) — see
|
|
86694
|
+
* picker_confirm.jsx, and `message`, `confirmLabel`, `cancelLabel`,
|
|
86695
|
+
* `focusOnOpen` below.
|
|
86696
|
+
*
|
|
86697
|
+
* @type {import("preact").FunctionComponent<{
|
|
86698
|
+
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
86699
|
+
* value?: any,
|
|
86700
|
+
* defaultValue?: any,
|
|
86701
|
+
* name?: string,
|
|
86702
|
+
* placeholder?: import("preact").ComponentChildren,
|
|
86703
|
+
* ui?: import("preact").ComponentChildren | "default" | import("preact").ComponentType<{ value: any, loading: boolean, interactive: boolean }>,
|
|
86704
|
+
* required?: boolean,
|
|
86705
|
+
* min?: Date | string | number,
|
|
86706
|
+
* max?: Date | string | number,
|
|
86707
|
+
* step?: string | number,
|
|
86708
|
+
* disabled?: boolean,
|
|
86709
|
+
* readOnly?: boolean,
|
|
86710
|
+
* openWhileReadOnly?: boolean,
|
|
86711
|
+
* error?: boolean | string,
|
|
86712
|
+
* uiAction?: (value: any, event: Event) => void,
|
|
86713
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void,
|
|
86714
|
+
* children?: import("preact").ComponentChildren,
|
|
86715
|
+
* mode?: "popover" | "dialog" | "callout",
|
|
86716
|
+
* openOn?: "press" | "longpress" | "contextmenu" | string | string[],
|
|
86717
|
+
* animation?: boolean | "auto" | "fading" | "scaling" | "sliding" | "lifting" | `slide-from-${string}` | { open: boolean | "auto" | "fading" | "scaling" | "sliding" | `slide-from-${string}`, close: "lifting" },
|
|
86718
|
+
* lift?: "box" | "scene",
|
|
86719
|
+
* liftAnchor?: Element | { current: Element } | string,
|
|
86720
|
+
* animationDuration?: string,
|
|
86721
|
+
* calloutStatus?: "info" | "warning" | "error" | "success" | "none",
|
|
86722
|
+
* calloutIcon?: boolean,
|
|
86723
|
+
* calloutCloseButton?: boolean,
|
|
86724
|
+
* calloutCloseByScroll?: boolean | number,
|
|
86725
|
+
* popoverMode?: "nearby" | "overlay",
|
|
86726
|
+
* positionArea?: string,
|
|
86727
|
+
* popupWidthFitContent?: boolean,
|
|
86728
|
+
* loadingOutline?: "custom",
|
|
86729
|
+
* loadingOutlineInset?: number,
|
|
86730
|
+
* variant?: "icon" | "circle" | "headless" | "discrete" | "button" | "text" | "bare" | "picker",
|
|
86731
|
+
* icon?: boolean,
|
|
86732
|
+
* alignX?: "start" | "center" | "end",
|
|
86733
|
+
* alignY?: "start" | "center" | "end" | "stretch",
|
|
86734
|
+
* rightSlotIcon?: import("preact").ComponentChildren,
|
|
86735
|
+
* rightSlotIconSize?: number | string,
|
|
86736
|
+
* rightSlot?: import("preact").ComponentChildren,
|
|
86737
|
+
* chevron?: "end" | "start" | "hang-end",
|
|
86738
|
+
* clearConfirm?: string | import("preact").ComponentChildren,
|
|
86739
|
+
* message?: import("preact").ComponentChildren,
|
|
86740
|
+
* confirmLabel?: import("preact").ComponentChildren,
|
|
86741
|
+
* cancelLabel?: import("preact").ComponentChildren,
|
|
86742
|
+
* focusOnOpen?: "confirm" | "cancel" | "none",
|
|
86743
|
+
* onConfirm?: (confirmEvent: CustomEvent) => void,
|
|
86744
|
+
* onOpen?: (openEvent: CustomEvent) => void,
|
|
86745
|
+
* onClose?: (closeEvent: CustomEvent) => void,
|
|
86746
|
+
* maxLines?: number,
|
|
86747
|
+
* slotSpacing?: number | string,
|
|
86748
|
+
* popoverMaxHeight?: number | string,
|
|
86749
|
+
* dialogMinWidth?: number | string,
|
|
86750
|
+
* dialogMinHeight?: number | string,
|
|
86751
|
+
* dialogMaxWidth?: number | string,
|
|
86752
|
+
* dialogMaxHeight?: number | string,
|
|
86753
|
+
* popupBackgroundColor?: string,
|
|
86754
|
+
* popupBorderRadius?: number | string,
|
|
86755
|
+
* popupBoxShadow?: string,
|
|
86756
|
+
* dialogBorderWidth?: number | string,
|
|
86757
|
+
* clearable?: boolean,
|
|
86758
|
+
* picksNothing?: boolean,
|
|
86759
|
+
* standalone?: boolean,
|
|
86760
|
+
* popupLayer?: "top" | "local",
|
|
86761
|
+
* popupTestId?: string,
|
|
86762
|
+
* popupAriaLabel?: string,
|
|
86763
|
+
* popupAriaLabelledBy?: string,
|
|
86764
|
+
* popupAriaDescribedBy?: string,
|
|
86765
|
+
* confirmTestId?: string,
|
|
86766
|
+
* cancelTestId?: string,
|
|
86767
|
+
* mount?: "always" | "idle" | "from-first-open" | "while-opened",
|
|
86768
|
+
* dialogExpand?: boolean,
|
|
86769
|
+
* dialogExpandX?: boolean,
|
|
86770
|
+
* dialogExpandY?: boolean,
|
|
86771
|
+
* dialogSizeFromAnchor?: boolean,
|
|
86772
|
+
* dockedOnSmallTouchScreen?: boolean | "top" | "bottom",
|
|
86773
|
+
* marginWithContainer?: number | string,
|
|
86774
|
+
* anchor?: import("preact").RefObject<HTMLElement> | HTMLElement,
|
|
86775
|
+
* escapeEffect?: "cancel" | "close",
|
|
86776
|
+
* pressOutside?: "close" | "cancel" | "capture",
|
|
86777
|
+
* backdrop?: boolean,
|
|
86778
|
+
* backdropVariant?: "auto" | "lift" | "discrete" | "invisible",
|
|
86779
|
+
* backdropColor?: string,
|
|
86780
|
+
* backdropFilter?: string,
|
|
86781
|
+
* ref?: import("preact").RefObject<HTMLElement>,
|
|
86782
|
+
* [key: string]: any,
|
|
86783
|
+
* }>}
|
|
86784
|
+
* @param {string} [popupBackgroundColor] The popup's surface. Left out it is
|
|
86785
|
+
* the one every navi popup uses (`--navi-popup-background-color`, which
|
|
86786
|
+
* follows the theme) — the trigger's own `backgroundColor` is not it, so a
|
|
86787
|
+
* variant that paints the trigger transparent leaves the popup a real sheet.
|
|
86788
|
+
* @param {number|string} [popupBorderRadius] The popup's corners. Left out they
|
|
86789
|
+
* echo the trigger's `borderRadius`.
|
|
86790
|
+
* @param {string} [popupBoxShadow] The popup's shadow, popover or dialog. Left
|
|
86791
|
+
* out it is the one every navi popup casts. `"none"` for a popup that is
|
|
86792
|
+
* not a sheet — a card lifted out of a page draws its own edges, and the
|
|
86793
|
+
* popup only carries it (see `dialogSizeFromAnchor`).
|
|
86794
|
+
* @param {"press"|"longpress"|"contextmenu"|string|string[]} [openOn="press"]
|
|
86795
|
+
* What opens the popup. The press, by default — the way a native select
|
|
86796
|
+
* opens. Any other name is an interaction navi detects (see
|
|
86797
|
+
* docs/interactions.md: `"longpress"`, `"contextmenu"`, `"double_click"`…), or
|
|
86798
|
+
* a list of them — `["longpress", "contextmenu"]` is the hold on a phone and
|
|
86799
|
+
* the right click at a desk. The picker declares it on itself, so it is
|
|
86800
|
+
* arbitrated like any other gesture: a swipe on the same card takes the
|
|
86801
|
+
* press a hold would have taken. A tap then opens nothing — the press stays
|
|
86802
|
+
* free for what the `ui` holds: a link in the card navigates, a button in it
|
|
86803
|
+
* presses, a picker in it opens on its own click, with nothing to declare
|
|
86804
|
+
* (`selfInteractions` is for a drawing that DOES open on the press). The
|
|
86805
|
+
* trigger says so under the mouse too: it drops the hand cursor, which is
|
|
86806
|
+
* the press's promise, and every pixel of the drawing keeps the cursor it
|
|
86807
|
+
* would have on its own. That is what lets a whole card be a picker without
|
|
86808
|
+
* every touch on it opening the sheet — and the keyboard keeps its ways in
|
|
86809
|
+
* (Enter, Space, the arrows). A hold declared INSIDE the card (`interactions={{ longpress }}`
|
|
86810
|
+
* on something it holds) answers before this one: the nearer hold takes the
|
|
86811
|
+
* press, the way a click is the innermost target's. Being a gesture, the open goes through
|
|
86812
|
+
* the interaction gate as one: a `readOnly` picker refuses it and says so
|
|
86813
|
+
* where the finger is, whatever `openWhileReadOnly` says.
|
|
86814
|
+
* @param {number} [maxLines=1] How many lines the value may take before it is
|
|
86815
|
+
* cut with an ellipsis — `Text`'s `maxLines`, put on the value box, and
|
|
86816
|
+
* granted to a `BadgeList` in the `ui`, which caps its rows to it (see
|
|
86817
|
+
* max_lines_context.js). `variant="text"` and `variant="bare"` are not
|
|
86818
|
+
* clamped: their drawing is the caller's own, a `Text` in the `ui` cuts
|
|
86819
|
+
* itself with its own `maxLines`. See docs/typography.md.
|
|
86820
|
+
* @param {import("preact").ComponentChildren | "default" | import("preact").ComponentType} [ui] What the
|
|
86821
|
+
* trigger draws in place of the value's default rendering (a date, a list
|
|
86822
|
+
* joined by commas…) — and then all it draws: `placeholder` is not shown
|
|
86823
|
+
* next to it, and an empty value is not greyed as a placeholder either: what
|
|
86824
|
+
* this draws when the picker holds nothing may well be an answer ("no
|
|
86825
|
+
* filter", "anywhere"), so its empty look is the caller's. A `ui` must
|
|
86826
|
+
* therefore say on its own that the picker is empty, and take the same room
|
|
86827
|
+
* empty as it does filled. A <BadgeList> gets both from its `fallback` — the
|
|
86828
|
+
* placeholder text, as plain text, see docs/typography.md#badgelist.
|
|
86829
|
+
* `"default"` draws nothing in the slot at all.
|
|
86830
|
+
*
|
|
86831
|
+
* Under `variant="icon"` there is no value to draw, so this IS the trigger:
|
|
86832
|
+
* left out it is the icon the right slot would have shown (`rightSlotIcon`,
|
|
86833
|
+
* or the chevron), and given it is drawn instead — one icon of the caller's
|
|
86834
|
+
* own, and nothing else.
|
|
86835
|
+
*
|
|
86836
|
+
* Under `variant="bare"` it is the trigger as well, and the picker adds
|
|
86837
|
+
* nothing around it: whatever this draws is what the picker measures. Its
|
|
86838
|
+
* own padding, its own lines, its own corners — a `<Box>` here gives a
|
|
86839
|
+
* picker the exact box that `<Box>` has on its own. Anything interactive
|
|
86840
|
+
* inside it takes its clicks back with `data-self-interactions`, the press
|
|
86841
|
+
* everywhere else opening the popup. There is no right slot beside it either,
|
|
86842
|
+
* so what a field would have got from navi this drawing places itself: a
|
|
86843
|
+
* `<Picker.Clear />` where the cross belongs, laid out like anything else it
|
|
86844
|
+
* holds. What the picker catches the press over is that box and no more, so
|
|
86845
|
+
* a drawing that paints outside it on purpose — an affordance in the margin
|
|
86846
|
+
* beside it, out of the flow so the rest keeps its width — is drawn and
|
|
86847
|
+
* opens nothing; `pressPadding` is how far the press area follows it out
|
|
86848
|
+
* there. The chevron itself is the `chevron` prop's to draw and place.
|
|
86849
|
+
*
|
|
86850
|
+
* A drawing that follows what the picker HOLDS — the value the popup just
|
|
86851
|
+
* closed on, before the server has answered, the same state navi's own
|
|
86852
|
+
* default `ui` reads, rolled back if the action fails (`resetOnError`) —
|
|
86853
|
+
* reads it one of two ways. As a component, `ui={MyCard}`: the picker
|
|
86854
|
+
* renders it and hands it `value`, `loading` and `interactive` as props.
|
|
86855
|
+
* As an element, `ui={<MyCard game={game} />}`, with `usePickerState()`
|
|
86856
|
+
* inside it returning the same three — for a drawing that needs props of
|
|
86857
|
+
* its own as well. Either way a card shows the score the moment the sheet
|
|
86858
|
+
* leaves and takes it back on a refusal, with nothing held by the caller.
|
|
86859
|
+
* `loading` is the wait it wears meanwhile: navi draws its loading outline
|
|
86860
|
+
* on every trigger, two pixels out of the box. On a card-sized
|
|
86861
|
+
* `variant="bare"` that run sits on the card's own border and reads as a
|
|
86862
|
+
* border blinking; `loadingOutlineInset` moves it clear of it. And when the
|
|
86863
|
+
* picker's box is not the shape of what waits, `loadingOutline="custom"`
|
|
86864
|
+
* lets the drawing draw the wait itself, from that flag or from
|
|
86865
|
+
* `[data-loading]` on the picker root in css.
|
|
86866
|
+
* @param {boolean} [readOnly] Nothing in this picker can be changed — and it
|
|
86867
|
+
* still opens, so what is in the popup can be read: everything in there is
|
|
86868
|
+
* held read-only in turn, each control greying out and saying why on its own.
|
|
86869
|
+
* Which is the point of opening it at all: a picker's answer often exists
|
|
86870
|
+
* only in the shape the popup draws (a plan with one tile ringed, a wheel
|
|
86871
|
+
* stopped on a time), and the trigger's one line is a summary of it, not the
|
|
86872
|
+
* whole of it. The clear cross goes, closing commits nothing, and a `value`
|
|
86873
|
+
* the popup would otherwise have confirmed stays a suggestion.
|
|
86874
|
+
* A picker with no `children` opens the browser's own picker instead, and
|
|
86875
|
+
* that one cannot be held read-only — so it goes on refusing (see
|
|
86876
|
+
* PickerNative).
|
|
86877
|
+
* @param {boolean} [openWhileReadOnly=true] Pass false to make `readOnly`
|
|
86878
|
+
* refuse the open as well, for a popup with nothing to read: a form of
|
|
86879
|
+
* controls to fill in, a menu of gestures. Opening then only shows what is
|
|
86880
|
+
* refused, so the picker says why on the trigger instead — where the
|
|
86881
|
+
* interaction happened. A busy picker follows the same rule: it still
|
|
86882
|
+
* opens to be read while its action runs, and with `false` the press is
|
|
86883
|
+
* refused with the wait as its reason — what a card that is syncing wants
|
|
86884
|
+
* for the sheet that would write it again.
|
|
86885
|
+
* @param {boolean|string} [error] Something went wrong around this picker (its
|
|
86886
|
+
* content failed to load, its value could not be resolved…). Shown as a
|
|
86887
|
+
* callout on the trigger, open or closed — the caller has nothing to place.
|
|
86888
|
+
* Dismissing it discards that error; a new `error` value raises another one.
|
|
86889
|
+
* @param {string} [id] What the popup's open state is kept under in the
|
|
86890
|
+
* history entry: a screen left and come back to finds the picker open, and
|
|
86891
|
+
* in dialog mode the opening is an entry of its own, closed by the back
|
|
86892
|
+
* button before the screen is left. Left out, the key is a generated id,
|
|
86893
|
+
* which names one mount: the state survives neither leaving the screen nor a
|
|
86894
|
+
* reload. A picker whose popup leads somewhere (a link inside it) has one.
|
|
86895
|
+
* @param {"popover"|"dialog"|"callout"} [mode] Which popup the children open
|
|
86896
|
+
* in. Left out, a popover on a large screen and a dialog on a narrow one.
|
|
86897
|
+
* `"callout"` shows them in the picker's own callout — the speech bubble its
|
|
86898
|
+
* constraints speak in, with its status icon, its cross and its arrow on
|
|
86899
|
+
* the trigger — for a tooltip that opens on a press. It closes the way a
|
|
86900
|
+
* callout does: its cross, Escape, a click outside.
|
|
86901
|
+
* @param {"info"|"warning"|"error"|"success"|"none"} [calloutStatus]
|
|
86902
|
+
* `mode="callout"`: what the callout says about what it holds — its border,
|
|
86903
|
+
* its icon, and whether a click outside closes it (an "error" stays).
|
|
86904
|
+
* `"none"` is a plain tooltip: no icon, a neutral frame. "info" by default,
|
|
86905
|
+
* "none" under `variant="text"`. The trigger's default icon is the
|
|
86906
|
+
* callout's own status icon in that status (see `CalloutStatusIcon`) — left
|
|
86907
|
+
* out, the trigger is that icon; `variant="circle"` draws it round. The callout's arrow points at
|
|
86908
|
+
* the middle of the trigger (`data-callout-arrow-x="center"` unless the
|
|
86909
|
+
* picker says otherwise).
|
|
86910
|
+
* @param {boolean} [calloutIcon] `mode="callout"`: whether the callout shows
|
|
86911
|
+
* its status icon beside what it holds. On by default, off under
|
|
86912
|
+
* `variant="text"` — a word asks for a plain bubble — and never shown
|
|
86913
|
+
* without a status, whatever this says.
|
|
86914
|
+
* @param {boolean} [calloutCloseButton=true] `mode="callout"`: pass false to
|
|
86915
|
+
* take the cross off the callout. It still closes on Escape, a click
|
|
86916
|
+
* outside, and a `--navi-close` from what it holds — for a tooltip that is
|
|
86917
|
+
* read rather than dismissed. Off by nobody's default: a text trigger
|
|
86918
|
+
* keeps the cross unless told not to.
|
|
86919
|
+
* @param {boolean|number} [calloutCloseByScroll=false] `mode="callout"`: close
|
|
86920
|
+
* the callout once the page has scrolled under it — `true` for a short
|
|
86921
|
+
* distance, a number for that many pixels. For a tooltip read in place,
|
|
86922
|
+
* which scrolling away is done with.
|
|
86923
|
+
* @param {"nearby"|"overlay"} [popoverMode="nearby"] "overlay" lays the popover
|
|
86924
|
+
* over the trigger, "nearby" leaves a small gap below it.
|
|
86925
|
+
* @param {string} [positionArea] Where the popup goes — relative to the trigger
|
|
86926
|
+
* in popover mode, relative to the viewport in dialog mode. Same grammar as
|
|
86927
|
+
* Popover/Dialog's own `positionArea` ("top", "right-end", "inset(top-left)",
|
|
86928
|
+
* …). Defaults to "bottom-start" in popover mode ("inset(top-left)" when
|
|
86929
|
+
* popoverMode is "overlay"), and to Dialog's own "center" in dialog mode. A
|
|
86930
|
+
* popover still flips to the opposite side on its own when there isn't
|
|
86931
|
+
* enough room.
|
|
86932
|
+
* @param {boolean} [popupWidthFitContent] By default the popover is at least as
|
|
86933
|
+
* wide as the trigger. Set this to let the content size it instead, so a
|
|
86934
|
+
* popover narrower than the trigger stays narrow. Popover mode only — a
|
|
86935
|
+
* dialog never takes the trigger's width to begin with (see
|
|
86936
|
+
* `dialogMinWidth`).
|
|
86937
|
+
* @param {import("preact").ComponentChildren} [rightSlotIcon] What the right
|
|
86938
|
+
* slot draws in place of the chevron. It is the whole slot, not an addition
|
|
86939
|
+
* to it: the picker then no longer says on its own that it opens, so pass
|
|
86940
|
+
* something that does. Decoration only — it is wrapped in an `<Icon>`, which
|
|
86941
|
+
* is `aria-hidden`, so anything focusable in there would be reachable by tab
|
|
86942
|
+
* while invisible to assistive tech. Use `rightSlot` for that.
|
|
86943
|
+
*
|
|
86944
|
+
* The slot only exists beside a value. `variant="icon"` has none, so there is
|
|
86945
|
+
* no slot either: the icon becomes the trigger itself and is drawn as the
|
|
86946
|
+
* `ui` — which is also what a caller overrides to draw their own. Nothing
|
|
86947
|
+
* else is drawn there, the clear cross included: one icon has room for one
|
|
86948
|
+
* thing.
|
|
86949
|
+
|
|
86950
|
+
* `variant="bare"` has no slot either, for the same reason turned around: the
|
|
86951
|
+
* picker is the drawing's box to the pixel, so nothing may be added beside it
|
|
86952
|
+
* — the drawing holds what it needs (`<Picker.Clear />`) and places it. Its
|
|
86953
|
+
* `chevron` prop is the exception: the slot, put where the drawing is not.
|
|
86954
|
+
* @param {"end"|"start"|"hang-end"} [chevron] `variant="bare"` only: the
|
|
86955
|
+
* picker draws no chevron beside the drawing, so nothing says it opens; this
|
|
86956
|
+
* draws one — the icon the right slot would hold, `rightSlotIcon` or the
|
|
86957
|
+
* chevron, at `rightSlotIconSize`, `slotSpacing` away from the drawing (xxs
|
|
86958
|
+
* by default), taking the clear cross's place under `clearable` like any
|
|
86959
|
+
* slot — and says where, so the drawing keeps the box it has when nothing
|
|
86960
|
+
* opens. Which side depends on where the drawing is anchored in its line.
|
|
86961
|
+
* `"end"`: after it, in the flow — a label with room after it. `"start"`:
|
|
86962
|
+
* before it — a label pushed against the right edge, which the chevron
|
|
86963
|
+
* would otherwise push left by its width. `"hang-end"`: after it and OUT of
|
|
86964
|
+
* the flow, in the margin beside the box — a drawing filling its whole
|
|
86965
|
+
* line; the press follows it out there by its size plus its gap, on top of
|
|
86966
|
+
* any `pressPaddingRight`. The chevron is drawn in `--picker-icon-color`
|
|
86967
|
+
* (`currentColor` here) and `--picker-icon-color-hover`; a dimmed one is
|
|
86968
|
+
* `--picker-icon-color: color-mix(in srgb, currentColor 55%, transparent)`.
|
|
86969
|
+
* @param {import("preact").ComponentChildren} [rightSlot] Same place, rendered
|
|
86970
|
+
* as-is: no `<Icon>` around it, nothing `aria-hidden`. This is where an
|
|
86971
|
+
* interactive right slot goes. `null` takes the slot away entirely — the
|
|
86972
|
+
* gutter it carries with it, so the trigger measures its drawing and centers
|
|
86973
|
+
* on it.
|
|
86974
|
+
* @param {boolean} [picksNothing] The popup asks something rather than holding
|
|
86975
|
+
* an answer, so the trigger draws a label and never a value: no chevron, no
|
|
86976
|
+
* clear cross, in any variant — and the controls in the popup are their own
|
|
86977
|
+
* (a checkbox gating the yes), never the picker's value, so a bound `action`
|
|
86978
|
+
* keeps the params it was bound with. `type="confirm"` says it for itself; say it
|
|
86979
|
+
* for a popup of your own that is a menu of actions rather than a choice —
|
|
86980
|
+
* or a DOOR: a drawing that grows to be looked at (a weather scene, a plan),
|
|
86981
|
+
* with no `action` at all. The picker is then a trigger and a popup, and
|
|
86982
|
+
* nothing more: its close does nothing, and what it keeps from being a
|
|
86983
|
+
* control is what a door still needs — the role of a button, the keyboard
|
|
86984
|
+
* opening it, the anchor being itself, and one popup per instance without
|
|
86985
|
+
* an id to wire. See "The trigger wears the wait" in docs/popup_open.md for
|
|
86986
|
+
* when that beats a `Button` opening a shared `Dialog`.
|
|
86987
|
+
* @param {string|import("preact").ComponentChildren} [clearConfirm] The
|
|
86988
|
+
* question asked before the clear cross clears anything — the `message` of
|
|
86989
|
+
* the `<Picker type="confirm">` the cross then is, plain text or JSX. Asked
|
|
86990
|
+
* BEFORE the ui state is emptied, so answering no leaves the field exactly
|
|
86991
|
+
* as it was; answering yes clears and sends, and the picker's own action
|
|
86992
|
+
* receives the cleared value like any other choice. A cut (Ctrl+X) on such a
|
|
86993
|
+
* picker copies the value and leaves it in place: no clear without the
|
|
86994
|
+
* question.
|
|
86995
|
+
* @param {"icon"|"headless"|"discrete"|"button"|"text"|"bare"|"picker"} [variant]
|
|
86996
|
+
* How the trigger is drawn. `"button"` is a Button's drawing — surface,
|
|
86997
|
+
* padding, a centered label, no chevron — and what a `type="confirm"` picker
|
|
86998
|
+
* draws unless told otherwise: nothing is picked, the popup is a question.
|
|
86999
|
+
* `"text"` is a word in a sentence, underlined with dots, no box and no
|
|
87000
|
+
* slot: what `mode="callout"` uses for a callout on a term rather than on an
|
|
87001
|
+
* icon (`"icon"` being its default, with an info icon in place of the
|
|
87002
|
+
* chevron). `"bare"` draws no trigger of its own at all: the `ui` is it, and
|
|
87003
|
+
* the picker is exactly that drawing's box — no padding, no frame, no
|
|
87004
|
+
* control line, no clamp — for a picker that is a whole piece of a layout
|
|
87005
|
+
* rather than a field, and which must measure the same as the same drawing
|
|
87006
|
+
* placed anywhere else. No right slot comes with it: a cross belongs inside
|
|
87007
|
+
* that drawing, at the place and the size the drawing decides, so it is
|
|
87008
|
+
* `<Picker.Clear />` the `ui` holds rather than `clearable` the picker
|
|
87009
|
+
* takes. What navi still draws on that box — the focus ring, the loading
|
|
87010
|
+
* outline — follows the picker's `borderRadius`, which stays the control's
|
|
87011
|
+
* unless told: a drawing with corners of its own states them on the picker
|
|
87012
|
+
* as well. `"headless"` draws nothing either, and is not the `ui`'s box but
|
|
87013
|
+
* its parent's: it stretches to whatever positioned element holds it, for a
|
|
87014
|
+
* picker put inside the thing that opens it (a button, a row) so that the
|
|
87015
|
+
* popup hangs off that whole element. A picker opened from something it does
|
|
87016
|
+
* not sit in — an object on a map saying
|
|
87017
|
+
* `triggerNaviCommand(pickerEl, "--navi-open", event, { anchor, value })` —
|
|
87018
|
+
* therefore wants an element of its own to stretch into rather than the
|
|
87019
|
+
* nearest positioned ancestor it happens to find. `"picker"` (or an explicit
|
|
87020
|
+
* `variant={undefined}`) asks a confirm or callout picker for the field-like
|
|
87021
|
+
* drawing every other picker has.
|
|
87022
|
+
* @param {"custom"} [loadingOutline] Who draws the loading outline while the
|
|
87023
|
+
* action runs. Left unsaid, the picker does, around its own box. `"custom"`
|
|
87024
|
+
* says someone else does — what it means on a `Button` too — for a picker
|
|
87025
|
+
* that carries a wait drawn elsewhere: two nested pickers sharing one run
|
|
87026
|
+
* (a score picker drawn inside the card an edit picker wraps, see
|
|
87027
|
+
* `12_picker_card_demo.html`) are one wait, and one outline; and a
|
|
87028
|
+
* `variant="bare"` whose box is not
|
|
87029
|
+
* the shape of what waits (a card with a note pinned under it) leaves the
|
|
87030
|
+
* outline to the card, which knows its own box. The wait itself is still
|
|
87031
|
+
* worn: `aria-busy`, the busy refusal, the error callout, `[data-loading]`.
|
|
87032
|
+
* @param {number} [loadingOutlineInset=-2] Where the loading outline runs, in
|
|
87033
|
+
* px from the picker's box — negative is outside. A field wants it right on
|
|
87034
|
+
* its border; a card-sized trigger wants it a few pixels out, clear of the
|
|
87035
|
+
* card's own frame, so it reads as a run around the card rather than as its
|
|
87036
|
+
* border blinking.
|
|
87037
|
+
* @param {boolean} [icon] The trigger is one icon — the `ui`, or the chevron
|
|
87038
|
+
* when there is none. Its box is that icon's box plus the padding asked for
|
|
87039
|
+
* and nothing else: no right slot, and no control line to be as tall as, so
|
|
87040
|
+
* it measures the same as `<Button icon>` and stands in a row of icon
|
|
87041
|
+
* buttons. A shape, not a paint: the variant still decides what is drawn
|
|
87042
|
+
* around it, so `variant="discrete" icon` is the discrete hover wash on an
|
|
87043
|
+
* icon-sized square. `variant="icon"` is this shape with no surface at all.
|
|
87044
|
+
* @param {import("preact").ComponentChildren} [message] `type="confirm"`: the
|
|
87045
|
+
* question, in the popup's default body — text, or JSX when it needs an
|
|
87046
|
+
* emphasis, a link. Left out, a generic "are you sure?" in the current
|
|
87047
|
+
* language. To draw the whole popup instead, pass `children`: yes is then
|
|
87048
|
+
* whatever carries `--navi-confirm`, no whatever closes (`--navi-cancel`).
|
|
87049
|
+
* @param {import("preact").ComponentChildren} [confirmLabel] `type="confirm"`,
|
|
87050
|
+
* default body: what the yes button reads. Defaults to the translated
|
|
87051
|
+
* "Confirm".
|
|
87052
|
+
* @param {import("preact").ComponentChildren} [cancelLabel] Same, for the no
|
|
87053
|
+
* button. Defaults to the translated "Cancel".
|
|
87054
|
+
* @param {"confirm"|"cancel"|"none"} [focusOnOpen="confirm"] `type="confirm"`,
|
|
87055
|
+
* default body: which button the keyboard lands on when the popup opens.
|
|
87056
|
+
* `"cancel"` is the careful choice before something that cannot be undone —
|
|
87057
|
+
* a stray Enter then answers no.
|
|
87058
|
+
* @param {(confirmEvent: CustomEvent) => void} [onConfirm] Called once the
|
|
87059
|
+
* popup has closed on a `--navi-confirm` said inside it. What a confirm
|
|
87060
|
+
* picker uses to run its press; a picker of any type may listen too.
|
|
87061
|
+
* @param {boolean} [canSendWhileUnchanged] Run `action` when a choice pressed
|
|
87062
|
+
* inside the popup — a row's `--navi-send`, a `--navi-confirm` — is the
|
|
87063
|
+
* value the picker already held. By default that close runs nothing: the
|
|
87064
|
+
* answer has not moved, so there is nothing new to say, same as `Form`.
|
|
87065
|
+
* Set this for a picker whose value is a setting that can be valid while
|
|
87066
|
+
* what it points to is not — a channel whose account came unlinked — so
|
|
87067
|
+
* picking it again is how the person asks for it to be put right, and the
|
|
87068
|
+
* action sees the same value and re-asks what there is to re-ask. A press
|
|
87069
|
+
* outside, Escape and the cross still send nothing: nothing was pressed.
|
|
87070
|
+
* @param {(openEvent: CustomEvent) => void} [onOpen] Called when the popup
|
|
87071
|
+
* opens, whatever asked for it (a press, a command, history bringing it
|
|
87072
|
+
* back). You most likely do NOT need this pair: "the user validated" is
|
|
87073
|
+
* already `action` — it fires on every close that keeps (click outside, the
|
|
87074
|
+
* cross, Enter, a `--navi-send` inside the popup) and never on a cancel.
|
|
87075
|
+
* Reach for `onOpen`/`onClose` only for the one thing `action` cannot say:
|
|
87076
|
+
* the popup being up at all, independent of the value — e.g. a derived
|
|
87077
|
+
* computation held while the sheet is open, released on close.
|
|
87078
|
+
* @param {(closeEvent: CustomEvent) => void} [onClose] Called when the popup
|
|
87079
|
+
* actually closes — not preventable, and (see `onOpen`) not the way to know
|
|
87080
|
+
* the user validated: that is `action`. `closeEvent.detail.isCancel` tells a
|
|
87081
|
+
* cancel (Escape, the back gesture) from a close that keeps. Fires after the
|
|
87082
|
+
* picker's value has settled — committed, or restored on a cancel — so the
|
|
87083
|
+
* value read in here is the one the picker ends up holding, and before the
|
|
87084
|
+
* popup leaves the history entry it pushed, so a route param cleared in here
|
|
87085
|
+
* is part of the url a close that keeps carries back.
|
|
87086
|
+
* @param {number|string} [rightSlotIconSize="inherit"] How big what sits in the
|
|
87087
|
+
* right slot is drawn — the chevron, a `rightSlotIcon`, or the clear button's
|
|
87088
|
+
* cross. "inherit" takes the picker's own font size.
|
|
87089
|
+
* @param {number|string} [slotSpacing] Gap kept between what sits in the right
|
|
87090
|
+
* slot (the chevron, or the clear button) and the picker's own edge — same
|
|
87091
|
+
* prop name Input uses for its own slots. Accepts a spacing token ("s",
|
|
87092
|
+
* "m"…) like any other spacing prop, or a length. Defaults to half the
|
|
87093
|
+
* horizontal padding.
|
|
87094
|
+
* @param {number|string} [pressPadding] How far a press counts as the trigger
|
|
87095
|
+
* past the picker's own box — a spacing token ("s", "m"…) or a length, zero
|
|
87096
|
+
* by default. What the picker draws is transparent to the pointer: the press
|
|
87097
|
+
* lands on an invisible input covering the box, so the box is the whole
|
|
87098
|
+
* answer to "does this open". That is right while what is drawn stays inside
|
|
87099
|
+
* it, which is what `variant="bare"` measures. A `ui` that paints OUTSIDE
|
|
87100
|
+
* its box on purpose — a chevron pushed into the margin beside it, so the
|
|
87101
|
+
* label under it keeps the width it has when nothing opens — draws something
|
|
87102
|
+
* nobody can press, and this is how far the press area goes to reach it. The
|
|
87103
|
+
* press area only; nothing moves and nothing is drawn. Font-relative lengths
|
|
87104
|
+
* are read against the picker's own text, so "0.75em" is the drawing's em.
|
|
87105
|
+
* Only the sides that overhang: the area grows over whatever is next to the
|
|
87106
|
+
* picker, and takes the presses aimed at it.
|
|
87107
|
+
* @param {number|string} [pressPaddingX] Same, left and right only;
|
|
87108
|
+
* `pressPaddingY` top and bottom, `pressPaddingTop`/`Right`/`Bottom`/`Left`
|
|
87109
|
+
* one side each.
|
|
87110
|
+
* @param {number|string} [popoverMaxHeight] Soft cap on the popover's height
|
|
87111
|
+
* (default 300px). The popover shrinks below it when space is tight.
|
|
87112
|
+
* @param {number|string} [dialogMinWidth] Floor on the dialog's width. A dialog
|
|
87113
|
+
* picker is not attached to its trigger and does not take its width (unlike
|
|
87114
|
+
* the popover, which uses it as a floor) — this is how a dialog too narrow
|
|
87115
|
+
* for what it holds is widened. Clamped by `dialogMaxWidth`/the container.
|
|
87116
|
+
* @param {number|string} [dialogMinHeight] Same, on the height.
|
|
87117
|
+
* @param {number|string} [dialogMaxWidth] Ceiling on the dialog's width, the
|
|
87118
|
+
* one `dialogExpand`/`dialogExpandX` grows up to — what makes an expanded
|
|
87119
|
+
* dialog a large sheet rather than a full screen. Capped in turn by the
|
|
87120
|
+
* container minus `marginWithContainer`. Describes the centered shape only:
|
|
87121
|
+
* a dialog docked by `dockedOnSmallTouchScreen` withdraws it and stays
|
|
87122
|
+
* container-wide, so both can be stated at once.
|
|
87123
|
+
* @param {number|string} [dialogMaxHeight] Same, on the height.
|
|
87124
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|"lifting"|`slide-from-${string}`} [animation]
|
|
87125
|
+
* The popup's own (see Popover/Dialog). `"lifting"` is the one a picker
|
|
87126
|
+
* whose `ui` is a whole piece of the page wants: the popup is the browser
|
|
87127
|
+
* morphing the trigger's box into what carries `data-lift` inside the popup
|
|
87128
|
+
* (the popup itself when it carries it), and back on close. The trigger IS
|
|
87129
|
+
* the anchor, so nothing has to be named for it — `dialogSizeFromAnchor`
|
|
87130
|
+
* for a card that must keep its width on the way, left out for a drawing
|
|
87131
|
+
* that opens precisely to get bigger. It brings an opaque, blurred backdrop
|
|
87132
|
+
* with it (`--navi-backdrop-lift-*`); `backdropVariant="discrete"` asks for
|
|
87133
|
+
* the light wash back.
|
|
87134
|
+
* @param {Element|{current: Element}|string} [liftAnchor] Dialog mode,
|
|
87135
|
+
* Dialog's own: where a lifting close lands when that is not the trigger
|
|
87136
|
+
* (`animation="lifting"`, or `animation={{ open, close: "lifting" }}` for a
|
|
87137
|
+
* popup that opens the plain way and only lands on close),
|
|
87138
|
+
* read once the close is made (after `onClose`).
|
|
87139
|
+
* @param {"box"|"scene"} [lift="box"] Dialog's own, under `animation="lifting"`:
|
|
87140
|
+
* `"box"` for a card that extends (its top stays, the box uncovers the rest),
|
|
87141
|
+
* `"scene"` for a thumbnail that is a band cut from the middle of the bigger
|
|
87142
|
+
* view, as wide as it (each picture at the box's width, centred in it).
|
|
87143
|
+
* @param {string} [animationDuration] The popup's own (`--popup-animation-duration`).
|
|
87144
|
+
* @param {boolean} [dialogSizeFromAnchor] Dialog mode: the dialog is as wide
|
|
87145
|
+
* as the trigger — its box as a floor (Dialog's own `sizeFromAnchor`) and
|
|
87146
|
+
* as a ceiling, unless a `dialogMaxWidth` says otherwise. What keeps a card
|
|
87147
|
+
* its own width once lifted out of the page (`animation="lifting"`, with
|
|
87148
|
+
* `data-lift` on the card inside the popup): it moves, and nothing else
|
|
87149
|
+
* about it changes. `dialogMaxWidth="var(--anchor-width)"` says the same
|
|
87150
|
+
* ceiling by hand: the popup props are written on the dialog element, where
|
|
87151
|
+
* that variable lives.
|
|
87152
|
+
* @param {boolean|"top"|"bottom"} [dockedOnSmallTouchScreen] Dialog mode,
|
|
87153
|
+
* Dialog's own: on a small touch screen the dialog becomes a sheet docked
|
|
87154
|
+
* full width to one edge — `true` or `"top"` the top edge, out of the
|
|
87155
|
+
* virtual keyboard's way; `"bottom"` the bottom one, for a sheet that is
|
|
87156
|
+
* read and tapped. Anywhere else it stays the centered box. Ignored in
|
|
87157
|
+
* popover mode.
|
|
87158
|
+
* @param {"cancel"|"close"} [escapeEffect="cancel"] What Escape does to an open
|
|
87159
|
+
* picker. "cancel" puts back the value the picker had at open, and a dialog
|
|
87160
|
+
* picker with an explicit `id` (its opening being an entry of its own)
|
|
87161
|
+
* also goes back in history — so anything written to the url while it was
|
|
87162
|
+
* open (a route `stateSignal`, a search param) goes back with it. "close"
|
|
87163
|
+
* makes Escape say what clicking outside says: keep what was chosen,
|
|
87164
|
+
* close — a last resort, see
|
|
87165
|
+
* docs/popup_open.md#escape-cancels-the-other-gestures-keep for why Escape
|
|
87166
|
+
* should go on meaning cancel, and
|
|
87167
|
+
* docs/popup_open.md#what-cancel-actually-undoes for what a cancel puts
|
|
87168
|
+
* back.
|
|
87169
|
+
* @param {import("preact").RefObject<HTMLElement>|HTMLElement} [anchor] What
|
|
87170
|
+
* the popup hangs off, when that is not the picker itself: a picker whose
|
|
87171
|
+
* trigger is one piece of a bigger control (the chevron half of a split
|
|
87172
|
+
* button) points at the whole control, so the popup lines up with it and is
|
|
87173
|
+
* at least as wide as it.
|
|
87174
|
+
* @param {boolean} [standalone] - This picker answers for itself: it does not
|
|
87175
|
+
* register with the control group around it, so what it holds never becomes
|
|
87176
|
+
* part of that group's value and nothing that group does — distributing,
|
|
87177
|
+
* resetting, validating — reaches it. For a door that only opens something,
|
|
87178
|
+
* and for a picker that commits on its own inside a form.
|
|
87179
|
+
* @param {"close"|"cancel"|"capture"} [pressOutside="close"]
|
|
87180
|
+
* What a press outside the popup does: close and keep ("close"), close and
|
|
87181
|
+
* put back the value at open ("cancel"), or nothing at all ("capture"). The
|
|
87182
|
+
* default is what gives a popup with no confirm button its way out that
|
|
87183
|
+
* keeps — see the same section.
|
|
87184
|
+
* @param {boolean} [backdrop=true] Whether anything is laid between the popup
|
|
87185
|
+
* and the page at all. `false` lets a press outside both close the popup and
|
|
87186
|
+
* reach whatever it landed on, in one gesture — for a picker opened over a
|
|
87187
|
+
* page that stays as pressable as it looks (a plan, a map, a canvas), where a
|
|
87188
|
+
* wall would spend the first press on dismissing. Honoured in every mode: a
|
|
87189
|
+
* dialog with no wall is shown through the Popover API rather than
|
|
87190
|
+
* `showModal()`, so a picker docked as a bottom sheet on a phone still
|
|
87191
|
+
* leaves the plan behind it live. `false` by default under `mode="callout"`,
|
|
87192
|
+
* a bubble that dims nothing; `true` there spends the press outside on
|
|
87193
|
+
* closing it — the link under it not followed, the card under it not held —
|
|
87194
|
+
* without a wall: the scroll a finger starts beside the bubble still
|
|
87195
|
+
* scrolls. Not read inside a `<PopupGroup>`, whose wall it is.
|
|
87196
|
+
* @param {"auto"|"lift"|"discrete"|"invisible"} [backdropVariant="auto"] How
|
|
87197
|
+
* visible the popup's backdrop is, independently of what a click outside
|
|
87198
|
+
* does: `"auto"` is the paint `pressOutside` implies,
|
|
87199
|
+
* `"lift"` the opaque blurred wall `animation="lifting"` brings, asked for
|
|
87200
|
+
* without the movement, `"discrete"` a barely-there dim, `"invisible"` fully
|
|
87201
|
+
* transparent — a wall that is not seen is still a wall, which is what
|
|
87202
|
+
* `backdrop` above answers. For a picker that closes on an outside click
|
|
87203
|
+
* without wanting to dim the page for it.
|
|
87204
|
+
* @param {string} [backdropColor] The wash the popup paints over what is
|
|
87205
|
+
* behind, for this picker alone. See Dialog's own doc.
|
|
87206
|
+
* @param {string} [backdropFilter] What that wash does to the picture
|
|
87207
|
+
* underneath (`"blur(4px)"`), independently of what a click outside does.
|
|
87208
|
+
* See Dialog's own doc.
|
|
87209
|
+
* @param {number|string} [marginWithContainer] Minimum gap kept between the
|
|
87210
|
+
* popup and the edges of what contains it (the viewport, or the picker's own
|
|
87211
|
+
* positioned ancestor for `popupLayer="local"`). Caps the popup's size as
|
|
87212
|
+
* well as its placement, so what an expanded dialog leaves visible around
|
|
87213
|
+
* itself is set here. Defaults to `popoverSpacing` in popover mode, and to
|
|
87214
|
+
* Dialog's own in dialog mode: `3appw` in the top layer, `3cqw` under
|
|
87215
|
+
* `popupLayer="local"`, 0 for a dialog docked by
|
|
87216
|
+
* `dockedOnSmallTouchScreen`.
|
|
87217
|
+
* @param {string} [popupTestId] The `data-testid` of the popup element — the
|
|
87218
|
+
* popover or dialog the children open in. A `data-testid` on the picker
|
|
87219
|
+
* itself names the trigger (see docs/testid.md); this one is for a test that
|
|
87220
|
+
* needs the popup's own box — its surface, its position, its backdrop — and
|
|
87221
|
+
* not only what it holds, which a testid inside the children already names.
|
|
87222
|
+
* Under `mode="callout"` it names the callout element — the control's own,
|
|
87223
|
+
* shared with its constraint messages — while the children are shown in it.
|
|
87224
|
+
* @param {string} [popupAriaLabelledBy] The id(s) of what names the popup:
|
|
87225
|
+
* `aria-labelledby` on the popup element, which is a dialog in either mode
|
|
87226
|
+
* (the trigger says `aria-haspopup="dialog"`). `aria-label`/`aria-labelledby`
|
|
87227
|
+
* on the picker itself name the trigger. Rarely needed: a `<Popup.Title>`
|
|
87228
|
+
* in the children names the popup with nothing to wire, and without one
|
|
87229
|
+
* the popup takes the trigger's own name — the picker's `aria-labelledby`,
|
|
87230
|
+
* the label a picker that picks nothing draws, its `aria-label`, or the
|
|
87231
|
+
* `<Label>`/`<label for>` pointing at it (one that has an id, and does not
|
|
87232
|
+
* wrap the picker). A `mode="callout"` popup is read as its message.
|
|
87233
|
+
* @param {string} [popupAriaLabel] The same, as a string, for a popup whose
|
|
87234
|
+
* title is not an element.
|
|
87235
|
+
* @param {string} [popupAriaDescribedBy] The id(s) of what describes the
|
|
87236
|
+
* popup: `aria-describedby` on the popup element.
|
|
87237
|
+
* @param {string} [confirmTestId] `type="confirm"`, default body: the
|
|
87238
|
+
* `data-testid` of the yes button; `cancelTestId` that of the no button.
|
|
87239
|
+
* Both are elements navi builds, which a test may not name by class (see
|
|
87240
|
+
* docs/testid.md).
|
|
87241
|
+
* @param {string} [cancelTestId]
|
|
87242
|
+
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [mount] The popup's own
|
|
87243
|
+
* (see Popup): when the children are built and thrown away. Left out, a
|
|
87244
|
+
* picker told no value holds what the control they hold holds, and builds
|
|
87245
|
+
* them as soon as that value is read: right away (`"always"`) when navi draws
|
|
87246
|
+
* it (no `ui`, a `Picker.UI.*`, a component `ui`), when there is a clear
|
|
87247
|
+
* cross, or when a form or group around the picker collects it; when an
|
|
87248
|
+
* element `ui` calls `usePickerState()`. Read by nothing, it waits for the
|
|
87249
|
+
* first open, like a picker told a value. A `picksNothing` picker (and so
|
|
87250
|
+
* `type="confirm"`) never builds them early: its popup holds no value, there
|
|
87251
|
+
* is nothing to read.
|
|
87252
|
+
*/
|
|
85754
87253
|
// Assembled in pure expressions rather than written onto Picker afterwards
|
|
85755
87254
|
// (`Picker.Chip = PickerChip`): what createComponentResolver returns is an object
|
|
85756
87255
|
// a bundler cannot see through, so a property assigned to it later is a side
|
|
@@ -86007,7 +87506,7 @@ const css$q = /* css */`@layer navi {
|
|
|
86007
87506
|
const TRAVELS_NONE = [];
|
|
86008
87507
|
|
|
86009
87508
|
/**
|
|
86010
|
-
* @type {import("
|
|
87509
|
+
* @type {import("preact").FunctionComponent<{
|
|
86011
87510
|
* value?: any,
|
|
86012
87511
|
* defaultValue?: any,
|
|
86013
87512
|
* signal?: import("@preact/signals").Signal<any>,
|
|
@@ -86023,7 +87522,7 @@ const TRAVELS_NONE = [];
|
|
|
86023
87522
|
* fallbackValue?: any,
|
|
86024
87523
|
* valueAtStep: (value: any, count: number) => any,
|
|
86025
87524
|
* compareValues?: (a: any, b: any) => number,
|
|
86026
|
-
* renderValue?: (value: any, context: { maxLines?: number }) => import("
|
|
87525
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("preact").ComponentChildren,
|
|
86027
87526
|
* controlProps?: object,
|
|
86028
87527
|
* duration?: number,
|
|
86029
87528
|
* vertical?: boolean,
|
|
@@ -86810,14 +88309,14 @@ const renderValueDefault = value => String(value ?? "");
|
|
|
86810
88309
|
* single `value`/`signal` like any other control. `TimeSpin` is that, for a
|
|
86811
88310
|
* time of day.
|
|
86812
88311
|
*
|
|
86813
|
-
* @type {import("
|
|
88312
|
+
* @type {import("preact").FunctionComponent<{
|
|
86814
88313
|
* name?: string,
|
|
86815
88314
|
* value?: any,
|
|
86816
88315
|
* defaultValue?: any,
|
|
86817
88316
|
* signal?: import("@preact/signals").Signal<any>,
|
|
86818
88317
|
* aggregateChildStates?: (childUIStateControllers: any[]) => any,
|
|
86819
88318
|
* distributeChildUIState?: (groupState: any, childUIStateController: any) => any,
|
|
86820
|
-
* children?: import("
|
|
88319
|
+
* children?: import("preact").ComponentChildren,
|
|
86821
88320
|
* [key: string]: any,
|
|
86822
88321
|
* }>}
|
|
86823
88322
|
* Everything a box takes is taken here too — `width`, `borderWidth`,
|
|
@@ -86920,7 +88419,7 @@ SpinGroup.Separator = SpinGroupSeparator;
|
|
|
86920
88419
|
* above and below it by default — sideways they would be where the caret
|
|
86921
88420
|
* moves — with the bigger number up top.
|
|
86922
88421
|
*
|
|
86923
|
-
* @type {import("
|
|
88422
|
+
* @type {import("preact").FunctionComponent<{
|
|
86924
88423
|
* value?: number|string,
|
|
86925
88424
|
* defaultValue?: number|string,
|
|
86926
88425
|
* min?: number,
|
|
@@ -87025,7 +88524,7 @@ const numberAtStep = (value, count, {
|
|
|
87025
88524
|
* A day one steps through: the date plus what it is to today when there is a
|
|
87026
88525
|
* word for it, and a calendar behind it for the day that is far from here.
|
|
87027
88526
|
*
|
|
87028
|
-
* @type {import("
|
|
88527
|
+
* @type {import("preact").FunctionComponent<{
|
|
87029
88528
|
* value?: string,
|
|
87030
88529
|
* defaultValue?: string,
|
|
87031
88530
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -87036,7 +88535,7 @@ const numberAtStep = (value, count, {
|
|
|
87036
88535
|
* maxMessage?: any,
|
|
87037
88536
|
* step?: number,
|
|
87038
88537
|
* lang?: string,
|
|
87039
|
-
* renderDay?: (day: string) => import("
|
|
88538
|
+
* renderDay?: (day: string) => import("preact").ComponentChildren,
|
|
87040
88539
|
* [key: string]: any,
|
|
87041
88540
|
* }>}
|
|
87042
88541
|
* @param {string} [value] The day shown, as "YYYY-MM-DD".
|
|
@@ -87047,7 +88546,7 @@ const numberAtStep = (value, count, {
|
|
|
87047
88546
|
* @param {"long"|"short"|"numeric"} [format="long"] How the date is written.
|
|
87048
88547
|
* Long by default, since this is a control one reads rather than a column
|
|
87049
88548
|
* one scans; `short` where the room is not there.
|
|
87050
|
-
* @param {(day: string) => import("
|
|
88549
|
+
* @param {(day: string) => import("preact").ComponentChildren} [renderDay] What
|
|
87051
88550
|
* to write for a day. Defaults to the date plus what it is to today when
|
|
87052
88551
|
* there is a word for it ("samedi 8 août (demain)"), since a day near now is
|
|
87053
88552
|
* read as a distance from now before it is read as a date.
|
|
@@ -87164,7 +88663,7 @@ const HOUR_MAX = 23;
|
|
|
87164
88663
|
const MINUTE_MAX = 59;
|
|
87165
88664
|
|
|
87166
88665
|
/**
|
|
87167
|
-
* @type {import("
|
|
88666
|
+
* @type {import("preact").FunctionComponent<{
|
|
87168
88667
|
* name?: string,
|
|
87169
88668
|
* value?: string,
|
|
87170
88669
|
* defaultValue?: string,
|
|
@@ -87172,7 +88671,7 @@ const MINUTE_MAX = 59;
|
|
|
87172
88671
|
* minuteStep?: number,
|
|
87173
88672
|
* pad?: number,
|
|
87174
88673
|
* loop?: boolean,
|
|
87175
|
-
* separator?: import("
|
|
88674
|
+
* separator?: import("preact").ComponentChildren,
|
|
87176
88675
|
* hourLabel?: string,
|
|
87177
88676
|
* minuteLabel?: string,
|
|
87178
88677
|
* [key: string]: any,
|
|
@@ -87185,7 +88684,7 @@ const MINUTE_MAX = 59;
|
|
|
87185
88684
|
* of a day to stop at. Say `loop={false}` for two ends one cannot step past.
|
|
87186
88685
|
* @param {number} [pad=2] How many digits an hour and a minute are written on:
|
|
87187
88686
|
* a clock says "07:00", never "7:0". Say `pad={0}` for the bare numbers.
|
|
87188
|
-
* @param {import("
|
|
88687
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
87189
88688
|
* between the hours and the minutes. "h" in French, ":" elsewhere.
|
|
87190
88689
|
* Everything a box takes is taken here too — `width`, `borderRadius`, `size`.
|
|
87191
88690
|
*/
|
|
@@ -87251,7 +88750,7 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87251
88750
|
};
|
|
87252
88751
|
|
|
87253
88752
|
/**
|
|
87254
|
-
* @type {import("
|
|
88753
|
+
* @type {import("preact").FunctionComponent<{
|
|
87255
88754
|
* name?: string,
|
|
87256
88755
|
* value?: { start?: string, end?: string },
|
|
87257
88756
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -87261,13 +88760,13 @@ const distributeTime$1 = (groupState, childUIStateController) => {
|
|
|
87261
88760
|
* timeProps?: object,
|
|
87262
88761
|
* loop?: boolean,
|
|
87263
88762
|
* size?: string,
|
|
87264
|
-
* startLabel?: import("
|
|
87265
|
-
* endLabel?: import("
|
|
88763
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
88764
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
87266
88765
|
* [key: string]: any,
|
|
87267
88766
|
* }>}
|
|
87268
88767
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
87269
88768
|
* "HH:MM".
|
|
87270
|
-
* @param {import("
|
|
88769
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
87271
88770
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
87272
88771
|
* `null` for neither.
|
|
87273
88772
|
* @param {number} [minuteStep=1] How many minutes a press on a minute chevron
|
|
@@ -87364,7 +88863,7 @@ const css$p = /* css */`.navi_checkbox_group {
|
|
|
87364
88863
|
*
|
|
87365
88864
|
* See `<RadioGroup>` for the same shape holding a single value.
|
|
87366
88865
|
*
|
|
87367
|
-
* @type {import("
|
|
88866
|
+
* @type {import("preact").FunctionComponent<{
|
|
87368
88867
|
* maxLength?: number,
|
|
87369
88868
|
* maxLengthGuard?: number,
|
|
87370
88869
|
* [key: string]: any,
|
|
@@ -87425,6 +88924,19 @@ const CheckboxGroupInterface = props => {
|
|
|
87425
88924
|
});
|
|
87426
88925
|
};
|
|
87427
88926
|
|
|
88927
|
+
/**
|
|
88928
|
+
* @type {import("preact").FunctionComponent<{
|
|
88929
|
+
* unit: string,
|
|
88930
|
+
* plural?: boolean,
|
|
88931
|
+
* format?: "long" | "short" | "narrow",
|
|
88932
|
+
* lang?: string,
|
|
88933
|
+
* label?: string,
|
|
88934
|
+
* size?: string,
|
|
88935
|
+
* sizeRatio?: number,
|
|
88936
|
+
* style?: import("preact").JSX.CSSProperties,
|
|
88937
|
+
* [key: string]: any,
|
|
88938
|
+
* }>}
|
|
88939
|
+
*/
|
|
87428
88940
|
const Unit = ({
|
|
87429
88941
|
unit,
|
|
87430
88942
|
plural,
|
|
@@ -88182,7 +89694,7 @@ const css$m = /* css */`.navi_input.navi_select {
|
|
|
88182
89694
|
`;
|
|
88183
89695
|
|
|
88184
89696
|
/**
|
|
88185
|
-
* @type {import("
|
|
89697
|
+
* @type {import("preact").FunctionComponent<{
|
|
88186
89698
|
* value?: string,
|
|
88187
89699
|
* defaultValue?: string,
|
|
88188
89700
|
* signal?: import("@preact/signals").Signal<string>,
|
|
@@ -88261,30 +89773,40 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88261
89773
|
|
|
88262
89774
|
& > .navi_group {
|
|
88263
89775
|
flex: auto;
|
|
89776
|
+
|
|
89777
|
+
& > .navi_button {
|
|
89778
|
+
flex: auto;
|
|
89779
|
+
}
|
|
88264
89780
|
}
|
|
88265
89781
|
}
|
|
88266
89782
|
|
|
88267
89783
|
.navi_split_button_menu {
|
|
89784
|
+
--border-width: 0px;
|
|
88268
89785
|
display: flex;
|
|
88269
89786
|
position: relative;
|
|
89787
|
+
|
|
89788
|
+
& > .navi_button {
|
|
89789
|
+
margin-left: calc(var(--border-width) * -1);
|
|
89790
|
+
}
|
|
88270
89791
|
}
|
|
88271
89792
|
`;
|
|
88272
89793
|
|
|
88273
89794
|
/**
|
|
88274
|
-
* @type {import("
|
|
88275
|
-
* options: Array<{value: any, label: import("
|
|
89795
|
+
* @type {import("preact").FunctionComponent<{
|
|
89796
|
+
* options: Array<{value: any, label: import("preact").ComponentChildren, [key: string]: any}>,
|
|
88276
89797
|
* value?: any,
|
|
88277
89798
|
* defaultValue?: any,
|
|
88278
|
-
* label?: import("
|
|
88279
|
-
* action?: (value: any, event: Event) => void | Promise<void>,
|
|
89799
|
+
* label?: import("preact").ComponentChildren,
|
|
89800
|
+
* action?: (value: any, { event, signal }: { event: Event, signal: AbortSignal }) => void | Promise<void>,
|
|
88280
89801
|
* command?: string,
|
|
88281
89802
|
* commandFor?: string,
|
|
88282
89803
|
* prefetch?: boolean,
|
|
88283
|
-
* onValueChange?: (value: any, event: Event) => void,
|
|
89804
|
+
* onValueChange?: (value: any, { event }: { event: Event }) => void,
|
|
88284
89805
|
* chooseEffect?: "select" | "run",
|
|
88285
89806
|
* menuLabel?: string,
|
|
88286
|
-
* menuIcon?: import("
|
|
89807
|
+
* menuIcon?: import("preact").ComponentChildren,
|
|
88287
89808
|
* menuIconSize?: number | string,
|
|
89809
|
+
* menuTestId?: string,
|
|
88288
89810
|
* loading?: boolean,
|
|
88289
89811
|
* readOnly?: boolean,
|
|
88290
89812
|
* disabled?: boolean,
|
|
@@ -88320,19 +89842,21 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88320
89842
|
* popupTestId?: string,
|
|
88321
89843
|
* [key: string]: any,
|
|
88322
89844
|
* }>}
|
|
88323
|
-
* @param {Array<{value: any, label: import("
|
|
89845
|
+
* @param {Array<{value: any, label: import("preact").ComponentChildren}>} options
|
|
88324
89846
|
* The ways of doing the thing, in the order the menu lists them. Anything
|
|
88325
89847
|
* else on an entry (`disabled`, `icon`, `padding`…) reaches its row.
|
|
88326
89848
|
* @param {any} [value] Which entry the button stands for — the one it runs, and
|
|
88327
89849
|
* the one it is named after. `defaultValue` is the uncontrolled form; without
|
|
88328
89850
|
* either, the first entry.
|
|
88329
|
-
* @param {import("
|
|
89851
|
+
* @param {import("preact").ComponentChildren} [label] What the button says, when
|
|
88330
89852
|
* that is not the name of the entry it stands for — a button that keeps one
|
|
88331
89853
|
* 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
|
-
*
|
|
89854
|
+
* @param {(value: any, { event, signal }) => void} [action] Do the thing, for
|
|
89855
|
+
* the entry given. Pressed on the button it is the entry the button stands
|
|
89856
|
+
* for; with `chooseEffect="run"` it is also the entry just chosen. Run as a
|
|
89857
|
+
* navi action: the second argument is the run's (see docs/actions.md), the
|
|
89858
|
+
* press being its `event`. Awaited: the whole split button is busy until it
|
|
89859
|
+
* settles.
|
|
88336
89860
|
* @param {string} [command] What a press on the button does once its `action`
|
|
88337
89861
|
* is done, as on a `<Button>` (with `commandFor`, and `command-value` when
|
|
88338
89862
|
* what the command is about is not the entry the button stands for). A
|
|
@@ -88346,12 +89870,14 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88346
89870
|
* menu of one-offs around a primary action.
|
|
88347
89871
|
* @param {string} [menuLabel] What the chevron half is called, for a screen
|
|
88348
89872
|
* reader and for the tooltip a long press raises.
|
|
88349
|
-
* @param {import("
|
|
89873
|
+
* @param {import("preact").ComponentChildren} [menuIcon] What the chevron half
|
|
88350
89874
|
* draws, in place of the chevron. It is the whole of what that half says, so
|
|
88351
89875
|
* pass something that still reads as "there is more here" — and say what,
|
|
88352
89876
|
* through `menuLabel`.
|
|
88353
89877
|
* @param {number|string} [menuIconSize] How big that icon is drawn. Defaults to
|
|
88354
89878
|
* the button's own font size.
|
|
89879
|
+
* @param {string} [menuTestId] The `data-testid` of the chevron half, which
|
|
89880
|
+
* navi builds and the caller has no other way to name.
|
|
88355
89881
|
* @param {"popover"|"dialog"} [mode="popover"] What the menu is drawn as. A
|
|
88356
89882
|
* picker left to itself turns into a dialog on a small screen; a split
|
|
88357
89883
|
* button's menu stays hung off the button there, so this says popover unless
|
|
@@ -88372,8 +89898,12 @@ const css$l = /* css */`.navi_split_button {
|
|
|
88372
89898
|
* `popupBorderRadius`, `animation`. See picker.jsx for what each one says.
|
|
88373
89899
|
* An `aria-*` attribute describes the button and reaches the main half —
|
|
88374
89900
|
* `aria-pressed` for a split button whose main half toggles a mode;
|
|
88375
|
-
* `aria-hidden` excepted, which hides the whole control.
|
|
88376
|
-
*
|
|
89901
|
+
* `aria-hidden` excepted, which hides the whole control. How the words are
|
|
89902
|
+
* written (`size`, `bold`, `lineHeight`, `noWrap`…) reaches both halves, and so
|
|
89903
|
+
* does vertical padding (`paddingY`, `paddingTop`, `paddingBottom`); the rest
|
|
89904
|
+
* of the padding is the main half's — the chevron half keeps its own, which
|
|
89905
|
+
* gives it its width. Anything else (`className`, `width`, margins, `data-*`)
|
|
89906
|
+
* lands on the split button's own box.
|
|
88377
89907
|
*/
|
|
88378
89908
|
const SplitButton = props => {
|
|
88379
89909
|
import.meta.css = [css$l, "@jsenv/navi/src/control/input/split_button.jsx"];
|
|
@@ -88392,6 +89922,7 @@ const SplitButton = props => {
|
|
|
88392
89922
|
menuLabel = buttonI18n("button.more_actions"),
|
|
88393
89923
|
menuIcon = jsx(ChevronDownSvg$1, {}),
|
|
88394
89924
|
menuIconSize,
|
|
89925
|
+
menuTestId,
|
|
88395
89926
|
loading,
|
|
88396
89927
|
readOnly,
|
|
88397
89928
|
disabled,
|
|
@@ -88411,11 +89942,13 @@ const SplitButton = props => {
|
|
|
88411
89942
|
...rest
|
|
88412
89943
|
} = props;
|
|
88413
89944
|
// 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
|
-
|
|
89945
|
+
// the button a screen reader meets, which is the main half; how the words are
|
|
89946
|
+
// written and how far they sit from the edge belong to the halves, since a
|
|
89947
|
+
// <button> inherits neither from the box around it; everything else is the
|
|
89948
|
+
// split button's own box (margins, width, data-*). Sorted by name rather
|
|
89949
|
+
// than named one by one so a Picker popup prop is forwarded by adding it to
|
|
89950
|
+
// that list, not by threading it through here.
|
|
89951
|
+
const [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps] = sortRestProps(rest);
|
|
88419
89952
|
// A split button is a control on the page, not a place one goes: its menu
|
|
88420
89953
|
// hangs off it even on a phone, where a picker left to itself would decide a
|
|
88421
89954
|
// small screen means a dialog. Passing mode="dialog" asks for that back.
|
|
@@ -88460,6 +89993,7 @@ const SplitButton = props => {
|
|
|
88460
89993
|
borderColor,
|
|
88461
89994
|
borderWidth,
|
|
88462
89995
|
borderRadius,
|
|
89996
|
+
...halfStyleProps,
|
|
88463
89997
|
pressEffect,
|
|
88464
89998
|
loading: loadingResolved,
|
|
88465
89999
|
readOnly,
|
|
@@ -88469,7 +90003,7 @@ const SplitButton = props => {
|
|
|
88469
90003
|
};
|
|
88470
90004
|
return jsxs(Box, {
|
|
88471
90005
|
ref: rootRef,
|
|
88472
|
-
|
|
90006
|
+
baseClassName: "navi_split_button",
|
|
88473
90007
|
borderRadius: borderRadius,
|
|
88474
90008
|
...boxProps,
|
|
88475
90009
|
children: [jsx(LoadingOutline, {
|
|
@@ -88486,6 +90020,7 @@ const SplitButton = props => {
|
|
|
88486
90020
|
"command-value": commandValue,
|
|
88487
90021
|
prefetch: prefetch,
|
|
88488
90022
|
...buttonAriaProps,
|
|
90023
|
+
...mainPaddingProps,
|
|
88489
90024
|
...halfProps,
|
|
88490
90025
|
children: label === undefined ? optionShown?.label : label
|
|
88491
90026
|
}), jsxs("div", {
|
|
@@ -88495,6 +90030,7 @@ const SplitButton = props => {
|
|
|
88495
90030
|
paddingX: "s",
|
|
88496
90031
|
expandY: true,
|
|
88497
90032
|
"aria-label": menuLabel,
|
|
90033
|
+
"data-testid": menuTestId,
|
|
88498
90034
|
command: "--navi-open",
|
|
88499
90035
|
commandFor: menuId,
|
|
88500
90036
|
...halfProps,
|
|
@@ -88574,9 +90110,16 @@ const SplitButton = props => {
|
|
|
88574
90110
|
// a caller reaches all of them through the split button (see picker.jsx's JSDoc
|
|
88575
90111
|
// for what each one says).
|
|
88576
90112
|
const POPUP_PROP_SET = new Set(["mode", "popupLayer", "popupTestId", "positionArea", "popoverMode", "popoverSpacing", "popupWidthFitContent", "popoverMaxHeight", "dialogMinWidth", "dialogMinHeight", "dialogMaxWidth", "dialogMaxHeight", "dialogExpand", "dialogExpandX", "dialogExpandY", "dockedOnSmallTouchScreen", "marginWithContainer", "backdrop", "backdropVariant", "backdropColor", "backdropFilter", "pressOutside", "escapeEffect", "closeOnFocusOut", "scrollCapture", "focusCapture", "popupBackgroundColor", "popupBorderRadius", "animation"]);
|
|
90113
|
+
// Vertical padding is said about both halves so they stay the same height; the
|
|
90114
|
+
// horizontal one is the main half's alone, the chevron half's own (paddingX="s")
|
|
90115
|
+
// being what gives it its width.
|
|
90116
|
+
const HALF_PADDING_PROP_SET = new Set(["paddingY", "paddingTop", "paddingBottom"]);
|
|
90117
|
+
const MAIN_PADDING_PROP_SET = new Set(["padding", "paddingX", "paddingLeft", "paddingRight"]);
|
|
88577
90118
|
const sortRestProps = props => {
|
|
88578
90119
|
const popupProps = {};
|
|
88579
90120
|
const buttonAriaProps = {};
|
|
90121
|
+
const halfStyleProps = {};
|
|
90122
|
+
const mainPaddingProps = {};
|
|
88580
90123
|
const boxProps = {};
|
|
88581
90124
|
for (const key of Object.keys(props)) {
|
|
88582
90125
|
if (POPUP_PROP_SET.has(key)) {
|
|
@@ -88585,11 +90128,15 @@ const sortRestProps = props => {
|
|
|
88585
90128
|
// aria-hidden hides a subtree rather than describing a button: on the
|
|
88586
90129
|
// main half alone it would leave the chevron half exposed.
|
|
88587
90130
|
buttonAriaProps[key] = props[key];
|
|
90131
|
+
} else if (isTypoProp(key) || HALF_PADDING_PROP_SET.has(key)) {
|
|
90132
|
+
halfStyleProps[key] = props[key];
|
|
90133
|
+
} else if (MAIN_PADDING_PROP_SET.has(key)) {
|
|
90134
|
+
mainPaddingProps[key] = props[key];
|
|
88588
90135
|
} else {
|
|
88589
90136
|
boxProps[key] = props[key];
|
|
88590
90137
|
}
|
|
88591
90138
|
}
|
|
88592
|
-
return [popupProps, buttonAriaProps, boxProps];
|
|
90139
|
+
return [popupProps, buttonAriaProps, halfStyleProps, mainPaddingProps, boxProps];
|
|
88593
90140
|
};
|
|
88594
90141
|
|
|
88595
90142
|
/**
|
|
@@ -89791,7 +91338,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89791
91338
|
* value, the value lives in one hidden input for the form. The visible rows are
|
|
89792
91339
|
* presentational.
|
|
89793
91340
|
*
|
|
89794
|
-
* @type {import("
|
|
91341
|
+
* @type {import("preact").FunctionComponent<{
|
|
89795
91342
|
* value?: any,
|
|
89796
91343
|
* defaultValue?: any,
|
|
89797
91344
|
* action?: (value: any) => void,
|
|
@@ -89809,7 +91356,7 @@ const WheelGroupContext = createContext(null);
|
|
|
89809
91356
|
* readOnly?: boolean,
|
|
89810
91357
|
* disabled?: boolean,
|
|
89811
91358
|
* loading?: boolean,
|
|
89812
|
-
* children?: import("
|
|
91359
|
+
* children?: import("preact").ComponentChildren,
|
|
89813
91360
|
* [key: string]: any,
|
|
89814
91361
|
* }>}
|
|
89815
91362
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
@@ -90437,8 +91984,9 @@ function WheelUI(props) {
|
|
|
90437
91984
|
}, {
|
|
90438
91985
|
controlType: "input",
|
|
90439
91986
|
// The host is the hidden input carrying the value: the container is
|
|
90440
|
-
// what is focused and spun.
|
|
90441
|
-
listenersOn: "root"
|
|
91987
|
+
// what is focused and spun, and the spinbutton its name has to name.
|
|
91988
|
+
listenersOn: "root",
|
|
91989
|
+
nameOn: "root"
|
|
90442
91990
|
});
|
|
90443
91991
|
const uiStateController = getUIStateControllerById(controlHostProps.id);
|
|
90444
91992
|
const {
|
|
@@ -91288,9 +92836,9 @@ const WheelWindow = ({
|
|
|
91288
92836
|
* of rows and fills each from this tracked data — so the value list is the source
|
|
91289
92837
|
* of truth, not a per-item DOM node. Selection lives on the wheel's value.
|
|
91290
92838
|
*
|
|
91291
|
-
* @type {import("
|
|
92839
|
+
* @type {import("preact").FunctionComponent<{
|
|
91292
92840
|
* value: any,
|
|
91293
|
-
* children?: import("
|
|
92841
|
+
* children?: import("preact").ComponentChildren,
|
|
91294
92842
|
* [key: string]: any,
|
|
91295
92843
|
* }>}
|
|
91296
92844
|
*/
|
|
@@ -91325,9 +92873,9 @@ Wheel.Item = WheelItem;
|
|
|
91325
92873
|
* each Wheel.Item (it's a Box: paddingX="0.5ch") so the gap stays scrollable.
|
|
91326
92874
|
* `spacing` is just the Box flex gap between children (a layout gap, not padding).
|
|
91327
92875
|
*
|
|
91328
|
-
* @type {import("
|
|
92876
|
+
* @type {import("preact").FunctionComponent<{
|
|
91329
92877
|
* horizontal?: boolean,
|
|
91330
|
-
* children?: import("
|
|
92878
|
+
* children?: import("preact").ComponentChildren,
|
|
91331
92879
|
* [key: string]: any,
|
|
91332
92880
|
* }>}
|
|
91333
92881
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
@@ -91524,14 +93072,14 @@ const LAST_MINUTE_OF_DAY = 23 * 60 + 59;
|
|
|
91524
93072
|
const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
91525
93073
|
|
|
91526
93074
|
/**
|
|
91527
|
-
* @type {import("
|
|
93075
|
+
* @type {import("preact").FunctionComponent<{
|
|
91528
93076
|
* name?: string,
|
|
91529
93077
|
* value?: string,
|
|
91530
93078
|
* defaultValue?: string,
|
|
91531
93079
|
* signal?: import("@preact/signals").Signal<string>,
|
|
91532
93080
|
* minuteStep?: number,
|
|
91533
93081
|
* loop?: boolean,
|
|
91534
|
-
* separator?: import("
|
|
93082
|
+
* separator?: import("preact").ComponentChildren,
|
|
91535
93083
|
* hourLabel?: string,
|
|
91536
93084
|
* minuteLabel?: string,
|
|
91537
93085
|
* size?: string,
|
|
@@ -91552,7 +93100,7 @@ const END_OF_DAY = END_OF_DAY_HOUR * MINUTES_PER_HOUR;
|
|
|
91552
93100
|
* @param {boolean} [loop=true] The wheels go round: 23h then 0h, 59 minutes
|
|
91553
93101
|
* then 0. What a clock does. Say `loop={false}` for two ends one cannot turn
|
|
91554
93102
|
* past.
|
|
91555
|
-
* @param {import("
|
|
93103
|
+
* @param {import("preact").ComponentChildren} [separator] What is written
|
|
91556
93104
|
* between the hours and the minutes. "h" in French, ":" elsewhere. In a time
|
|
91557
93105
|
* of whole hours it follows each hour on its row instead ("8h"), and nothing
|
|
91558
93106
|
* is written by default outside French.
|
|
@@ -91671,7 +93219,7 @@ const TimeWheel = ({
|
|
|
91671
93219
|
};
|
|
91672
93220
|
|
|
91673
93221
|
/**
|
|
91674
|
-
* @type {import("
|
|
93222
|
+
* @type {import("preact").FunctionComponent<{
|
|
91675
93223
|
* name?: string,
|
|
91676
93224
|
* value?: { start?: string, end?: string },
|
|
91677
93225
|
* defaultValue?: { start?: string, end?: string },
|
|
@@ -91680,15 +93228,15 @@ const TimeWheel = ({
|
|
|
91680
93228
|
* minDuration?: number,
|
|
91681
93229
|
* loop?: boolean,
|
|
91682
93230
|
* size?: string,
|
|
91683
|
-
* startLabel?: import("
|
|
91684
|
-
* endLabel?: import("
|
|
93231
|
+
* startLabel?: import("preact").ComponentChildren,
|
|
93232
|
+
* endLabel?: import("preact").ComponentChildren,
|
|
91685
93233
|
* labelPosition?: "before" | "above",
|
|
91686
93234
|
* timeProps?: object,
|
|
91687
93235
|
* [key: string]: any,
|
|
91688
93236
|
* }>}
|
|
91689
93237
|
* @param {{ start?: string, end?: string }} [value] The span shown, as two
|
|
91690
93238
|
* "HH:MM".
|
|
91691
|
-
* @param {import("
|
|
93239
|
+
* @param {import("preact").ComponentChildren} [startLabel] What is written
|
|
91692
93240
|
* before the first time ("De"), and `endLabel` between the two ("à"). Say
|
|
91693
93241
|
* `null` for neither.
|
|
91694
93242
|
* @param {"before"|"above"} [labelPosition="before"] Where each label stands.
|
|
@@ -95673,17 +97221,25 @@ const Address = ({
|
|
|
95673
97221
|
};
|
|
95674
97222
|
|
|
95675
97223
|
installImportMetaCssBuild(import.meta);
|
|
95676
|
-
const css$a = /* css */`@layer navi
|
|
97224
|
+
const css$a = /* css */`@layer navi {
|
|
97225
|
+
.navi_text.navi_badge_count {
|
|
97226
|
+
--font-size: .7em;
|
|
97227
|
+
--badge-count-padding-x-default: .5em;
|
|
97228
|
+
--badge-count-padding-y-default: .3em;
|
|
97229
|
+
|
|
97230
|
+
&[data-ellipse][data-single-char], &[data-circle] {
|
|
97231
|
+
--badge-count-padding-x-default: 0;
|
|
97232
|
+
--badge-count-padding-y-default: 0;
|
|
97233
|
+
}
|
|
97234
|
+
}
|
|
97235
|
+
}
|
|
95677
97236
|
|
|
95678
97237
|
.navi_text.navi_badge_count {
|
|
95679
97238
|
white-space: nowrap;
|
|
95680
|
-
--font-size: .7em;
|
|
95681
97239
|
--x-background: var(--background);
|
|
95682
|
-
--x-background-color: var(--background-color
|
|
97240
|
+
--x-background-color: var(--background-color);
|
|
95683
97241
|
--x-color-contrasting: var(--navi-color-white);
|
|
95684
97242
|
--x-color: var(--color, var(--x-color-contrasting));
|
|
95685
|
-
--badge-count-padding-x-default: .5em;
|
|
95686
|
-
--badge-count-padding-y-default: .3em;
|
|
95687
97243
|
--x-badge-count-padding-top: var(--badge-count-padding-top, var(--badge-count-padding-y, var(--badge-count-padding, var(--badge-count-padding-y-default))));
|
|
95688
97244
|
--x-badge-count-padding-right: var(--badge-count-padding-right, var(--badge-count-padding-x, var(--badge-count-padding, var(--badge-count-padding-x-default))));
|
|
95689
97245
|
--x-badge-count-padding-bottom: var(--badge-count-padding-bottom, var(--badge-count-padding-y, var(--badge-count-padding, var(--badge-count-padding-y-default))));
|
|
@@ -95715,12 +97271,9 @@ const css$a = /* css */`@layer navi;
|
|
|
95715
97271
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95716
97272
|
padding-left: var(--x-badge-count-padding-left);
|
|
95717
97273
|
background: var(--x-background);
|
|
95718
|
-
background-color: var(--x-background-color);
|
|
95719
97274
|
border-radius: 1em;
|
|
95720
97275
|
|
|
95721
97276
|
&[data-single-char] {
|
|
95722
|
-
--badge-count-padding-x-default: 0;
|
|
95723
|
-
--badge-count-padding-y-default: 0;
|
|
95724
97277
|
box-sizing: content-box;
|
|
95725
97278
|
text-align: center;
|
|
95726
97279
|
border-radius: 100em;
|
|
@@ -95733,8 +97286,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95733
97286
|
|
|
95734
97287
|
&[data-circle] {
|
|
95735
97288
|
--x-number-font-size: var(--font-size);
|
|
95736
|
-
--badge-count-padding-x-default: 0;
|
|
95737
|
-
--badge-count-padding-y-default: 0;
|
|
95738
97289
|
box-sizing: content-box;
|
|
95739
97290
|
aspect-ratio: 1;
|
|
95740
97291
|
width: var(--x-radius);
|
|
@@ -95744,7 +97295,6 @@ const css$a = /* css */`@layer navi;
|
|
|
95744
97295
|
padding-bottom: var(--x-badge-count-padding-bottom);
|
|
95745
97296
|
padding-left: var(--x-badge-count-padding-left);
|
|
95746
97297
|
background: var(--x-background);
|
|
95747
|
-
background-color: var(--x-background-color);
|
|
95748
97298
|
border-radius: 100em;
|
|
95749
97299
|
justify-content: center;
|
|
95750
97300
|
align-items: center;
|
|
@@ -95769,6 +97319,10 @@ const css$a = /* css */`@layer navi;
|
|
|
95769
97319
|
font-size: var(--x-number-font-size);
|
|
95770
97320
|
}
|
|
95771
97321
|
}
|
|
97322
|
+
|
|
97323
|
+
&[data-background-color] {
|
|
97324
|
+
background-color: var(--x-background-color);
|
|
97325
|
+
}
|
|
95772
97326
|
}
|
|
95773
97327
|
`;
|
|
95774
97328
|
const BadgeCountOverflow = () => jsx("span", {
|
|
@@ -95905,6 +97459,7 @@ const BadgeCountEllipse = ({
|
|
|
95905
97459
|
"data-ellipse": "",
|
|
95906
97460
|
"data-value-overflow": hasOverflow ? "" : undefined,
|
|
95907
97461
|
"data-loading": loading ? "" : undefined,
|
|
97462
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95908
97463
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95909
97464
|
...props,
|
|
95910
97465
|
styleCSSVars: BadgeCountStyleCSSVars,
|
|
@@ -95927,6 +97482,7 @@ const BadgeCountCircle = ({
|
|
|
95927
97482
|
"data-circle": "",
|
|
95928
97483
|
bold: true,
|
|
95929
97484
|
"data-loading": loading ? "" : undefined,
|
|
97485
|
+
"data-background-color": props.backgroundColor === undefined ? undefined : "",
|
|
95930
97486
|
"data-single-char": charCount === 1 ? "" : undefined,
|
|
95931
97487
|
"data-two-chars": charCount === 2 ? "" : undefined,
|
|
95932
97488
|
"data-three-chars": charCount === 3 ? "" : undefined,
|
|
@@ -96419,7 +97975,7 @@ const css$8 = /* css */`@layer navi;
|
|
|
96419
97975
|
`;
|
|
96420
97976
|
|
|
96421
97977
|
/**
|
|
96422
|
-
* @type {import("
|
|
97978
|
+
* @type {import("preact").FunctionComponent<{
|
|
96423
97979
|
* children?: number | string,
|
|
96424
97980
|
* unit?: string | [string, string],
|
|
96425
97981
|
* unitPosition?: "right" | "bottom",
|
|
@@ -97011,8 +98567,8 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
97011
98567
|
*
|
|
97012
98568
|
* @param {Object} props - Component properties
|
|
97013
98569
|
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
97014
|
-
* @param {import("
|
|
97015
|
-
* @returns {import("
|
|
98570
|
+
* @param {import("preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
98571
|
+
* @returns {import("preact").VNode} A composed SVG with all elements properly masked
|
|
97016
98572
|
*/
|
|
97017
98573
|
const SVGMaskOverlay = ({
|
|
97018
98574
|
viewBox,
|
|
@@ -97102,7 +98658,6 @@ const css$3 = /* css */`@layer navi {
|
|
|
97102
98658
|
padding-bottom: var(--layout-padding-bottom, var(--layout-padding-y, var(--layout-padding)));
|
|
97103
98659
|
padding-left: var(--layout-padding-left, var(--layout-padding-x, var(--layout-padding)));
|
|
97104
98660
|
background: var(--layout-background);
|
|
97105
|
-
background-color: var(--layout-background-color, var(--layout-background));
|
|
97106
98661
|
border-width: var(--layout-border-width);
|
|
97107
98662
|
border-style: solid;
|
|
97108
98663
|
border-color: var(--layout-border-color);
|
|
@@ -97119,7 +98674,6 @@ const CardLayoutStyleCSSVars = {
|
|
|
97119
98674
|
borderWidth: "--layout-border-width",
|
|
97120
98675
|
borderColor: "--layout-border-color",
|
|
97121
98676
|
background: "--layout-background",
|
|
97122
|
-
backgroundColor: "--layout-background-color",
|
|
97123
98677
|
padding: "--layout-padding",
|
|
97124
98678
|
paddingTop: "--layout-padding-top",
|
|
97125
98679
|
paddingBottom: "--layout-padding-bottom",
|
|
@@ -97134,7 +98688,7 @@ const CardLayoutStyleCSSVars = {
|
|
|
97134
98688
|
* what holds it, so how tall that area is is up to the page around it, as for
|
|
97135
98689
|
* `ViewportLayout`.
|
|
97136
98690
|
*
|
|
97137
|
-
* @type {import("
|
|
98691
|
+
* @type {import("preact").FunctionComponent<{
|
|
97138
98692
|
* alignX?: string,
|
|
97139
98693
|
* alignY?: string,
|
|
97140
98694
|
* margin?: string|number,
|
|
@@ -97159,6 +98713,7 @@ const CardLayout = ({
|
|
|
97159
98713
|
children,
|
|
97160
98714
|
alignX = "center",
|
|
97161
98715
|
alignY = "center",
|
|
98716
|
+
backgroundColor,
|
|
97162
98717
|
...props
|
|
97163
98718
|
}) => {
|
|
97164
98719
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
@@ -97177,6 +98732,7 @@ const CardLayout = ({
|
|
|
97177
98732
|
className: "navi_card",
|
|
97178
98733
|
alignX: alignX,
|
|
97179
98734
|
alignY: alignY,
|
|
98735
|
+
backgroundColor: backgroundColor,
|
|
97180
98736
|
children: children
|
|
97181
98737
|
})
|
|
97182
98738
|
});
|
|
@@ -97350,7 +98906,7 @@ const LINE_GAP = 5;
|
|
|
97350
98906
|
const StepListContext = createContext(null);
|
|
97351
98907
|
|
|
97352
98908
|
/**
|
|
97353
|
-
* @type {import("
|
|
98909
|
+
* @type {import("preact").FunctionComponent<{
|
|
97354
98910
|
* current?: string,
|
|
97355
98911
|
* slideContainer?: string,
|
|
97356
98912
|
* travelByClick?: boolean,
|
|
@@ -97707,7 +99263,7 @@ const sameShallow = (previousObject, nextObject) => {
|
|
|
97707
99263
|
* It is both StepList.Item and an export of its own, the way Slide is to
|
|
97708
99264
|
* SlideContainer.
|
|
97709
99265
|
*
|
|
97710
|
-
* @type {import("
|
|
99266
|
+
* @type {import("preact").FunctionComponent<{
|
|
97711
99267
|
* value: string,
|
|
97712
99268
|
* done?: boolean,
|
|
97713
99269
|
* [key: string]: any,
|
|
@@ -97769,7 +99325,7 @@ const ViewportLayoutStyleCSSVars = {
|
|
|
97769
99325
|
* same inside a route, a preview pane or a demo box, and it is up to the page
|
|
97770
99326
|
* around it to be full-height.
|
|
97771
99327
|
*
|
|
97772
|
-
* @type {import("
|
|
99328
|
+
* @type {import("preact").FunctionComponent<{
|
|
97773
99329
|
* padding?: string|number,
|
|
97774
99330
|
* paddingTop?: string|number,
|
|
97775
99331
|
* paddingBottom?: string|number,
|
|
@@ -97994,7 +99550,7 @@ const css = /* css */`.navi_side_panel {
|
|
|
97994
99550
|
* `closeByPressOutside`/`pressOutside` only say what a
|
|
97995
99551
|
* press on the wall does, not whether there is one (see `dialog.jsx`'s own
|
|
97996
99552
|
* doc).
|
|
97997
|
-
* @param {import("
|
|
99553
|
+
* @param {import("preact").ComponentChildren} props.children - No built-in
|
|
97998
99554
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
97999
99555
|
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
98000
99556
|
* `closeByPressOutside`/Escape instead.
|
|
@@ -98093,7 +99649,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
98093
99649
|
* @param {object} props
|
|
98094
99650
|
* @param {string} [props.className] - Merged with the shared
|
|
98095
99651
|
* `"navi_side_panel_head"` class this file's own CSS targets.
|
|
98096
|
-
* @param {import("
|
|
99652
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98097
99653
|
*/
|
|
98098
99654
|
const SidePanelHead = props => jsx(Box, {
|
|
98099
99655
|
baseClassName: "navi_side_panel_head",
|
|
@@ -98106,7 +99662,7 @@ const SidePanelHead = props => jsx(Box, {
|
|
|
98106
99662
|
* @param {object} props
|
|
98107
99663
|
* @param {string} [props.className] - Merged with the shared
|
|
98108
99664
|
* `"navi_side_panel_foot"` class this file's own CSS targets.
|
|
98109
|
-
* @param {import("
|
|
99665
|
+
* @param {import("preact").ComponentChildren} props.children
|
|
98110
99666
|
*/
|
|
98111
99667
|
const SidePanelFoot = props => jsx(Box, {
|
|
98112
99668
|
className: "navi_side_panel_foot",
|
|
@@ -98115,6 +99671,58 @@ const SidePanelFoot = props => jsx(Box, {
|
|
|
98115
99671
|
SidePanel.Head = SidePanelHead;
|
|
98116
99672
|
SidePanel.Foot = SidePanelFoot;
|
|
98117
99673
|
|
|
99674
|
+
/**
|
|
99675
|
+
* Creates a linked `[Slot, SlotFill]` pair so content rendered anywhere in
|
|
99676
|
+
* the tree (`SlotFill`) appears at a fixed location elsewhere (`Slot`) — a
|
|
99677
|
+
* lightweight, signal-based alternative to a DOM portal: `SlotFill` itself
|
|
99678
|
+
* renders nothing (no extra DOM node at its own call site), it just writes
|
|
99679
|
+
* its props into a shared signal that `Slot` reads reactively.
|
|
99680
|
+
*
|
|
99681
|
+
* Holds at most one filler at a time — there is no stacking/queueing. The
|
|
99682
|
+
* filler is the last `SlotFill` (from this same `createSlot()` call) to
|
|
99683
|
+
* mount, or the first to render into an empty slot; later renders of a
|
|
99684
|
+
* `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
|
|
99685
|
+
* unmounting empties the slot only while it is still that one, and so does one
|
|
99686
|
+
* in a page a route transition keeps while leaving (see keepLeavingPages in
|
|
99687
|
+
* nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
|
|
99688
|
+
* take it back from the page arriving.
|
|
99689
|
+
*
|
|
99690
|
+
* **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
|
|
99691
|
+
* the fills as they mount and unmount, in the order the tree walks them. A
|
|
99692
|
+
* slot fed from several places — a `SlotFill` in each card of a board, each
|
|
99693
|
+
* card deciding on its own whether it is the open one — reads as empty
|
|
99694
|
+
* between the leaver and the newcomer whenever they render separately, and
|
|
99695
|
+
* anything acting on `isFilled` acts on that empty frame (a `SidePanel` with
|
|
99696
|
+
* `open={isFilled}` closes for good: its `onClose` clears the state the
|
|
99697
|
+
* newcomer was waiting for). The slot cannot tell that gap from a real
|
|
99698
|
+
* emptying, and does not try to: render one `SlotFill` at the level that
|
|
99699
|
+
* holds the choice, with the chosen content as its children, and bind a
|
|
99700
|
+
* state that must not blink (a panel's `open`) to the app's own signal
|
|
99701
|
+
* rather than to `isFilled` — see docs/popup_open.md, "One panel, fed by a
|
|
99702
|
+
* slot".
|
|
99703
|
+
*
|
|
99704
|
+
* `Slot` keeps `SlotRenderer` mounted permanently — even while unfilled it
|
|
99705
|
+
* still renders it, with no props and `isFilled={false}` — instead of
|
|
99706
|
+
* unmounting it. This lets `SlotRenderer` be a persistent wrapper (a
|
|
99707
|
+
* `SidePanel` that opens and closes with an animation instead of being
|
|
99708
|
+
* mounted/unmounted alongside the filler — see 7_slot_demo.html's "Two
|
|
99709
|
+
* side-panel slots" section). A `SlotRenderer` that wants the
|
|
99710
|
+
* render-nothing-when-unfilled behavior can opt in with its own
|
|
99711
|
+
* `if (!isFilled) return null;` (the default `Box` renderer doesn't do
|
|
99712
|
+
* this — an unfilled default slot just renders an empty `<Box/>`).
|
|
99713
|
+
*
|
|
99714
|
+
* @param {import("preact").ComponentType} [SlotRenderer=Box] - Rendered by
|
|
99715
|
+
* `Slot`, always, with whatever props `SlotFill` last set (spread
|
|
99716
|
+
* directly) plus `isFilled`. Swap this out to reuse the same slot
|
|
99717
|
+
* mechanism for something other than a plain `Box` (e.g. a specific
|
|
99718
|
+
* component expecting its own particular props, or a persistent wrapper
|
|
99719
|
+
* like `SidePanel`).
|
|
99720
|
+
* @returns {[import("preact").ComponentType, import("preact").ComponentType<object>]}
|
|
99721
|
+
* `[Slot, SlotFill]` — `Slot` takes no props, render it once wherever the
|
|
99722
|
+
* content should appear. `SlotFill` takes whatever props `SlotRenderer`
|
|
99723
|
+
* expects, render it anywhere else in the tree to supply/update that
|
|
99724
|
+
* content.
|
|
99725
|
+
*/
|
|
98118
99726
|
const createSlot = (SlotRenderer = Box) => {
|
|
98119
99727
|
const slotPropsSignal = signal();
|
|
98120
99728
|
let filler = null;
|