@jsenv/navi 0.29.166 → 0.29.168
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/jsenv_navi.js +222 -115
- package/dist/jsenv_navi.js.map +19 -19
- package/docs/AI_INSTRUCTIONS.md +11 -0
- package/docs/actions.md +6 -4
- package/docs/data_states.md +147 -0
- package/docs/error_handling.md +9 -0
- package/docs/interactions.md +10 -0
- package/docs/list_refresh.md +16 -1
- package/docs/popup_open.md +7 -0
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
7
|
-
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, scrollIntoViewThroughScrollables, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, getScrollContainer, closestOpenableAncestor, isAncestorOpen, isDisplayedDespiteClosedAncestor, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, releaseWheelGesture, getScrollIntoViewScopedOffsets, wheelGestureIsTakenFrom, claimWheelGesture, scrollIntoViewScoped, initFocusGroup, isTouchDrivenEvent, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, isPressDisputedByDrag, canScroll, measureWidestChildRow, performTabNavigation, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { chainEvent, clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, untracked, batch, useComputed, useSignal } from "@preact/signals";
|
|
10
10
|
import { isValidElement, h, Fragment, createContext, render, toChildArray, options, cloneElement } from "preact";
|
|
@@ -7284,7 +7284,9 @@ const openCallout = (message, {
|
|
|
7284
7284
|
// that whole window: by the time anything measures anchorElement, it's
|
|
7285
7285
|
// already at its final position, so there's no transient frame left to
|
|
7286
7286
|
// latch onto.
|
|
7287
|
-
|
|
7287
|
+
// Through the boxes that scroll and none of the ones that only clip: an
|
|
7288
|
+
// `overflow: hidden` card takes a scroll no user can give back.
|
|
7289
|
+
scrollIntoViewThroughScrollables(anchorElement, {
|
|
7288
7290
|
behavior: "instant",
|
|
7289
7291
|
block: "nearest"
|
|
7290
7292
|
});
|
|
@@ -8276,13 +8278,20 @@ const formatCompactNumber = (value, lang) => {
|
|
|
8276
8278
|
* Formats a date as a human-readable day string.
|
|
8277
8279
|
*
|
|
8278
8280
|
* @param {Date} date
|
|
8279
|
-
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"numeric" }} [options]
|
|
8281
|
+
* @param {{ lang?: string, format?: "long"|"short"|"narrow"|"numeric"|{ weekday?: "long"|"short"|"narrow", month?: "long"|"short"|"narrow"|"numeric" } }} [options]
|
|
8282
|
+
* A string spells the weekday and the month the same way. An object spells
|
|
8283
|
+
* them apart, each key defaulting to `"long"`: a narrow card usually wants
|
|
8284
|
+
* the weekday whole (it is the reading anchor) and the month abbreviated (it
|
|
8285
|
+
* is where the characters are — "septembre" is 9 of them, "sept." reads the
|
|
8286
|
+
* same). `"numeric"` stays a string-only spelling: it drops the weekday and
|
|
8287
|
+
* writes the whole date in digits.
|
|
8280
8288
|
*
|
|
8281
8289
|
* @example
|
|
8282
8290
|
* formatDay(new Date(), { lang: "fr" }) // "lundi 11 mai" (long, default)
|
|
8283
8291
|
* formatDay(new Date(), { lang: "fr", format: "short" }) // "lun. 11 mai"
|
|
8284
8292
|
* formatDay(new Date(), { lang: "fr", format: "narrow" }) // "lu. 11 mai"
|
|
8285
8293
|
* formatDay(new Date(), { lang: "fr", format: "numeric" }) // "11/05/2026"
|
|
8294
|
+
* formatDay(new Date(), { lang: "fr", format: { weekday: "long", month: "short" } }) // "mercredi 2 sept."
|
|
8286
8295
|
*/
|
|
8287
8296
|
const formatDay = (
|
|
8288
8297
|
date,
|
|
@@ -8295,10 +8304,12 @@ const formatDay = (
|
|
|
8295
8304
|
year: "numeric",
|
|
8296
8305
|
}).format(date);
|
|
8297
8306
|
}
|
|
8307
|
+
const { weekday = "long", month = "long" } =
|
|
8308
|
+
typeof format === "string" ? { weekday: format, month: format } : format;
|
|
8298
8309
|
return new Intl.DateTimeFormat(lang, {
|
|
8299
|
-
weekday
|
|
8310
|
+
weekday, // "long", "short", or "narrow"
|
|
8300
8311
|
day: "numeric",
|
|
8301
|
-
month
|
|
8312
|
+
month,
|
|
8302
8313
|
}).format(date);
|
|
8303
8314
|
};
|
|
8304
8315
|
|
|
@@ -9909,7 +9920,7 @@ const formatDateIso = (iso, inputType) => {
|
|
|
9909
9920
|
const locale = languagesSignal.value;
|
|
9910
9921
|
if (inputType === "month") {
|
|
9911
9922
|
const date = new Date(`${iso}-01T00:00:00`);
|
|
9912
|
-
return formatMonth(date, locale);
|
|
9923
|
+
return formatMonth(date, { lang: locale });
|
|
9913
9924
|
}
|
|
9914
9925
|
// date, week, datetime-local: extract YYYY-MM-DD part and parse as local date
|
|
9915
9926
|
const isoMatch = /^(\d{4}-\d{2}-\d{2})/.exec(iso);
|
|
@@ -9921,7 +9932,7 @@ const formatDateIso = (iso, inputType) => {
|
|
|
9921
9932
|
if (isNaN(date.getTime())) {
|
|
9922
9933
|
return iso;
|
|
9923
9934
|
}
|
|
9924
|
-
return formatDay(date, locale);
|
|
9935
|
+
return formatDay(date, { lang: locale });
|
|
9925
9936
|
};
|
|
9926
9937
|
|
|
9927
9938
|
const READONLY_CONSTRAINT = {
|
|
@@ -11045,6 +11056,12 @@ installImportMetaCssBuild(import.meta);/**
|
|
|
11045
11056
|
* wants the right click to do what the hold does says so, with `contextmenu`
|
|
11046
11057
|
* beside it.
|
|
11047
11058
|
*
|
|
11059
|
+
* It DOES take the selection, and so does a swipe: nothing under either is text
|
|
11060
|
+
* to select, because the browser answers that same press by selecting the word
|
|
11061
|
+
* under the thumb and leaves it selected once its menu is gone. What never
|
|
11062
|
+
* answered the press keeps its text — a field, a popover, a dialog (see the
|
|
11063
|
+
* stylesheet below).
|
|
11064
|
+
*
|
|
11048
11065
|
* A hold CAN open a popup while the finger is still down — a menu appearing under
|
|
11049
11066
|
* a waiting finger, which is the native gesture. navi's Popover is `popover="manual"`
|
|
11050
11067
|
* and owns its own dismissal, so the `pointerup` that ends the press is not read as
|
|
@@ -11129,6 +11146,14 @@ import.meta.css = [/* css */`@property --swipe-progress {
|
|
|
11129
11146
|
-webkit-touch-callout: none;
|
|
11130
11147
|
}
|
|
11131
11148
|
|
|
11149
|
+
[data-longpress], [data-swipe] {
|
|
11150
|
+
user-select: none;
|
|
11151
|
+
}
|
|
11152
|
+
|
|
11153
|
+
:is([data-longpress], [data-swipe]) :is([data-drag-ignore], [popover], dialog), :is([data-longpress], [data-swipe]) :is(input:not([data-press-only]), textarea), :is([data-longpress], [data-swipe]) :is([contenteditable=""], [contenteditable="true"]) {
|
|
11154
|
+
user-select: text;
|
|
11155
|
+
}
|
|
11156
|
+
|
|
11132
11157
|
[data-swiping="left"], [data-swiping="right"] {
|
|
11133
11158
|
translate: var(--swipe-pulled) 0;
|
|
11134
11159
|
}
|
|
@@ -17635,7 +17660,9 @@ const useActionAsyncData = (action, {
|
|
|
17635
17660
|
onLoad
|
|
17636
17661
|
}) => {
|
|
17637
17662
|
const loadingRef = useContext(LoadingContext);
|
|
17638
|
-
|
|
17663
|
+
// The boundary is where a suspension lands, so only the delegating path needs
|
|
17664
|
+
// one: `loading: true` draws the wait itself and never suspends.
|
|
17665
|
+
if (!loadingRef && loadingEffect !== "use") {
|
|
17639
17666
|
throw new Error(`Missing <Loading>: useAsyncData delegates the wait, so it needs a <Loading> boundary above it — or "loading: true" to draw that wait here.`);
|
|
17640
17667
|
}
|
|
17641
17668
|
useOnLoad(action, onLoad);
|
|
@@ -17719,15 +17746,19 @@ const useActionAsyncData = (action, {
|
|
|
17719
17746
|
}
|
|
17720
17747
|
if (runningState === FAILED) {
|
|
17721
17748
|
if (dismissedActionWeakSet.has(action)) {
|
|
17749
|
+
// Dismissing removes the error and nothing else: what is handed back is
|
|
17750
|
+
// the action's state without it — the data it holds, and no run in
|
|
17751
|
+
// progress. Holding nothing is one of those states, not a wait, so a
|
|
17752
|
+
// component drawing its own states gets it rather than being suspended
|
|
17753
|
+
// away from the retry it is offering.
|
|
17722
17754
|
const staleData = action.dataSignal.peek();
|
|
17723
|
-
if (staleData !== undefined) {
|
|
17724
|
-
// Dismissed with stale data — return it so children render normally
|
|
17755
|
+
if (staleData !== undefined || loadingEffect === "use") {
|
|
17725
17756
|
return [staleData, false, undefined];
|
|
17726
17757
|
}
|
|
17727
|
-
//
|
|
17728
|
-
//
|
|
17729
|
-
// so
|
|
17730
|
-
//
|
|
17758
|
+
// Nothing to hand over and the waits are delegated: there is no state
|
|
17759
|
+
// this component can draw, so it waits. The promise resolves on RUNNING
|
|
17760
|
+
// rather than never, so a run started from anywhere brings it back
|
|
17761
|
+
// through the ordinary loading path instead of leaving it stuck.
|
|
17731
17762
|
let dismissedPromise = dismissedActionPendingPromiseWeakMap.get(action);
|
|
17732
17763
|
if (!dismissedPromise) {
|
|
17733
17764
|
dismissedPromise = new Promise(resolve => {
|
|
@@ -17756,7 +17787,10 @@ const useActionAsyncData = (action, {
|
|
|
17756
17787
|
dismissedActionWeakSet.add(action);
|
|
17757
17788
|
setTick(n => n + 1);
|
|
17758
17789
|
};
|
|
17759
|
-
|
|
17790
|
+
// The previous answer is still there and a failed refresh does not unmake
|
|
17791
|
+
// it, so both are handed over: the component can say the failure over the
|
|
17792
|
+
// content it already has instead of choosing between the two.
|
|
17793
|
+
return [action.dataSignal.peek(), false, actionError, dismissError];
|
|
17760
17794
|
}
|
|
17761
17795
|
// Not marked: nothing is displayed yet — the boundary that catches this is
|
|
17762
17796
|
// what says so, and only if it has something to show.
|
|
@@ -18916,9 +18950,15 @@ const getHowToHandleStyleProp = (name) => {
|
|
|
18916
18950
|
}
|
|
18917
18951
|
return getStyle;
|
|
18918
18952
|
};
|
|
18919
|
-
|
|
18953
|
+
// The css value `name` would take, written in the scale that style is read in:
|
|
18954
|
+
// "s" on a spacing style is a length, "s" on a typo style is a font size, and a
|
|
18955
|
+
// bare number gets the unit the style expects.
|
|
18956
|
+
const stringifyStyleValue = (value, name, styleContext) => {
|
|
18920
18957
|
const stringifier = getStringifier(name);
|
|
18921
|
-
|
|
18958
|
+
return stringifier(value, name, styleContext);
|
|
18959
|
+
};
|
|
18960
|
+
const prepareStyleValue = (existingValue, value, name, styleContext) => {
|
|
18961
|
+
const cssValue = stringifyStyleValue(value, name, styleContext);
|
|
18922
18962
|
const mergedValue = mergeOneStyle(existingValue, cssValue, name, "css");
|
|
18923
18963
|
return mergedValue;
|
|
18924
18964
|
};
|
|
@@ -20954,6 +20994,15 @@ import.meta.css = [/* css */`[data-scrollable] > .navi_loading_outline_wrapper,
|
|
|
20954
20994
|
const PSEUDO_CLASSES_DEFAULT = [];
|
|
20955
20995
|
const PSEUDO_ELEMENTS_DEFAULT = [];
|
|
20956
20996
|
const STYLE_CSS_VARS_DEFAULT = {};
|
|
20997
|
+
// An entry of styleCSSVars is the css variable the prop writes into, alone when
|
|
20998
|
+
// the prop is named after a css style — Box then already knows how to read its
|
|
20999
|
+
// value — or paired with the style whose values it borrows when it is not.
|
|
21000
|
+
const readCSSVarEntry = entry => {
|
|
21001
|
+
if (Array.isArray(entry)) {
|
|
21002
|
+
return entry;
|
|
21003
|
+
}
|
|
21004
|
+
return [entry, null];
|
|
21005
|
+
};
|
|
20957
21006
|
// When only pseudoStateSelector is set (no visualSelector), the box owns its
|
|
20958
21007
|
// visual identity. Only event handlers and these explicit props are forwarded
|
|
20959
21008
|
// to the inner semantic/interactive child element.
|
|
@@ -20964,7 +21013,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
20964
21013
|
* as?: string,
|
|
20965
21014
|
* className?: string,
|
|
20966
21015
|
* style?: import("ignore:preact").JSX.CSSProperties & { [pseudo: string]: import("ignore:preact").JSX.CSSProperties },
|
|
20967
|
-
* styleCSSVars?: { [
|
|
21016
|
+
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
20968
21017
|
* inline?: boolean,
|
|
20969
21018
|
* block?: boolean,
|
|
20970
21019
|
* flex?: "x" | "y" | boolean,
|
|
@@ -21325,7 +21374,7 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21325
21374
|
const canForwardToChild = hasChildUsingForwardedProps;
|
|
21326
21375
|
const addStyle = (value, name, styleContext, stylesTarget) => {
|
|
21327
21376
|
const mergedValue = prepareStyleValue(stylesTarget[name], value, name, styleContext);
|
|
21328
|
-
const cssVar = styleContext.styleCSSVars[name];
|
|
21377
|
+
const [cssVar] = readCSSVarEntry(styleContext.styleCSSVars[name]);
|
|
21329
21378
|
if (cssVar) {
|
|
21330
21379
|
addCSSVar(mergedValue, cssVar, stylesTarget);
|
|
21331
21380
|
if (name === "borderRadius" && value === "inherit") {
|
|
@@ -21446,10 +21495,18 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21446
21495
|
}
|
|
21447
21496
|
return;
|
|
21448
21497
|
}
|
|
21449
|
-
const
|
|
21450
|
-
if (
|
|
21498
|
+
const cssVarEntry = styleCSSVars[name];
|
|
21499
|
+
if (cssVarEntry) {
|
|
21451
21500
|
if (value !== undefined) {
|
|
21452
|
-
|
|
21501
|
+
const [cssVarName, valueStyleName] = readCSSVarEntry(cssVarEntry);
|
|
21502
|
+
const cssValue = valueStyleName ? stringifyStyleValue(value, valueStyleName, styleContext) : value;
|
|
21503
|
+
if (isSizeSpacingKey(cssValue)) {
|
|
21504
|
+
// A size keyword reaching a custom property stays that word: every
|
|
21505
|
+
// declaration reading the variable is then invalid, drops back to
|
|
21506
|
+
// its initial value, and says nothing about it.
|
|
21507
|
+
console.warn(`"${name}" cannot take the size keyword "${cssValue}": it goes into ${cssVarName} as-is, which makes every declaration reading that variable invalid. Pass "${stringifySpacingStyle(cssValue)}" instead.`);
|
|
21508
|
+
}
|
|
21509
|
+
addCSSVar(cssValue, cssVarName, boxStylesTarget);
|
|
21453
21510
|
}
|
|
21454
21511
|
return;
|
|
21455
21512
|
}
|
|
@@ -31244,7 +31301,32 @@ const anyMatchingRouteSignal = (routes) => {
|
|
|
31244
31301
|
* when that is not the element asking: a menu opened by a press belongs at the
|
|
31245
31302
|
* point the press happened, and the row that was pressed is not that point.
|
|
31246
31303
|
*/
|
|
31247
|
-
const triggerNaviCommand = (
|
|
31304
|
+
const triggerNaviCommand = (element, command, event, options) => {
|
|
31305
|
+
const run = resolveNaviCommand(element, command, event, options);
|
|
31306
|
+
return run ? run() : false;
|
|
31307
|
+
};
|
|
31308
|
+
|
|
31309
|
+
/**
|
|
31310
|
+
* The same command, in its two moments: WHAT it aims at, decided here and now,
|
|
31311
|
+
* and what it then does, in the function returned. One caller needs them apart
|
|
31312
|
+
* — a button whose command waits for its own action (see the command trigger in
|
|
31313
|
+
* ui_state_controller.js) — and it needs it because resolution reads the DOM
|
|
31314
|
+
* AROUND the source: the closest expandable, the parent control, the slide it
|
|
31315
|
+
* stands in. An action that succeeds has usually re-rendered the tree it was
|
|
31316
|
+
* asked from, which is what an action is for, so by then the source is
|
|
31317
|
+
* detached: `closest` walks a subtree the popup is no longer in, and a command
|
|
31318
|
+
* that had a perfectly good target at the press finds none and is dropped with
|
|
31319
|
+
* a warning — the popup stays open over a request that went through.
|
|
31320
|
+
*
|
|
31321
|
+
* Freezing the target at the press is also what one means by it: the popup
|
|
31322
|
+
* pressed in is the popup to close, even when the press is what emptied it.
|
|
31323
|
+
* What the command DOES stays live — every handler reads the state it needs
|
|
31324
|
+
* inside its implementation, never while resolving.
|
|
31325
|
+
*
|
|
31326
|
+
* Returns undefined when there is nothing to run (unknown command, no target).
|
|
31327
|
+
* See triggerNaviCommand's jsdoc for the parameters.
|
|
31328
|
+
*/
|
|
31329
|
+
const resolveNaviCommand = (
|
|
31248
31330
|
element,
|
|
31249
31331
|
command,
|
|
31250
31332
|
event,
|
|
@@ -31259,7 +31341,7 @@ const triggerNaviCommand = (
|
|
|
31259
31341
|
NAVI_COMMANDS[command] || NAVI_COMMANDS[commandName(command)];
|
|
31260
31342
|
if (!naviCommand) {
|
|
31261
31343
|
console.warn(`Unknown command "${command}"`);
|
|
31262
|
-
return
|
|
31344
|
+
return undefined;
|
|
31263
31345
|
}
|
|
31264
31346
|
// Check for explicit HTML target overrides early so a misconfigured commandfor
|
|
31265
31347
|
// attribute (id not found) aborts immediately rather than silently falling back
|
|
@@ -31267,7 +31349,7 @@ const triggerNaviCommand = (
|
|
|
31267
31349
|
const explicitTarget = resolveExplicitTarget(element);
|
|
31268
31350
|
if (explicitTarget === null) {
|
|
31269
31351
|
// attribute was present but target not found — already warned inside resolveExplicitTarget
|
|
31270
|
-
return
|
|
31352
|
+
return undefined;
|
|
31271
31353
|
}
|
|
31272
31354
|
const execute = naviCommand.commandHandler(element, event, {
|
|
31273
31355
|
// Whatever followed the colon: "--navi-go-to-slide:edit" → "edit".
|
|
@@ -31280,37 +31362,40 @@ const triggerNaviCommand = (
|
|
|
31280
31362
|
});
|
|
31281
31363
|
if (!execute) {
|
|
31282
31364
|
if (optional) {
|
|
31283
|
-
return
|
|
31365
|
+
return undefined;
|
|
31284
31366
|
}
|
|
31285
31367
|
console.warn(
|
|
31286
31368
|
`"${command}" triggered on element but no suitable target found`,
|
|
31287
31369
|
element,
|
|
31288
31370
|
);
|
|
31289
|
-
return
|
|
31371
|
+
return undefined;
|
|
31290
31372
|
}
|
|
31291
31373
|
const { target, implementation } = execute;
|
|
31292
|
-
|
|
31293
|
-
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
|
|
31306
|
-
|
|
31307
|
-
|
|
31374
|
+
return () => {
|
|
31375
|
+
// The event is how the target takes part in what it was asked to do (a
|
|
31376
|
+
// popup closing on --navi-close also has its own things to do); the command
|
|
31377
|
+
// itself is the implementation, and it must not depend on someone
|
|
31378
|
+
// listening. Nobody does when the target has left the document — a trigger
|
|
31379
|
+
// whose own action took it away, which is the shape of "delete this, then
|
|
31380
|
+
// leave the page it was on" (see runWhenActionSucceeded in
|
|
31381
|
+
// rules/control_action.js): the command was decided above, while the
|
|
31382
|
+
// trigger was there, and runs once the action has succeeded, by which time
|
|
31383
|
+
// the row it stood in is gone. So the command runs here rather than being
|
|
31384
|
+
// lost in silence.
|
|
31385
|
+
const detail = {
|
|
31386
|
+
command,
|
|
31387
|
+
event,
|
|
31388
|
+
source: element,
|
|
31389
|
+
implementation,
|
|
31390
|
+
value,
|
|
31391
|
+
answered: false,
|
|
31392
|
+
};
|
|
31393
|
+
const dispatched = dispatchCustomEvent(target, "navi_command", detail);
|
|
31394
|
+
if (!detail.answered) {
|
|
31395
|
+
implementation();
|
|
31396
|
+
}
|
|
31397
|
+
return dispatched;
|
|
31308
31398
|
};
|
|
31309
|
-
const dispatched = dispatchCustomEvent(target, "navi_command", detail);
|
|
31310
|
-
if (!detail.answered) {
|
|
31311
|
-
implementation();
|
|
31312
|
-
}
|
|
31313
|
-
return dispatched;
|
|
31314
31399
|
};
|
|
31315
31400
|
|
|
31316
31401
|
// Returns the target explicitly declared via HTML attributes (commandfor / navi-command-target),
|
|
@@ -33722,18 +33807,26 @@ const useUIStateController = (
|
|
|
33722
33807
|
debugUIState(
|
|
33723
33808
|
`triggering command "${command}" for "${controlType}"`,
|
|
33724
33809
|
);
|
|
33725
|
-
const runCommand = () => {
|
|
33726
|
-
triggerNaviCommand(element, command, e);
|
|
33727
|
-
};
|
|
33728
33810
|
// What the press means may not be due yet: a button with an
|
|
33729
33811
|
// action of its own runs the work first and lets its command
|
|
33730
33812
|
// follow only once that work succeeded (see control_hooks).
|
|
33731
33813
|
// The command is handed over rather than run; nobody claiming
|
|
33732
33814
|
// it means now.
|
|
33815
|
+
//
|
|
33816
|
+
// Only the RUN waits. What the command aims at is decided here,
|
|
33817
|
+
// while the button is still in the document, because that is
|
|
33818
|
+
// what resolving reads — the popup around it, the control above
|
|
33819
|
+
// it — and the action it waits for is likely to re-render that
|
|
33820
|
+
// away before it succeeds. Resolved late, the command would
|
|
33821
|
+
// find nothing to aim at and be dropped (see
|
|
33822
|
+
// resolveNaviCommand).
|
|
33733
33823
|
if (controller.commandDeferral) {
|
|
33734
|
-
|
|
33824
|
+
const runCommand = resolveNaviCommand(element, command, e);
|
|
33825
|
+
if (runCommand) {
|
|
33826
|
+
controller.commandDeferral(runCommand);
|
|
33827
|
+
}
|
|
33735
33828
|
} else {
|
|
33736
|
-
|
|
33829
|
+
triggerNaviCommand(element, command, e);
|
|
33737
33830
|
}
|
|
33738
33831
|
}
|
|
33739
33832
|
}
|
|
@@ -36346,6 +36439,14 @@ const useControlProps = (props, {
|
|
|
36346
36439
|
return false;
|
|
36347
36440
|
}
|
|
36348
36441
|
const control = ref.current;
|
|
36442
|
+
if (!control) {
|
|
36443
|
+
// The ui action the reaction ran a line earlier is free to take this
|
|
36444
|
+
// control away: a list row answering its popup's question closes it, and
|
|
36445
|
+
// mount="while-opened" unmounts the row. Nothing left to read a value
|
|
36446
|
+
// from, and what the press was for has already happened (the button and
|
|
36447
|
+
// link reactions bail on the same ground).
|
|
36448
|
+
return false;
|
|
36449
|
+
}
|
|
36349
36450
|
const currentValue = readControlValue(control);
|
|
36350
36451
|
// For checkables: skip value dedup. The browser only fires `input` when state
|
|
36351
36452
|
// actually changes, so there is no spurious double-dispatch to guard against.
|
|
@@ -39522,8 +39623,19 @@ const TextAnchor = ({
|
|
|
39522
39623
|
// which reads as the child "jumping" even though nothing about its own
|
|
39523
39624
|
// geometry changed. useDisplayedLayoutEffect skips the initial run in
|
|
39524
39625
|
// that case and reruns once the ancestor actually opens instead.
|
|
39525
|
-
|
|
39526
|
-
|
|
39626
|
+
//
|
|
39627
|
+
// It is asked about the CHILD, not about the anchor: the anchor is this
|
|
39628
|
+
// effect's own measuring device, displayed or not according to what the
|
|
39629
|
+
// effect last decided (see the hidden attribute below), so asking whether
|
|
39630
|
+
// it is on screen answers with that decision rather than with a fact about
|
|
39631
|
+
// the screen. On the façade of something closed — an icon in a picker's
|
|
39632
|
+
// trigger, where the openable ancestor is the picker itself — that answer
|
|
39633
|
+
// is "hidden", the correction waits for the popup to open, and the icon
|
|
39634
|
+
// jumps half a pixel on the first click. The child is what is being
|
|
39635
|
+
// positioned, and it is on screen exactly when the correction means
|
|
39636
|
+
// something.
|
|
39637
|
+
useDisplayedLayoutEffect(childRef, childEl => {
|
|
39638
|
+
const anchorEl = anchorRef.current;
|
|
39527
39639
|
if (!anchorEl || !childEl) {
|
|
39528
39640
|
return;
|
|
39529
39641
|
}
|
|
@@ -41884,7 +41996,7 @@ const NavStyleCSSVars = {
|
|
|
41884
41996
|
paddingLeft: "--nav-padding-left",
|
|
41885
41997
|
background: "--nav-background",
|
|
41886
41998
|
currentIndicatorColor: "--nav-current-indicator-color",
|
|
41887
|
-
currentIndicatorSize: "--nav-current-indicator-size"
|
|
41999
|
+
currentIndicatorSize: ["--nav-current-indicator-size", "height"]
|
|
41888
42000
|
};
|
|
41889
42001
|
const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
41890
42002
|
if (currentIndicator === true) {
|
|
@@ -42979,7 +43091,7 @@ const css$V = /* css */`@layer navi {
|
|
|
42979
43091
|
const BinderStyleCSSVars = {
|
|
42980
43092
|
borderWidth: "--binder-border-width",
|
|
42981
43093
|
borderRadius: "--binder-border-radius",
|
|
42982
|
-
tabBorderRadius: "--binder-tab-border-radius",
|
|
43094
|
+
tabBorderRadius: ["--binder-tab-border-radius", "borderRadius"],
|
|
42983
43095
|
borderColor: "--binder-border-color",
|
|
42984
43096
|
background: "--binder-background",
|
|
42985
43097
|
tabBackground: "--binder-tab-background",
|
|
@@ -42988,7 +43100,7 @@ const BinderStyleCSSVars = {
|
|
|
42988
43100
|
tabColor: "--binder-tab-color",
|
|
42989
43101
|
paddingX: "--binder-padding-x",
|
|
42990
43102
|
paddingY: "--binder-padding-y",
|
|
42991
|
-
pagePadding: "--binder-page-padding"
|
|
43103
|
+
pagePadding: ["--binder-page-padding", "padding"]
|
|
42992
43104
|
};
|
|
42993
43105
|
const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
42994
43106
|
start: "flex-start",
|
|
@@ -43139,12 +43251,12 @@ const Binder = ({
|
|
|
43139
43251
|
"data-tabs-position": tabsPosition,
|
|
43140
43252
|
"data-tabs-align": tabsAlign,
|
|
43141
43253
|
"data-scrollable-page": scrollablePage ? "" : undefined,
|
|
43142
|
-
borderWidth:
|
|
43143
|
-
borderRadius:
|
|
43144
|
-
tabBorderRadius:
|
|
43145
|
-
paddingX:
|
|
43146
|
-
paddingY:
|
|
43147
|
-
pagePadding:
|
|
43254
|
+
borderWidth: borderWidth,
|
|
43255
|
+
borderRadius: borderRadius,
|
|
43256
|
+
tabBorderRadius: tabBorderRadius,
|
|
43257
|
+
paddingX: paddingX,
|
|
43258
|
+
paddingY: paddingY,
|
|
43259
|
+
pagePadding: pagePadding,
|
|
43148
43260
|
...props,
|
|
43149
43261
|
styleCSSVars: BinderStyleCSSVars,
|
|
43150
43262
|
children: [jsx(BinderOutline, {
|
|
@@ -43377,15 +43489,6 @@ const measureDrawing = ({
|
|
|
43377
43489
|
};
|
|
43378
43490
|
};
|
|
43379
43491
|
|
|
43380
|
-
// A bare number in a CSS var stays a bare number and the declaration is
|
|
43381
|
-
// dropped; every length prop here is in pixels when it is not spelled out.
|
|
43382
|
-
const withPixelUnit = value => {
|
|
43383
|
-
if (typeof value === "number") {
|
|
43384
|
-
return `${value}px`;
|
|
43385
|
-
}
|
|
43386
|
-
return value;
|
|
43387
|
-
};
|
|
43388
|
-
|
|
43389
43492
|
/**
|
|
43390
43493
|
* How much room the fixed bars take on each edge, published for the safe area
|
|
43391
43494
|
* to add up (see layout/safe_area.js — it declares the four variables written
|
|
@@ -48101,14 +48204,13 @@ const FieldAsLabel = props => {
|
|
|
48101
48204
|
});
|
|
48102
48205
|
};
|
|
48103
48206
|
const FieldCSSVars = {
|
|
48104
|
-
spacingWithControl: "--spacing-with-control"
|
|
48207
|
+
spacingWithControl: ["--spacing-with-control", "padding"]
|
|
48105
48208
|
};
|
|
48106
48209
|
const FieldAsContainer = props => {
|
|
48107
48210
|
import.meta.css = [css$L, "@jsenv/navi/src/control/field.jsx"];
|
|
48108
48211
|
const {
|
|
48109
48212
|
children
|
|
48110
48213
|
} = props;
|
|
48111
|
-
props.spacingWithControl = resolveSpacingSize(props.spacingWithControl);
|
|
48112
48214
|
const isVertical = props.flex === "y";
|
|
48113
48215
|
const [messageProps, remainingProps] = extractMessageAndRemainingProps({
|
|
48114
48216
|
...props,
|
|
@@ -50222,10 +50324,6 @@ const useInputTextualProps = props => {
|
|
|
50222
50324
|
};
|
|
50223
50325
|
const InputTextualUI = props => {
|
|
50224
50326
|
installInputCss();
|
|
50225
|
-
// Spacing props travel to CSS as a raw custom property value, so the size
|
|
50226
|
-
// keywords have to become lengths here — "s" reaching CSS untouched makes the
|
|
50227
|
-
// declaration invalid, silently, and the gap just goes away.
|
|
50228
|
-
props.slotSpacing = resolveSpacingSize(props.slotSpacing);
|
|
50229
50327
|
const {
|
|
50230
50328
|
ui,
|
|
50231
50329
|
variant,
|
|
@@ -50430,7 +50528,7 @@ const RealInput = ({
|
|
|
50430
50528
|
// Shared with textarea.jsx: a textarea is styled as a .navi_input box, so the
|
|
50431
50529
|
// two read the same style props and pseudo states.
|
|
50432
50530
|
const InputStyleCSSVars = {
|
|
50433
|
-
"slotSpacing": "--slot-spacing",
|
|
50531
|
+
"slotSpacing": ["--slot-spacing", "margin"],
|
|
50434
50532
|
"outlineWidth": "--outline-width",
|
|
50435
50533
|
"borderWidth": "--border-width",
|
|
50436
50534
|
"borderRadius": "--border-radius",
|
|
@@ -51068,6 +51166,10 @@ const css$I = /* css */`.navi_textarea {
|
|
|
51068
51166
|
}
|
|
51069
51167
|
}
|
|
51070
51168
|
|
|
51169
|
+
&[data-width-from-container] {
|
|
51170
|
+
contain: inline-size;
|
|
51171
|
+
}
|
|
51172
|
+
|
|
51071
51173
|
&[data-resizable] {
|
|
51072
51174
|
& .navi_control_input {
|
|
51073
51175
|
height: calc(var(--textarea-min-rows, 1.5) * 1lh);
|
|
@@ -51118,9 +51220,13 @@ const TextareaStyleCSSVars = {
|
|
|
51118
51220
|
* @param {number} [maxLength] The character limit, validated at submit. Pair
|
|
51119
51221
|
* with `maxLengthGuard` to block typing past it, and render a
|
|
51120
51222
|
* TextareaCharCount to show it.
|
|
51121
|
-
* @param {string} [width="35ch"] The control's width.
|
|
51122
|
-
* field-sizing the width would otherwise follow the longest
|
|
51123
|
-
* that widens while one types is a box one chases.
|
|
51223
|
+
* @param {string} [width="35ch"] The control's width. A length by default, on
|
|
51224
|
+
* purpose: with field-sizing the width would otherwise follow the longest
|
|
51225
|
+
* line, and a box that widens while one types is a box one chases. A
|
|
51226
|
+
* percentage means "fill what contains me" and holds the same promise — the
|
|
51227
|
+
* control then takes the width it is given without having a say in it, even
|
|
51228
|
+
* inside a container that sizes itself from its content (a Popover, a
|
|
51229
|
+
* Picker's popup), where the text wraps instead of widening the popup.
|
|
51124
51230
|
*/
|
|
51125
51231
|
const Textarea = ({
|
|
51126
51232
|
// Destructured, never deleted off the props object: Preact reuses the same
|
|
@@ -51147,8 +51253,21 @@ const Textarea = ({
|
|
|
51147
51253
|
// element they would become its text content.
|
|
51148
51254
|
delete hostProps.children;
|
|
51149
51255
|
const loading = basePseudoState[":-navi-loading"];
|
|
51256
|
+
|
|
51257
|
+
// A percentage is not a definite length: while a container is being sized
|
|
51258
|
+
// from its content it cannot be resolved, so the box falls back to its own
|
|
51259
|
+
// max-content — which `field-sizing: content` makes the longest line typed,
|
|
51260
|
+
// and the field widens the very box it asked to fill. The CSS above takes
|
|
51261
|
+
// that vote away, which is why the percentage lands on the contained box
|
|
51262
|
+
// rather than on the field: the field fills it from the inside (flex-grow
|
|
51263
|
+
// in input_css.js).
|
|
51264
|
+
const widthFromContainer = String(width).includes("%");
|
|
51150
51265
|
delete rootProps.width;
|
|
51151
|
-
|
|
51266
|
+
if (widthFromContainer) {
|
|
51267
|
+
rootProps.width = width;
|
|
51268
|
+
} else {
|
|
51269
|
+
hostProps.width = width;
|
|
51270
|
+
}
|
|
51152
51271
|
return jsxs(Box, {
|
|
51153
51272
|
as: "span",
|
|
51154
51273
|
inline: true,
|
|
@@ -51157,6 +51276,7 @@ const Textarea = ({
|
|
|
51157
51276
|
...rootProps,
|
|
51158
51277
|
basePseudoState: basePseudoState,
|
|
51159
51278
|
"data-resizable": resizable ? "" : undefined,
|
|
51279
|
+
"data-width-from-container": widthFromContainer ? "" : undefined,
|
|
51160
51280
|
styleCSSVars: TextareaStyleCSSVars,
|
|
51161
51281
|
pseudoStateSelector: ".navi_control_input",
|
|
51162
51282
|
pseudoClasses: InputPseudoClasses,
|
|
@@ -68781,16 +68901,6 @@ const PickerButton = props => {
|
|
|
68781
68901
|
if (typeof props.maxLines === "string") {
|
|
68782
68902
|
props.maxLines = parseInt(props.maxLines);
|
|
68783
68903
|
}
|
|
68784
|
-
// Spacing props travel to CSS as a raw custom property value, so the size
|
|
68785
|
-
// keywords have to become lengths here — "s" reaching CSS untouched makes
|
|
68786
|
-
// the declaration invalid, silently, and the gap just goes away.
|
|
68787
|
-
props.slotSpacing = resolveSpacingSize(props.slotSpacing);
|
|
68788
|
-
for (const pressPaddingPropName of PRESS_PADDING_PROP_NAMES) {
|
|
68789
|
-
const pressPadding = props[pressPaddingPropName];
|
|
68790
|
-
if (pressPadding !== undefined) {
|
|
68791
|
-
props[pressPaddingPropName] = stringifySpacingStyle(pressPadding);
|
|
68792
|
-
}
|
|
68793
|
-
}
|
|
68794
68904
|
const {
|
|
68795
68905
|
ref,
|
|
68796
68906
|
variant,
|
|
@@ -69388,32 +69498,29 @@ const NON_MOBILE_KEYBOARD_TYPES = new Set(["date", "month", "week", "time", "dat
|
|
|
69388
69498
|
const PICKER_BUTTON_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":focus-within", ":read-only", ":disabled", ":-navi-loading", ":-navi-expanded", ":-navi-has-value"];
|
|
69389
69499
|
const PickerInputPseudoClasses = [":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value", ":-navi-expanded"];
|
|
69390
69500
|
|
|
69391
|
-
//
|
|
69392
|
-
//
|
|
69393
|
-
//
|
|
69394
|
-
// invalid and drops the press area back to the box. Hence the pass in
|
|
69395
|
-
// PickerButton, which turns them into lengths.
|
|
69396
|
-
const PRESS_PADDING_PROP_NAMES = ["pressPadding", "pressPaddingX", "pressPaddingY", "pressPaddingTop", "pressPaddingRight", "pressPaddingBottom", "pressPaddingLeft"];
|
|
69501
|
+
// A pair is [the css variable, the style whose values the prop is written in]:
|
|
69502
|
+
// popupBorderRadius takes what borderRadius takes, dialogMinWidth what minWidth
|
|
69503
|
+
// takes — see box.jsx's readCSSVarEntry.
|
|
69397
69504
|
const PickerStyleCSSVars = {
|
|
69398
69505
|
"outlineWidth": "--picker-outline-width",
|
|
69399
69506
|
"borderWidth": "--picker-border-width",
|
|
69400
69507
|
"borderRadius": "--picker-border-radius",
|
|
69401
|
-
"popoverMaxHeight": "--picker-popover-max-height",
|
|
69402
|
-
"dialogMinWidth": "--picker-dialog-min-width",
|
|
69403
|
-
"dialogMinHeight": "--picker-dialog-min-height",
|
|
69404
|
-
"dialogMaxWidth": "--picker-dialog-max-width",
|
|
69405
|
-
"dialogMaxHeight": "--picker-dialog-max-height",
|
|
69508
|
+
"popoverMaxHeight": ["--picker-popover-max-height", "maxHeight"],
|
|
69509
|
+
"dialogMinWidth": ["--picker-dialog-min-width", "minWidth"],
|
|
69510
|
+
"dialogMinHeight": ["--picker-dialog-min-height", "minHeight"],
|
|
69511
|
+
"dialogMaxWidth": ["--picker-dialog-max-width", "maxWidth"],
|
|
69512
|
+
"dialogMaxHeight": ["--picker-dialog-max-height", "maxHeight"],
|
|
69406
69513
|
"popupBackgroundColor": "--picker-popup-background-color",
|
|
69407
|
-
"popupBorderRadius": "--picker-popup-border-radius",
|
|
69408
|
-
"dialogBorderWidth": "--picker-dialog-border-width",
|
|
69409
|
-
"slotSpacing": "--picker-slot-spacing",
|
|
69410
|
-
"pressPadding": "--picker-press-padding",
|
|
69411
|
-
"pressPaddingX": "--picker-press-padding-x",
|
|
69412
|
-
"pressPaddingY": "--picker-press-padding-y",
|
|
69413
|
-
"pressPaddingTop": "--picker-press-padding-top",
|
|
69414
|
-
"pressPaddingRight": "--picker-press-padding-right",
|
|
69415
|
-
"pressPaddingBottom": "--picker-press-padding-bottom",
|
|
69416
|
-
"pressPaddingLeft": "--picker-press-padding-left",
|
|
69514
|
+
"popupBorderRadius": ["--picker-popup-border-radius", "borderRadius"],
|
|
69515
|
+
"dialogBorderWidth": ["--picker-dialog-border-width", "borderWidth"],
|
|
69516
|
+
"slotSpacing": ["--picker-slot-spacing", "margin"],
|
|
69517
|
+
"pressPadding": ["--picker-press-padding", "padding"],
|
|
69518
|
+
"pressPaddingX": ["--picker-press-padding-x", "padding"],
|
|
69519
|
+
"pressPaddingY": ["--picker-press-padding-y", "padding"],
|
|
69520
|
+
"pressPaddingTop": ["--picker-press-padding-top", "padding"],
|
|
69521
|
+
"pressPaddingRight": ["--picker-press-padding-right", "padding"],
|
|
69522
|
+
"pressPaddingBottom": ["--picker-press-padding-bottom", "padding"],
|
|
69523
|
+
"pressPaddingLeft": ["--picker-press-padding-left", "padding"],
|
|
69417
69524
|
// alignX/alignY resolve to these two on a flex-x box; naming the CSS style
|
|
69418
69525
|
// (not the prop) is what styleCSSVars matches, so justifyContent/alignItems
|
|
69419
69526
|
// passed directly land in the same variables.
|