@jsenv/navi 0.29.167 → 0.29.169
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 +231 -109
- package/dist/jsenv_navi.js.map +18 -18
- package/docs/interactions.md +10 -0
- 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
|
}
|
|
@@ -18925,9 +18950,15 @@ const getHowToHandleStyleProp = (name) => {
|
|
|
18925
18950
|
}
|
|
18926
18951
|
return getStyle;
|
|
18927
18952
|
};
|
|
18928
|
-
|
|
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) => {
|
|
18929
18957
|
const stringifier = getStringifier(name);
|
|
18930
|
-
|
|
18958
|
+
return stringifier(value, name, styleContext);
|
|
18959
|
+
};
|
|
18960
|
+
const prepareStyleValue = (existingValue, value, name, styleContext) => {
|
|
18961
|
+
const cssValue = stringifyStyleValue(value, name, styleContext);
|
|
18931
18962
|
const mergedValue = mergeOneStyle(existingValue, cssValue, name, "css");
|
|
18932
18963
|
return mergedValue;
|
|
18933
18964
|
};
|
|
@@ -20963,6 +20994,15 @@ import.meta.css = [/* css */`[data-scrollable] > .navi_loading_outline_wrapper,
|
|
|
20963
20994
|
const PSEUDO_CLASSES_DEFAULT = [];
|
|
20964
20995
|
const PSEUDO_ELEMENTS_DEFAULT = [];
|
|
20965
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
|
+
};
|
|
20966
21006
|
// When only pseudoStateSelector is set (no visualSelector), the box owns its
|
|
20967
21007
|
// visual identity. Only event handlers and these explicit props are forwarded
|
|
20968
21008
|
// to the inner semantic/interactive child element.
|
|
@@ -20973,7 +21013,7 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
20973
21013
|
* as?: string,
|
|
20974
21014
|
* className?: string,
|
|
20975
21015
|
* style?: import("ignore:preact").JSX.CSSProperties & { [pseudo: string]: import("ignore:preact").JSX.CSSProperties },
|
|
20976
|
-
* styleCSSVars?: { [
|
|
21016
|
+
* styleCSSVars?: { [propName: string]: string | [string, string] },
|
|
20977
21017
|
* inline?: boolean,
|
|
20978
21018
|
* block?: boolean,
|
|
20979
21019
|
* flex?: "x" | "y" | boolean,
|
|
@@ -21334,7 +21374,7 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21334
21374
|
const canForwardToChild = hasChildUsingForwardedProps;
|
|
21335
21375
|
const addStyle = (value, name, styleContext, stylesTarget) => {
|
|
21336
21376
|
const mergedValue = prepareStyleValue(stylesTarget[name], value, name, styleContext);
|
|
21337
|
-
const cssVar = styleContext.styleCSSVars[name];
|
|
21377
|
+
const [cssVar] = readCSSVarEntry(styleContext.styleCSSVars[name]);
|
|
21338
21378
|
if (cssVar) {
|
|
21339
21379
|
addCSSVar(mergedValue, cssVar, stylesTarget);
|
|
21340
21380
|
if (name === "borderRadius" && value === "inherit") {
|
|
@@ -21455,10 +21495,18 @@ const computeBox = (props, parentBoxFlow) => {
|
|
|
21455
21495
|
}
|
|
21456
21496
|
return;
|
|
21457
21497
|
}
|
|
21458
|
-
const
|
|
21459
|
-
if (
|
|
21498
|
+
const cssVarEntry = styleCSSVars[name];
|
|
21499
|
+
if (cssVarEntry) {
|
|
21460
21500
|
if (value !== undefined) {
|
|
21461
|
-
|
|
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);
|
|
21462
21510
|
}
|
|
21463
21511
|
return;
|
|
21464
21512
|
}
|
|
@@ -31253,7 +31301,32 @@ const anyMatchingRouteSignal = (routes) => {
|
|
|
31253
31301
|
* when that is not the element asking: a menu opened by a press belongs at the
|
|
31254
31302
|
* point the press happened, and the row that was pressed is not that point.
|
|
31255
31303
|
*/
|
|
31256
|
-
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 = (
|
|
31257
31330
|
element,
|
|
31258
31331
|
command,
|
|
31259
31332
|
event,
|
|
@@ -31268,7 +31341,7 @@ const triggerNaviCommand = (
|
|
|
31268
31341
|
NAVI_COMMANDS[command] || NAVI_COMMANDS[commandName(command)];
|
|
31269
31342
|
if (!naviCommand) {
|
|
31270
31343
|
console.warn(`Unknown command "${command}"`);
|
|
31271
|
-
return
|
|
31344
|
+
return undefined;
|
|
31272
31345
|
}
|
|
31273
31346
|
// Check for explicit HTML target overrides early so a misconfigured commandfor
|
|
31274
31347
|
// attribute (id not found) aborts immediately rather than silently falling back
|
|
@@ -31276,7 +31349,7 @@ const triggerNaviCommand = (
|
|
|
31276
31349
|
const explicitTarget = resolveExplicitTarget(element);
|
|
31277
31350
|
if (explicitTarget === null) {
|
|
31278
31351
|
// attribute was present but target not found — already warned inside resolveExplicitTarget
|
|
31279
|
-
return
|
|
31352
|
+
return undefined;
|
|
31280
31353
|
}
|
|
31281
31354
|
const execute = naviCommand.commandHandler(element, event, {
|
|
31282
31355
|
// Whatever followed the colon: "--navi-go-to-slide:edit" → "edit".
|
|
@@ -31289,37 +31362,40 @@ const triggerNaviCommand = (
|
|
|
31289
31362
|
});
|
|
31290
31363
|
if (!execute) {
|
|
31291
31364
|
if (optional) {
|
|
31292
|
-
return
|
|
31365
|
+
return undefined;
|
|
31293
31366
|
}
|
|
31294
31367
|
console.warn(
|
|
31295
31368
|
`"${command}" triggered on element but no suitable target found`,
|
|
31296
31369
|
element,
|
|
31297
31370
|
);
|
|
31298
|
-
return
|
|
31371
|
+
return undefined;
|
|
31299
31372
|
}
|
|
31300
31373
|
const { target, implementation } = execute;
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
|
|
31306
|
-
|
|
31307
|
-
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
|
|
31311
|
-
|
|
31312
|
-
|
|
31313
|
-
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
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;
|
|
31317
31398
|
};
|
|
31318
|
-
const dispatched = dispatchCustomEvent(target, "navi_command", detail);
|
|
31319
|
-
if (!detail.answered) {
|
|
31320
|
-
implementation();
|
|
31321
|
-
}
|
|
31322
|
-
return dispatched;
|
|
31323
31399
|
};
|
|
31324
31400
|
|
|
31325
31401
|
// Returns the target explicitly declared via HTML attributes (commandfor / navi-command-target),
|
|
@@ -33731,18 +33807,26 @@ const useUIStateController = (
|
|
|
33731
33807
|
debugUIState(
|
|
33732
33808
|
`triggering command "${command}" for "${controlType}"`,
|
|
33733
33809
|
);
|
|
33734
|
-
const runCommand = () => {
|
|
33735
|
-
triggerNaviCommand(element, command, e);
|
|
33736
|
-
};
|
|
33737
33810
|
// What the press means may not be due yet: a button with an
|
|
33738
33811
|
// action of its own runs the work first and lets its command
|
|
33739
33812
|
// follow only once that work succeeded (see control_hooks).
|
|
33740
33813
|
// The command is handed over rather than run; nobody claiming
|
|
33741
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).
|
|
33742
33823
|
if (controller.commandDeferral) {
|
|
33743
|
-
|
|
33824
|
+
const runCommand = resolveNaviCommand(element, command, e);
|
|
33825
|
+
if (runCommand) {
|
|
33826
|
+
controller.commandDeferral(runCommand);
|
|
33827
|
+
}
|
|
33744
33828
|
} else {
|
|
33745
|
-
|
|
33829
|
+
triggerNaviCommand(element, command, e);
|
|
33746
33830
|
}
|
|
33747
33831
|
}
|
|
33748
33832
|
}
|
|
@@ -34399,7 +34483,12 @@ const GROUP_DEFAULTS = {
|
|
|
34399
34483
|
}
|
|
34400
34484
|
// Merged in on the way up (see above), so on the way down it takes the
|
|
34401
34485
|
// whole object and picks out its own keys — the same value it produced.
|
|
34402
|
-
|
|
34486
|
+
// Only for a child with no name of its own: the same condition the
|
|
34487
|
+
// aggregate applies before merging. A NAMED child holding an object (a
|
|
34488
|
+
// `type="object"` picker, say) answers for one key, and the object it is
|
|
34489
|
+
// not mentioned in says nothing about it — handing it the whole value
|
|
34490
|
+
// would make it hold the group.
|
|
34491
|
+
if (!childName && isNamelessGrouping(child, child.uiState)) {
|
|
34403
34492
|
return newUIState;
|
|
34404
34493
|
}
|
|
34405
34494
|
if (
|
|
@@ -34687,7 +34776,17 @@ const useUIGroupStateController = (
|
|
|
34687
34776
|
e,
|
|
34688
34777
|
`${controlType}.getUIState -> ${JSON.stringify(groupUIState)}`,
|
|
34689
34778
|
);
|
|
34690
|
-
if (notifyExternal === true) {
|
|
34779
|
+
if (notifyExternal === true || notifyExternal === "if-it-moves") {
|
|
34780
|
+
if (
|
|
34781
|
+
notifyExternal === "if-it-moves" &&
|
|
34782
|
+
compareTwoJsValues(groupUIState, controller.uiState)
|
|
34783
|
+
) {
|
|
34784
|
+
// A child confirming what it already shows, to a group already
|
|
34785
|
+
// worth it: the value arrived while the popup was open and the
|
|
34786
|
+
// group reacted then. One trip through a picker is one answer, so
|
|
34787
|
+
// there is nothing left to say here.
|
|
34788
|
+
return;
|
|
34789
|
+
}
|
|
34691
34790
|
// Somebody answered: what the group is worth is what its children say
|
|
34692
34791
|
// between them, from here on.
|
|
34693
34792
|
controller.stateGivenFromAbove = false;
|
|
@@ -35001,7 +35100,7 @@ const useUIGroupStateController = (
|
|
|
35001
35100
|
onChildUIAction: (
|
|
35002
35101
|
childUIStateController,
|
|
35003
35102
|
e,
|
|
35004
|
-
{ stateChanged, silent },
|
|
35103
|
+
{ stateChanged, silent, onlyIfGroupValueMoves },
|
|
35005
35104
|
) => {
|
|
35006
35105
|
const delegatedTo = delegatedChildrenRef.current.get(
|
|
35007
35106
|
childUIStateController,
|
|
@@ -35010,6 +35109,7 @@ const useUIGroupStateController = (
|
|
|
35010
35109
|
delegatedTo.onChildUIAction(childUIStateController, e, {
|
|
35011
35110
|
stateChanged,
|
|
35012
35111
|
silent,
|
|
35112
|
+
onlyIfGroupValueMoves,
|
|
35013
35113
|
});
|
|
35014
35114
|
return;
|
|
35015
35115
|
}
|
|
@@ -35024,7 +35124,11 @@ const useUIGroupStateController = (
|
|
|
35024
35124
|
);
|
|
35025
35125
|
if (stateChanged) {
|
|
35026
35126
|
onChange(e, {
|
|
35027
|
-
notifyExternal: silent
|
|
35127
|
+
notifyExternal: silent
|
|
35128
|
+
? "silent"
|
|
35129
|
+
: onlyIfGroupValueMoves
|
|
35130
|
+
? "if-it-moves"
|
|
35131
|
+
: true,
|
|
35028
35132
|
actingChild: childUIStateController,
|
|
35029
35133
|
});
|
|
35030
35134
|
} else {
|
|
@@ -36355,6 +36459,14 @@ const useControlProps = (props, {
|
|
|
36355
36459
|
return false;
|
|
36356
36460
|
}
|
|
36357
36461
|
const control = ref.current;
|
|
36462
|
+
if (!control) {
|
|
36463
|
+
// The ui action the reaction ran a line earlier is free to take this
|
|
36464
|
+
// control away: a list row answering its popup's question closes it, and
|
|
36465
|
+
// mount="while-opened" unmounts the row. Nothing left to read a value
|
|
36466
|
+
// from, and what the press was for has already happened (the button and
|
|
36467
|
+
// link reactions bail on the same ground).
|
|
36468
|
+
return false;
|
|
36469
|
+
}
|
|
36358
36470
|
const currentValue = readControlValue(control);
|
|
36359
36471
|
// For checkables: skip value dedup. The browser only fires `input` when state
|
|
36360
36472
|
// actually changes, so there is no spurious double-dispatch to guard against.
|
|
@@ -41904,7 +42016,7 @@ const NavStyleCSSVars = {
|
|
|
41904
42016
|
paddingLeft: "--nav-padding-left",
|
|
41905
42017
|
background: "--nav-background",
|
|
41906
42018
|
currentIndicatorColor: "--nav-current-indicator-color",
|
|
41907
|
-
currentIndicatorSize: "--nav-current-indicator-size"
|
|
42019
|
+
currentIndicatorSize: ["--nav-current-indicator-size", "height"]
|
|
41908
42020
|
};
|
|
41909
42021
|
const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
41910
42022
|
if (currentIndicator === true) {
|
|
@@ -42999,7 +43111,7 @@ const css$V = /* css */`@layer navi {
|
|
|
42999
43111
|
const BinderStyleCSSVars = {
|
|
43000
43112
|
borderWidth: "--binder-border-width",
|
|
43001
43113
|
borderRadius: "--binder-border-radius",
|
|
43002
|
-
tabBorderRadius: "--binder-tab-border-radius",
|
|
43114
|
+
tabBorderRadius: ["--binder-tab-border-radius", "borderRadius"],
|
|
43003
43115
|
borderColor: "--binder-border-color",
|
|
43004
43116
|
background: "--binder-background",
|
|
43005
43117
|
tabBackground: "--binder-tab-background",
|
|
@@ -43008,7 +43120,7 @@ const BinderStyleCSSVars = {
|
|
|
43008
43120
|
tabColor: "--binder-tab-color",
|
|
43009
43121
|
paddingX: "--binder-padding-x",
|
|
43010
43122
|
paddingY: "--binder-padding-y",
|
|
43011
|
-
pagePadding: "--binder-page-padding"
|
|
43123
|
+
pagePadding: ["--binder-page-padding", "padding"]
|
|
43012
43124
|
};
|
|
43013
43125
|
const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
43014
43126
|
start: "flex-start",
|
|
@@ -43159,12 +43271,12 @@ const Binder = ({
|
|
|
43159
43271
|
"data-tabs-position": tabsPosition,
|
|
43160
43272
|
"data-tabs-align": tabsAlign,
|
|
43161
43273
|
"data-scrollable-page": scrollablePage ? "" : undefined,
|
|
43162
|
-
borderWidth:
|
|
43163
|
-
borderRadius:
|
|
43164
|
-
tabBorderRadius:
|
|
43165
|
-
paddingX:
|
|
43166
|
-
paddingY:
|
|
43167
|
-
pagePadding:
|
|
43274
|
+
borderWidth: borderWidth,
|
|
43275
|
+
borderRadius: borderRadius,
|
|
43276
|
+
tabBorderRadius: tabBorderRadius,
|
|
43277
|
+
paddingX: paddingX,
|
|
43278
|
+
paddingY: paddingY,
|
|
43279
|
+
pagePadding: pagePadding,
|
|
43168
43280
|
...props,
|
|
43169
43281
|
styleCSSVars: BinderStyleCSSVars,
|
|
43170
43282
|
children: [jsx(BinderOutline, {
|
|
@@ -43397,15 +43509,6 @@ const measureDrawing = ({
|
|
|
43397
43509
|
};
|
|
43398
43510
|
};
|
|
43399
43511
|
|
|
43400
|
-
// A bare number in a CSS var stays a bare number and the declaration is
|
|
43401
|
-
// dropped; every length prop here is in pixels when it is not spelled out.
|
|
43402
|
-
const withPixelUnit = value => {
|
|
43403
|
-
if (typeof value === "number") {
|
|
43404
|
-
return `${value}px`;
|
|
43405
|
-
}
|
|
43406
|
-
return value;
|
|
43407
|
-
};
|
|
43408
|
-
|
|
43409
43512
|
/**
|
|
43410
43513
|
* How much room the fixed bars take on each edge, published for the safe area
|
|
43411
43514
|
* to add up (see layout/safe_area.js — it declares the four variables written
|
|
@@ -48121,14 +48224,13 @@ const FieldAsLabel = props => {
|
|
|
48121
48224
|
});
|
|
48122
48225
|
};
|
|
48123
48226
|
const FieldCSSVars = {
|
|
48124
|
-
spacingWithControl: "--spacing-with-control"
|
|
48227
|
+
spacingWithControl: ["--spacing-with-control", "padding"]
|
|
48125
48228
|
};
|
|
48126
48229
|
const FieldAsContainer = props => {
|
|
48127
48230
|
import.meta.css = [css$L, "@jsenv/navi/src/control/field.jsx"];
|
|
48128
48231
|
const {
|
|
48129
48232
|
children
|
|
48130
48233
|
} = props;
|
|
48131
|
-
props.spacingWithControl = resolveSpacingSize(props.spacingWithControl);
|
|
48132
48234
|
const isVertical = props.flex === "y";
|
|
48133
48235
|
const [messageProps, remainingProps] = extractMessageAndRemainingProps({
|
|
48134
48236
|
...props,
|
|
@@ -50242,10 +50344,6 @@ const useInputTextualProps = props => {
|
|
|
50242
50344
|
};
|
|
50243
50345
|
const InputTextualUI = props => {
|
|
50244
50346
|
installInputCss();
|
|
50245
|
-
// Spacing props travel to CSS as a raw custom property value, so the size
|
|
50246
|
-
// keywords have to become lengths here — "s" reaching CSS untouched makes the
|
|
50247
|
-
// declaration invalid, silently, and the gap just goes away.
|
|
50248
|
-
props.slotSpacing = resolveSpacingSize(props.slotSpacing);
|
|
50249
50347
|
const {
|
|
50250
50348
|
ui,
|
|
50251
50349
|
variant,
|
|
@@ -50450,7 +50548,7 @@ const RealInput = ({
|
|
|
50450
50548
|
// Shared with textarea.jsx: a textarea is styled as a .navi_input box, so the
|
|
50451
50549
|
// two read the same style props and pseudo states.
|
|
50452
50550
|
const InputStyleCSSVars = {
|
|
50453
|
-
"slotSpacing": "--slot-spacing",
|
|
50551
|
+
"slotSpacing": ["--slot-spacing", "margin"],
|
|
50454
50552
|
"outlineWidth": "--outline-width",
|
|
50455
50553
|
"borderWidth": "--border-width",
|
|
50456
50554
|
"borderRadius": "--border-radius",
|
|
@@ -51088,6 +51186,10 @@ const css$I = /* css */`.navi_textarea {
|
|
|
51088
51186
|
}
|
|
51089
51187
|
}
|
|
51090
51188
|
|
|
51189
|
+
&[data-width-from-container] {
|
|
51190
|
+
contain: inline-size;
|
|
51191
|
+
}
|
|
51192
|
+
|
|
51091
51193
|
&[data-resizable] {
|
|
51092
51194
|
& .navi_control_input {
|
|
51093
51195
|
height: calc(var(--textarea-min-rows, 1.5) * 1lh);
|
|
@@ -51138,9 +51240,13 @@ const TextareaStyleCSSVars = {
|
|
|
51138
51240
|
* @param {number} [maxLength] The character limit, validated at submit. Pair
|
|
51139
51241
|
* with `maxLengthGuard` to block typing past it, and render a
|
|
51140
51242
|
* TextareaCharCount to show it.
|
|
51141
|
-
* @param {string} [width="35ch"] The control's width.
|
|
51142
|
-
* field-sizing the width would otherwise follow the longest
|
|
51143
|
-
* that widens while one types is a box one chases.
|
|
51243
|
+
* @param {string} [width="35ch"] The control's width. A length by default, on
|
|
51244
|
+
* purpose: with field-sizing the width would otherwise follow the longest
|
|
51245
|
+
* line, and a box that widens while one types is a box one chases. A
|
|
51246
|
+
* percentage means "fill what contains me" and holds the same promise — the
|
|
51247
|
+
* control then takes the width it is given without having a say in it, even
|
|
51248
|
+
* inside a container that sizes itself from its content (a Popover, a
|
|
51249
|
+
* Picker's popup), where the text wraps instead of widening the popup.
|
|
51144
51250
|
*/
|
|
51145
51251
|
const Textarea = ({
|
|
51146
51252
|
// Destructured, never deleted off the props object: Preact reuses the same
|
|
@@ -51167,8 +51273,21 @@ const Textarea = ({
|
|
|
51167
51273
|
// element they would become its text content.
|
|
51168
51274
|
delete hostProps.children;
|
|
51169
51275
|
const loading = basePseudoState[":-navi-loading"];
|
|
51276
|
+
|
|
51277
|
+
// A percentage is not a definite length: while a container is being sized
|
|
51278
|
+
// from its content it cannot be resolved, so the box falls back to its own
|
|
51279
|
+
// max-content — which `field-sizing: content` makes the longest line typed,
|
|
51280
|
+
// and the field widens the very box it asked to fill. The CSS above takes
|
|
51281
|
+
// that vote away, which is why the percentage lands on the contained box
|
|
51282
|
+
// rather than on the field: the field fills it from the inside (flex-grow
|
|
51283
|
+
// in input_css.js).
|
|
51284
|
+
const widthFromContainer = String(width).includes("%");
|
|
51170
51285
|
delete rootProps.width;
|
|
51171
|
-
|
|
51286
|
+
if (widthFromContainer) {
|
|
51287
|
+
rootProps.width = width;
|
|
51288
|
+
} else {
|
|
51289
|
+
hostProps.width = width;
|
|
51290
|
+
}
|
|
51172
51291
|
return jsxs(Box, {
|
|
51173
51292
|
as: "span",
|
|
51174
51293
|
inline: true,
|
|
@@ -51177,6 +51296,7 @@ const Textarea = ({
|
|
|
51177
51296
|
...rootProps,
|
|
51178
51297
|
basePseudoState: basePseudoState,
|
|
51179
51298
|
"data-resizable": resizable ? "" : undefined,
|
|
51299
|
+
"data-width-from-container": widthFromContainer ? "" : undefined,
|
|
51180
51300
|
styleCSSVars: TextareaStyleCSSVars,
|
|
51181
51301
|
pseudoStateSelector: ".navi_control_input",
|
|
51182
51302
|
pseudoClasses: InputPseudoClasses,
|
|
@@ -51739,6 +51859,17 @@ const isUIStateHeld = (controller) => {
|
|
|
51739
51859
|
* `command` on a control is its reaction to being used, and this is a
|
|
51740
51860
|
* confirmation happening elsewhere, whose own command (the picker's) is already
|
|
51741
51861
|
* running.
|
|
51862
|
+
*
|
|
51863
|
+
* And up one, to the group the control answers to: a picker inside a form is
|
|
51864
|
+
* one of its fields, and a form is worth what its fields say. What the popup
|
|
51865
|
+
* put there arrived through a mount sync, which is deliberately silent — a
|
|
51866
|
+
* popup opening is nobody answering (see onChange in ui_state_controller.js) —
|
|
51867
|
+
* so this is the first moment the form can be told.
|
|
51868
|
+
*
|
|
51869
|
+
* That group only hears about it when it moves: a value the user picked in the
|
|
51870
|
+
* popup reached it already, and one trip through a picker is one answer. Down
|
|
51871
|
+
* the subtree the reaction re-runs either way — a control saying again what it
|
|
51872
|
+
* holds says the same thing, and it is where a signal is written.
|
|
51742
51873
|
*/
|
|
51743
51874
|
const commitUIStateAsAnswer = (controller, e) => {
|
|
51744
51875
|
if (!controller) {
|
|
@@ -51746,6 +51877,10 @@ const commitUIStateAsAnswer = (controller, e) => {
|
|
|
51746
51877
|
}
|
|
51747
51878
|
const answering = controller.facadeChild || controller;
|
|
51748
51879
|
commitSubtree(answering, e);
|
|
51880
|
+
controller.parentUIStateController?.onChildUIAction(controller, e, {
|
|
51881
|
+
stateChanged: true,
|
|
51882
|
+
onlyIfGroupValueMoves: true,
|
|
51883
|
+
});
|
|
51749
51884
|
};
|
|
51750
51885
|
const commitSubtree = (controller, e) => {
|
|
51751
51886
|
controller.onUIAction?.(e, { skipCommand: true });
|
|
@@ -68801,16 +68936,6 @@ const PickerButton = props => {
|
|
|
68801
68936
|
if (typeof props.maxLines === "string") {
|
|
68802
68937
|
props.maxLines = parseInt(props.maxLines);
|
|
68803
68938
|
}
|
|
68804
|
-
// Spacing props travel to CSS as a raw custom property value, so the size
|
|
68805
|
-
// keywords have to become lengths here — "s" reaching CSS untouched makes
|
|
68806
|
-
// the declaration invalid, silently, and the gap just goes away.
|
|
68807
|
-
props.slotSpacing = resolveSpacingSize(props.slotSpacing);
|
|
68808
|
-
for (const pressPaddingPropName of PRESS_PADDING_PROP_NAMES) {
|
|
68809
|
-
const pressPadding = props[pressPaddingPropName];
|
|
68810
|
-
if (pressPadding !== undefined) {
|
|
68811
|
-
props[pressPaddingPropName] = stringifySpacingStyle(pressPadding);
|
|
68812
|
-
}
|
|
68813
|
-
}
|
|
68814
68939
|
const {
|
|
68815
68940
|
ref,
|
|
68816
68941
|
variant,
|
|
@@ -69408,32 +69533,29 @@ const NON_MOBILE_KEYBOARD_TYPES = new Set(["date", "month", "week", "time", "dat
|
|
|
69408
69533
|
const PICKER_BUTTON_PSEUDO_CLASSES = [":hover", ":focus", ":focus-visible", ":focus-within", ":read-only", ":disabled", ":-navi-loading", ":-navi-expanded", ":-navi-has-value"];
|
|
69409
69534
|
const PickerInputPseudoClasses = [":focus", ":focus-visible", ":read-only", ":disabled", ":-navi-loading", ":-navi-has-value", ":-navi-expanded"];
|
|
69410
69535
|
|
|
69411
|
-
//
|
|
69412
|
-
//
|
|
69413
|
-
//
|
|
69414
|
-
// invalid and drops the press area back to the box. Hence the pass in
|
|
69415
|
-
// PickerButton, which turns them into lengths.
|
|
69416
|
-
const PRESS_PADDING_PROP_NAMES = ["pressPadding", "pressPaddingX", "pressPaddingY", "pressPaddingTop", "pressPaddingRight", "pressPaddingBottom", "pressPaddingLeft"];
|
|
69536
|
+
// A pair is [the css variable, the style whose values the prop is written in]:
|
|
69537
|
+
// popupBorderRadius takes what borderRadius takes, dialogMinWidth what minWidth
|
|
69538
|
+
// takes — see box.jsx's readCSSVarEntry.
|
|
69417
69539
|
const PickerStyleCSSVars = {
|
|
69418
69540
|
"outlineWidth": "--picker-outline-width",
|
|
69419
69541
|
"borderWidth": "--picker-border-width",
|
|
69420
69542
|
"borderRadius": "--picker-border-radius",
|
|
69421
|
-
"popoverMaxHeight": "--picker-popover-max-height",
|
|
69422
|
-
"dialogMinWidth": "--picker-dialog-min-width",
|
|
69423
|
-
"dialogMinHeight": "--picker-dialog-min-height",
|
|
69424
|
-
"dialogMaxWidth": "--picker-dialog-max-width",
|
|
69425
|
-
"dialogMaxHeight": "--picker-dialog-max-height",
|
|
69543
|
+
"popoverMaxHeight": ["--picker-popover-max-height", "maxHeight"],
|
|
69544
|
+
"dialogMinWidth": ["--picker-dialog-min-width", "minWidth"],
|
|
69545
|
+
"dialogMinHeight": ["--picker-dialog-min-height", "minHeight"],
|
|
69546
|
+
"dialogMaxWidth": ["--picker-dialog-max-width", "maxWidth"],
|
|
69547
|
+
"dialogMaxHeight": ["--picker-dialog-max-height", "maxHeight"],
|
|
69426
69548
|
"popupBackgroundColor": "--picker-popup-background-color",
|
|
69427
|
-
"popupBorderRadius": "--picker-popup-border-radius",
|
|
69428
|
-
"dialogBorderWidth": "--picker-dialog-border-width",
|
|
69429
|
-
"slotSpacing": "--picker-slot-spacing",
|
|
69430
|
-
"pressPadding": "--picker-press-padding",
|
|
69431
|
-
"pressPaddingX": "--picker-press-padding-x",
|
|
69432
|
-
"pressPaddingY": "--picker-press-padding-y",
|
|
69433
|
-
"pressPaddingTop": "--picker-press-padding-top",
|
|
69434
|
-
"pressPaddingRight": "--picker-press-padding-right",
|
|
69435
|
-
"pressPaddingBottom": "--picker-press-padding-bottom",
|
|
69436
|
-
"pressPaddingLeft": "--picker-press-padding-left",
|
|
69549
|
+
"popupBorderRadius": ["--picker-popup-border-radius", "borderRadius"],
|
|
69550
|
+
"dialogBorderWidth": ["--picker-dialog-border-width", "borderWidth"],
|
|
69551
|
+
"slotSpacing": ["--picker-slot-spacing", "margin"],
|
|
69552
|
+
"pressPadding": ["--picker-press-padding", "padding"],
|
|
69553
|
+
"pressPaddingX": ["--picker-press-padding-x", "padding"],
|
|
69554
|
+
"pressPaddingY": ["--picker-press-padding-y", "padding"],
|
|
69555
|
+
"pressPaddingTop": ["--picker-press-padding-top", "padding"],
|
|
69556
|
+
"pressPaddingRight": ["--picker-press-padding-right", "padding"],
|
|
69557
|
+
"pressPaddingBottom": ["--picker-press-padding-bottom", "padding"],
|
|
69558
|
+
"pressPaddingLeft": ["--picker-press-padding-left", "padding"],
|
|
69437
69559
|
// alignX/alignY resolve to these two on a flex-x box; naming the CSS style
|
|
69438
69560
|
// (not the prop) is what styleCSSVars matches, so justifyContent/alignItems
|
|
69439
69561
|
// passed directly land in the same variables.
|