@jsenv/navi 0.29.387 → 0.29.389
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/README.md +4 -4
- package/dist/dev/jsenv_navi.js +556 -392
- package/dist/dev/jsenv_navi.js.map +81 -81
- package/dist/dev/jsenv_navi_side_effects.js +87 -78
- package/dist/dev/jsenv_navi_side_effects.js.map +4 -4
- package/dist/jsenv_navi.js +551 -387
- package/dist/jsenv_navi.js.map +81 -81
- package/dist/jsenv_navi_side_effects.js +87 -78
- package/dist/jsenv_navi_side_effects.js.map +4 -4
- package/docs/AI_INSTRUCTIONS.md +39 -23
- package/docs/actions.md +6 -4
- package/docs/autofocus.md +2 -1
- package/docs/badge_list.md +5 -3
- package/docs/control_group.md +4 -3
- package/docs/control_object.md +1 -1
- package/docs/control_value.md +8 -9
- package/docs/css_architecture.md +16 -14
- package/docs/dialog_shape.md +15 -10
- package/docs/drag_interactions.md +49 -41
- package/docs/drag_to_travel.md +32 -24
- package/docs/dynamic_import.md +7 -5
- package/docs/error_handling.md +31 -30
- package/docs/field_validation.md +35 -6
- package/docs/i18n.md +16 -7
- package/docs/interactions.md +28 -23
- package/docs/list_action.md +8 -5
- package/docs/list_refresh.md +8 -1
- package/docs/mobile_layout_pitfalls.md +6 -5
- package/docs/navigation.md +7 -5
- package/docs/network_policy.md +22 -7
- package/docs/pan_zoom.md +16 -9
- package/docs/popup_backdrop.md +11 -5
- package/docs/popup_lift.md +5 -3
- package/docs/popup_open.md +39 -28
- package/docs/resource.md +31 -20
- package/docs/route_transitions.md +13 -10
- package/docs/safe_area.md +59 -39
- package/docs/scroll.md +11 -9
- package/docs/testid.md +10 -1
- package/docs/typography.md +2 -3
- package/docs/view_transitions.md +9 -9
- package/docs/z_index.md +6 -5
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -554,12 +554,17 @@ effect(() => {
|
|
|
554
554
|
* The active language is read from `languagesSignal` (see lang_signal.js —
|
|
555
555
|
* combines the browser's own `navigator.languages`, an optional
|
|
556
556
|
* `setPreferredLanguage()` user override, and an optional
|
|
557
|
-
* `setSupportedLanguages()` app-wide allow-list), live on every lookup.
|
|
557
|
+
* `setSupportedLanguages()` app-wide allow-list), live on every lookup. Each
|
|
558
|
+
* key is looked up in those languages in order, then in English, which
|
|
559
|
+
* translates every key: a language shipped for part of the keys only (German
|
|
560
|
+
* has the `time.*` words) never shows a raw key.
|
|
558
561
|
*
|
|
559
562
|
* Built-in key namespaces, all overridable — the registrations below are the
|
|
560
563
|
* exhaustive list, read them to find the exact key to override:
|
|
561
564
|
* - `"button.*"` — Clear, Reset, Send, Open, Close, Cancel, Confirm…
|
|
562
|
-
* - `"
|
|
565
|
+
* - `"confirm.*"` — the question a confirmation asks by default
|
|
566
|
+
* - `"time.*"` — what a clock writes between hours and minutes, and the labels of its hour and minute parts; the wording the formatters use is registered by @jsenv/humanize
|
|
567
|
+
* - `"time_range.*"` — how the two ends of a span are named ("From", "to")
|
|
563
568
|
* - `"spin.*"` — the ends of a steppable range
|
|
564
569
|
* - `"list.*"` — empty/no-match/failed-rows messages
|
|
565
570
|
* - `"badge_list.*"` — the "+[count] more" overflow badge
|
|
@@ -1472,19 +1477,25 @@ const isNetworkPolicyError = (error) => {
|
|
|
1472
1477
|
* is already saying it in words the person understands.
|
|
1473
1478
|
*
|
|
1474
1479
|
* navi's own report leaves it alone (action_error_report.js). What is left is
|
|
1475
|
-
* the
|
|
1476
|
-
*
|
|
1477
|
-
*
|
|
1478
|
-
*
|
|
1479
|
-
* asked. Cancelling the event is what says it is handled: the browser drops
|
|
1480
|
+
* the error reaching `window` on its own — a run called by hand whose failure
|
|
1481
|
+
* nobody catches, a render throw no boundary took — where it lands as an
|
|
1482
|
+
* uncaught error in the console, about a request the app itself declared would
|
|
1483
|
+
* not leave. Cancelling the event is what says it is handled: the browser drops
|
|
1480
1484
|
* the console line, and the jsenv supervisor skips prevented events too.
|
|
1481
1485
|
*
|
|
1482
1486
|
* Both shapes a failure travels in are covered, since which one it is depends
|
|
1483
1487
|
* on whether the resource callback happened to be async — the throw reaching
|
|
1484
1488
|
* `window`, and the rejection nobody caught.
|
|
1485
1489
|
*
|
|
1486
|
-
*
|
|
1487
|
-
*
|
|
1490
|
+
* A boundary displaying it raises no event at all. In dev, `preact/debug` hands
|
|
1491
|
+
* every error a boundary caught to `console.error`, after the boundary took it
|
|
1492
|
+
* and before it rendered anything; it reads no option and no flag, so that line
|
|
1493
|
+
* stays. Muting it from here is not done: wrapping `console.error` rewrites a
|
|
1494
|
+
* global, and wrapping `options._catchError` reaches into preact's private
|
|
1495
|
+
* hooks, both to hide one class of error.
|
|
1496
|
+
*
|
|
1497
|
+
* Scoped to the policy's own error and to nothing else — every other error
|
|
1498
|
+
* thrown at window, and every other rejection let go, stays exactly as loud as
|
|
1488
1499
|
* it is.
|
|
1489
1500
|
*/
|
|
1490
1501
|
let networkPolicyErrorSilenced = false;
|
|
@@ -1572,11 +1583,10 @@ const errorIsDisplayed = (error) => {
|
|
|
1572
1583
|
*
|
|
1573
1584
|
* Whatever the reader does with it is already covered without any deadline: it
|
|
1574
1585
|
* displays it (and marks it), or it throws it, and a thrown error either finds a
|
|
1575
|
-
* boundary that displays it or reaches window on its own —
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
* happened.
|
|
1586
|
+
* boundary that displays it or reaches window on its own — preact re-throws
|
|
1587
|
+
* what no boundary handled. Reporting it here as well would be a second voice
|
|
1588
|
+
* saying the same thing, always the wrong one, since this module cannot see
|
|
1589
|
+
* which of those happened.
|
|
1580
1590
|
*/
|
|
1581
1591
|
const errorTakenByRenderSet = new WeakSet();
|
|
1582
1592
|
const markErrorAsTakenByRender = (error) => {
|
|
@@ -1651,8 +1661,8 @@ const markErrorAsStoppingRender = (error) => {
|
|
|
1651
1661
|
* An error the network policy produced is never reported: the app declared the
|
|
1652
1662
|
* state that produced it, the request never left, and there is nothing for a
|
|
1653
1663
|
* developer to fix. It is data a screen shows, and it stays data whether or not
|
|
1654
|
-
* one does (see network_policy.js, which also
|
|
1655
|
-
*
|
|
1664
|
+
* one does (see network_policy.js, which also cancels it when it reaches
|
|
1665
|
+
* window).
|
|
1656
1666
|
*/
|
|
1657
1667
|
const errorIsAccountedFor = (error) => {
|
|
1658
1668
|
return (
|
|
@@ -1666,7 +1676,7 @@ const errorIsAccountedFor = (error) => {
|
|
|
1666
1676
|
/**
|
|
1667
1677
|
* Rethrown rather than logged: an error nobody took is an unhandled error, and
|
|
1668
1678
|
* the runtime already knows what to do with those (window "error" event, jsenv
|
|
1669
|
-
* overlay in dev). Same
|
|
1679
|
+
* overlay in dev). Same answer preact gives an error no boundary handled.
|
|
1670
1680
|
*/
|
|
1671
1681
|
const errorReportedSet = new WeakSet();
|
|
1672
1682
|
const reportErrorIfNobodyDisplaysIt = (error, { action } = {}) => {
|
|
@@ -2187,7 +2197,7 @@ const compareTwoJsValues = (
|
|
|
2187
2197
|
}
|
|
2188
2198
|
return true;
|
|
2189
2199
|
}
|
|
2190
|
-
// Ordered array comparison
|
|
2200
|
+
// Ordered array comparison
|
|
2191
2201
|
let i = 0;
|
|
2192
2202
|
while (i < a.length) {
|
|
2193
2203
|
const aValue = a[i];
|
|
@@ -3370,6 +3380,8 @@ const actionWeakMap = new WeakMap();
|
|
|
3370
3380
|
* long is this still true?" belongs to the question: a court list is good for
|
|
3371
3381
|
* the session, a count of people watching grows on its own and is worth a few
|
|
3372
3382
|
* minutes, a payment status is worth nothing at all.
|
|
3383
|
+
* @param {{ verb?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" }} [rootOptions.meta] -
|
|
3384
|
+
* `verb` puts the action under the network policy (docs/network_policy.md).
|
|
3373
3385
|
*/
|
|
3374
3386
|
const createAction = (callback, rootOptions = {}) => {
|
|
3375
3387
|
const existing = actionWeakMap.get(callback);
|
|
@@ -4484,7 +4496,7 @@ const runUnwatched = (startRun) => {
|
|
|
4484
4496
|
/**
|
|
4485
4497
|
* Reactively runs an action whenever the params derived from signals change.
|
|
4486
4498
|
*
|
|
4487
|
-
* @param {object} action - The action to run.
|
|
4499
|
+
* @param {object | Function} action - The action to run, or a function made into one.
|
|
4488
4500
|
* @param {Function} deriveActionParamsFromSignals - A function that reads signals and returns
|
|
4489
4501
|
* the params to pass to the action. It is re-evaluated automatically whenever a signal it
|
|
4490
4502
|
* read changes. Return `false`/`null`/`undefined` to skip running the action.
|
|
@@ -4507,6 +4519,8 @@ const runUnwatched = (startRun) => {
|
|
|
4507
4519
|
* ```
|
|
4508
4520
|
* The action will not fire while the user is actively changing filters; it fires once
|
|
4509
4521
|
* they pause for half a second.
|
|
4522
|
+
* @returns {object} the instance bound to the derived params: the one to read
|
|
4523
|
+
* (`useAsyncData`), since the answer lands there and never on `action` itself.
|
|
4510
4524
|
*/
|
|
4511
4525
|
// The run is not awaited here, and a rejection nobody waits for is an unhandled
|
|
4512
4526
|
// one — in dev, an error overlay thrown over a page that is already saying what
|
|
@@ -5021,11 +5035,12 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
5021
5035
|
*/
|
|
5022
5036
|
/**
|
|
5023
5037
|
* A bound signal that carries a default of its own says the same thing on every
|
|
5024
|
-
* control: the control starts there and
|
|
5025
|
-
*
|
|
5026
|
-
*
|
|
5027
|
-
*
|
|
5028
|
-
*
|
|
5038
|
+
* control: the control starts there, uncontrolled, and a reset goes back to it.
|
|
5039
|
+
* Uncontrolled here is about where the control starts, not about whether it
|
|
5040
|
+
* follows the signal — the binding stays two-way either way (see
|
|
5041
|
+
* stateFromSignal in control_hooks.jsx) — nor about what a form counts as
|
|
5042
|
+
* held: that is the signal's content (isUIStateHeld in held_ui_state.js), and a
|
|
5043
|
+
* stateSignal holds its default. Written once and used by everything
|
|
5029
5044
|
* that takes a `signal`, so one signal cannot mean two different things
|
|
5030
5045
|
* depending on which control it was handed to.
|
|
5031
5046
|
*/
|
|
@@ -5085,8 +5100,7 @@ const resolveInputProps = (props, { controlType = "input" } = {}) => {
|
|
|
5085
5100
|
// If no explicit defaultChecked, derive it from the signal's default
|
|
5086
5101
|
// value so that resetUIState restores to the original default.
|
|
5087
5102
|
// Only a stateSignal carries a default of its own; a plain signal has
|
|
5088
|
-
// no `options` at all,
|
|
5089
|
-
// the same optional read every other branch here already does.
|
|
5103
|
+
// no `options` at all, hence the optional read.
|
|
5090
5104
|
const defaultVal = signalOptions?.getDefaultValue(false);
|
|
5091
5105
|
if (defaultVal === undefined) ; else if (props.type === "radio") {
|
|
5092
5106
|
if (defaultVal === true) {
|
|
@@ -9169,8 +9183,8 @@ const isLanguageSupported = (lang, supportedLanguages) => {
|
|
|
9169
9183
|
* supported), falls back to `supportedLanguagesSignal` itself so callers
|
|
9170
9184
|
* still get *something* usable rather than an empty array.
|
|
9171
9185
|
*
|
|
9172
|
-
* Consumers that accept either a single lang or an ordered array
|
|
9173
|
-
*
|
|
9186
|
+
* Consumers that accept either a single lang or an ordered array
|
|
9187
|
+
* (@jsenv/humanize's `createI18n` and formatters, and native `Intl.NumberFormat`/
|
|
9174
9188
|
* `Intl.DateTimeFormat`) can pass this straight through: anything not
|
|
9175
9189
|
* covered by the first entry falls through to the next, rather than
|
|
9176
9190
|
* jumping straight to an unrelated default like "en".
|
|
@@ -10153,15 +10167,15 @@ const readOnlyMessage = (field) => {
|
|
|
10153
10167
|
* - Action dispatch + validity: `control_action.js` / `dispatchRequestAction`
|
|
10154
10168
|
* - Validity checking: `control_validation.js`
|
|
10155
10169
|
*
|
|
10156
|
-
* Each UI state controller gets its own `
|
|
10157
|
-
* `createControlInteraction`)
|
|
10170
|
+
* Each UI state controller gets its own instance, `controller.rules.interaction`
|
|
10171
|
+
* (created by `createControlInteraction`), beside `controller.rules.validation`.
|
|
10158
10172
|
*
|
|
10159
10173
|
* Flow:
|
|
10160
10174
|
* user interaction
|
|
10161
10175
|
* → dispatchRequestInteraction
|
|
10162
10176
|
* → "navi_request_interaction" event
|
|
10163
10177
|
* → onRequestInteraction
|
|
10164
|
-
* → check disabled / read-only / busy (via controller.
|
|
10178
|
+
* → check disabled / read-only / busy (via controller.rules.interaction)
|
|
10165
10179
|
* against the interaction's `intent` ("write" by default, "read" for one
|
|
10166
10180
|
* that only shows what is already there — see READONLY_CONSTRAINT)
|
|
10167
10181
|
* → if blocked → prevented()
|
|
@@ -11004,7 +11018,8 @@ const interactionsDisputeThePress = (interactions) => {
|
|
|
11004
11018
|
* control (a Button) is its own; a Box inside one, or wrapping exactly one,
|
|
11005
11019
|
* reaches it; a Box with no control anywhere near it — or laying out several,
|
|
11006
11020
|
* which belongs to none of them — still answers with a callback of the caller's,
|
|
11007
|
-
* and only "request_action"
|
|
11021
|
+
* and only the two requests ("request_action", "request_ui_action") have
|
|
11022
|
+
* nothing to ask.
|
|
11008
11023
|
*
|
|
11009
11024
|
* Set up once per element rather than on every render, which is what lets a
|
|
11010
11025
|
* detector be a plain `setup`/teardown pair. So the interactions themselves are
|
|
@@ -11173,7 +11188,7 @@ const useInteractionsEffect = (ref, interactionsRef) => {
|
|
|
11173
11188
|
cancelable: true,
|
|
11174
11189
|
});
|
|
11175
11190
|
// The event the interaction was read from stays reachable from it:
|
|
11176
|
-
// `findEvent(actionEvent, "pointerdown")` still finds the press a
|
|
11191
|
+
// `findEvent(actionEvent, "pointerdown")` still finds the press a hold
|
|
11177
11192
|
// was made of.
|
|
11178
11193
|
chainEvent(interactionEvent, originalEvent);
|
|
11179
11194
|
if (!element.dispatchEvent(interactionEvent)) {
|
|
@@ -11939,7 +11954,7 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
11939
11954
|
* …
|
|
11940
11955
|
* }}}
|
|
11941
11956
|
*
|
|
11942
|
-
* `toId`
|
|
11957
|
+
* `toId` names an element and is never null: a copy over nothing is a release that meant
|
|
11943
11958
|
* nothing, and the interaction does not happen at all — unless the element
|
|
11944
11959
|
* declares `leave` (see below), which gives that release a meaning of its own.
|
|
11945
11960
|
*
|
|
@@ -12634,8 +12649,9 @@ defineInteractionDetector({
|
|
|
12634
12649
|
name: "surface",
|
|
12635
12650
|
claims: (type) =>
|
|
12636
12651
|
type === PAN || type === ZOOM || type === GRAB || type === RELEASE,
|
|
12637
|
-
// A press on the surface may be a tap, a hold or the
|
|
12638
|
-
// nothing may read it as the first until the pointer has said
|
|
12652
|
+
// A press on the surface may be a tap, a hold or the first finger of a pan or
|
|
12653
|
+
// a pinch, and nothing may read it as the first until the pointer has said
|
|
12654
|
+
// which.
|
|
12639
12655
|
disputesPress: true,
|
|
12640
12656
|
setup: (element, trigger, { types, readConfig }) => {
|
|
12641
12657
|
const canPan = types.includes(PAN);
|
|
@@ -15757,21 +15773,33 @@ const getActionResultProperties = (action) => {
|
|
|
15757
15773
|
return actionResultPropertiesMap.get(action);
|
|
15758
15774
|
};
|
|
15759
15775
|
|
|
15760
|
-
// PUT/PATCH results update the UI through the store, and DELETE resets the GET
|
|
15761
|
-
// instead of rerunning it, so a GET rerun would only ever cost a request.
|
|
15762
|
-
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15763
|
-
// belongs to a list depends on filters/pagination the backend owns.
|
|
15764
15776
|
// Rationale in full, plus when to override: docs/list_refresh.md
|
|
15765
15777
|
const defaultRerunOn = {
|
|
15778
|
+
// PUT/PATCH results update the UI through the store, and DELETE resets the
|
|
15779
|
+
// GET rather than rerunning it, so a GET rerun would only ever cost a request.
|
|
15766
15780
|
GET: false,
|
|
15781
|
+
// A POST is the one case the client cannot decide alone: whether a new item
|
|
15782
|
+
// belongs to a list depends on filters/pagination the backend owns.
|
|
15767
15783
|
GET_MANY: ["POST"],
|
|
15784
|
+
// DELETE is in there, unlike for GET_MANY: an action holds ids and the store
|
|
15785
|
+
// drops the deleted one out of every list holding it, while a list reading
|
|
15786
|
+
// by slices holds places — the row that left takes the ones after it one
|
|
15787
|
+
// rank up, and only the collection knows who fills the last one.
|
|
15788
|
+
GET_RANGE: ["POST", "DELETE"],
|
|
15789
|
+
};
|
|
15790
|
+
// A key a `rerunOn` leaves out keeps the value it would have had without it:
|
|
15791
|
+
// the default for a resource, the value of the resource it is declared on for
|
|
15792
|
+
// a withParams() scope or a relation.
|
|
15793
|
+
const resolveRerunOn = (rerunOn, inheritedRerunOn = defaultRerunOn) => {
|
|
15794
|
+
if (!rerunOn) {
|
|
15795
|
+
return inheritedRerunOn;
|
|
15796
|
+
}
|
|
15797
|
+
return {
|
|
15798
|
+
GET: rerunOn.GET ?? inheritedRerunOn.GET,
|
|
15799
|
+
GET_MANY: rerunOn.GET_MANY ?? inheritedRerunOn.GET_MANY,
|
|
15800
|
+
GET_RANGE: rerunOn.GET_RANGE ?? inheritedRerunOn.GET_RANGE,
|
|
15801
|
+
};
|
|
15768
15802
|
};
|
|
15769
|
-
// What makes a range reader stale (rerunOn.GET_RANGE overrides it). DELETE is
|
|
15770
|
-
// in there, unlike for GET_MANY: an action holds ids and the store drops the
|
|
15771
|
-
// deleted one out of every list holding it, while a list reading by slices
|
|
15772
|
-
// holds places — the row that left takes the ones after it one rank up, and
|
|
15773
|
-
// only the collection knows who fills the last one.
|
|
15774
|
-
const defaultInvalidateRangeOn = ["POST", "DELETE"];
|
|
15775
15803
|
|
|
15776
15804
|
// This handles ALL resource lifecycle logic (rerun/reset) across all resources
|
|
15777
15805
|
const createResourceLifecycleManager = () => {
|
|
@@ -15781,7 +15809,7 @@ const createResourceLifecycleManager = () => {
|
|
|
15781
15809
|
|
|
15782
15810
|
const registerResource = (resourceScope, config) => {
|
|
15783
15811
|
const {
|
|
15784
|
-
rerunOn
|
|
15812
|
+
rerunOn,
|
|
15785
15813
|
paramScope = null,
|
|
15786
15814
|
dependencies = [],
|
|
15787
15815
|
uniqueKeys = [],
|
|
@@ -16064,8 +16092,7 @@ const createResourceLifecycleManager = () => {
|
|
|
16064
16092
|
if (!isSameResource && !isDependent) {
|
|
16065
16093
|
continue;
|
|
16066
16094
|
}
|
|
16067
|
-
|
|
16068
|
-
if (!shouldRerunAfter(invalidateOn, triggerVerb)) {
|
|
16095
|
+
if (!shouldRerunAfter(config.rerunOn.GET_RANGE, triggerVerb)) {
|
|
16069
16096
|
continue;
|
|
16070
16097
|
}
|
|
16071
16098
|
for (const rangeReader of config.rangeReaderSet) {
|
|
@@ -16403,7 +16430,7 @@ const debug$3 = (...args) => {
|
|
|
16403
16430
|
* @param {string} name - resource name, used in action names and error messages
|
|
16404
16431
|
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
16405
16432
|
* @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"`
|
|
16406
|
-
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
16433
|
+
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
16407
16434
|
* @see docs/resource.md — relationships, callback return contracts, decision table
|
|
16408
16435
|
*
|
|
16409
16436
|
* @example
|
|
@@ -16566,7 +16593,7 @@ const resource = (
|
|
|
16566
16593
|
addItemSetup,
|
|
16567
16594
|
createRestAction: createRestActionForRoot,
|
|
16568
16595
|
paramScope: getParamScope(undefined),
|
|
16569
|
-
rerunOn,
|
|
16596
|
+
rerunOn: resolveRerunOn(rerunOn),
|
|
16570
16597
|
dependencies,
|
|
16571
16598
|
});
|
|
16572
16599
|
};
|
|
@@ -16630,7 +16657,7 @@ const createResource = (
|
|
|
16630
16657
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16631
16658
|
*
|
|
16632
16659
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16633
|
-
* @param {Object} options -
|
|
16660
|
+
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; a `rerunOn` key left out keeps the resource's value, and `dependencies` left out are the resource's
|
|
16634
16661
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
16635
16662
|
* @see docs/resource.md — what a scope isolates, and `dependencies`
|
|
16636
16663
|
*
|
|
@@ -16648,7 +16675,7 @@ const createResource = (
|
|
|
16648
16675
|
* const ROLE_WITH_OWNERSHIP = role.withParams({ owners: true }, {
|
|
16649
16676
|
* dependencies: [role, database, tables],
|
|
16650
16677
|
* });
|
|
16651
|
-
* // ROLE_WITH_OWNERSHIP.GET_MANY
|
|
16678
|
+
* // ROLE_WITH_OWNERSHIP.GET_MANY reruns after any write (POST/PUT/PATCH/DELETE) on a role, a database or a table
|
|
16652
16679
|
*/
|
|
16653
16680
|
const withParams = (
|
|
16654
16681
|
paramsToInject,
|
|
@@ -16676,7 +16703,7 @@ const createResource = (
|
|
|
16676
16703
|
addItemSetup,
|
|
16677
16704
|
createRestAction: createRestActionWithParams,
|
|
16678
16705
|
paramScope: resolvedParamScope,
|
|
16679
|
-
rerunOn: withParamsRerunOn
|
|
16706
|
+
rerunOn: resolveRerunOn(withParamsRerunOn, rerunOn),
|
|
16680
16707
|
dependencies: withParamsDeps ?? dependencies,
|
|
16681
16708
|
});
|
|
16682
16709
|
};
|
|
@@ -16694,13 +16721,15 @@ const createResource = (
|
|
|
16694
16721
|
* Callback return contracts:
|
|
16695
16722
|
* - GET / PUT → the parent object with the relationship nested inside:
|
|
16696
16723
|
* `async ({ id }) => ({ id, session: { id: 10, token: "abc" } })`; `null` for no relationship
|
|
16697
|
-
* - DELETE → the parent id
|
|
16724
|
+
* - DELETE → the parent id, or `{ id }`; the property is set to `null`
|
|
16698
16725
|
*
|
|
16699
16726
|
* The backend may also embed the child inline in a parent GET/POST response — the
|
|
16700
16727
|
* setter on the property upserts the nested object into the child store.
|
|
16701
16728
|
*
|
|
16702
|
-
* Returns the
|
|
16703
|
-
* `USER_SESSION.one("device", DEVICE)`
|
|
16729
|
+
* Returns the relationship resource, itself chainable. Its items are the child
|
|
16730
|
+
* resource's own, shared by every parent: `USER_SESSION.one("device", DEVICE)`
|
|
16731
|
+
* adds a reactive `.device` property to every session, as
|
|
16732
|
+
* `SESSION.one("device", DEVICE)` would.
|
|
16704
16733
|
*
|
|
16705
16734
|
* @param {string} propertyName - property holding the child on each parent item
|
|
16706
16735
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
@@ -16783,11 +16812,11 @@ const createResource = (
|
|
|
16783
16812
|
const createRestActionForOne = (verb, callback, { onActionComplete }) => {
|
|
16784
16813
|
const applyResultToValue =
|
|
16785
16814
|
verb === "DELETE"
|
|
16786
|
-
? (
|
|
16787
|
-
const item = store.select(
|
|
16815
|
+
? (itemIdOrItemProps) => {
|
|
16816
|
+
const item = store.select(itemIdOrItemProps);
|
|
16788
16817
|
const childItemId = item[propertyName][childIdKey];
|
|
16789
16818
|
store.upsert({
|
|
16790
|
-
[idKey]:
|
|
16819
|
+
[idKey]: item[idKey],
|
|
16791
16820
|
[propertyName]: null,
|
|
16792
16821
|
});
|
|
16793
16822
|
return childItemId;
|
|
@@ -16815,7 +16844,7 @@ const createResource = (
|
|
|
16815
16844
|
if (!isProps(result) && !primitiveCanBeId(result)) {
|
|
16816
16845
|
throwInvalidResult(
|
|
16817
16846
|
action.name,
|
|
16818
|
-
`
|
|
16847
|
+
`the "${name}" id, or { ${idKey} } (the item whose "${propertyName}" becomes null)`,
|
|
16819
16848
|
result,
|
|
16820
16849
|
);
|
|
16821
16850
|
}
|
|
@@ -16840,11 +16869,11 @@ const createResource = (
|
|
|
16840
16869
|
PUT,
|
|
16841
16870
|
DELETE,
|
|
16842
16871
|
},
|
|
16843
|
-
store,
|
|
16844
|
-
addItemSetup,
|
|
16872
|
+
store: childStore,
|
|
16873
|
+
addItemSetup: childResource.addItemSetup,
|
|
16845
16874
|
createRestAction: createRestActionForOne,
|
|
16846
16875
|
paramScope,
|
|
16847
|
-
rerunOn: oneRerunOn
|
|
16876
|
+
rerunOn: resolveRerunOn(oneRerunOn, rerunOn),
|
|
16848
16877
|
dependencies: oneDependencies ?? dependencies,
|
|
16849
16878
|
});
|
|
16850
16879
|
};
|
|
@@ -16867,6 +16896,9 @@ const createResource = (
|
|
|
16867
16896
|
* - DELETE → `[parentId, childId]`
|
|
16868
16897
|
* - DELETE_MANY → `[parentId, [childId, childId, …]]`
|
|
16869
16898
|
*
|
|
16899
|
+
* Returns the relationship resource, itself chainable; its items are the child
|
|
16900
|
+
* resource's own, as for `.one()`.
|
|
16901
|
+
*
|
|
16870
16902
|
* @param {string} propertyName - property holding the child array on each parent item
|
|
16871
16903
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
16872
16904
|
* @param {Object} [restCallbacks] - `{ rerunOn, dependencies, GET, GET_MANY, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
@@ -17102,11 +17134,11 @@ const createResource = (
|
|
|
17102
17134
|
DELETE,
|
|
17103
17135
|
DELETE_MANY,
|
|
17104
17136
|
},
|
|
17105
|
-
store,
|
|
17106
|
-
addItemSetup,
|
|
17137
|
+
store: childStore,
|
|
17138
|
+
addItemSetup: childResource.addItemSetup,
|
|
17107
17139
|
createRestAction: createRestActionForMany,
|
|
17108
17140
|
paramScope,
|
|
17109
|
-
rerunOn: manyRerunOn
|
|
17141
|
+
rerunOn: resolveRerunOn(manyRerunOn, rerunOn),
|
|
17110
17142
|
dependencies: manyDependencies ?? dependencies,
|
|
17111
17143
|
});
|
|
17112
17144
|
};
|
|
@@ -17235,7 +17267,7 @@ const createResource = (
|
|
|
17235
17267
|
addItemSetup: childAddItemSetup,
|
|
17236
17268
|
createRestAction: createRestActionForScopedOne,
|
|
17237
17269
|
paramScope,
|
|
17238
|
-
rerunOn: scopedOneRerunOn
|
|
17270
|
+
rerunOn: resolveRerunOn(scopedOneRerunOn, rerunOn),
|
|
17239
17271
|
dependencies: scopedOneDependencies ?? dependencies,
|
|
17240
17272
|
});
|
|
17241
17273
|
};
|
|
@@ -17470,7 +17502,7 @@ const createResource = (
|
|
|
17470
17502
|
addItemSetup: childAddItemSetup,
|
|
17471
17503
|
createRestAction: createRestActionForScopedMany,
|
|
17472
17504
|
paramScope,
|
|
17473
|
-
rerunOn: scopedManyRerunOn
|
|
17505
|
+
rerunOn: resolveRerunOn(scopedManyRerunOn, rerunOn),
|
|
17474
17506
|
dependencies: scopedManyDependencies ?? dependencies,
|
|
17475
17507
|
});
|
|
17476
17508
|
// When a scoped child collection is mutated (POST etc.), the parent GET must
|
|
@@ -17861,7 +17893,7 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17861
17893
|
* });
|
|
17862
17894
|
*
|
|
17863
17895
|
* syncResourceToSignals(USER, { username: usernameSignal });
|
|
17864
|
-
* //
|
|
17896
|
+
* // When a user item's username is updated via USER.PUT,
|
|
17865
17897
|
* // usernameSignal.value is set to the new username,
|
|
17866
17898
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17867
17899
|
*/
|
|
@@ -17874,6 +17906,17 @@ const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
|
17874
17906
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17875
17907
|
};
|
|
17876
17908
|
|
|
17909
|
+
/**
|
|
17910
|
+
* The same, for a `scopedOne`/`scopedMany` resource: its items live in one store
|
|
17911
|
+
* per owner, so the store to watch is the one of the owner `ownerSignal` names
|
|
17912
|
+
* (by id or by any unique key), and it is switched when the signal changes.
|
|
17913
|
+
* Nothing is synced while `ownerSignal` holds `null`/`undefined` or names an
|
|
17914
|
+
* owner nothing has been loaded for yet.
|
|
17915
|
+
*
|
|
17916
|
+
* @param {Object} resource - a resource made by `.scopedOne()` / `.scopedMany()`
|
|
17917
|
+
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17918
|
+
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17919
|
+
*/
|
|
17877
17920
|
const syncOwnedResourceToSignals = (
|
|
17878
17921
|
resource,
|
|
17879
17922
|
ownerSignal,
|
|
@@ -17921,7 +17964,7 @@ const generateSignalId = () => {
|
|
|
17921
17964
|
* 1. Initially takes value from the default signal
|
|
17922
17965
|
* 2. When explicitly set (programmatically or via localStorage), the explicit value takes precedence
|
|
17923
17966
|
* 3. When default signal changes, it only updates if no explicit value was ever set
|
|
17924
|
-
* 4.
|
|
17967
|
+
* 4. Setting it to undefined makes the signal use the dynamic default again
|
|
17925
17968
|
* 5. If dynamic default is undefined and options.default is provided, uses the static fallback
|
|
17926
17969
|
*
|
|
17927
17970
|
* This is useful for:
|
|
@@ -17935,7 +17978,9 @@ const generateSignalId = () => {
|
|
|
17935
17978
|
* @param {string|number} [options.id] - Custom ID for the signal. If not provided, an auto-generated ID will be used. Used for localStorage key and route pattern detection.
|
|
17936
17979
|
* @param {any} [options.default] - Static fallback value used when defaultValue is a signal and that signal's value is undefined
|
|
17937
17980
|
* @param {boolean} [options.persists=false] - Whether to persist the signal value in localStorage using the signal ID as key
|
|
17938
|
-
* @param {
|
|
17981
|
+
* @param {string} [options.type] - Type the value is coerced to, and read back as from localStorage and the url:
|
|
17982
|
+
* any @jsenv/validity type (`"string"`, `"number"`, `"boolean"`, `"array"`, `"object"`, `"integer"`, `"date"`,
|
|
17983
|
+
* `"time"`…). Without it the value is not coerced.
|
|
17939
17984
|
* @param {"string" | "number" | "boolean"} [options.itemType] - For array type: type of the array items.
|
|
17940
17985
|
* Used when reading the value back from a url search param, where everything is a string:
|
|
17941
17986
|
* `?level=3,4` becomes `[3, 4]` instead of `["3", "4"]`. Without it items stay strings.
|
|
@@ -19105,14 +19150,12 @@ const LoadingFallback = ({
|
|
|
19105
19150
|
* Two things it gets right that a hand-written boundary rarely does, both
|
|
19106
19151
|
* explained in docs/error_handling.md:
|
|
19107
19152
|
*
|
|
19108
|
-
* - It marks the error as displayed ONLY when it actually displays it.
|
|
19109
|
-
*
|
|
19110
|
-
*
|
|
19111
|
-
*
|
|
19112
|
-
*
|
|
19113
|
-
*
|
|
19114
|
-
* silent one. Without a `fallback` there is nothing to display, so the error is
|
|
19115
|
-
* left alone and continues up.
|
|
19153
|
+
* - It marks the error as displayed ONLY when it actually displays it. The
|
|
19154
|
+
* jsenv overlay skips an error carrying `__handled_by__`, so a mark set before
|
|
19155
|
+
* knowing whether anything is rendered mutes an error that then continues up:
|
|
19156
|
+
* a TypeError in a component becomes a blank page AND a silent one. Without a
|
|
19157
|
+
* `fallback` there is nothing to display, so the error is left alone and
|
|
19158
|
+
* continues up.
|
|
19116
19159
|
*
|
|
19117
19160
|
* - It resets on navigation, not only on rerun. Rerunning the failed action is
|
|
19118
19161
|
* one way out; going somewhere else is the common one. Without a reset on the
|
|
@@ -20178,9 +20221,9 @@ const isSizeSpacingKey = (key) => {
|
|
|
20178
20221
|
// "vvw"/"vvh" are navi's own: the *visual* viewport, which — unlike vw/dvw —
|
|
20179
20222
|
// shrinks when the mobile virtual keyboard opens (see layout/responsive.js), so
|
|
20180
20223
|
// they are what a popup meant to stay clear of the keyboard should use.
|
|
20181
|
-
// "appw"/"apph" are the same thing narrowed to the app's own screen:
|
|
20182
|
-
//
|
|
20183
|
-
//
|
|
20224
|
+
// "appw"/"apph" are the same thing narrowed to the app's own screen: the
|
|
20225
|
+
// visual viewport minus the app's bands (--navi-app-inset-*), so identical to
|
|
20226
|
+
// vvw/vvh until the app declares bands. A gap meant to read as "a small margin" must use these —
|
|
20184
20227
|
// 3vvw on a 1500px window is a 45px gap around a 600px app.
|
|
20185
20228
|
// Functions rather than the signals themselves: appw/apph are not a signal to
|
|
20186
20229
|
// read but a value to compute (a signal, then a CSS var read back). Reading the
|
|
@@ -22222,9 +22265,48 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
22222
22265
|
* separator?: import("ignore:preact").ComponentChildren | ((index: number) => import("ignore:preact").ComponentChildren),
|
|
22223
22266
|
* selfInteractions?: string,
|
|
22224
22267
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
22268
|
+
* alignX?: string,
|
|
22269
|
+
* alignY?: string,
|
|
22270
|
+
* spacing?: string | number,
|
|
22271
|
+
* overflow?: string,
|
|
22272
|
+
* header?: boolean,
|
|
22273
|
+
* footer?: boolean,
|
|
22274
|
+
* body?: boolean,
|
|
22275
|
+
* sticky?: boolean,
|
|
22225
22276
|
* children?: import("ignore:preact").ComponentChildren,
|
|
22226
22277
|
* [key: string]: any,
|
|
22227
22278
|
* }>}
|
|
22279
|
+
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
22280
|
+
* side, `"y"` stacked. Named by axis because `flex-direction: column` stacks
|
|
22281
|
+
* items vertically, the opposite of what "column" suggests.
|
|
22282
|
+
* @param {boolean} [inline] The inline form of the box's display
|
|
22283
|
+
* (`inline-flex`, `inline-grid`…). An inline box given a `width`/`height`
|
|
22284
|
+
* becomes an inline flex row, since an inline box ignores both.
|
|
22285
|
+
* @param {string} [alignX] Horizontal alignment of the content whatever the
|
|
22286
|
+
* flex direction: `justify-content` or `align-items` is picked from it
|
|
22287
|
+
* (`text-align` outside flex and grid).
|
|
22288
|
+
* @param {string} [alignY] Vertical alignment, the same way.
|
|
22289
|
+
* @param {string|number} [spacing] The gap between children in a flex or grid
|
|
22290
|
+
* box (`spacingX`/`spacingY` per axis) — there is no `gap` prop. Spacing
|
|
22291
|
+
* props (`spacing`, `margin*`, `padding*`) take the scale `"xxs"`…`"xxl"`
|
|
22292
|
+
* (`--navi-xxs`…), `"-s"` for a negative, or any CSS value.
|
|
22293
|
+
* @param {object|string} [style] CSS properties, plus pseudo keys holding
|
|
22294
|
+
* their own: `style={{ color: "blue", ":hover": { color: "red" } }}`
|
|
22295
|
+
* (`":-navi-pressed"`, `"::before"`…). A pseudo class used there is tracked
|
|
22296
|
+
* on the element; nothing re-renders when it changes.
|
|
22297
|
+
* @param {string} [overflow] `"auto"`/`"scroll"` (`overflowX`/`overflowY`
|
|
22298
|
+
* too) makes the box a scrolling area, which is what gives `header`/`body`/
|
|
22299
|
+
* `footer` their meaning; such a box is also `isolation: isolate` and gets a
|
|
22300
|
+
* `scroll-padding` (see docs/scroll.md).
|
|
22301
|
+
* @param {boolean} [header] A part that stays put at the top of the scrolling
|
|
22302
|
+
* box it is a direct child of — nothing at all without that box's overflow.
|
|
22303
|
+
* Rendered as `<header>` unless `as` says otherwise.
|
|
22304
|
+
* @param {boolean} [footer] The same at the bottom, rendered as `<footer>`.
|
|
22305
|
+
* @param {boolean} [body] The only part of its scrolling box that scrolls,
|
|
22306
|
+
* `header`/`footer` sitting outside it. It shrinks, it never grows.
|
|
22307
|
+
* @param {boolean} [sticky] `position: sticky` in the sticky z-index band
|
|
22308
|
+
* (`--navi-z-index-sticky`), stuck or not; an explicit `zIndex`, `"auto"`
|
|
22309
|
+
* included, wins (see docs/z_index.md). `top`/`bottom`/… take `true` for 0.
|
|
22228
22310
|
* @param {string} [background] Any CSS background, plus two keywords for the
|
|
22229
22311
|
* planes an app paints: `"surface"` is the paper content sits on,
|
|
22230
22312
|
* `"chrome"` the frame around a screen — a top bar, a side menu, a toolbar.
|
|
@@ -23968,21 +24050,16 @@ const createUITransitionController = (
|
|
|
23968
24050
|
/**
|
|
23969
24051
|
* UITransition
|
|
23970
24052
|
*
|
|
23971
|
-
* A Preact component that
|
|
23972
|
-
*
|
|
24053
|
+
* A Preact component that animates the change between its children: the
|
|
24054
|
+
* outgoing content fades out while the incoming one fades in, and the box
|
|
24055
|
+
* resizes from the one to the other (the mechanics are in ui_transition.js).
|
|
23973
24056
|
*
|
|
23974
|
-
*
|
|
23975
|
-
* -
|
|
23976
|
-
*
|
|
23977
|
-
*
|
|
23978
|
-
* -
|
|
23979
|
-
*
|
|
23980
|
-
*
|
|
23981
|
-
* Usage:
|
|
23982
|
-
* - Wrap dynamic content in <UITransition> to animate between states
|
|
23983
|
-
* - Set a unique `data-content-id` on your rendered content to identify each content variant
|
|
23984
|
-
* - Use `data-content-phase` to mark loading/error states for phase transitions
|
|
23985
|
-
* - Configure transition types and durations for both content and phase changes
|
|
24057
|
+
* Two kinds of change are told apart, from attributes on the rendered content:
|
|
24058
|
+
* - a content transition, when the content key changes — `data-content-key` on
|
|
24059
|
+
* the rendered element (a user id, a search), or `useUITransitionContentId`
|
|
24060
|
+
* called from a component inside (what `<Route>` does with its url pattern);
|
|
24061
|
+
* - a phase transition, when `data-content-phase` (a loading or error state)
|
|
24062
|
+
* changes for the same content key.
|
|
23986
24063
|
*
|
|
23987
24064
|
* Example:
|
|
23988
24065
|
*
|
|
@@ -23991,9 +24068,6 @@ const createUITransitionController = (
|
|
|
23991
24068
|
* ? <Spinner data-content-key={userId} data-content-phase />
|
|
23992
24069
|
* : <UserProfile user={user} data-content-key={userId} />}
|
|
23993
24070
|
* </UITransition>
|
|
23994
|
-
*
|
|
23995
|
-
* When `data-content-id` changes, UITransition animates content transitions.
|
|
23996
|
-
* When `data-content-phase` changes for the same key, it animates phase transitions.
|
|
23997
24071
|
*/
|
|
23998
24072
|
|
|
23999
24073
|
const UITransitionContentIdContext = createContext();
|
|
@@ -24115,16 +24189,12 @@ const UITransition = ({
|
|
|
24115
24189
|
};
|
|
24116
24190
|
|
|
24117
24191
|
/**
|
|
24118
|
-
*
|
|
24119
|
-
*
|
|
24120
|
-
*
|
|
24121
|
-
* When the key changes, UITransition will be able to detect that and consider the content
|
|
24122
|
-
* as changed even if the component is still the same
|
|
24123
|
-
*
|
|
24124
|
-
* This is used by <Route> to set the content key to the route path
|
|
24125
|
-
* When the route becomes inactive it will call useUITransitionContentId(undefined)
|
|
24126
|
-
* And if a sibling route becones active it will call useUITransitionContentId with its own path
|
|
24192
|
+
* Names the content a component renders, for the <UITransition> around it:
|
|
24193
|
+
* everything rendered inside is identified by that key, and the key changing
|
|
24194
|
+
* is read as a content change even though the component is the same.
|
|
24127
24195
|
*
|
|
24196
|
+
* <Route> calls it with the url pattern of the branch it renders; the branch
|
|
24197
|
+
* unmounting removes its part, and a sibling branch mounting adds its own.
|
|
24128
24198
|
*/
|
|
24129
24199
|
const useUITransitionContentId = value => {
|
|
24130
24200
|
const contentId = useContext(UITransitionContentIdContext);
|
|
@@ -24149,8 +24219,8 @@ const useUITransitionContentId = value => {
|
|
|
24149
24219
|
};
|
|
24150
24220
|
|
|
24151
24221
|
/**
|
|
24152
|
-
* Custom route pattern matching system
|
|
24153
|
-
*
|
|
24222
|
+
* Custom route pattern matching system.
|
|
24223
|
+
* Not built on URLPattern: matching here is simpler and more predictable.
|
|
24154
24224
|
*/
|
|
24155
24225
|
|
|
24156
24226
|
|
|
@@ -27482,7 +27552,7 @@ const useRouteStatus = (route) => {
|
|
|
27482
27552
|
const assertRoute = (route) => {
|
|
27483
27553
|
if (!route.isRoute) {
|
|
27484
27554
|
throw new Error(
|
|
27485
|
-
"The route prop must be a route object created with
|
|
27555
|
+
"The route prop must be a route object created with route()",
|
|
27486
27556
|
);
|
|
27487
27557
|
}
|
|
27488
27558
|
if (!setupRoutesCalled) {
|
|
@@ -30157,8 +30227,8 @@ const TRANSITION_FURNITURE_CSS = /* css */ `.navi_fixed_bar[data-navi-transition
|
|
|
30157
30227
|
}
|
|
30158
30228
|
`;
|
|
30159
30229
|
|
|
30160
|
-
// Called
|
|
30161
|
-
//
|
|
30230
|
+
// Called as a movement starts over a marked area, never at module scope: a
|
|
30231
|
+
// page that never travels between routes must not carry this sheet, and a
|
|
30162
30232
|
// build that sees no caller drops the css with the function.
|
|
30163
30233
|
const installTransitionFurnitureCss = () => {
|
|
30164
30234
|
import.meta.css = [TRANSITION_FURNITURE_CSS, "@jsenv/navi/src/nav/transition_furniture.js"];
|
|
@@ -30621,9 +30691,10 @@ const TRANSITION_WINDOW_CSS = /* css */ `@property --navi-transition-cover-top {
|
|
|
30621
30691
|
}
|
|
30622
30692
|
`;
|
|
30623
30693
|
|
|
30624
|
-
// Called
|
|
30625
|
-
// a page that never travels between routes must not
|
|
30626
|
-
// build that sees no caller drops the css with the
|
|
30694
|
+
// Called by whatever needs the window — a render, or a movement starting —
|
|
30695
|
+
// never at module scope: a page that never travels between routes must not
|
|
30696
|
+
// carry this sheet, and a build that sees no caller drops the css with the
|
|
30697
|
+
// function.
|
|
30627
30698
|
const installTransitionWindowCss = () => {
|
|
30628
30699
|
import.meta.css = [TRANSITION_WINDOW_CSS, "@jsenv/navi/src/nav/transition_window.js"];
|
|
30629
30700
|
};
|
|
@@ -31436,9 +31507,6 @@ const RouteTransitionArea = ({
|
|
|
31436
31507
|
children,
|
|
31437
31508
|
...rest
|
|
31438
31509
|
}) => {
|
|
31439
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31440
|
-
installTransitionWindowCss();
|
|
31441
|
-
installTransitionFurnitureCss();
|
|
31442
31510
|
const props = {
|
|
31443
31511
|
...rest,
|
|
31444
31512
|
[TRANSITION_AREA_ATTRIBUTE]: ""
|
|
@@ -31453,8 +31521,9 @@ const RouteTransitionArea = ({
|
|
|
31453
31521
|
* Declare how a pair of routes moves against each other — or how one route is
|
|
31454
31522
|
* entered, from wherever its door happens to be.
|
|
31455
31523
|
*
|
|
31456
|
-
* @param {object|null} from - a route
|
|
31457
|
-
* a param of a route rather than a route
|
|
31524
|
+
* @param {object|null} from - a route (or the `routeFallback()` page), or
|
|
31525
|
+
* `{ route, params }` when the page is a param of a route rather than a route
|
|
31526
|
+
* of its own. `null` says the page is
|
|
31458
31527
|
* reached FROM ANYWHERE: its door is in the furniture (a gear in the top
|
|
31459
31528
|
* bar, a "+" in the tab bar) rather than on a screen, so there is no pair to
|
|
31460
31529
|
* write it on. Arriving at `to` from any page then plays forward and leaving
|
|
@@ -31502,13 +31571,10 @@ const RouteTransitionArea = ({
|
|
|
31502
31571
|
* Whatever is written here is what EVERY crossing of the pair plays. One
|
|
31503
31572
|
* crossing can ask for something else — `<Link routeTransition>`, or
|
|
31504
31573
|
* navTo(url, { routeTransition }) — which overrides this field by field, for
|
|
31505
|
-
* that
|
|
31506
|
-
* navigation alone.
|
|
31574
|
+
* that navigation alone.
|
|
31507
31575
|
* @returns {() => void} remove this relation.
|
|
31508
31576
|
*/
|
|
31509
31577
|
const defineRouteTransition = (from, to, transition) => {
|
|
31510
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31511
|
-
installTransitionWindowCss();
|
|
31512
31578
|
if (!to) {
|
|
31513
31579
|
throw new TypeError(`defineRouteTransition needs a destination: "to" is ${to}. The page reached from anywhere is written defineRouteTransition(null, THAT_PAGE, ...) — there is no relation the other way round, a page LEFT for anywhere being the back half of that one.`);
|
|
31514
31580
|
}
|
|
@@ -31548,8 +31614,6 @@ const defineRouteTransition = (from, to, transition) => {
|
|
|
31548
31614
|
* @returns {() => void} remove this default.
|
|
31549
31615
|
*/
|
|
31550
31616
|
const defineRouteDefaultTransition = transition => {
|
|
31551
|
-
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
31552
|
-
installTransitionWindowCss();
|
|
31553
31617
|
const value = normalizeTransition(transition);
|
|
31554
31618
|
defaultTransition = value;
|
|
31555
31619
|
return () => {
|
|
@@ -31586,9 +31650,8 @@ const normalizeTransition = transition => {
|
|
|
31586
31650
|
*
|
|
31587
31651
|
* Two mouths, one meaning: the element being pressed wears it (a `<Link
|
|
31588
31652
|
* routeTransition>`, or the attribute by hand on any anchor), or navTo() is
|
|
31589
|
-
* handed
|
|
31590
|
-
* it
|
|
31591
|
-
* writes anything (see before_routing.js).
|
|
31653
|
+
* handed it. Both arrive here through the announcement the navigation makes
|
|
31654
|
+
* before it writes anything (see before_routing.js).
|
|
31592
31655
|
*
|
|
31593
31656
|
* A request answers FIELD BY FIELD: what it does not say, the relation — or
|
|
31594
31657
|
* the default — still answers for. So `{ direction: "back" }` keeps the pair's
|
|
@@ -32132,6 +32195,10 @@ const beginTransition = ({
|
|
|
32132
32195
|
// Said before the picture is taken: whoever names something for a movement
|
|
32133
32196
|
// between two pages decides on it now (see transition_destination.js).
|
|
32134
32197
|
holdTransitionDestination(transition, url);
|
|
32198
|
+
// Adopted by what starts a movement rather than by what declares one: a link
|
|
32199
|
+
// or a navTo() asks for a movement in an app that declared none, and an app
|
|
32200
|
+
// that never moves never carries the sheet.
|
|
32201
|
+
import.meta.css = [css$12, "@jsenv/navi/src/nav/route_transition.jsx"];
|
|
32135
32202
|
documentElement.setAttribute(TRANSITION_ATTRIBUTE, direction);
|
|
32136
32203
|
if (type) {
|
|
32137
32204
|
documentElement.setAttribute(TRANSITION_TYPE_ATTRIBUTE, type);
|
|
@@ -32153,6 +32220,10 @@ const beginTransition = ({
|
|
|
32153
32220
|
// left (see transition_window.js).
|
|
32154
32221
|
let areaStateBefore = null;
|
|
32155
32222
|
if (areaElement) {
|
|
32223
|
+
// The area is the application's element as often as <RouteTransitionArea>,
|
|
32224
|
+
// so what a movement on it plays with is adopted here, where it is found.
|
|
32225
|
+
installTransitionWindowCss();
|
|
32226
|
+
installTransitionFurnitureCss();
|
|
32156
32227
|
documentElement.setAttribute(TRANSITION_TARGET_ATTRIBUTE, "area");
|
|
32157
32228
|
// Said before the picture is taken, like every name (see
|
|
32158
32229
|
// transition_furniture.js): what the bars are wearing when the transition
|
|
@@ -32835,10 +32906,10 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
32835
32906
|
* The pages are cut at the edge of this box while they travel, and at the app's
|
|
32836
32907
|
* safe area the box runs under, which is written on the transition's own
|
|
32837
32908
|
* pseudo-elements — no overflow of the document reaches pictures drawn in the
|
|
32838
|
-
* top layer. It needs nothing of the browser beyond view
|
|
32839
|
-
*
|
|
32840
|
-
*
|
|
32841
|
-
*
|
|
32909
|
+
* top layer. It needs nothing of the browser beyond view transitions
|
|
32910
|
+
* themselves: a browser without them navigates without the movement, and the
|
|
32911
|
+
* gesture applies its change on release instead of dragging a picture that does
|
|
32912
|
+
* not exist.
|
|
32842
32913
|
*
|
|
32843
32914
|
* While a travel plays, the rest of the page is taken as a picture too — this
|
|
32844
32915
|
* box asks for `view-transition-name: root` back for that time, so an
|
|
@@ -34429,9 +34500,6 @@ const onNaviCommand = (e, { debugCommand = () => {} } = {}) => {
|
|
|
34429
34500
|
`targeting`,
|
|
34430
34501
|
commandTarget,
|
|
34431
34502
|
);
|
|
34432
|
-
// Timed once, for the wheel-in-dialog "Définir feels frozen on mobile" case;
|
|
34433
|
-
// the line is gone now that the answer is known — a command that runs in 1ms
|
|
34434
|
-
// said nothing, and it said it on every single interaction.
|
|
34435
34503
|
return implementation();
|
|
34436
34504
|
};
|
|
34437
34505
|
|
|
@@ -35076,8 +35144,8 @@ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
|
|
|
35076
35144
|
target,
|
|
35077
35145
|
implementation: () => {
|
|
35078
35146
|
// The popup reads `anchor` first and falls back to the source (see
|
|
35079
|
-
// onnavi_request_open in popover.jsx)
|
|
35080
|
-
//
|
|
35147
|
+
// onnavi_request_open in popover.jsx): without an `anchor`, the element
|
|
35148
|
+
// asking is the element opened against.
|
|
35081
35149
|
return dispatchCustomEvent(target, "navi_request_open", {
|
|
35082
35150
|
event,
|
|
35083
35151
|
source: resolveCommandProxySource(source),
|
|
@@ -37548,8 +37616,8 @@ const usePopupContentMount = (
|
|
|
37548
37616
|
* are no-ops.
|
|
37549
37617
|
*
|
|
37550
37618
|
* Behavior:
|
|
37551
|
-
* - No <dialog>/<details>/[popover] ancestor → runs like a
|
|
37552
|
-
* useLayoutEffect with the provided deps.
|
|
37619
|
+
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor → runs like a
|
|
37620
|
+
* normal useLayoutEffect with the provided deps.
|
|
37553
37621
|
* - Inside a closed/hidden ancestor → skips the initial run; instead runs
|
|
37554
37622
|
* the callback once the ancestor opens — see @jsenv/dom's own
|
|
37555
37623
|
* observeAncestorOpenState for exactly how that's detected, and why it
|
|
@@ -37563,8 +37631,9 @@ const usePopupContentMount = (
|
|
|
37563
37631
|
* reveals nothing about them, so it does not re-run them either. See
|
|
37564
37632
|
* isDisplayedDespiteClosedAncestor in @jsenv/dom.
|
|
37565
37633
|
*
|
|
37566
|
-
* The callback
|
|
37567
|
-
*
|
|
37634
|
+
* The callback is called with the element (`ref.current`), and a
|
|
37635
|
+
* `navi_displayed` CustomEvent as its second argument, with
|
|
37636
|
+
* `detail: { ancestor, ancestorType, becauseAncestorOpened }`:
|
|
37568
37637
|
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor at all →
|
|
37569
37638
|
* `{ ancestor: document, ancestorType: "document" }`.
|
|
37570
37639
|
* - Otherwise → `{ ancestor: <the matched element>, ancestorType: "dialog"
|
|
@@ -37729,6 +37798,8 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
37729
37798
|
// see also https://github.com/preactjs/preact/issues/1255
|
|
37730
37799
|
|
|
37731
37800
|
|
|
37801
|
+
const NO_ELEMENT_REF = { current: null };
|
|
37802
|
+
|
|
37732
37803
|
/**
|
|
37733
37804
|
* Programmatic autofocus that runs after Preact layout effects are flushed.
|
|
37734
37805
|
*
|
|
@@ -37781,13 +37852,12 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
37781
37852
|
* the browser's built-in scroll-into-view that accompanies focus.
|
|
37782
37853
|
* @param {boolean} [options.focusVisible]
|
|
37783
37854
|
* Passed as `focusVisible` to `element.focus()`.
|
|
37784
|
-
* @param {boolean} [options.
|
|
37855
|
+
* @param {boolean} [options.autoSelect]
|
|
37785
37856
|
* When true, also calls `element.select()` after focusing (useful for text inputs).
|
|
37786
|
-
* @returns {
|
|
37787
|
-
*
|
|
37857
|
+
* @returns {object} The attributes to spread on the element: `navi-autofocus`
|
|
37858
|
+
* (and `navi-autofocus-select`), with `autoFocus` set to undefined so the
|
|
37859
|
+
* native attribute never lands on the node.
|
|
37788
37860
|
*/
|
|
37789
|
-
const NO_ELEMENT_REF = { current: null };
|
|
37790
|
-
|
|
37791
37861
|
const useAutoFocus = (
|
|
37792
37862
|
focusableElementRef,
|
|
37793
37863
|
autoFocus,
|
|
@@ -37982,9 +38052,10 @@ document.body.addEventListener(
|
|
|
37982
38052
|
* and nothing about it stops someone from starting a run on every row before
|
|
37983
38053
|
* any of them comes back: a dozen requests in flight, a dozen rows waiting, and
|
|
37984
38054
|
* a server asked to do a dozen things at once because a list happened to be
|
|
37985
|
-
* long. The guard is what makes the surface say "not yet" instead — the
|
|
37986
|
-
* that
|
|
37987
|
-
* next press is possible again the moment one of the runs
|
|
38055
|
+
* long. The guard is what makes the surface say "not yet" instead — the
|
|
38056
|
+
* controls that would start another run go read-only and say what they are
|
|
38057
|
+
* waiting for, and the next press is possible again the moment one of the runs
|
|
38058
|
+
* comes back.
|
|
37988
38059
|
*
|
|
37989
38060
|
* It counts runs, not values — which is what separates it from
|
|
37990
38061
|
* `maxLengthGuard`, the one that says how many things a selection may HOLD.
|
|
@@ -38329,14 +38400,13 @@ const createControlGuard = (controller) => {
|
|
|
38329
38400
|
};
|
|
38330
38401
|
|
|
38331
38402
|
/**
|
|
38332
|
-
* Orchestrates the
|
|
38333
|
-
*
|
|
38334
|
-
* Instead of holding `controller.controlInteraction` + `controller.controlValidity`
|
|
38335
|
-
* independently, a controller now has a single `controller.rules` object:
|
|
38403
|
+
* Orchestrates the rule managers of a UI state controller, held together in
|
|
38404
|
+
* one `controller.rules` object:
|
|
38336
38405
|
*
|
|
38337
38406
|
* controller.rules.callout — shared callout display manager
|
|
38338
38407
|
* controller.rules.interaction — interactivity gate (disabled/readonly/busy)
|
|
38339
38408
|
* controller.rules.validation — value validity gate (required/pattern/etc.)
|
|
38409
|
+
* controller.rules.guard — keystroke guard (charGuard/maxLengthGuard)
|
|
38340
38410
|
*
|
|
38341
38411
|
* The callout manager is created first so both interaction and validation
|
|
38342
38412
|
* can use the same callout slot and the same lifecycle hooks.
|
|
@@ -40487,9 +40557,8 @@ const EMPTY_OBJECT = {};
|
|
|
40487
40557
|
* child changes → we update picker input → navi_ui_state_change fires →
|
|
40488
40558
|
* we see updatingRef=true → skip → no loop.
|
|
40489
40559
|
*
|
|
40490
|
-
*
|
|
40491
|
-
*
|
|
40492
|
-
* manually re-dispatch to inner controls.
|
|
40560
|
+
* So a control placed inside the picker popup needs no
|
|
40561
|
+
* `command="--navi-update"` to reach the picker input.
|
|
40493
40562
|
*/
|
|
40494
40563
|
const useUIFacadeStateController = (props, realUIStateController) => {
|
|
40495
40564
|
const firstChildControllerRef = useRef(null);
|
|
@@ -40981,9 +41050,10 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
40981
41050
|
* use `onInput` internally and route through `dispatchRequestAction` so the behavior
|
|
40982
41051
|
* is consistent regardless of input type.
|
|
40983
41052
|
*
|
|
40984
|
-
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce
|
|
40985
|
-
*
|
|
40986
|
-
* rather than each component having to manage its
|
|
41053
|
+
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce with
|
|
41054
|
+
* `actionDebounce` (ms). The input effect every field installs (input_effect.js)
|
|
41055
|
+
* handles the timing centrally rather than each component having to manage its
|
|
41056
|
+
* own debounce logic.
|
|
40987
41057
|
*/
|
|
40988
41058
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
40989
41059
|
|
|
@@ -41059,7 +41129,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
41059
41129
|
});
|
|
41060
41130
|
|
|
41061
41131
|
/**
|
|
41062
|
-
* Core hook for interactive field components (
|
|
41132
|
+
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
41063
41133
|
*
|
|
41064
41134
|
* Sets up the full field lifecycle:
|
|
41065
41135
|
* - Creates a UI state controller that manages state divergence between props and user interactions
|
|
@@ -41070,7 +41140,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
41070
41140
|
* - Handles constraint validation and message props
|
|
41071
41141
|
*
|
|
41072
41142
|
* All state changes route through DOM events on the field element so that
|
|
41073
|
-
* external subscribers (e.g.
|
|
41143
|
+
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
41074
41144
|
*
|
|
41075
41145
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
41076
41146
|
*/
|
|
@@ -42163,10 +42233,10 @@ const useReadOnlyUncontrolled = (props, controlInfo) => {
|
|
|
42163
42233
|
};
|
|
42164
42234
|
|
|
42165
42235
|
/**
|
|
42166
|
-
* Core hook for field group components (
|
|
42236
|
+
* Core hook for field group components (List selectable, CheckboxGroup, etc.).
|
|
42167
42237
|
* - Creates a UI group state controller that aggregates child states into one group state
|
|
42168
42238
|
* - Binds the group's action to the aggregated state signal
|
|
42169
|
-
* - Provides context to children: ParentUIStateController,
|
|
42239
|
+
* - Provides context to children: ParentUIStateController, ControlName, Disabled, ReadOnly,
|
|
42170
42240
|
* Required, Loading, Action, ActionRequester
|
|
42171
42241
|
* - Overrides `onnavi_reset_ui_state` to cascade resets to all monitored children
|
|
42172
42242
|
* by dispatching `navi_reset_ui_state` DOM events on each child's DOM element
|
|
@@ -44701,10 +44771,10 @@ const LoadingRectangleSvg = ({
|
|
|
44701
44771
|
}) => {
|
|
44702
44772
|
// The stroke is centered on the path, so half of it falls outside: drawing
|
|
44703
44773
|
// at half the stroke width puts its outer edge exactly on the box, whatever
|
|
44704
|
-
// the box.
|
|
44705
|
-
//
|
|
44706
|
-
//
|
|
44707
|
-
//
|
|
44774
|
+
// the box. Not a share of the smaller dimension (3%, floored at 2px): that
|
|
44775
|
+
// holds the line further and further inside as the box grows — a caller
|
|
44776
|
+
// placing the outline with `inset` gets the distance it asked for on a field
|
|
44777
|
+
// and something several pixels inward on a card. Nothing needs the
|
|
44708
44778
|
// extra room: the svg is `overflow: visible` and whoever positions the
|
|
44709
44779
|
// wrapper already reserves the half stroke.
|
|
44710
44780
|
const margin = strokeWidth / 2;
|
|
@@ -45357,16 +45427,6 @@ const css$Z = /* css */`@layer navi {
|
|
|
45357
45427
|
border-radius: .2em;
|
|
45358
45428
|
}
|
|
45359
45429
|
|
|
45360
|
-
&[data-capitalize] {
|
|
45361
|
-
text-transform: capitalize;
|
|
45362
|
-
|
|
45363
|
-
& .navi_text_sizer {
|
|
45364
|
-
& .navi_text {
|
|
45365
|
-
display: inline-block;
|
|
45366
|
-
}
|
|
45367
|
-
}
|
|
45368
|
-
}
|
|
45369
|
-
|
|
45370
45430
|
&[data-shrinkwrap] {
|
|
45371
45431
|
display: inline-block;
|
|
45372
45432
|
}
|
|
@@ -45389,12 +45449,6 @@ time.navi_text {
|
|
|
45389
45449
|
}
|
|
45390
45450
|
|
|
45391
45451
|
.navi_text {
|
|
45392
|
-
&[data-capitalize] {
|
|
45393
|
-
&:first-letter, & .navi_text_sizer_placeholder:first-letter, & .navi_text_sizer_overlay:first-letter {
|
|
45394
|
-
text-transform: uppercase;
|
|
45395
|
-
}
|
|
45396
|
-
}
|
|
45397
|
-
|
|
45398
45452
|
& .navi_text_sizer, & .navi_text_sizer_placeholder, & .navi_text_sizer_overlay {
|
|
45399
45453
|
display: inherit;
|
|
45400
45454
|
width: inherit;
|
|
@@ -45776,7 +45830,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
45776
45830
|
* boldStable?: boolean,
|
|
45777
45831
|
* shrinkWrap?: boolean,
|
|
45778
45832
|
* capitalize?: boolean,
|
|
45779
|
-
* selectRange?: string | [number, number],
|
|
45833
|
+
* selectRange?: string | [number, number] | [string, string],
|
|
45780
45834
|
* childrenOutsideFlow?: import("ignore:preact").ComponentChildren,
|
|
45781
45835
|
* [key: string]: any,
|
|
45782
45836
|
* }>}
|
|
@@ -45842,11 +45896,14 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
45842
45896
|
* text block from being wider than its content when inside a flex/grid container.
|
|
45843
45897
|
*
|
|
45844
45898
|
* @param {boolean} [capitalize]
|
|
45845
|
-
* Uppercases the first letter of the text
|
|
45899
|
+
* Uppercases the first letter of the text, and only that one: "lundi 11 mai"
|
|
45900
|
+
* reads "Lundi 11 mai". Applied to the string the text starts with; a text
|
|
45901
|
+
* starting with an element keeps its first letter as written.
|
|
45846
45902
|
*
|
|
45847
|
-
* @param {string|[number,number]} [selectRange]
|
|
45848
|
-
* Selects a portion of the text on mount. Pass a substring to search for,
|
|
45849
|
-
*
|
|
45903
|
+
* @param {string|[number,number]|[string,string]} [selectRange]
|
|
45904
|
+
* Selects a portion of the text on mount. Pass a substring to search for, a
|
|
45905
|
+
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
45906
|
+
* substrings selecting from the first to the end of the second.
|
|
45850
45907
|
*
|
|
45851
45908
|
* @param {import("ignore:preact").ComponentChildren} [childrenOutsideFlow]
|
|
45852
45909
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
@@ -45961,11 +46018,13 @@ const TextUI = props => {
|
|
|
45961
46018
|
shrinkWrap,
|
|
45962
46019
|
...rest
|
|
45963
46020
|
} = props;
|
|
46021
|
+
if (capitalize) {
|
|
46022
|
+
children = capitalizeFirstLetter(children);
|
|
46023
|
+
}
|
|
45964
46024
|
const defaultSpace = preventSpaceUnderlines ? FAKE_SPACE : REGULAR_SPACE;
|
|
45965
46025
|
const resolvedSpacing = spacing ?? defaultSpace;
|
|
45966
46026
|
const boxProps = {
|
|
45967
46027
|
"as": "span",
|
|
45968
|
-
"data-capitalize": capitalize ? "" : undefined,
|
|
45969
46028
|
"data-shrinkwrap": shrinkWrap ? "" : undefined,
|
|
45970
46029
|
...rest,
|
|
45971
46030
|
ref,
|
|
@@ -46266,6 +46325,28 @@ const Icon = ({
|
|
|
46266
46325
|
});
|
|
46267
46326
|
};
|
|
46268
46327
|
|
|
46328
|
+
// On the string rather than with ::first-letter, which needs a block container:
|
|
46329
|
+
// an inline text made one is an atomic box, and the ellipsis of the line
|
|
46330
|
+
// holding it (a Picker's value) can then no longer cut it. The string is also
|
|
46331
|
+
// what every copy of the children (sizer, bold background) renders, so they
|
|
46332
|
+
// all read the same. Leading spaces and punctuation are skipped, and a text
|
|
46333
|
+
// starting with a digit is left as it is, as ::first-letter would.
|
|
46334
|
+
const capitalizeFirstLetter = children => {
|
|
46335
|
+
if (typeof children === "string") {
|
|
46336
|
+
return children.replace(FIRST_LOWERCASE_LETTER_REGEXP, (match, lead, letter) => `${lead}${letter.toLocaleUpperCase()}`);
|
|
46337
|
+
}
|
|
46338
|
+
if (Array.isArray(children) && children.length > 0) {
|
|
46339
|
+
const first = children[0];
|
|
46340
|
+
const firstCapitalized = capitalizeFirstLetter(first);
|
|
46341
|
+
if (firstCapitalized === first) {
|
|
46342
|
+
return children;
|
|
46343
|
+
}
|
|
46344
|
+
return [firstCapitalized, ...children.slice(1)];
|
|
46345
|
+
}
|
|
46346
|
+
return children;
|
|
46347
|
+
};
|
|
46348
|
+
const FIRST_LOWERCASE_LETTER_REGEXP = /^([\s\p{P}]*)(\p{Ll})/u;
|
|
46349
|
+
|
|
46269
46350
|
/**
|
|
46270
46351
|
* Where a link leads, asked for when the pointer or the focus arrives on it:
|
|
46271
46352
|
* the moment the user signals a destination, before the press. Only what the
|
|
@@ -46716,7 +46797,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
46716
46797
|
* @param {object} props
|
|
46717
46798
|
* @param {string} [props.href] - Destination. Also the default `value`, and
|
|
46718
46799
|
* (when `hrefFallback`) the default visible text.
|
|
46719
|
-
* @param {
|
|
46800
|
+
* @param {object} [props.route] - Renders via `route`
|
|
46720
46801
|
* instead of a raw `href`: the URL is built from the route (see
|
|
46721
46802
|
* `routeParams`) and "current" is derived from whether the route matches.
|
|
46722
46803
|
* @param {object} [props.routeParams] - Params passed to `route.buildUrl`.
|
|
@@ -46740,14 +46821,14 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
46740
46821
|
* (`navi_value`); defaults to `href`.
|
|
46741
46822
|
* @param {boolean} [props.current] - Forces the "current" state on (otherwise
|
|
46742
46823
|
* derived from the href/route).
|
|
46743
|
-
* @param {
|
|
46824
|
+
* @param {object|object[]} [props.currentExcept] -
|
|
46744
46825
|
* Route(s) inside this link's own that are NOT it: while one of them matches,
|
|
46745
46826
|
* the link is not current even though its route still is. A bar entry
|
|
46746
46827
|
* standing for a whole section, and one place under that section the entry
|
|
46747
46828
|
* does not stand for — settings reached from everywhere and shown over
|
|
46748
46829
|
* whatever the reader was on. Nothing about the routing changes: the url is
|
|
46749
46830
|
* in the section, only the link stops claiming "you are here".
|
|
46750
|
-
* @param {
|
|
46831
|
+
* @param {object|object[]} [props.currentAlso] -
|
|
46751
46832
|
* Route(s) other than this link's own that are it: the link is current while
|
|
46752
46833
|
* one of them matches. The other side of `currentExcept` — an entry standing
|
|
46753
46834
|
* for more than the one page it opens. `currentExcept` wins over it.
|
|
@@ -47660,6 +47741,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
47660
47741
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
47661
47742
|
* currentIndicatorSlides?: boolean,
|
|
47662
47743
|
* slideContainer?: string,
|
|
47744
|
+
* panelPosition?: "before"|"after",
|
|
47663
47745
|
* }>}
|
|
47664
47746
|
* @param {boolean|"top"|"bottom"|"left"|"right"} [props.currentIndicator] - the
|
|
47665
47747
|
* bar that says which tab one is on, said once here rather than on every
|
|
@@ -47685,6 +47767,9 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
47685
47767
|
* container says which one is on screen, and pressing a tab travels there.
|
|
47686
47768
|
* Tabs that are places in the same screen rather than pages of their own —
|
|
47687
47769
|
* nothing is written to the URL and nothing is a link.
|
|
47770
|
+
* @param {"before"|"after"} [props.panelPosition] - turns the row into folder
|
|
47771
|
+
* tabs: the current tab and the panel it opens share one surface, and this
|
|
47772
|
+
* says which side of the row the panel sits on.
|
|
47688
47773
|
*/
|
|
47689
47774
|
const Nav = ({
|
|
47690
47775
|
children,
|
|
@@ -48807,6 +48892,8 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
48807
48892
|
* onChange?: (value: any) => void,
|
|
48808
48893
|
* tabsPosition?: "top"|"bottom"|"left"|"right",
|
|
48809
48894
|
* tabsAlign?: "start"|"center"|"end"|"stretch",
|
|
48895
|
+
* scrollablePage?: boolean,
|
|
48896
|
+
* maxLines?: number|false,
|
|
48810
48897
|
* borderWidth?: string|number,
|
|
48811
48898
|
* borderRadius?: string|number,
|
|
48812
48899
|
* tabBorderRadius?: string|number,
|
|
@@ -48986,7 +49073,7 @@ const Binder = ({
|
|
|
48986
49073
|
* position among its siblings.
|
|
48987
49074
|
* @param {import("ignore:preact").ComponentChildren} [props.label] - What the tab
|
|
48988
49075
|
* shows.
|
|
48989
|
-
* @param {
|
|
49076
|
+
* @param {object} [props.route] - Makes the tab a `Link`
|
|
48990
49077
|
* to that route, and the binder follows the url: the open item is the one
|
|
48991
49078
|
* whose route matches, no `value`/`onChange` needed.
|
|
48992
49079
|
* @param {string} [props.href] - Same, for a plain url.
|
|
@@ -49425,6 +49512,9 @@ const FixedBarStyleCSSVars = {
|
|
|
49425
49512
|
* `border={false}` for none.
|
|
49426
49513
|
* @param {string|number} [props.maxWidth] - Keeps the bar lined up with a
|
|
49427
49514
|
* content column narrower than the window (it stays centered).
|
|
49515
|
+
* @param {string|number} [props.padding=0] - Along the bar only: left/right on
|
|
49516
|
+
* a top or bottom bar, top/bottom on a side one. Across it, the size is
|
|
49517
|
+
* `height`/`width`.
|
|
49428
49518
|
*/
|
|
49429
49519
|
const FixedBar = ({
|
|
49430
49520
|
children,
|
|
@@ -50293,9 +50383,10 @@ const useExpandableContext = partName => {
|
|
|
50293
50383
|
* height too. `"idle"` builds it in a browser idle moment after load.
|
|
50294
50384
|
* `"while-opened"` throws the content away once the collapse
|
|
50295
50385
|
* settles — after the closing animation, so it still plays on real
|
|
50296
|
-
* content — and rebuilds it from scratch on every expansion.
|
|
50297
|
-
*
|
|
50298
|
-
* builds the content ahead of the click
|
|
50386
|
+
* content — and rebuilds it from scratch on every expansion. Under the
|
|
50387
|
+
* other three, intent on the UI part (pointer entering it, focus landing in
|
|
50388
|
+
* it) builds the content ahead of the click; `"while-opened"` content is
|
|
50389
|
+
* never warmed, it is built at the open.
|
|
50299
50390
|
*/
|
|
50300
50391
|
const Expandable = props => {
|
|
50301
50392
|
import.meta.css = [css$R, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
@@ -51072,7 +51163,8 @@ const DARK_CONTRAST_LIGHTNESS_THRESHOLD = 0.65;
|
|
|
51072
51163
|
* @param {string} accentColor - The accent color value. When it changes, attributes are recomputed.
|
|
51073
51164
|
* @param {object} [options]
|
|
51074
51165
|
* @param {string} [options.elementSelector] - CSS selector to find the element whose computed color is read.
|
|
51075
|
-
* Defaults to the root
|
|
51166
|
+
* Defaults to the root's own `data-visual-selector` attribute, else the root element itself.
|
|
51167
|
+
* Useful when the color is applied to a probe/child element.
|
|
51076
51168
|
* @param {string} [options.colorProperty="backgroundColor"] - Computed style property to read (e.g. "color", "borderColor").
|
|
51077
51169
|
*/
|
|
51078
51170
|
const useAccentColorAttributes = (
|
|
@@ -53117,16 +53209,18 @@ const css$L = /* css */`@layer navi {
|
|
|
53117
53209
|
/**
|
|
53118
53210
|
* Field — a semantic wrapper that connects a label to a form control.
|
|
53119
53211
|
*
|
|
53120
|
-
* It generates a stable `fieldId` (or accepts an explicit
|
|
53212
|
+
* It generates a stable `fieldId` (or accepts an explicit one) that is
|
|
53121
53213
|
* automatically forwarded to the `Label` inside the field as `htmlFor` and to
|
|
53122
|
-
*
|
|
53123
|
-
* label focuses the control without requiring manual wiring.
|
|
53214
|
+
* the labelable control inside it (Input, Select, Picker, Button) as its `id`,
|
|
53215
|
+
* so clicking the label focuses the control without requiring manual wiring.
|
|
53124
53216
|
*
|
|
53125
53217
|
* It also tracks the readOnly / disabled / interactive state reported by its
|
|
53126
53218
|
* child control and reflects it on the `Label` (dimmed color, cursor change).
|
|
53127
53219
|
*
|
|
53128
53220
|
* Props:
|
|
53129
|
-
*
|
|
53221
|
+
* fieldId — optional explicit field id instead of the auto-generated one (an `id`
|
|
53222
|
+
* is the wrapper's own DOM id)
|
|
53223
|
+
* as="label" — the field IS the label, wrapping its control: no id needed
|
|
53130
53224
|
* flex="y" — vertical layout; automatically sets alignX="start" and data-vertical
|
|
53131
53225
|
* children — any JSX; should contain a `Label` and a form control
|
|
53132
53226
|
* ...rest — forwarded to the wrapping element (className, style, flex, spacing, …)
|
|
@@ -53333,6 +53427,7 @@ const InputUnitSlot = ({
|
|
|
53333
53427
|
};
|
|
53334
53428
|
const InputSlot = ({
|
|
53335
53429
|
side,
|
|
53430
|
+
children,
|
|
53336
53431
|
...props
|
|
53337
53432
|
}) => {
|
|
53338
53433
|
const ctx = useContext(InputTextualContext);
|
|
@@ -53362,7 +53457,11 @@ const InputSlot = ({
|
|
|
53362
53457
|
e.preventDefault();
|
|
53363
53458
|
}
|
|
53364
53459
|
},
|
|
53365
|
-
...props
|
|
53460
|
+
...props,
|
|
53461
|
+
children: jsx(ControlIdContext.Provider, {
|
|
53462
|
+
value: undefined,
|
|
53463
|
+
children: children
|
|
53464
|
+
})
|
|
53366
53465
|
});
|
|
53367
53466
|
};
|
|
53368
53467
|
|
|
@@ -54827,9 +54926,9 @@ const InputWithListResolver = props => {
|
|
|
54827
54926
|
};
|
|
54828
54927
|
|
|
54829
54928
|
/**
|
|
54830
|
-
* InputWithList — connects an input to a
|
|
54929
|
+
* InputWithList — connects an input to a selectable List via its id.
|
|
54831
54930
|
*
|
|
54832
|
-
* Usage: <Input navi-list="my-list-id" /> next to <
|
|
54931
|
+
* Usage: <Input navi-list="my-list-id" /> next to <List selectable id="my-list-id">
|
|
54833
54932
|
*
|
|
54834
54933
|
* Behavior:
|
|
54835
54934
|
* - ArrowDown / ArrowUp move the list's "current item" without moving focus
|
|
@@ -55197,11 +55296,14 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
55197
55296
|
*
|
|
55198
55297
|
* - charGuard — restricts which characters can be typed, pasted, or set externally.
|
|
55199
55298
|
* Accepts a preset name or a raw regex character class:
|
|
55200
|
-
* "
|
|
55299
|
+
* true / "auto" → picked from the field: inputMode numeric/decimal, type tel/email
|
|
55300
|
+
* "numeric" → digits only, sets inputMode="numeric"
|
|
55301
|
+
* "decimal" → digits, minus, dot, comma, sets inputMode="decimal"
|
|
55201
55302
|
* "alpha" → letters only
|
|
55202
55303
|
* "alphanumeric" → letters and digits
|
|
55203
55304
|
* "uppercase" → uppercase letters only
|
|
55204
55305
|
* "tel" → phone chars (digits, +, -, parens, space), sets inputMode="tel"
|
|
55306
|
+
* "email" → what an email address is made of
|
|
55205
55307
|
* "card" → credit card (digits and spaces), sets inputMode="numeric"
|
|
55206
55308
|
* "hex" → hexadecimal digits
|
|
55207
55309
|
* "pin" → numeric PIN, sets inputMode="numeric"
|
|
@@ -56272,18 +56374,13 @@ const commitSubtree = (controller, e) => {
|
|
|
56272
56374
|
};
|
|
56273
56375
|
|
|
56274
56376
|
/**
|
|
56275
|
-
*
|
|
56276
|
-
*
|
|
56277
|
-
*
|
|
56278
|
-
*
|
|
56279
|
-
*
|
|
56280
|
-
*
|
|
56281
|
-
*
|
|
56282
|
-
* I could create an example where we would put a link on the page to let user see what he created
|
|
56283
|
-
* but by default user stays on the form allowing to create multiple resources at once
|
|
56284
|
-
* And an other where he is redirected to the resource he created
|
|
56285
|
-
* 3. If form submission fails ideally we should display this somewhere on the UI
|
|
56286
|
-
* right now it's just logged to the console I need to see how we can achieve this
|
|
56377
|
+
* <Form>: the <form> element as a navi control group. Its named fields
|
|
56378
|
+
* aggregate into one object; a submit checks their constraints, runs `action`
|
|
56379
|
+
* with that object when it changed (docs/form_changed.md), and `command` — or
|
|
56380
|
+
* the surface the form sits in — says what follows. The URL does not change on
|
|
56381
|
+
* a submit, and a failing action is drawn as an error callout on the button
|
|
56382
|
+
* that sent it (docs/error_handling.md). A form inside another form renders no
|
|
56383
|
+
* <form> element (FormNested).
|
|
56287
56384
|
*/
|
|
56288
56385
|
|
|
56289
56386
|
const Form = props => {
|
|
@@ -56923,9 +57020,9 @@ const offsetToPx = (offset, box) => {
|
|
|
56923
57020
|
|
|
56924
57021
|
// A press landing while the track is already travelling: what is playing is
|
|
56925
57022
|
// sent home in a fifth of the time it has left, and the press it could not take
|
|
56926
|
-
// yet is taken as soon as it lands. A press has to be FELT — nudging the
|
|
56927
|
-
// of a travel already in flight
|
|
56928
|
-
//
|
|
57023
|
+
// yet is taken as soon as it lands. A press has to be FELT — merely nudging the
|
|
57024
|
+
// pace of a travel already in flight reads as "nothing happened", because the
|
|
57025
|
+
// thing was moving before the click too. Getting there
|
|
56929
57026
|
// almost at once and setting off again is the click being answered.
|
|
56930
57027
|
// Played out fast rather than cut short: ending it on the spot would jump.
|
|
56931
57028
|
// Compounds, so two presses during one travel bring it home twice as sharply,
|
|
@@ -59523,13 +59620,12 @@ const SlideNavButton = ({
|
|
|
59523
59620
|
autoFocus: "last-resort",
|
|
59524
59621
|
icon: true,
|
|
59525
59622
|
variant: "discrete"
|
|
59526
|
-
// Takes the focus like any other button, on purpose.
|
|
59623
|
+
// Takes the focus like any other button, on purpose. Do not refuse it
|
|
59527
59624
|
// (mousedown.preventDefault) to keep the keyboard where the user had put
|
|
59528
|
-
// it
|
|
59529
|
-
//
|
|
59530
|
-
//
|
|
59531
|
-
//
|
|
59532
|
-
// page rather than from what is on screen.
|
|
59625
|
+
// it: the travel below only hands the focus to the slide arriving when it
|
|
59626
|
+
// was leaving a slide, so a press with nothing focused would arrive on
|
|
59627
|
+
// document.body, and the next Tab would start from the top of the page
|
|
59628
|
+
// rather than from what is on screen.
|
|
59533
59629
|
//
|
|
59534
59630
|
// Letting it focus is both the plain behaviour of a button and what makes
|
|
59535
59631
|
// the rest fall into place: the press lands on the chevron, the slide left
|
|
@@ -59546,12 +59642,6 @@ const SlideNavButton = ({
|
|
|
59546
59642
|
})
|
|
59547
59643
|
});
|
|
59548
59644
|
|
|
59549
|
-
/**
|
|
59550
|
-
* The way out of a slide, and the way into the next one. Nothing but the
|
|
59551
|
-
* command plus the chevron that matches the travel: a row goes left/right, a
|
|
59552
|
-
* column up/down — so the button points where the slide actually goes without
|
|
59553
|
-
* the caller having to keep the two in sync.
|
|
59554
|
-
*/
|
|
59555
59645
|
/**
|
|
59556
59646
|
* A way out pointing where it goes. It says a direction, not a slide: what is
|
|
59557
59647
|
* over there is the map's business, and moving a screen changes nothing here.
|
|
@@ -61193,7 +61283,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
61193
61283
|
panelEl.style.transform = "";
|
|
61194
61284
|
panelEl.style.clipPath = "";
|
|
61195
61285
|
panelEl.style.transitionProperty = "";
|
|
61196
|
-
panelEl.style.userSelect = "";
|
|
61197
61286
|
};
|
|
61198
61287
|
// The resting value is written first and the animation only covers the way
|
|
61199
61288
|
// to it: both end on the same number, so nothing is seen changing when the
|
|
@@ -61237,10 +61326,6 @@ const createSwipeToClose = (side, { grip } = {}) => {
|
|
|
61237
61326
|
return false;
|
|
61238
61327
|
}
|
|
61239
61328
|
panelEl.style.transitionProperty = "none";
|
|
61240
|
-
panelEl.style.userSelect = "none";
|
|
61241
|
-
// What the first pixels of the gesture may have started selecting is
|
|
61242
|
-
// not a selection, it is the beginning of this travel.
|
|
61243
|
-
window.getSelection().removeAllRanges();
|
|
61244
61329
|
// The way out is the only way there is anything: pulling the other way
|
|
61245
61330
|
// leans against a wall and comes back.
|
|
61246
61331
|
return {
|
|
@@ -61946,7 +62031,7 @@ const css$E = /* css */`
|
|
|
61946
62031
|
--navi-app-width rather than 3vvw): the gap must read as a small
|
|
61947
62032
|
margin around the dialog, and 3% of a 1500px window is a 45px gap
|
|
61948
62033
|
around a 600px app. Identical to 3vvw until the app declares
|
|
61949
|
-
--navi-app-max-width. */
|
|
62034
|
+
bands (--navi-app-max-width, or --navi-app-inset-* written by it). */
|
|
61950
62035
|
--x-dialog-container-spacing: calc(0.03 * var(--navi-app-width));
|
|
61951
62036
|
|
|
61952
62037
|
/* --navi-app-width, not --navi-vvw: a top-layer dialog is calibrated on
|
|
@@ -62663,7 +62748,7 @@ const css$E = /* css */`
|
|
|
62663
62748
|
* within its container (the viewport for `layer="top"`, the positioned
|
|
62664
62749
|
* ancestor for `layer="local"`) — Dialog is never anchored to a real
|
|
62665
62750
|
* element for positioning purposes. Same grammar as `Popover`'s own
|
|
62666
|
-
* `positionArea` (see `
|
|
62751
|
+
* `positionArea` (see `parsePositionArea` in @jsenv/dom): a single
|
|
62667
62752
|
* compass token — `top`/`top-start`/`top-end`/`top-left`/`top-right`,
|
|
62668
62753
|
* `right`/`right-start`/`right-end`, `bottom`/`bottom-start`/
|
|
62669
62754
|
* `bottom-end`/`bottom-left`/`bottom-right`, `left`/`left-start`/
|
|
@@ -62682,12 +62767,12 @@ const css$E = /* css */`
|
|
|
62682
62767
|
* between the dialog and the edges of its container, whatever its
|
|
62683
62768
|
* `positionArea`: it both caps the dialog's own size (via
|
|
62684
62769
|
* `--x-dialog-container-spacing`, written from this prop) and offsets a docked
|
|
62685
|
-
* one from the edge it docks to. Accepts a
|
|
62686
|
-
*
|
|
62687
|
-
*
|
|
62688
|
-
*
|
|
62689
|
-
*
|
|
62690
|
-
* meant to sit flush (a side panel).
|
|
62770
|
+
* one from the edge it docks to. Accepts a number of pixels, a viewport
|
|
62771
|
+
* length — "appw"/"apph" being the app's own screen (the visual viewport
|
|
62772
|
+
* minus the app's bands, see docs/safe_area.md) and
|
|
62773
|
+
* "vvw"/"vvh" the visual viewport itself, which shrinks when the mobile
|
|
62774
|
+
* keyboard opens — or a container length ("3cqw", the `layer="local"`
|
|
62775
|
+
* default). Pass 0 for a dialog meant to sit flush (a side panel).
|
|
62691
62776
|
* @param {boolean} [props.backdrop=true] - Whether anything is laid between
|
|
62692
62777
|
* the dialog and the page at all — asked before any question of what an
|
|
62693
62778
|
* outside press does or how the backdrop is painted. `false` leaves the page
|
|
@@ -62700,10 +62785,12 @@ const css$E = /* css */`
|
|
|
62700
62785
|
* needs. What it gives up is what only a modal gets natively: the hardware
|
|
62701
62786
|
* back button no longer dismisses it. See docs/popup_backdrop.md.
|
|
62702
62787
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="close"]
|
|
62703
|
-
* - `"close"` closes the dialog on an outside press
|
|
62704
|
-
*
|
|
62705
|
-
*
|
|
62706
|
-
*
|
|
62788
|
+
* - `"close"` closes the dialog on an outside press, `"cancel"` closes it
|
|
62789
|
+
* as a cancel (what it holds reverts, as on Escape). `"capture"`/`"ignore"`
|
|
62790
|
+
* both just absorb the press without closing (`"capture"` paints the
|
|
62791
|
+
* blurred glass, `"ignore"` leaves the wall's paint alone) — a wall absorbs
|
|
62792
|
+
* the press whatever this prop says, natively for a modal and through the
|
|
62793
|
+
* sibling element for a local one.
|
|
62707
62794
|
* "Outside" is the dialog's own border box; a see-through dialog whose box is
|
|
62708
62795
|
* bigger than what it paints marks the difference with
|
|
62709
62796
|
* `data-navi-popup-outside` (see docs/popup_backdrop.md).
|
|
@@ -62778,11 +62865,10 @@ const css$E = /* css */`
|
|
|
62778
62865
|
* @param {string} [props.animationDuration] - Maps to
|
|
62779
62866
|
* `--popup-animation-duration`.
|
|
62780
62867
|
* @param {Element|{current: Element}|string} [props.anchor] - Never used for
|
|
62781
|
-
* positioning (see this file's top comment)
|
|
62782
|
-
* `
|
|
62783
|
-
* the dialog
|
|
62784
|
-
*
|
|
62785
|
-
* itself names none — an anchor carried by the opening event
|
|
62868
|
+
* positioning (see this file's top comment); read by `sizeFromAnchor` (the
|
|
62869
|
+
* `--anchor-width`/`--anchor-height` CSS vars), by `animation="lifting"`
|
|
62870
|
+
* (the box the dialog comes out of) and by `mount`'s warm-on-intent. Used
|
|
62871
|
+
* when the open itself names none — an anchor carried by the opening event
|
|
62786
62872
|
* (`e.detail.anchor`) is about that one opening and comes first; left out,
|
|
62787
62873
|
* whatever triggered the open (`e.detail.source`). A string is resolved via
|
|
62788
62874
|
* `document.getElementById` when the dialog opens — see popover.jsx's own
|
|
@@ -62844,6 +62930,9 @@ const css$E = /* css */`
|
|
|
62844
62930
|
* focusable of its own.
|
|
62845
62931
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
62846
62932
|
* held focus when it closed.
|
|
62933
|
+
* - `false` — the dialog element itself is never where the focus lands; the
|
|
62934
|
+
* opening still hands the focus to what the dialog holds. Unlike
|
|
62935
|
+
* `Popover`'s `false`, it does not leave the keyboard where it was.
|
|
62847
62936
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
62848
62937
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
62849
62938
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -63089,8 +63178,8 @@ const useDialogProps = props => {
|
|
|
63089
63178
|
// `backdrop` — see this file's top comment.
|
|
63090
63179
|
layer = "top",
|
|
63091
63180
|
dockedOnSmallTouchScreen,
|
|
63092
|
-
// Same grammar as Popover's own positionArea — see
|
|
63093
|
-
//
|
|
63181
|
+
// Same grammar as Popover's own positionArea — see parsePositionArea in
|
|
63182
|
+
// @jsenv/dom.
|
|
63094
63183
|
positionArea: positionAreaProp,
|
|
63095
63184
|
// A dialog docked against an edge must keep the same gap its own size cap
|
|
63096
63185
|
// already guarantees a centered one — so this drives both (see
|
|
@@ -63130,8 +63219,8 @@ const useDialogProps = props => {
|
|
|
63130
63219
|
sizing = "auto",
|
|
63131
63220
|
animation,
|
|
63132
63221
|
lift = "box",
|
|
63133
|
-
//
|
|
63134
|
-
//
|
|
63222
|
+
// Never positioned against (see this file's top comment): read by
|
|
63223
|
+
// sizeFromAnchor, by a lift, and by the warm-on-intent of `mount`.
|
|
63135
63224
|
anchor,
|
|
63136
63225
|
// Where a lift comes back to, when that is no longer the box it came out
|
|
63137
63226
|
// of. Read at the close, not kept from the opening — see
|
|
@@ -63141,8 +63230,8 @@ const useDialogProps = props => {
|
|
|
63141
63230
|
// See this prop's own JSDoc above for why a dialog does not follow its
|
|
63142
63231
|
// trigger's box by default.
|
|
63143
63232
|
sizeFromAnchor = false,
|
|
63144
|
-
// Same meaning as Popover's own prop, applied to
|
|
63145
|
-
//
|
|
63233
|
+
// Same meaning as Popover's own prop, applied to what an anchor does here:
|
|
63234
|
+
// sizing under sizeFromAnchor, and a lift.
|
|
63146
63235
|
anchorCustomEventDetail = "override",
|
|
63147
63236
|
// Makes the dialog itself a valid focus target so
|
|
63148
63237
|
// autoFocus="last-resort" below has somewhere to land when it contains
|
|
@@ -63201,8 +63290,8 @@ const useDialogProps = props => {
|
|
|
63201
63290
|
// narrower one) — and the positioned ancestor for a local one, where
|
|
63202
63291
|
// reading 3% of the screen gives an absurd gap inside a small box.
|
|
63203
63292
|
isTopLayer ? "3appw" : "3cqw");
|
|
63204
|
-
//
|
|
63205
|
-
//
|
|
63293
|
+
// `expand` is the shorthand for both axes; the docked default only applies
|
|
63294
|
+
// when neither was said.
|
|
63206
63295
|
const expandXUnset = expand === undefined && expandXProp === undefined;
|
|
63207
63296
|
const expandX = expandXUnset ? isDocked && DOCKED.expandX : Boolean(expand) || Boolean(expandXProp);
|
|
63208
63297
|
const scrollCapture = scrollCaptureProp ?? (isDocked ? DOCKED.scrollCapture : false);
|
|
@@ -63941,8 +64030,8 @@ const useDialogProps = props => {
|
|
|
63941
64030
|
// ("[navi-control='dialog'], [navi-control='popover']" — see openEffect
|
|
63942
64031
|
// above), and --navi-open/--navi-close resolve their target this way.
|
|
63943
64032
|
"navi-control": "dialog",
|
|
63944
|
-
// The protocol every command target answers
|
|
63945
|
-
//
|
|
64033
|
+
// The protocol every command target answers: a dialog is layout, not a
|
|
64034
|
+
// control, and still has to answer --navi-open,
|
|
63946
64035
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
63947
64036
|
// bubble.
|
|
63948
64037
|
"onnavi_command": e => {
|
|
@@ -63964,14 +64053,12 @@ const useDialogProps = props => {
|
|
|
63964
64053
|
"popover": isTopLayerPopover ? "manual" : undefined,
|
|
63965
64054
|
"baseClassName": "navi_dialog",
|
|
63966
64055
|
"pseudoClasses": DIALOG_PSEUDO_CLASSES,
|
|
63967
|
-
// Distinguishes the two renderers for the CSS above (position: fixed
|
|
63968
|
-
// vs. absolute) — positioning itself is entirely JS-driven now (see
|
|
63969
|
-
// openEffect's own positionDialog above), no data-position-area
|
|
63970
|
-
// attribute needed at all.
|
|
63971
64056
|
// A popup scrolls, and asking Box for that overflow is also what lets what
|
|
63972
64057
|
// it contains claim header/footer/body (see box.jsx) — a popup is always a
|
|
63973
64058
|
// scrolling area, so it says so once, here.
|
|
63974
64059
|
"overflow": "auto",
|
|
64060
|
+
// Tells the two renderers apart for the CSS above (position: fixed vs.
|
|
64061
|
+
// absolute); the placement itself is JS-driven (positionDialog).
|
|
63975
64062
|
"data-layer": layer,
|
|
63976
64063
|
// The sheet shape is live in CSS, not just a set of resolved defaults:
|
|
63977
64064
|
// it is what withdraws the caller's --dialog-max-width (see the stylesheet
|
|
@@ -64475,8 +64562,8 @@ const css$D = /* css */`
|
|
|
64475
64562
|
* whole viewport. A real `anchor` works with either.
|
|
64476
64563
|
* @param {string} [props.positionArea="bottom"] - Where to place the popover
|
|
64477
64564
|
* relative to its `anchor` (or its container, if there is none). Same
|
|
64478
|
-
* grammar as `Dialog`'s own `positionArea` (see `
|
|
64479
|
-
*
|
|
64565
|
+
* grammar as `Dialog`'s own `positionArea` (see `parsePositionArea` in
|
|
64566
|
+
* @jsenv/dom): a single compass token — `top`/`top-start`/
|
|
64480
64567
|
* `top-end`/`top-left`/`top-right`, `right`/`right-start`/`right-end`,
|
|
64481
64568
|
* `bottom`/`bottom-start`/`bottom-end`/`bottom-left`/`bottom-right`,
|
|
64482
64569
|
* `left`/`left-start`/`left-end`, or `center`. A bare token means no
|
|
@@ -64505,8 +64592,9 @@ const css$D = /* css */`
|
|
|
64505
64592
|
* See docs/popup_backdrop.md.
|
|
64506
64593
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="ignore"]
|
|
64507
64594
|
* - `"ignore"` (default): no backdrop at all, outside presses pass straight
|
|
64508
|
-
* through. `"close"` closes the popover on an outside press
|
|
64509
|
-
*
|
|
64595
|
+
* through. `"close"` closes the popover on an outside press, `"cancel"`
|
|
64596
|
+
* closes it as a cancel (what it holds reverts, as on Escape). `"capture"`
|
|
64597
|
+
* absorbs the press (blurred glass) without closing. Note this
|
|
64510
64598
|
* default differs from `Dialog`'s own (`"close"`) — a popover is
|
|
64511
64599
|
* typically a lightweight, non-modal affordance. "Outside" is the popover's
|
|
64512
64600
|
* own border box; a see-through popover whose box is bigger than what it
|
|
@@ -64540,9 +64628,11 @@ const css$D = /* css */`
|
|
|
64540
64628
|
* popover so the page/container behind it can't scroll while it's open.
|
|
64541
64629
|
* @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
|
|
64542
64630
|
* popover (see `focus_trap.js`).
|
|
64543
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`} [props.animation]
|
|
64544
|
-
* - `true`/`"auto"` resolves to
|
|
64545
|
-
*
|
|
64631
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`} [props.animation]
|
|
64632
|
+
* - `true`/`"auto"` resolves to `"scaling"` for a real anchor or a centered
|
|
64633
|
+
* `positionArea`, and to a concrete `"slide-from-*"` direction otherwise.
|
|
64634
|
+
* `"expanding"`/`"expand-*"` grow out of a real anchor (see popup_css.js).
|
|
64635
|
+
* Any other explicit value is used as-is.
|
|
64546
64636
|
* @param {string} [props.animationDuration] - Maps to
|
|
64547
64637
|
* `--popup-animation-duration`.
|
|
64548
64638
|
* @param {Element|{current: Element}|string} [props.anchor] - The element the
|
|
@@ -64654,7 +64744,7 @@ const css$D = /* css */`
|
|
|
64654
64744
|
* @param {object} [props.openController] - Advanced: an externally-owned
|
|
64655
64745
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
64656
64746
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
64657
|
-
* by `picker_custom.jsx
|
|
64747
|
+
* by `picker_custom.jsx`).
|
|
64658
64748
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
64659
64749
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
64660
64750
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
@@ -64966,12 +65056,10 @@ const usePopoverProps = props => {
|
|
|
64966
65056
|
unfreezeSize(popoverEl);
|
|
64967
65057
|
}
|
|
64968
65058
|
}, [sizing]);
|
|
64969
|
-
// The custom renderer
|
|
64970
|
-
//
|
|
64971
|
-
//
|
|
64972
|
-
//
|
|
64973
|
-
// leaving this to a layout effect meant an actual (if narrow) window
|
|
64974
|
-
// where the browser could paint it visible before this ever ran.
|
|
65059
|
+
// The custom renderer starts hidden from the stylesheet ([navi-hidden] on
|
|
65060
|
+
// .navi_popover/.navi_popover_backdrop above), never from a layout effect:
|
|
65061
|
+
// a plain div has no native hidden default the way [popover]/<dialog> do,
|
|
65062
|
+
// and an effect leaves a window where the browser paints it visible first.
|
|
64975
65063
|
// aria-expanded starts "false" the same way, but via a static literal
|
|
64976
65064
|
// JSX prop on contentProps/backdropProps below instead of a layout
|
|
64977
65065
|
// effect — see that prop's own comment for why a *constant* value there
|
|
@@ -64990,8 +65078,8 @@ const usePopoverProps = props => {
|
|
|
64990
65078
|
openController.getElement = () => ref.current;
|
|
64991
65079
|
openController.openEffect = e => {
|
|
64992
65080
|
const popoverEl = ref.current;
|
|
64993
|
-
// backdropEl is null when
|
|
64994
|
-
// the backdrop isn't rendered at all
|
|
65081
|
+
// backdropEl is null when there is no wall (pressOutside "ignore", or
|
|
65082
|
+
// backdrop={false}): the backdrop isn't rendered at all then.
|
|
64995
65083
|
const backdropEl = backdropRef.current;
|
|
64996
65084
|
if (!popoverEl) {
|
|
64997
65085
|
{
|
|
@@ -65052,10 +65140,8 @@ const usePopoverProps = props => {
|
|
|
65052
65140
|
// needed here.
|
|
65053
65141
|
const positionedAncestor = getPositionedParent(popoverEl);
|
|
65054
65142
|
// Drives the via-attribute renderer's own position: fixed/absolute
|
|
65055
|
-
// switch (see this file's top comment) — set here,
|
|
65056
|
-
// positioning/measurement runs, so
|
|
65057
|
-
// wrong (unlike the CSS this replaced, which keyed off the resolved
|
|
65058
|
-
// animation instead, known too late relative to the first measurement).
|
|
65143
|
+
// switch (see this file's top comment) — set here, before any
|
|
65144
|
+
// positioning/measurement runs, so the first measurement already sees it.
|
|
65059
65145
|
// True only for a real anchor that itself scrolls with the document —
|
|
65060
65146
|
// that's the one case `absolute` (scrolling in lockstep with it) is
|
|
65061
65147
|
// correct; not just "has an anchor at all", since an anchor that's
|
|
@@ -65105,10 +65191,9 @@ const usePopoverProps = props => {
|
|
|
65105
65191
|
disarmBackdropHideRef.current?.();
|
|
65106
65192
|
disarmBackdropHideRef.current = null;
|
|
65107
65193
|
// transitionProperty stays "none" here for both — reset later, in the
|
|
65108
|
-
// final commit step alongside popoverEl's own
|
|
65109
|
-
//
|
|
65110
|
-
//
|
|
65111
|
-
// aria-expanded flip, the same way it does for popoverEl.
|
|
65194
|
+
// final commit step alongside popoverEl's own, so a `silent` open (see
|
|
65195
|
+
// above) can keep both elements' transitions suppressed right up until
|
|
65196
|
+
// after their aria-expanded flip, the same way it does for popoverEl.
|
|
65112
65197
|
backdropEl.style.transitionProperty = "none";
|
|
65113
65198
|
if (isTopLayer) {
|
|
65114
65199
|
// Hidden first if a previous close's deferred hidePopover() (see
|
|
@@ -65534,16 +65619,10 @@ const usePopoverProps = props => {
|
|
|
65534
65619
|
// current unconditionally on every call, and treats their mere
|
|
65535
65620
|
// *presence* as "already positioned this session — stay on this
|
|
65536
65621
|
// side unless it no longer fits" (its own anti-oscillation guard for
|
|
65537
|
-
// repositioning mid-open, e.g. on scroll/resize).
|
|
65538
|
-
//
|
|
65539
|
-
//
|
|
65540
|
-
//
|
|
65541
|
-
// still-live purpose: without clearing them on close, a later reopen
|
|
65542
|
-
// sees a stale, "already positioned" value left over from a
|
|
65543
|
-
// *previous* open (e.g. the container-aligned collapse from an
|
|
65544
|
-
// anchorless silent open) and wrongly stays sticky to it instead of
|
|
65545
|
-
// resolving fresh from the current positionArea/anchor — a real,
|
|
65546
|
-
// reproduced bug, not just a leftover no-op.
|
|
65622
|
+
// repositioning mid-open, e.g. on scroll/resize). Cleared on close so a
|
|
65623
|
+
// later reopen resolves fresh from the current positionArea/anchor
|
|
65624
|
+
// instead of staying sticky to a side left over from a previous open
|
|
65625
|
+
// (e.g. the container-aligned collapse from an anchorless silent open).
|
|
65547
65626
|
popoverEl.removeAttribute("data-position-y-current");
|
|
65548
65627
|
popoverEl.removeAttribute("data-position-x-current");
|
|
65549
65628
|
cleanup();
|
|
@@ -65673,8 +65752,8 @@ const usePopoverProps = props => {
|
|
|
65673
65752
|
// ("[navi-control='dialog'], [navi-control='popover']"), and
|
|
65674
65753
|
// --navi-open/--navi-close resolve their target this way.
|
|
65675
65754
|
"navi-control": "popover",
|
|
65676
|
-
// The protocol every command target answers
|
|
65677
|
-
//
|
|
65755
|
+
// The protocol every command target answers: a popover is layout, not a
|
|
65756
|
+
// control, and still has to answer --navi-open,
|
|
65678
65757
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
65679
65758
|
// bubble.
|
|
65680
65759
|
"onnavi_command": e => {
|
|
@@ -65714,7 +65793,7 @@ const usePopoverProps = props => {
|
|
|
65714
65793
|
// already arrive here as plain props via ...rest (wired by
|
|
65715
65794
|
// UncontrolledPopover above, forwarded through ControlledPopover's own
|
|
65716
65795
|
// {...props} spread) — nothing extra to add here. A controlled caller
|
|
65717
|
-
// (picker_custom.jsx
|
|
65796
|
+
// (picker_custom.jsx) wires its own equivalent handling
|
|
65718
65797
|
// directly against its own openController instead.
|
|
65719
65798
|
});
|
|
65720
65799
|
return [hasBackdrop ? backdropProps : null, contentProps];
|
|
@@ -65746,9 +65825,6 @@ const POPUP_STYLE_CSS_VARS = {
|
|
|
65746
65825
|
maxHeight: "--popover-max-height"
|
|
65747
65826
|
};
|
|
65748
65827
|
|
|
65749
|
-
// parsePositionArea/POSITION_AREA_X/Y_VALUES moved to popup_shared.js — same
|
|
65750
|
-
// grammar Dialog's own layer="local"/"top" now shares.
|
|
65751
|
-
|
|
65752
65828
|
/**
|
|
65753
65829
|
* Shared by both renderers: parses `positionArea` and resolves
|
|
65754
65830
|
* `animation="auto"`/`true`. `animationAnchor` is the real anchor element
|
|
@@ -65776,15 +65852,6 @@ const resolvePositionAreaAndAnimationKind = ({
|
|
|
65776
65852
|
resolvedAnimationKind
|
|
65777
65853
|
};
|
|
65778
65854
|
};
|
|
65779
|
-
|
|
65780
|
-
// resolveDirectionValue/resolveAutoAnimationKind moved to popup_shared.js —
|
|
65781
|
-
// same logic Dialog's own auto-animation resolution now shares, since it
|
|
65782
|
-
// never has a real anchor either (always the "anchor === undefined" path).
|
|
65783
|
-
|
|
65784
|
-
// suppressPointerEventsDuringTransition/armPointerDownOutsideClose moved to
|
|
65785
|
-
// popup_shared.js — same helpers Dialog's own custom renderer needs, no
|
|
65786
|
-
// Popover-specific knowledge in either.
|
|
65787
|
-
|
|
65788
65855
|
Popover.Close = PopupClose;
|
|
65789
65856
|
|
|
65790
65857
|
installImportMetaCssBuild(import.meta);
|
|
@@ -65823,11 +65890,12 @@ const css$C = /* css */`@layer navi {
|
|
|
65823
65890
|
* @param {"top"|"local"} [props.layer] - Forwarded as-is to whichever of
|
|
65824
65891
|
* `Dialog`/`Popover` renders — see either component's own doc.
|
|
65825
65892
|
* @param {Element|{current: Element}} [props.anchor] - Forwarded as-is —
|
|
65826
|
-
* positioning for `Popover
|
|
65827
|
-
* `sizeFromAnchor`
|
|
65893
|
+
* positioning for `Popover`; for `Dialog`, the box it sizes from under
|
|
65894
|
+
* `sizeFromAnchor` and lifts out of under `animation="lifting"`. In both,
|
|
65895
|
+
* intent on the anchor warms the content (see `mount`).
|
|
65828
65896
|
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] - Forwarded
|
|
65829
65897
|
* as-is to both — what it governs differs (positioning for `Popover`,
|
|
65830
|
-
*
|
|
65898
|
+
* sizing and lifting for `Dialog`), but "ignore whatever anchor the
|
|
65831
65899
|
* triggering event carried" has to mean the same thing in either mode.
|
|
65832
65900
|
* @param {string} [props.marginWithAnchor] - **Popover-only**, destructured
|
|
65833
65901
|
* out so it can't leak onto the real `<dialog>` element as a stray DOM
|
|
@@ -65845,6 +65913,8 @@ const css$C = /* css */`@layer navi {
|
|
|
65845
65913
|
* `maxWidth` below.
|
|
65846
65914
|
* @param {boolean} [props.sizeFromAnchor] - **Dialog-only**, same guard.
|
|
65847
65915
|
* @param {"box"|"scene"} [props.lift] - **Dialog-only**, same guard.
|
|
65916
|
+
* @param {Element|{current: Element}|string} [props.liftAnchor] -
|
|
65917
|
+
* **Dialog-only**, same guard.
|
|
65848
65918
|
* @param {string} [props.positionArea] - Forwarded as-is — `Dialog` and
|
|
65849
65919
|
* `Popover` have different own defaults (`"center"` vs. `"bottom"`),
|
|
65850
65920
|
* deliberately not homogenized here (each reads best for its own typical
|
|
@@ -65857,8 +65927,9 @@ const css$C = /* css */`@layer navi {
|
|
|
65857
65927
|
* the screen-size check happens to pick, which defeats the point of
|
|
65858
65928
|
* having one shared API in the first place. Note this only says what a
|
|
65859
65929
|
* press outside *does*; whether it reaches the page at all is `backdrop`
|
|
65860
|
-
* below
|
|
65861
|
-
*
|
|
65930
|
+
* below. `"capture"` is a wall that absorbs the press without closing, in
|
|
65931
|
+
* either mode. `"ignore"` is not the same in both: a `Dialog` keeps its
|
|
65932
|
+
* wall, a `Popover` lays none and the press reaches the page.
|
|
65862
65933
|
* @param {boolean} [props.backdrop] - Whether anything is laid between the
|
|
65863
65934
|
* popup and the page at all: `false` lets a press outside both dismiss the
|
|
65864
65935
|
* popup and reach whatever it landed on, in one gesture. Forwarded as-is and
|
|
@@ -65877,8 +65948,13 @@ const css$C = /* css */`@layer navi {
|
|
|
65877
65948
|
* identically): the wash the backdrop paints over what is behind.
|
|
65878
65949
|
* @param {string} [props.backdropFilter] - Forwarded as-is: what that wash
|
|
65879
65950
|
* does to the picture underneath, `"blur(4px)"` and the like.
|
|
65880
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"
|
|
65881
|
-
* - Forwarded
|
|
65951
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`|"lifting"|{open: boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`, close: "lifting"}} [props.animation]
|
|
65952
|
+
* - Forwarded to whichever renders — see either component's own doc.
|
|
65953
|
+
* `"expanding"`/`"expand-*"` are `Popover`'s own; `"lifting"` and
|
|
65954
|
+
* `{ open, close: "lifting" }` are `Dialog`'s. A popover has no lift, so in
|
|
65955
|
+
* popover mode `"lifting"` plays no movement (what a dialog does with no
|
|
65956
|
+
* anchor to lift out of) and the object form opens with its `open`, which
|
|
65957
|
+
* the close plays backwards like every other kind.
|
|
65882
65958
|
* @param {string} [props.animationDuration] - Forwarded as-is.
|
|
65883
65959
|
* @param {string} [props.maxWidth] - Forwarded as-is to both; also read
|
|
65884
65960
|
* here directly to help decide the automatic `mode` (a fixed length under
|
|
@@ -65898,11 +65974,15 @@ const css$C = /* css */`@layer navi {
|
|
|
65898
65974
|
* `--x-popover-max-width`) instead of its content width — same meaning
|
|
65899
65975
|
* whichever mode the screen-size resolution picks.
|
|
65900
65976
|
* @param {boolean} [props.expandY] - Same, vertically (`data-expand-y`).
|
|
65901
|
-
* @param {boolean} [props.scrollCapture] - Forwarded
|
|
65977
|
+
* @param {boolean|"dialog"|"popover"} [props.scrollCapture] - Forwarded to
|
|
65978
|
+
* both; a mode name traps scroll only when that mode is the one rendered.
|
|
65902
65979
|
* @param {boolean} [props.open] - Forwarded as-is (controlled).
|
|
65903
|
-
* @param {import("@preact/signals").Signal<
|
|
65980
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] -
|
|
65904
65981
|
* Forwarded as-is: one binding to both drive the popup's open state and
|
|
65905
65982
|
* know where it is (see `Dialog`/`Popover`'s own `signal`).
|
|
65983
|
+
* @param {any} [props.value] - Forwarded as-is: what `signal` holds while
|
|
65984
|
+
* THIS popup is open, for several sharing one signal (see
|
|
65985
|
+
* `Dialog`/`Popover`'s own `value`).
|
|
65906
65986
|
* @param {boolean} [props.defaultOpen] - Forwarded as-is (uncontrolled,
|
|
65907
65987
|
* mount-only).
|
|
65908
65988
|
* @param {(event: Event) => void} [props.onClose] - Forwarded as-is.
|
|
@@ -65917,6 +65997,8 @@ const css$C = /* css */`@layer navi {
|
|
|
65917
65997
|
* them afterwards. `"always"` builds them right away, for content something
|
|
65918
65998
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
65919
65999
|
* surrounding form collects on submit, a size measured from outside.
|
|
66000
|
+
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
66001
|
+
* minus the cost on the critical render.
|
|
65920
66002
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
65921
66003
|
* content whose fresh state is its initial state: an uncontrolled field
|
|
65922
66004
|
* seeded from a `defaultValue` that changed while the popup was closed.
|
|
@@ -65938,6 +66020,8 @@ const Popup = props => {
|
|
|
65938
66020
|
// mode the automatic screen-size resolution happens to pick.
|
|
65939
66021
|
pressOutside = "close",
|
|
65940
66022
|
backdrop,
|
|
66023
|
+
// A popover is handed only the values it understands (toPopoverAnimation).
|
|
66024
|
+
animation,
|
|
65941
66025
|
// Popover-only (see this component's own doc) — destructured out so
|
|
65942
66026
|
// they're never part of ...rest, and therefore never forwarded to
|
|
65943
66027
|
// Dialog below, where they'd otherwise leak onto the real <dialog>
|
|
@@ -65952,6 +66036,7 @@ const Popup = props => {
|
|
|
65952
66036
|
dockedOnSmallTouchScreen,
|
|
65953
66037
|
sizeFromAnchor,
|
|
65954
66038
|
lift,
|
|
66039
|
+
liftAnchor,
|
|
65955
66040
|
...rest
|
|
65956
66041
|
} = props;
|
|
65957
66042
|
const [mode] = useResolvedPopupMode(modeProp, maxWidth, {
|
|
@@ -65971,6 +66056,8 @@ const Popup = props => {
|
|
|
65971
66056
|
dockedOnSmallTouchScreen: dockedOnSmallTouchScreen,
|
|
65972
66057
|
sizeFromAnchor: sizeFromAnchor,
|
|
65973
66058
|
lift: lift,
|
|
66059
|
+
liftAnchor: liftAnchor,
|
|
66060
|
+
animation: animation,
|
|
65974
66061
|
maxWidth: maxWidth,
|
|
65975
66062
|
pressOutside: pressOutside,
|
|
65976
66063
|
backdrop: backdrop,
|
|
@@ -65984,6 +66071,7 @@ const Popup = props => {
|
|
|
65984
66071
|
}
|
|
65985
66072
|
return jsx(Popover, {
|
|
65986
66073
|
...rest,
|
|
66074
|
+
animation: toPopoverAnimation(animation),
|
|
65987
66075
|
maxWidth: maxWidth,
|
|
65988
66076
|
pressOutside: pressOutside,
|
|
65989
66077
|
backdrop: backdrop,
|
|
@@ -66001,6 +66089,18 @@ const Popup = props => {
|
|
|
66001
66089
|
};
|
|
66002
66090
|
Popup.Close = PopupClose;
|
|
66003
66091
|
|
|
66092
|
+
// What Dialog's own animation values mean to a popover, which has no lift
|
|
66093
|
+
// (see the `animation` doc above).
|
|
66094
|
+
const toPopoverAnimation = animation => {
|
|
66095
|
+
if (animation === "lifting") {
|
|
66096
|
+
return undefined;
|
|
66097
|
+
}
|
|
66098
|
+
if (animation !== null && typeof animation === "object") {
|
|
66099
|
+
return animation.open;
|
|
66100
|
+
}
|
|
66101
|
+
return animation;
|
|
66102
|
+
};
|
|
66103
|
+
|
|
66004
66104
|
installImportMetaCssBuild(import.meta);
|
|
66005
66105
|
const css$B = /* css */`.navi_picker_callout_dock > [data-picker-content] {
|
|
66006
66106
|
display: none;
|
|
@@ -66515,7 +66615,7 @@ const PickerCustom = props => {
|
|
|
66515
66615
|
// Not on pickerProps (the trigger): commands.js's own
|
|
66516
66616
|
// resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
|
|
66517
66617
|
// find where to dispatch navi_request_open/navi_request_close — and
|
|
66518
|
-
// the popup itself
|
|
66618
|
+
// the popup itself carries its own aria-expanded (see
|
|
66519
66619
|
// popover.jsx/dialog.jsx), which is *closer* than the picker's own
|
|
66520
66620
|
// aria-expanded for anything dispatched from inside the popup's own
|
|
66521
66621
|
// content (e.g. a `command="--navi-close"` button rendered as
|
|
@@ -66795,12 +66895,8 @@ const PickerContentInsidePopup = props => {
|
|
|
66795
66895
|
mode,
|
|
66796
66896
|
pointerLock,
|
|
66797
66897
|
scrollCapture,
|
|
66798
|
-
// No default here
|
|
66799
|
-
//
|
|
66800
|
-
// Popover's real `focusCapture` prop (see this file's history), so
|
|
66801
|
-
// focus-trapping has never really been active for popover-mode pickers;
|
|
66802
|
-
// defaulting the now-correctly-named prop to `true` would be a real,
|
|
66803
|
-
// unintended behavior change riding along with the rename.
|
|
66898
|
+
// No default here: Popover's own `focusCapture` is inactive by default,
|
|
66899
|
+
// and a popover-mode picker traps the focus only when asked to.
|
|
66804
66900
|
focusCapture,
|
|
66805
66901
|
// Popup documents its own `layer` as forwarded as-is to Dialog/Popover,
|
|
66806
66902
|
// but popupProps is built explicitly here, so it only travels if named.
|
|
@@ -67329,9 +67425,14 @@ const PickerNaviMinute = props => {
|
|
|
67329
67425
|
step,
|
|
67330
67426
|
value
|
|
67331
67427
|
} = props;
|
|
67428
|
+
|
|
67429
|
+
// resolveInputProps has already run (PickerFirstResolver), so the type
|
|
67430
|
+
// handed on is one the picker renders as is — the text field a navi numeric
|
|
67431
|
+
// type resolves to, told what its value is.
|
|
67332
67432
|
return jsx(Next, {
|
|
67333
67433
|
...props,
|
|
67334
|
-
type: "
|
|
67434
|
+
type: "text",
|
|
67435
|
+
"navi-input-type": "minute",
|
|
67335
67436
|
children: jsxs(Box, {
|
|
67336
67437
|
flex: "y",
|
|
67337
67438
|
spacing: "s",
|
|
@@ -67437,6 +67538,8 @@ document.addEventListener(
|
|
|
67437
67538
|
{ capture: true, passive: true },
|
|
67438
67539
|
);
|
|
67439
67540
|
|
|
67541
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
67542
|
+
|
|
67440
67543
|
/**
|
|
67441
67544
|
* Calls back once the browser has painted what is committed now.
|
|
67442
67545
|
*
|
|
@@ -67448,10 +67551,9 @@ document.addEventListener(
|
|
|
67448
67551
|
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
67449
67552
|
* late, through the timeout.
|
|
67450
67553
|
*
|
|
67554
|
+
* @param {() => void} callback
|
|
67451
67555
|
* @returns {() => void} cancel
|
|
67452
67556
|
*/
|
|
67453
|
-
const FRAME_TIMEOUT_MS = 100;
|
|
67454
|
-
|
|
67455
67557
|
const afterPaint = (callback) => {
|
|
67456
67558
|
let called = false;
|
|
67457
67559
|
let timeoutId;
|
|
@@ -68998,7 +69100,7 @@ const SELECTABLE_INPUT_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-
|
|
|
68998
69100
|
const SelectableInputProxy = props => {
|
|
68999
69101
|
const selectableRealInputProps = useContext(SelectableRealInputContext);
|
|
69000
69102
|
if (!selectableRealInputProps) {
|
|
69001
|
-
throw new Error("
|
|
69103
|
+
throw new Error("SelectableInput must be used within a row of a selectable List");
|
|
69002
69104
|
}
|
|
69003
69105
|
|
|
69004
69106
|
// Reset FieldToInterfaceContext to ensure we don't read id or report our
|
|
@@ -69036,7 +69138,7 @@ const SelectableInput = SelectableInputProxy;
|
|
|
69036
69138
|
* background-color: var(--search-match-background-color);
|
|
69037
69139
|
* }
|
|
69038
69140
|
*
|
|
69039
|
-
* The `highlight`
|
|
69141
|
+
* The `highlight` argument of `useSearchHighlight` (a row's `matchInfo.matchRanges`) can be:
|
|
69040
69142
|
* - an array of [start, end] pairs — applied to all text nodes under the root element
|
|
69041
69143
|
* - an object { [domSelector]: [[start, end], …] } — applied to each sub-element
|
|
69042
69144
|
* matched by the selector (the format produced by createSearch)
|
|
@@ -72584,8 +72686,8 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
72584
72686
|
*
|
|
72585
72687
|
* All three are about the row as a thing the LIST holds, never about the
|
|
72586
72688
|
* selection: a selectable row taken while its list sends says so on its own
|
|
72587
|
-
* ("
|
|
72588
|
-
* that.
|
|
72689
|
+
* ("le choix est en cours d'enregistrement", "la sélection…" in a `multiple`
|
|
72690
|
+
* list), and needs no `loading` for that.
|
|
72589
72691
|
* @param {boolean} [props.readOnly]
|
|
72590
72692
|
* The row cannot be acted on: dimmed and click-through-proof, buttons inside
|
|
72591
72693
|
* it included.
|
|
@@ -73680,9 +73782,12 @@ const useItemStore = ({
|
|
|
73680
73782
|
* (accessible via aria-labelledby) and a <ul role="group"> for the items.
|
|
73681
73783
|
*
|
|
73682
73784
|
* Props:
|
|
73683
|
-
* label
|
|
73684
|
-
* labelProps
|
|
73685
|
-
*
|
|
73785
|
+
* label — group label content
|
|
73786
|
+
* labelProps — props forwarded to the label <span>
|
|
73787
|
+
* hiddenWhileEmpty — the group leaves the flow (`display: none`) while it
|
|
73788
|
+
* holds no real row — a search that emptied it, rows not
|
|
73789
|
+
* arrived yet
|
|
73790
|
+
* ...rest — forwarded to the outer <li role="presentation">
|
|
73686
73791
|
*/
|
|
73687
73792
|
const ListItemGroup = ({
|
|
73688
73793
|
label,
|
|
@@ -73794,7 +73899,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
73794
73899
|
* uiAction?: (value: any) => void,
|
|
73795
73900
|
* popover?: boolean,
|
|
73796
73901
|
* role?: string,
|
|
73797
|
-
* renderBudget?: number | string,
|
|
73902
|
+
* renderBudget?: number | string | {initial?: number, after: number},
|
|
73798
73903
|
* renderBudgetSkipCheck?: boolean,
|
|
73799
73904
|
* virtualItemSize?: number,
|
|
73800
73905
|
* onListVisibleItemsChange?: (visibleItems: any[]) => void,
|
|
@@ -73823,6 +73928,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
73823
73928
|
* alignX?: string,
|
|
73824
73929
|
* alignY?: string,
|
|
73825
73930
|
* flexWrap?: boolean,
|
|
73931
|
+
* overflow?: string,
|
|
73826
73932
|
* expandX?: boolean,
|
|
73827
73933
|
* expandY?: boolean,
|
|
73828
73934
|
* expand?: boolean,
|
|
@@ -74009,10 +74115,10 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
74009
74115
|
* @param {number} [props.parallelGuard=4]
|
|
74010
74116
|
* How many runs the rows may have in flight at once, for a list whose rows
|
|
74011
74117
|
* carry their own `action` (a button per row). While that many are out, every
|
|
74012
|
-
*
|
|
74013
|
-
* the next press is possible again as soon as one comes back.
|
|
74014
|
-
* lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
74015
|
-
* says how many things the selection may hold.
|
|
74118
|
+
* control that would start another run goes read-only and says how many it is
|
|
74119
|
+
* waiting on; the next press is possible again as soon as one comes back.
|
|
74120
|
+
* `Infinity` lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
74121
|
+
* the one that says how many things the selection may hold.
|
|
74016
74122
|
* @param {boolean} [props.standalone]
|
|
74017
74123
|
* This list answers for itself: it does not register with the control group
|
|
74018
74124
|
* or picker around it, so its selection stays out of that value and nothing
|
|
@@ -76481,7 +76587,7 @@ const PickerFirstResolver = props => {
|
|
|
76481
76587
|
* `focusOnOpen` below.
|
|
76482
76588
|
*
|
|
76483
76589
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
76484
|
-
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_number" | "navi_percentage",
|
|
76590
|
+
* type?: "date" | "month" | "week" | "time" | "datetime" | "duration" | "color" | "file" | "text" | "object" | "array" | "confirm" | "navi_time" | "navi_minute" | "navi_number" | "navi_percentage",
|
|
76485
76591
|
* value?: any,
|
|
76486
76592
|
* defaultValue?: any,
|
|
76487
76593
|
* name?: string,
|
|
@@ -76547,7 +76653,7 @@ const PickerFirstResolver = props => {
|
|
|
76547
76653
|
* popupTestId?: string,
|
|
76548
76654
|
* confirmTestId?: string,
|
|
76549
76655
|
* cancelTestId?: string,
|
|
76550
|
-
* mount?: "always" | "from-first-open" | "while-opened",
|
|
76656
|
+
* mount?: "always" | "idle" | "from-first-open" | "while-opened",
|
|
76551
76657
|
* dialogExpand?: boolean,
|
|
76552
76658
|
* dialogExpandX?: boolean,
|
|
76553
76659
|
* dialogExpandY?: boolean,
|
|
@@ -76984,8 +77090,8 @@ const PickerFirstResolver = props => {
|
|
|
76984
77090
|
* itself names the trigger (see docs/testid.md); this one is for a test that
|
|
76985
77091
|
* needs the popup's own box — its surface, its position, its backdrop — and
|
|
76986
77092
|
* not only what it holds, which a testid inside the children already names.
|
|
76987
|
-
*
|
|
76988
|
-
*
|
|
77093
|
+
* Under `mode="callout"` it names the callout element — the control's own,
|
|
77094
|
+
* shared with its constraint messages — while the children are shown in it.
|
|
76989
77095
|
* @param {string} [confirmTestId] `type="confirm"`, default body: the
|
|
76990
77096
|
* `data-testid` of the yes button; `cancelTestId` that of the no button.
|
|
76991
77097
|
* Both are elements navi builds, which a test may not name by class (see
|
|
@@ -77373,7 +77479,7 @@ const TRAVELS_NONE = [];
|
|
|
77373
77479
|
* fallbackValue?: any,
|
|
77374
77480
|
* valueAtStep: (value: any, count: number) => any,
|
|
77375
77481
|
* compareValues?: (a: any, b: any) => number,
|
|
77376
|
-
* renderValue?: (value: any) => import("ignore:preact").ComponentChildren,
|
|
77482
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("ignore:preact").ComponentChildren,
|
|
77377
77483
|
* controlProps?: object,
|
|
77378
77484
|
* duration?: number,
|
|
77379
77485
|
* vertical?: boolean,
|
|
@@ -78848,7 +78954,8 @@ const css$q = /* css */`.navi_input_duration {
|
|
|
78848
78954
|
* @param {boolean} [props.required]
|
|
78849
78955
|
* @param {boolean} [props.loading]
|
|
78850
78956
|
* @param {Function} [props.uiAction] - Called on every change with the ISO 8601 value
|
|
78851
|
-
* @param {Function} [props.action] -
|
|
78957
|
+
* @param {Function} [props.action] - Runs with the ISO 8601 value when it changes,
|
|
78958
|
+
* like any control's `action` (busy state, error callout)
|
|
78852
78959
|
* @param {preact.ComponentChild} [props.unitHour] - Custom label for the hour sub-field
|
|
78853
78960
|
* @param {"auto"|"left"|"center"|"right"} [props.textAlign="auto"] - Text alignment of sub-inputs.
|
|
78854
78961
|
* "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
|
|
@@ -79859,11 +79966,12 @@ const splitPopupProps = props => {
|
|
|
79859
79966
|
* multi-word, one word at start (all match) 0.75
|
|
79860
79967
|
* phrase / word at word boundary 0.625
|
|
79861
79968
|
* phrase / words mid-word 0.5
|
|
79969
|
+
* acronym (2+ chars, each the first letter of a word, in order) 0.4, 0.45 at start
|
|
79862
79970
|
* + case-exact bonus +0.125
|
|
79863
79971
|
* multi-word partial: score × (matched/total)
|
|
79864
79972
|
*
|
|
79865
79973
|
* matchRanges: [start, end] pairs (exclusive end) for CSS Highlight API.
|
|
79866
|
-
* Intended to be passed to
|
|
79974
|
+
* Intended to be passed to useSearchText as the matchFn parameter.
|
|
79867
79975
|
*/
|
|
79868
79976
|
const applySearch = (searchText, value) => {
|
|
79869
79977
|
if (!searchText) {
|
|
@@ -80073,7 +80181,7 @@ const mergeRanges = (ranges) => {
|
|
|
80073
80181
|
};
|
|
80074
80182
|
|
|
80075
80183
|
/**
|
|
80076
|
-
* createSearch — builds a matchFn compatible with
|
|
80184
|
+
* createSearch — builds a matchFn compatible with useSearchText that searches
|
|
80077
80185
|
* across multiple named fields of an item, each with its own DOM selector
|
|
80078
80186
|
* and optional priority weight.
|
|
80079
80187
|
*
|
|
@@ -80091,7 +80199,7 @@ const mergeRanges = (ranges) => {
|
|
|
80091
80199
|
* },
|
|
80092
80200
|
* });
|
|
80093
80201
|
*
|
|
80094
|
-
* const [orderedItems, getItemMatchInfo] =
|
|
80202
|
+
* const [orderedItems, getItemMatchInfo] = useSearchText(search, items, searchPerson);
|
|
80095
80203
|
* // getItemMatchInfo(item).matchRanges is { ".name": [[start,end],…], ".address": [[start,end],…] }
|
|
80096
80204
|
* // Pass the whole thing: <ListItem matchInfo={getItemMatchInfo(item)} />
|
|
80097
80205
|
* // — ListItem handles the per-selector object format for matchRanges.
|
|
@@ -80158,7 +80266,7 @@ const useSearchText = (searchText, items, matchFn = applySearch) => {
|
|
|
80158
80266
|
);
|
|
80159
80267
|
}
|
|
80160
80268
|
if (items === undefined) {
|
|
80161
|
-
throw new TypeError("
|
|
80269
|
+
throw new TypeError("useSearchText: items is undefined");
|
|
80162
80270
|
}
|
|
80163
80271
|
const { orderedItems, matchInfoMap } = useMemo(() => {
|
|
80164
80272
|
const { scoreEntries, nonMatched, matchInfoMap } = buildMatchInfo(
|
|
@@ -80283,17 +80391,19 @@ const buildMatchInfo = (searchText, items, matchFn) => {
|
|
|
80283
80391
|
* - orderedKeys: number[] of visible item keys sorted by explicit order
|
|
80284
80392
|
* - keyToOrderedIndex: Map key → orderedKeys index, gives O(1) indexOf equivalent
|
|
80285
80393
|
* - keyToExplicitOrder: Map key → explicitly passed index, used to maintain sort order
|
|
80286
|
-
* -
|
|
80287
|
-
* - visibleItemsSignal: signal(array),
|
|
80394
|
+
* - itemsSignal: signal(array), all items including hidden, ordered by explicit index
|
|
80395
|
+
* - visibleItemsSignal: signal(array), items neither hidden nor filtered
|
|
80288
80396
|
* - countSignal: signal(number), count of all items including hidden
|
|
80289
80397
|
* - visibleCountSignal: signal(number), updated in microtask batch, only when count changes
|
|
80290
|
-
* -
|
|
80398
|
+
* - noMatchCountSignal: signal(number), items registered with `match: false`
|
|
80291
80399
|
* - onChangeRef: holds the latest onChange callback, called once per microtask batch
|
|
80292
80400
|
*
|
|
80293
|
-
* useTrackItem(
|
|
80294
|
-
*
|
|
80295
|
-
* correct order and passes it directly — no render-sequence deduction
|
|
80296
|
-
*
|
|
80401
|
+
* useTrackItem(data): registers the item under `data.id`, at the position
|
|
80402
|
+
* `data.index` gives it among its siblings. The caller (e.g. items.map) knows
|
|
80403
|
+
* the correct order and passes it directly — no render-sequence deduction
|
|
80404
|
+
* needed. `data.role === "presentation"` keeps it out entirely.
|
|
80405
|
+
* Returns the visible rank (position among visible items), or -1 when hidden
|
|
80406
|
+
* or filtered.
|
|
80297
80407
|
* Signals and onChange are deferred to a microtask so multiple items updating
|
|
80298
80408
|
* in one commit cause only one notification.
|
|
80299
80409
|
*
|
|
@@ -81061,7 +81171,7 @@ const WheelGroupContext = createContext(null);
|
|
|
81061
81171
|
* [key: string]: any,
|
|
81062
81172
|
* }>}
|
|
81063
81173
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
81064
|
-
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (
|
|
81174
|
+
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (1.8em).
|
|
81065
81175
|
* @param {number|string} [props.itemWidth] - Main-axis size of a cell when horizontal (number = px). Defaults to the CSS var (3.5ch).
|
|
81066
81176
|
* @param {number|string} [props.size] - Font size of the wheel: a size token ("s", "m", "l", "xl", …), a number (px), or a CSS length. Scales the digits and, since the row size is em-based, the row height too — a simple way to make the whole wheel bigger. An explicit itemHeight/itemWidth still overrides the row size.
|
|
81067
81177
|
* @param {boolean} [props.bounded] - Give the wheel fixed ends instead of wrapping: it stops at the first/last value. By default the wheel loops endlessly (past the last value the first reappears, and vice-versa).
|
|
@@ -82557,7 +82667,7 @@ Wheel.Item = WheelItem;
|
|
|
82557
82667
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
82558
82668
|
* @param {boolean} [props.glass] - Frost every wheel's neighbouring rows (see Wheel's glass prop) with one prop for the whole group.
|
|
82559
82669
|
* @param {boolean} [props.frameBorder] - Line every wheel's center-window edges with a faint frame (off by default; independent of glass).
|
|
82560
|
-
* @param {boolean} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
82670
|
+
* @param {boolean|number} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
82561
82671
|
*/
|
|
82562
82672
|
const WheelGroup = props => {
|
|
82563
82673
|
import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
|
|
@@ -83342,8 +83452,8 @@ const stringifyTableSelectionValue = (type, value) => {
|
|
|
83342
83452
|
|
|
83343
83453
|
/**
|
|
83344
83454
|
* Check if a specific cell is selected
|
|
83345
|
-
* @param {
|
|
83346
|
-
* @param {{
|
|
83455
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83456
|
+
* @param {{columnId: string, rowId: string}} cell - The cell's column and row ids
|
|
83347
83457
|
* @returns {boolean} True if the cell is selected
|
|
83348
83458
|
*/
|
|
83349
83459
|
const isCellSelected = (selection, { columnId, rowId }) => {
|
|
@@ -83356,8 +83466,8 @@ const isCellSelected = (selection, { columnId, rowId }) => {
|
|
|
83356
83466
|
|
|
83357
83467
|
/**
|
|
83358
83468
|
* Check if a specific row is selected
|
|
83359
|
-
* @param {
|
|
83360
|
-
* @param {
|
|
83469
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83470
|
+
* @param {string} rowId - The row's id (the `id` of its `<Tr>`)
|
|
83361
83471
|
* @returns {boolean} True if the row is selected
|
|
83362
83472
|
*/
|
|
83363
83473
|
const isRowSelected = (selection, rowId) => {
|
|
@@ -83367,8 +83477,8 @@ const isRowSelected = (selection, rowId) => {
|
|
|
83367
83477
|
|
|
83368
83478
|
/**
|
|
83369
83479
|
* Check if a specific column is selected
|
|
83370
|
-
* @param {
|
|
83371
|
-
* @param {
|
|
83480
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83481
|
+
* @param {string} columnId - The column's id (the `id` of its `<Col>`)
|
|
83372
83482
|
* @returns {boolean} True if the column is selected
|
|
83373
83483
|
*/
|
|
83374
83484
|
const isColumnSelected = (selection, columnId) => {
|
|
@@ -83903,7 +84013,7 @@ const initDragTableColumnViaPointer = (pointerdownEvent, {
|
|
|
83903
84013
|
/**
|
|
83904
84014
|
* Creates a MutationObserver that syncs attribute changes from original table to clone
|
|
83905
84015
|
* @param {HTMLElement} table - The original table element
|
|
83906
|
-
* @param {HTMLElement}
|
|
84016
|
+
* @param {HTMLElement} tableClone - The cloned table element
|
|
83907
84017
|
* @returns {MutationObserver} The observer instance with disconnect method
|
|
83908
84018
|
*/
|
|
83909
84019
|
const createTableAttributeSync = (table, tableClone) => {
|
|
@@ -85868,9 +85978,10 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
85868
85978
|
* KEY PRINCIPLES:
|
|
85869
85979
|
* - Use inset box-shadow to ensure borders appear above table cell backgrounds
|
|
85870
85980
|
* - Use ::before pseudo-elements with position: absolute for flexible positioning
|
|
85871
|
-
* - Each cell draws its own borders
|
|
85981
|
+
* - Each cell draws its own borders; `borderCollapse` (on by default) keeps only
|
|
85982
|
+
* the right and bottom ones, plus top on the first row and left on the first column
|
|
85872
85983
|
* - Selection borders override table borders using higher CSS specificity
|
|
85873
|
-
* - Sticky borders use thicker box-shadows in accent color
|
|
85984
|
+
* - Sticky borders use thicker box-shadows in an accent color
|
|
85874
85985
|
*
|
|
85875
85986
|
* TECHNICAL IMPLEMENTATION:
|
|
85876
85987
|
* - All borders use inset box-shadow with specific directional mapping:
|
|
@@ -85878,9 +85989,8 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
85878
85989
|
* * Right: inset -1px 0 0 0
|
|
85879
85990
|
* * Bottom: inset 0 -1px 0 0
|
|
85880
85991
|
* * Left: inset 1px 0 0 0
|
|
85881
|
-
* - Selection borders
|
|
85992
|
+
* - Selection borders override table borders in the same pseudo-element
|
|
85882
85993
|
* - Sticky borders replace regular borders with thicker colored variants
|
|
85883
|
-
* - Border-collapse mode available as optional feature for future use
|
|
85884
85994
|
*
|
|
85885
85995
|
* Note how border disappear for sticky elements when using border-collapse (https://bugzilla.mozilla.org/show_bug.cgi?id=1727594)
|
|
85886
85996
|
*
|
|
@@ -87014,11 +87124,18 @@ const BadgeCountOverflow = () => jsx("span", {
|
|
|
87014
87124
|
});
|
|
87015
87125
|
const MAX_CHAR_AS_CIRCLE = 3;
|
|
87016
87126
|
const MAX_FOR_CIRCLE = 99;
|
|
87127
|
+
|
|
87128
|
+
/**
|
|
87129
|
+
* @param {boolean} [circle] Drawn round rather than as a pill. A circle holds
|
|
87130
|
+
* three characters at most, so `max` defaults to 99 with it ("99+"); a `max`
|
|
87131
|
+
* above 99, or `"none"`, lets a bigger count turn the circle into a pill as
|
|
87132
|
+
* it grows.
|
|
87133
|
+
* @param {number|"none"} [max] The count above which `max` and a "+" are
|
|
87134
|
+
* written in its place. 99 under `circle`, no cap otherwise.
|
|
87135
|
+
*/
|
|
87017
87136
|
const BadgeCount = ({
|
|
87018
87137
|
children,
|
|
87019
87138
|
maxElement = jsx(BadgeCountOverflow, {}),
|
|
87020
|
-
// When you use max="none" (or max > 99) it might be a good idea to force ellipse
|
|
87021
|
-
// so that visually the interface do not suddently switch from circle to ellipse depending on the count
|
|
87022
87139
|
circle,
|
|
87023
87140
|
max = circle ? MAX_FOR_CIRCLE : Infinity,
|
|
87024
87141
|
integer,
|
|
@@ -88201,12 +88318,13 @@ const css$7 = /* css */`.navi_text_box {
|
|
|
88201
88318
|
* - Displays optional icon(s) before and/or after the text
|
|
88202
88319
|
* - Shrinks its width to fit the longest rendered text line (no trailing blank space)
|
|
88203
88320
|
* - Wraps long text by default (overflow-wrap: anywhere)
|
|
88204
|
-
* - Shows ellipsis
|
|
88321
|
+
* - Shows an ellipsis instead under `singleLine`
|
|
88205
88322
|
*
|
|
88206
88323
|
* Props:
|
|
88207
88324
|
* iconBefore — element shown to the left of the text
|
|
88208
88325
|
* iconAfter — element shown to the right of the text (stays on the same line)
|
|
88209
|
-
*
|
|
88326
|
+
* singleLine — keeps the text on one line, cut with an ellipsis (no width shrinking)
|
|
88327
|
+
* maxHeight — CSS max-height string; content past it is clipped
|
|
88210
88328
|
* children — the text content
|
|
88211
88329
|
*/
|
|
88212
88330
|
const TextBox = ({
|
|
@@ -88385,6 +88503,41 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
88385
88503
|
stroke-opacity: 1 !important;
|
|
88386
88504
|
}
|
|
88387
88505
|
`;
|
|
88506
|
+
|
|
88507
|
+
/**
|
|
88508
|
+
* SVGMaskOverlay
|
|
88509
|
+
*
|
|
88510
|
+
* Creates composite SVGs by combining independent SVG elements with masking.
|
|
88511
|
+
*
|
|
88512
|
+
* This component solves the challenge of combining independently created SVGs into
|
|
88513
|
+
* a single visual composition. Each SVG can have its own coordinate system, viewBox,
|
|
88514
|
+
* and styling, allowing for maximum reusability of individual icons or graphics.
|
|
88515
|
+
*
|
|
88516
|
+
* When overlaying SVGs, each subsequent overlay "cuts out" its portion from the base SVG,
|
|
88517
|
+
* creating a seamless integration where SVGs appear to interact with each other visually.
|
|
88518
|
+
*
|
|
88519
|
+
* Key benefits:
|
|
88520
|
+
* - Maintains each SVG's independence - use them individually elsewhere
|
|
88521
|
+
* - Handles different viewBox dimensions automatically
|
|
88522
|
+
* - Works with any SVG components regardless of internal implementation
|
|
88523
|
+
* - Supports unlimited overlay elements
|
|
88524
|
+
* - Creates proper masking between elements for visual integration
|
|
88525
|
+
*
|
|
88526
|
+
* Usage example combining two independent icon components:
|
|
88527
|
+
* ```jsx
|
|
88528
|
+
* <SVGMaskOverlay viewBox="0 0 24 24">
|
|
88529
|
+
* <DatabaseSvg />
|
|
88530
|
+
* <svg x="12" y="12" width="16" height="16" overflow="visible">
|
|
88531
|
+
* <PlusSvg />
|
|
88532
|
+
* </svg>
|
|
88533
|
+
* </SVGMaskOverlay>
|
|
88534
|
+
* ```
|
|
88535
|
+
*
|
|
88536
|
+
* @param {Object} props - Component properties
|
|
88537
|
+
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
88538
|
+
* @param {import("ignore:preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
88539
|
+
* @returns {import("ignore:preact").VNode} A composed SVG with all elements properly masked
|
|
88540
|
+
*/
|
|
88388
88541
|
const SVGMaskOverlay = ({
|
|
88389
88542
|
viewBox,
|
|
88390
88543
|
children
|
|
@@ -88397,7 +88550,7 @@ const SVGMaskOverlay = ({
|
|
|
88397
88550
|
return children[0];
|
|
88398
88551
|
}
|
|
88399
88552
|
if (!viewBox) {
|
|
88400
|
-
console.error("
|
|
88553
|
+
console.error("SVGMaskOverlay requires an explicit viewBox");
|
|
88401
88554
|
return null;
|
|
88402
88555
|
}
|
|
88403
88556
|
|
|
@@ -88457,6 +88610,8 @@ const css$3 = /* css */`@layer navi {
|
|
|
88457
88610
|
}
|
|
88458
88611
|
|
|
88459
88612
|
.navi_card_layout {
|
|
88613
|
+
box-sizing: border-box;
|
|
88614
|
+
min-height: 100%;
|
|
88460
88615
|
padding-top: var(--layout-margin-top, var(--layout-margin-y, var(--layout-margin)));
|
|
88461
88616
|
padding-right: var(--layout-margin-right, var(--layout-margin-x, var(--layout-margin)));
|
|
88462
88617
|
padding-bottom: var(--layout-margin-bottom, var(--layout-margin-y, var(--layout-margin)));
|
|
@@ -88498,6 +88653,11 @@ const CardLayoutStyleCSSVars = {
|
|
|
88498
88653
|
minHeight: "--layout-min-height"
|
|
88499
88654
|
};
|
|
88500
88655
|
/**
|
|
88656
|
+
* The card is centered in the area both ways and is as wide as its content,
|
|
88657
|
+
* `minWidth` at least. Down the area, the layout takes at least the height of
|
|
88658
|
+
* what holds it, so how tall that area is is up to the page around it, as for
|
|
88659
|
+
* `ViewportLayout`.
|
|
88660
|
+
*
|
|
88501
88661
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
88502
88662
|
* alignX?: string,
|
|
88503
88663
|
* alignY?: string,
|
|
@@ -88527,6 +88687,11 @@ const CardLayout = ({
|
|
|
88527
88687
|
}) => {
|
|
88528
88688
|
import.meta.css = [css$3, "@jsenv/navi/src/layout/card_layout.jsx"];
|
|
88529
88689
|
return jsx(Box, {
|
|
88690
|
+
flex: true
|
|
88691
|
+
// The card's place in the area; alignX/alignY below are its content's.
|
|
88692
|
+
,
|
|
88693
|
+
alignX: "center",
|
|
88694
|
+
alignY: "center",
|
|
88530
88695
|
baseClassName: "navi_card_layout",
|
|
88531
88696
|
styleCSSVars: CardLayoutStyleCSSVars,
|
|
88532
88697
|
visualSelector: ".navi_card",
|
|
@@ -89338,7 +89503,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
89338
89503
|
* interaction only makes sense paired with not letting focus silently
|
|
89339
89504
|
* leave the panel first. A box of the page whose press must not close the
|
|
89340
89505
|
* panel (a card that fills it) names the panel:
|
|
89341
|
-
* `data-navi-popup-inside={id}
|
|
89506
|
+
* `data-navi-popup-inside={id}`, read when the panel has no wall
|
|
89507
|
+
* (`backdrop={false}`) — see docs/popup_backdrop.md.
|
|
89342
89508
|
* @param {boolean} [props.closeByDrag=true] - Pushing the panel back
|
|
89343
89509
|
* towards the edge it is docked to closes it: the panel follows the
|
|
89344
89510
|
* pointer and finishes leaving (or comes back to rest) when it is
|
|
@@ -89353,8 +89519,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
89353
89519
|
* doc).
|
|
89354
89520
|
* @param {import("ignore:preact").ComponentChildren} props.children - No built-in
|
|
89355
89521
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
89356
|
-
* plain `<Button command="--navi-close">`
|
|
89357
|
-
* `
|
|
89522
|
+
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
89523
|
+
* `closeByPressOutside`/Escape instead.
|
|
89358
89524
|
* A form sent inside the panel closes it, as in any popup: a panel one
|
|
89359
89525
|
* keeps editing in says `command="--navi-void"` on that form (see
|
|
89360
89526
|
* docs/form_changed.md).
|
|
@@ -89445,9 +89611,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
89445
89611
|
* Stuck to the top of the panel's own scrollable area (`position: sticky`)
|
|
89446
89612
|
* regardless of `side` — only the panel's content in between scrolls. No
|
|
89447
89613
|
* built-in padding or close button — add a `<Button command="--navi-close">`
|
|
89448
|
-
*
|
|
89449
|
-
* float-right utility this file's own CSS still provides) wherever it makes
|
|
89450
|
-
* sense for the layout.
|
|
89614
|
+
* wherever it makes sense for the layout.
|
|
89451
89615
|
*
|
|
89452
89616
|
* @param {object} props
|
|
89453
89617
|
* @param {string} [props.className] - Merged with the shared
|