@jsenv/navi 0.29.387 → 0.29.388
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 +400 -310
- package/dist/dev/jsenv_navi.js.map +72 -72
- package/dist/dev/jsenv_navi_side_effects.js.map +1 -1
- package/dist/jsenv_navi.js +396 -306
- package/dist/jsenv_navi.js.map +72 -72
- package/dist/jsenv_navi_side_effects.js.map +1 -1
- 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 +12 -8
- package/docs/drag_interactions.md +44 -36
- package/docs/drag_to_travel.md +29 -23
- package/docs/dynamic_import.md +7 -5
- package/docs/error_handling.md +26 -27
- package/docs/field_validation.md +35 -6
- package/docs/interactions.md +28 -23
- package/docs/list_action.md +8 -5
- package/docs/list_refresh.md +7 -1
- package/docs/mobile_layout_pitfalls.md +6 -5
- package/docs/navigation.md +7 -5
- package/docs/network_policy.md +10 -3
- package/docs/pan_zoom.md +2 -2
- 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 +24 -17
- package/docs/route_transitions.md +13 -10
- package/docs/safe_area.md +38 -22
- 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/jsenv_navi.js
CHANGED
|
@@ -532,7 +532,9 @@ effect(() => {
|
|
|
532
532
|
* Built-in key namespaces, all overridable — the registrations below are the
|
|
533
533
|
* exhaustive list, read them to find the exact key to override:
|
|
534
534
|
* - `"button.*"` — Clear, Reset, Send, Open, Close, Cancel, Confirm…
|
|
535
|
-
* - `"
|
|
535
|
+
* - `"confirm.*"` — the question a confirmation asks by default
|
|
536
|
+
* - `"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
|
|
537
|
+
* - `"time_range.*"` — how the two ends of a span are named ("From", "to")
|
|
536
538
|
* - `"spin.*"` — the ends of a steppable range
|
|
537
539
|
* - `"list.*"` — empty/no-match/failed-rows messages
|
|
538
540
|
* - `"badge_list.*"` — the "+[count] more" overflow badge
|
|
@@ -1540,11 +1542,10 @@ const errorIsDisplayed = (error) => {
|
|
|
1540
1542
|
*
|
|
1541
1543
|
* Whatever the reader does with it is already covered without any deadline: it
|
|
1542
1544
|
* displays it (and marks it), or it throws it, and a thrown error either finds a
|
|
1543
|
-
* boundary that displays it or reaches window on its own —
|
|
1544
|
-
*
|
|
1545
|
-
*
|
|
1546
|
-
*
|
|
1547
|
-
* happened.
|
|
1545
|
+
* boundary that displays it or reaches window on its own — preact re-throws
|
|
1546
|
+
* what no boundary handled. Reporting it here as well would be a second voice
|
|
1547
|
+
* saying the same thing, always the wrong one, since this module cannot see
|
|
1548
|
+
* which of those happened.
|
|
1548
1549
|
*/
|
|
1549
1550
|
const errorTakenByRenderSet = new WeakSet();
|
|
1550
1551
|
const markErrorAsTakenByRender = (error) => {
|
|
@@ -1634,7 +1635,7 @@ const errorIsAccountedFor = (error) => {
|
|
|
1634
1635
|
/**
|
|
1635
1636
|
* Rethrown rather than logged: an error nobody took is an unhandled error, and
|
|
1636
1637
|
* the runtime already knows what to do with those (window "error" event, jsenv
|
|
1637
|
-
* overlay in dev). Same
|
|
1638
|
+
* overlay in dev). Same answer preact gives an error no boundary handled.
|
|
1638
1639
|
*/
|
|
1639
1640
|
const errorReportedSet = new WeakSet();
|
|
1640
1641
|
const reportErrorIfNobodyDisplaysIt = (error, { action } = {}) => {
|
|
@@ -2155,7 +2156,7 @@ const compareTwoJsValues = (
|
|
|
2155
2156
|
}
|
|
2156
2157
|
return true;
|
|
2157
2158
|
}
|
|
2158
|
-
// Ordered array comparison
|
|
2159
|
+
// Ordered array comparison
|
|
2159
2160
|
let i = 0;
|
|
2160
2161
|
while (i < a.length) {
|
|
2161
2162
|
const aValue = a[i];
|
|
@@ -3222,6 +3223,8 @@ const actionWeakMap = new WeakMap();
|
|
|
3222
3223
|
* long is this still true?" belongs to the question: a court list is good for
|
|
3223
3224
|
* the session, a count of people watching grows on its own and is worth a few
|
|
3224
3225
|
* minutes, a payment status is worth nothing at all.
|
|
3226
|
+
* @param {{ verb?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" }} [rootOptions.meta] -
|
|
3227
|
+
* `verb` puts the action under the network policy (docs/network_policy.md).
|
|
3225
3228
|
*/
|
|
3226
3229
|
const createAction = (callback, rootOptions = {}) => {
|
|
3227
3230
|
const existing = actionWeakMap.get(callback);
|
|
@@ -4323,7 +4326,7 @@ const runUnwatched = (startRun) => {
|
|
|
4323
4326
|
/**
|
|
4324
4327
|
* Reactively runs an action whenever the params derived from signals change.
|
|
4325
4328
|
*
|
|
4326
|
-
* @param {object} action - The action to run.
|
|
4329
|
+
* @param {object | Function} action - The action to run, or a function made into one.
|
|
4327
4330
|
* @param {Function} deriveActionParamsFromSignals - A function that reads signals and returns
|
|
4328
4331
|
* the params to pass to the action. It is re-evaluated automatically whenever a signal it
|
|
4329
4332
|
* read changes. Return `false`/`null`/`undefined` to skip running the action.
|
|
@@ -4346,6 +4349,8 @@ const runUnwatched = (startRun) => {
|
|
|
4346
4349
|
* ```
|
|
4347
4350
|
* The action will not fire while the user is actively changing filters; it fires once
|
|
4348
4351
|
* they pause for half a second.
|
|
4352
|
+
* @returns {object} the instance bound to the derived params: the one to read
|
|
4353
|
+
* (`useAsyncData`), since the answer lands there and never on `action` itself.
|
|
4349
4354
|
*/
|
|
4350
4355
|
// The run is not awaited here, and a rejection nobody waits for is an unhandled
|
|
4351
4356
|
// one — in dev, an error overlay thrown over a page that is already saying what
|
|
@@ -4860,11 +4865,12 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
4860
4865
|
*/
|
|
4861
4866
|
/**
|
|
4862
4867
|
* A bound signal that carries a default of its own says the same thing on every
|
|
4863
|
-
* control: the control starts there and
|
|
4864
|
-
*
|
|
4865
|
-
*
|
|
4866
|
-
*
|
|
4867
|
-
*
|
|
4868
|
+
* control: the control starts there, uncontrolled, and a reset goes back to it.
|
|
4869
|
+
* Uncontrolled here is about where the control starts, not about whether it
|
|
4870
|
+
* follows the signal — the binding stays two-way either way (see
|
|
4871
|
+
* stateFromSignal in control_hooks.jsx) — nor about what a form counts as
|
|
4872
|
+
* held: that is the signal's content (isUIStateHeld in held_ui_state.js), and a
|
|
4873
|
+
* stateSignal holds its default. Written once and used by everything
|
|
4868
4874
|
* that takes a `signal`, so one signal cannot mean two different things
|
|
4869
4875
|
* depending on which control it was handed to.
|
|
4870
4876
|
*/
|
|
@@ -4924,8 +4930,7 @@ const resolveInputProps = (props, { controlType = "input" } = {}) => {
|
|
|
4924
4930
|
// If no explicit defaultChecked, derive it from the signal's default
|
|
4925
4931
|
// value so that resetUIState restores to the original default.
|
|
4926
4932
|
// Only a stateSignal carries a default of its own; a plain signal has
|
|
4927
|
-
// no `options` at all,
|
|
4928
|
-
// the same optional read every other branch here already does.
|
|
4933
|
+
// no `options` at all, hence the optional read.
|
|
4929
4934
|
const defaultVal = signalOptions?.getDefaultValue(false);
|
|
4930
4935
|
if (defaultVal === undefined) ; else if (props.type === "radio") {
|
|
4931
4936
|
if (defaultVal === true) {
|
|
@@ -8835,8 +8840,8 @@ const isLanguageSupported = (lang, supportedLanguages) => {
|
|
|
8835
8840
|
* supported), falls back to `supportedLanguagesSignal` itself so callers
|
|
8836
8841
|
* still get *something* usable rather than an empty array.
|
|
8837
8842
|
*
|
|
8838
|
-
* Consumers that accept either a single lang or an ordered array
|
|
8839
|
-
*
|
|
8843
|
+
* Consumers that accept either a single lang or an ordered array
|
|
8844
|
+
* (@jsenv/humanize's `createI18n` and formatters, and native `Intl.NumberFormat`/
|
|
8840
8845
|
* `Intl.DateTimeFormat`) can pass this straight through: anything not
|
|
8841
8846
|
* covered by the first entry falls through to the next, rather than
|
|
8842
8847
|
* jumping straight to an unrelated default like "en".
|
|
@@ -9819,15 +9824,15 @@ const readOnlyMessage = (field) => {
|
|
|
9819
9824
|
* - Action dispatch + validity: `control_action.js` / `dispatchRequestAction`
|
|
9820
9825
|
* - Validity checking: `control_validation.js`
|
|
9821
9826
|
*
|
|
9822
|
-
* Each UI state controller gets its own `
|
|
9823
|
-
* `createControlInteraction`)
|
|
9827
|
+
* Each UI state controller gets its own instance, `controller.rules.interaction`
|
|
9828
|
+
* (created by `createControlInteraction`), beside `controller.rules.validation`.
|
|
9824
9829
|
*
|
|
9825
9830
|
* Flow:
|
|
9826
9831
|
* user interaction
|
|
9827
9832
|
* → dispatchRequestInteraction
|
|
9828
9833
|
* → "navi_request_interaction" event
|
|
9829
9834
|
* → onRequestInteraction
|
|
9830
|
-
* → check disabled / read-only / busy (via controller.
|
|
9835
|
+
* → check disabled / read-only / busy (via controller.rules.interaction)
|
|
9831
9836
|
* against the interaction's `intent` ("write" by default, "read" for one
|
|
9832
9837
|
* that only shows what is already there — see READONLY_CONSTRAINT)
|
|
9833
9838
|
* → if blocked → prevented()
|
|
@@ -10647,7 +10652,8 @@ const interactionsDisputeThePress = (interactions) => {
|
|
|
10647
10652
|
* control (a Button) is its own; a Box inside one, or wrapping exactly one,
|
|
10648
10653
|
* reaches it; a Box with no control anywhere near it — or laying out several,
|
|
10649
10654
|
* which belongs to none of them — still answers with a callback of the caller's,
|
|
10650
|
-
* and only "request_action"
|
|
10655
|
+
* and only the two requests ("request_action", "request_ui_action") have
|
|
10656
|
+
* nothing to ask.
|
|
10651
10657
|
*
|
|
10652
10658
|
* Set up once per element rather than on every render, which is what lets a
|
|
10653
10659
|
* detector be a plain `setup`/teardown pair. So the interactions themselves are
|
|
@@ -10806,7 +10812,7 @@ const useInteractionsEffect = (ref, interactionsRef) => {
|
|
|
10806
10812
|
cancelable: true,
|
|
10807
10813
|
});
|
|
10808
10814
|
// The event the interaction was read from stays reachable from it:
|
|
10809
|
-
// `findEvent(actionEvent, "pointerdown")` still finds the press a
|
|
10815
|
+
// `findEvent(actionEvent, "pointerdown")` still finds the press a hold
|
|
10810
10816
|
// was made of.
|
|
10811
10817
|
chainEvent(interactionEvent, originalEvent);
|
|
10812
10818
|
if (!element.dispatchEvent(interactionEvent)) {
|
|
@@ -11554,7 +11560,7 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
11554
11560
|
* …
|
|
11555
11561
|
* }}}
|
|
11556
11562
|
*
|
|
11557
|
-
* `toId`
|
|
11563
|
+
* `toId` names an element and is never null: a copy over nothing is a release that meant
|
|
11558
11564
|
* nothing, and the interaction does not happen at all — unless the element
|
|
11559
11565
|
* declares `leave` (see below), which gives that release a meaning of its own.
|
|
11560
11566
|
*
|
|
@@ -15806,7 +15812,7 @@ const debug$3 = (...args) => {
|
|
|
15806
15812
|
* @param {string} name - resource name, used in action names and error messages
|
|
15807
15813
|
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
15808
15814
|
* @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"`
|
|
15809
|
-
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
15815
|
+
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
15810
15816
|
* @see docs/resource.md — relationships, callback return contracts, decision table
|
|
15811
15817
|
*
|
|
15812
15818
|
* @example
|
|
@@ -16033,7 +16039,7 @@ const createResource = (
|
|
|
16033
16039
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16034
16040
|
*
|
|
16035
16041
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16036
|
-
* @param {Object} options -
|
|
16042
|
+
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; left out, the scope inherits the resource's
|
|
16037
16043
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
16038
16044
|
* @see docs/resource.md — what a scope isolates, and `dependencies`
|
|
16039
16045
|
*
|
|
@@ -16051,7 +16057,7 @@ const createResource = (
|
|
|
16051
16057
|
* const ROLE_WITH_OWNERSHIP = role.withParams({ owners: true }, {
|
|
16052
16058
|
* dependencies: [role, database, tables],
|
|
16053
16059
|
* });
|
|
16054
|
-
* // ROLE_WITH_OWNERSHIP.GET_MANY
|
|
16060
|
+
* // ROLE_WITH_OWNERSHIP.GET_MANY reruns after any write (POST/PUT/PATCH/DELETE) on a role, a database or a table
|
|
16055
16061
|
*/
|
|
16056
16062
|
const withParams = (
|
|
16057
16063
|
paramsToInject,
|
|
@@ -16097,13 +16103,14 @@ const createResource = (
|
|
|
16097
16103
|
* Callback return contracts:
|
|
16098
16104
|
* - GET / PUT → the parent object with the relationship nested inside:
|
|
16099
16105
|
* `async ({ id }) => ({ id, session: { id: 10, token: "abc" } })`; `null` for no relationship
|
|
16100
|
-
* - DELETE → the parent id
|
|
16106
|
+
* - DELETE → the parent id; the property is set to `null`
|
|
16101
16107
|
*
|
|
16102
16108
|
* The backend may also embed the child inline in a parent GET/POST response — the
|
|
16103
16109
|
* setter on the property upserts the nested object into the child store.
|
|
16104
16110
|
*
|
|
16105
|
-
* Returns the
|
|
16106
|
-
* `
|
|
16111
|
+
* Returns the relationship resource: its actions (`USER_SESSION.GET`, `.PUT`,
|
|
16112
|
+
* `.DELETE`) write the parent's property. A relation of the child itself is
|
|
16113
|
+
* declared on the child resource (`SESSION.one("device", DEVICE)`).
|
|
16107
16114
|
*
|
|
16108
16115
|
* @param {string} propertyName - property holding the child on each parent item
|
|
16109
16116
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
@@ -17264,7 +17271,7 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17264
17271
|
* });
|
|
17265
17272
|
*
|
|
17266
17273
|
* syncResourceToSignals(USER, { username: usernameSignal });
|
|
17267
|
-
* //
|
|
17274
|
+
* // When a user item's username is updated via USER.PUT,
|
|
17268
17275
|
* // usernameSignal.value is set to the new username,
|
|
17269
17276
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17270
17277
|
*/
|
|
@@ -17277,6 +17284,17 @@ const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
|
17277
17284
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17278
17285
|
};
|
|
17279
17286
|
|
|
17287
|
+
/**
|
|
17288
|
+
* The same, for a `scopedOne`/`scopedMany` resource: its items live in one store
|
|
17289
|
+
* per owner, so the store to watch is the one of the owner `ownerSignal` names
|
|
17290
|
+
* (by id or by any unique key), and it is switched when the signal changes.
|
|
17291
|
+
* Nothing is synced while `ownerSignal` holds `null`/`undefined` or names an
|
|
17292
|
+
* owner nothing has been loaded for yet.
|
|
17293
|
+
*
|
|
17294
|
+
* @param {Object} resource - a resource made by `.scopedOne()` / `.scopedMany()`
|
|
17295
|
+
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17296
|
+
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17297
|
+
*/
|
|
17280
17298
|
const syncOwnedResourceToSignals = (
|
|
17281
17299
|
resource,
|
|
17282
17300
|
ownerSignal,
|
|
@@ -17324,7 +17342,7 @@ const generateSignalId = () => {
|
|
|
17324
17342
|
* 1. Initially takes value from the default signal
|
|
17325
17343
|
* 2. When explicitly set (programmatically or via localStorage), the explicit value takes precedence
|
|
17326
17344
|
* 3. When default signal changes, it only updates if no explicit value was ever set
|
|
17327
|
-
* 4.
|
|
17345
|
+
* 4. Setting it to undefined makes the signal use the dynamic default again
|
|
17328
17346
|
* 5. If dynamic default is undefined and options.default is provided, uses the static fallback
|
|
17329
17347
|
*
|
|
17330
17348
|
* This is useful for:
|
|
@@ -17338,7 +17356,9 @@ const generateSignalId = () => {
|
|
|
17338
17356
|
* @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.
|
|
17339
17357
|
* @param {any} [options.default] - Static fallback value used when defaultValue is a signal and that signal's value is undefined
|
|
17340
17358
|
* @param {boolean} [options.persists=false] - Whether to persist the signal value in localStorage using the signal ID as key
|
|
17341
|
-
* @param {
|
|
17359
|
+
* @param {string} [options.type] - Type the value is coerced to, and read back as from localStorage and the url:
|
|
17360
|
+
* any @jsenv/validity type (`"string"`, `"number"`, `"boolean"`, `"array"`, `"object"`, `"integer"`, `"date"`,
|
|
17361
|
+
* `"time"`…). Without it the value is not coerced.
|
|
17342
17362
|
* @param {"string" | "number" | "boolean"} [options.itemType] - For array type: type of the array items.
|
|
17343
17363
|
* Used when reading the value back from a url search param, where everything is a string:
|
|
17344
17364
|
* `?level=3,4` becomes `[3, 4]` instead of `["3", "4"]`. Without it items stay strings.
|
|
@@ -18501,14 +18521,12 @@ const LoadingFallback = ({
|
|
|
18501
18521
|
* Two things it gets right that a hand-written boundary rarely does, both
|
|
18502
18522
|
* explained in docs/error_handling.md:
|
|
18503
18523
|
*
|
|
18504
|
-
* - It marks the error as displayed ONLY when it actually displays it.
|
|
18505
|
-
*
|
|
18506
|
-
*
|
|
18507
|
-
*
|
|
18508
|
-
*
|
|
18509
|
-
*
|
|
18510
|
-
* silent one. Without a `fallback` there is nothing to display, so the error is
|
|
18511
|
-
* left alone and continues up.
|
|
18524
|
+
* - It marks the error as displayed ONLY when it actually displays it. The
|
|
18525
|
+
* jsenv overlay skips an error carrying `__handled_by__`, so a mark set before
|
|
18526
|
+
* knowing whether anything is rendered mutes an error that then continues up:
|
|
18527
|
+
* a TypeError in a component becomes a blank page AND a silent one. Without a
|
|
18528
|
+
* `fallback` there is nothing to display, so the error is left alone and
|
|
18529
|
+
* continues up.
|
|
18512
18530
|
*
|
|
18513
18531
|
* - It resets on navigation, not only on rerun. Rerunning the failed action is
|
|
18514
18532
|
* one way out; going somewhere else is the common one. Without a reset on the
|
|
@@ -21590,9 +21608,48 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
21590
21608
|
* separator?: import("ignore:preact").ComponentChildren | ((index: number) => import("ignore:preact").ComponentChildren),
|
|
21591
21609
|
* selfInteractions?: string,
|
|
21592
21610
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
21611
|
+
* alignX?: string,
|
|
21612
|
+
* alignY?: string,
|
|
21613
|
+
* spacing?: string | number,
|
|
21614
|
+
* overflow?: string,
|
|
21615
|
+
* header?: boolean,
|
|
21616
|
+
* footer?: boolean,
|
|
21617
|
+
* body?: boolean,
|
|
21618
|
+
* sticky?: boolean,
|
|
21593
21619
|
* children?: import("ignore:preact").ComponentChildren,
|
|
21594
21620
|
* [key: string]: any,
|
|
21595
21621
|
* }>}
|
|
21622
|
+
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
21623
|
+
* side, `"y"` stacked. Named by axis because `flex-direction: column` stacks
|
|
21624
|
+
* items vertically, the opposite of what "column" suggests.
|
|
21625
|
+
* @param {boolean} [inline] The inline form of the box's display
|
|
21626
|
+
* (`inline-flex`, `inline-grid`…). An inline box given a `width`/`height`
|
|
21627
|
+
* becomes an inline flex row, since an inline box ignores both.
|
|
21628
|
+
* @param {string} [alignX] Horizontal alignment of the content whatever the
|
|
21629
|
+
* flex direction: `justify-content` or `align-items` is picked from it
|
|
21630
|
+
* (`text-align` outside flex and grid).
|
|
21631
|
+
* @param {string} [alignY] Vertical alignment, the same way.
|
|
21632
|
+
* @param {string|number} [spacing] The gap between children in a flex or grid
|
|
21633
|
+
* box (`spacingX`/`spacingY` per axis) — there is no `gap` prop. Spacing
|
|
21634
|
+
* props (`spacing`, `margin*`, `padding*`) take the scale `"xxs"`…`"xxl"`
|
|
21635
|
+
* (`--navi-xxs`…), `"-s"` for a negative, or any CSS value.
|
|
21636
|
+
* @param {object|string} [style] CSS properties, plus pseudo keys holding
|
|
21637
|
+
* their own: `style={{ color: "blue", ":hover": { color: "red" } }}`
|
|
21638
|
+
* (`":-navi-pressed"`, `"::before"`…). A pseudo class used there is tracked
|
|
21639
|
+
* on the element; nothing re-renders when it changes.
|
|
21640
|
+
* @param {string} [overflow] `"auto"`/`"scroll"` (`overflowX`/`overflowY`
|
|
21641
|
+
* too) makes the box a scrolling area, which is what gives `header`/`body`/
|
|
21642
|
+
* `footer` their meaning; such a box is also `isolation: isolate` and gets a
|
|
21643
|
+
* `scroll-padding` (see docs/scroll.md).
|
|
21644
|
+
* @param {boolean} [header] A part that stays put at the top of the scrolling
|
|
21645
|
+
* box it is a direct child of — nothing at all without that box's overflow.
|
|
21646
|
+
* Rendered as `<header>` unless `as` says otherwise.
|
|
21647
|
+
* @param {boolean} [footer] The same at the bottom, rendered as `<footer>`.
|
|
21648
|
+
* @param {boolean} [body] The only part of its scrolling box that scrolls,
|
|
21649
|
+
* `header`/`footer` sitting outside it. It shrinks, it never grows.
|
|
21650
|
+
* @param {boolean} [sticky] `position: sticky` in the sticky z-index band
|
|
21651
|
+
* (`--navi-z-index-sticky`), stuck or not; an explicit `zIndex`, `"auto"`
|
|
21652
|
+
* included, wins (see docs/z_index.md). `top`/`bottom`/… take `true` for 0.
|
|
21596
21653
|
* @param {string} [background] Any CSS background, plus two keywords for the
|
|
21597
21654
|
* planes an app paints: `"surface"` is the paper content sits on,
|
|
21598
21655
|
* `"chrome"` the frame around a screen — a top bar, a side menu, a toolbar.
|
|
@@ -23284,21 +23341,16 @@ const createUITransitionController = (
|
|
|
23284
23341
|
/**
|
|
23285
23342
|
* UITransition
|
|
23286
23343
|
*
|
|
23287
|
-
* A Preact component that
|
|
23288
|
-
*
|
|
23344
|
+
* A Preact component that animates the change between its children: the
|
|
23345
|
+
* outgoing content fades out while the incoming one fades in, and the box
|
|
23346
|
+
* resizes from the one to the other (the mechanics are in ui_transition.js).
|
|
23289
23347
|
*
|
|
23290
|
-
*
|
|
23291
|
-
* -
|
|
23292
|
-
*
|
|
23293
|
-
*
|
|
23294
|
-
* -
|
|
23295
|
-
*
|
|
23296
|
-
*
|
|
23297
|
-
* Usage:
|
|
23298
|
-
* - Wrap dynamic content in <UITransition> to animate between states
|
|
23299
|
-
* - Set a unique `data-content-id` on your rendered content to identify each content variant
|
|
23300
|
-
* - Use `data-content-phase` to mark loading/error states for phase transitions
|
|
23301
|
-
* - Configure transition types and durations for both content and phase changes
|
|
23348
|
+
* Two kinds of change are told apart, from attributes on the rendered content:
|
|
23349
|
+
* - a content transition, when the content key changes — `data-content-key` on
|
|
23350
|
+
* the rendered element (a user id, a search), or `useUITransitionContentId`
|
|
23351
|
+
* called from a component inside (what `<Route>` does with its url pattern);
|
|
23352
|
+
* - a phase transition, when `data-content-phase` (a loading or error state)
|
|
23353
|
+
* changes for the same content key.
|
|
23302
23354
|
*
|
|
23303
23355
|
* Example:
|
|
23304
23356
|
*
|
|
@@ -23307,9 +23359,6 @@ const createUITransitionController = (
|
|
|
23307
23359
|
* ? <Spinner data-content-key={userId} data-content-phase />
|
|
23308
23360
|
* : <UserProfile user={user} data-content-key={userId} />}
|
|
23309
23361
|
* </UITransition>
|
|
23310
|
-
*
|
|
23311
|
-
* When `data-content-id` changes, UITransition animates content transitions.
|
|
23312
|
-
* When `data-content-phase` changes for the same key, it animates phase transitions.
|
|
23313
23362
|
*/
|
|
23314
23363
|
|
|
23315
23364
|
const UITransitionContentIdContext = createContext();
|
|
@@ -23431,16 +23480,12 @@ const UITransition = ({
|
|
|
23431
23480
|
};
|
|
23432
23481
|
|
|
23433
23482
|
/**
|
|
23434
|
-
*
|
|
23435
|
-
*
|
|
23436
|
-
*
|
|
23437
|
-
* When the key changes, UITransition will be able to detect that and consider the content
|
|
23438
|
-
* as changed even if the component is still the same
|
|
23439
|
-
*
|
|
23440
|
-
* This is used by <Route> to set the content key to the route path
|
|
23441
|
-
* When the route becomes inactive it will call useUITransitionContentId(undefined)
|
|
23442
|
-
* And if a sibling route becones active it will call useUITransitionContentId with its own path
|
|
23483
|
+
* Names the content a component renders, for the <UITransition> around it:
|
|
23484
|
+
* everything rendered inside is identified by that key, and the key changing
|
|
23485
|
+
* is read as a content change even though the component is the same.
|
|
23443
23486
|
*
|
|
23487
|
+
* <Route> calls it with the url pattern of the branch it renders; the branch
|
|
23488
|
+
* unmounting removes its part, and a sibling branch mounting adds its own.
|
|
23444
23489
|
*/
|
|
23445
23490
|
const useUITransitionContentId = value => {
|
|
23446
23491
|
const contentId = useContext(UITransitionContentIdContext);
|
|
@@ -23465,8 +23510,8 @@ const useUITransitionContentId = value => {
|
|
|
23465
23510
|
};
|
|
23466
23511
|
|
|
23467
23512
|
/**
|
|
23468
|
-
* Custom route pattern matching system
|
|
23469
|
-
*
|
|
23513
|
+
* Custom route pattern matching system.
|
|
23514
|
+
* Not built on URLPattern: matching here is simpler and more predictable.
|
|
23470
23515
|
*/
|
|
23471
23516
|
|
|
23472
23517
|
const debug$2 = (...args) => {
|
|
@@ -30547,8 +30592,9 @@ const RouteTransitionArea = ({
|
|
|
30547
30592
|
* Declare how a pair of routes moves against each other — or how one route is
|
|
30548
30593
|
* entered, from wherever its door happens to be.
|
|
30549
30594
|
*
|
|
30550
|
-
* @param {object|null} from - a route
|
|
30551
|
-
* a param of a route rather than a route
|
|
30595
|
+
* @param {object|null} from - a route (or the `routeFallback()` page), or
|
|
30596
|
+
* `{ route, params }` when the page is a param of a route rather than a route
|
|
30597
|
+
* of its own. `null` says the page is
|
|
30552
30598
|
* reached FROM ANYWHERE: its door is in the furniture (a gear in the top
|
|
30553
30599
|
* bar, a "+" in the tab bar) rather than on a screen, so there is no pair to
|
|
30554
30600
|
* write it on. Arriving at `to` from any page then plays forward and leaving
|
|
@@ -30596,8 +30642,7 @@ const RouteTransitionArea = ({
|
|
|
30596
30642
|
* Whatever is written here is what EVERY crossing of the pair plays. One
|
|
30597
30643
|
* crossing can ask for something else — `<Link routeTransition>`, or
|
|
30598
30644
|
* navTo(url, { routeTransition }) — which overrides this field by field, for
|
|
30599
|
-
* that
|
|
30600
|
-
* navigation alone.
|
|
30645
|
+
* that navigation alone.
|
|
30601
30646
|
* @returns {() => void} remove this relation.
|
|
30602
30647
|
*/
|
|
30603
30648
|
const defineRouteTransition = (from, to, transition) => {
|
|
@@ -30680,9 +30725,8 @@ const normalizeTransition = transition => {
|
|
|
30680
30725
|
*
|
|
30681
30726
|
* Two mouths, one meaning: the element being pressed wears it (a `<Link
|
|
30682
30727
|
* routeTransition>`, or the attribute by hand on any anchor), or navTo() is
|
|
30683
|
-
* handed
|
|
30684
|
-
* it
|
|
30685
|
-
* writes anything (see before_routing.js).
|
|
30728
|
+
* handed it. Both arrive here through the announcement the navigation makes
|
|
30729
|
+
* before it writes anything (see before_routing.js).
|
|
30686
30730
|
*
|
|
30687
30731
|
* A request answers FIELD BY FIELD: what it does not say, the relation — or
|
|
30688
30732
|
* the default — still answers for. So `{ direction: "back" }` keeps the pair's
|
|
@@ -31929,10 +31973,10 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
31929
31973
|
* The pages are cut at the edge of this box while they travel, and at the app's
|
|
31930
31974
|
* safe area the box runs under, which is written on the transition's own
|
|
31931
31975
|
* pseudo-elements — no overflow of the document reaches pictures drawn in the
|
|
31932
|
-
* top layer. It needs nothing of the browser beyond view
|
|
31933
|
-
*
|
|
31934
|
-
*
|
|
31935
|
-
*
|
|
31976
|
+
* top layer. It needs nothing of the browser beyond view transitions
|
|
31977
|
+
* themselves: a browser without them navigates without the movement, and the
|
|
31978
|
+
* gesture applies its change on release instead of dragging a picture that does
|
|
31979
|
+
* not exist.
|
|
31936
31980
|
*
|
|
31937
31981
|
* While a travel plays, the rest of the page is taken as a picture too — this
|
|
31938
31982
|
* box asks for `view-transition-name: root` back for that time, so an
|
|
@@ -33508,9 +33552,6 @@ const onNaviCommand = (e, { debugCommand = () => {} } = {}) => {
|
|
|
33508
33552
|
`targeting`,
|
|
33509
33553
|
commandTarget,
|
|
33510
33554
|
);
|
|
33511
|
-
// Timed once, for the wheel-in-dialog "Définir feels frozen on mobile" case;
|
|
33512
|
-
// the line is gone now that the answer is known — a command that runs in 1ms
|
|
33513
|
-
// said nothing, and it said it on every single interaction.
|
|
33514
33555
|
return implementation();
|
|
33515
33556
|
};
|
|
33516
33557
|
|
|
@@ -34154,8 +34195,8 @@ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
|
|
|
34154
34195
|
target,
|
|
34155
34196
|
implementation: () => {
|
|
34156
34197
|
// The popup reads `anchor` first and falls back to the source (see
|
|
34157
|
-
// onnavi_request_open in popover.jsx)
|
|
34158
|
-
//
|
|
34198
|
+
// onnavi_request_open in popover.jsx): without an `anchor`, the element
|
|
34199
|
+
// asking is the element opened against.
|
|
34159
34200
|
return dispatchCustomEvent(target, "navi_request_open", {
|
|
34160
34201
|
event,
|
|
34161
34202
|
source: resolveCommandProxySource(source),
|
|
@@ -36570,8 +36611,8 @@ const usePopupContentMount = (
|
|
|
36570
36611
|
* are no-ops.
|
|
36571
36612
|
*
|
|
36572
36613
|
* Behavior:
|
|
36573
|
-
* - No <dialog>/<details>/[popover] ancestor → runs like a
|
|
36574
|
-
* useLayoutEffect with the provided deps.
|
|
36614
|
+
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor → runs like a
|
|
36615
|
+
* normal useLayoutEffect with the provided deps.
|
|
36575
36616
|
* - Inside a closed/hidden ancestor → skips the initial run; instead runs
|
|
36576
36617
|
* the callback once the ancestor opens — see @jsenv/dom's own
|
|
36577
36618
|
* observeAncestorOpenState for exactly how that's detected, and why it
|
|
@@ -36585,8 +36626,9 @@ const usePopupContentMount = (
|
|
|
36585
36626
|
* reveals nothing about them, so it does not re-run them either. See
|
|
36586
36627
|
* isDisplayedDespiteClosedAncestor in @jsenv/dom.
|
|
36587
36628
|
*
|
|
36588
|
-
* The callback
|
|
36589
|
-
*
|
|
36629
|
+
* The callback is called with the element (`ref.current`), and a
|
|
36630
|
+
* `navi_displayed` CustomEvent as its second argument, with
|
|
36631
|
+
* `detail: { ancestor, ancestorType, becauseAncestorOpened }`:
|
|
36590
36632
|
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor at all →
|
|
36591
36633
|
* `{ ancestor: document, ancestorType: "document" }`.
|
|
36592
36634
|
* - Otherwise → `{ ancestor: <the matched element>, ancestorType: "dialog"
|
|
@@ -36751,6 +36793,8 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
36751
36793
|
// see also https://github.com/preactjs/preact/issues/1255
|
|
36752
36794
|
|
|
36753
36795
|
|
|
36796
|
+
const NO_ELEMENT_REF = { current: null };
|
|
36797
|
+
|
|
36754
36798
|
/**
|
|
36755
36799
|
* Programmatic autofocus that runs after Preact layout effects are flushed.
|
|
36756
36800
|
*
|
|
@@ -36803,13 +36847,12 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
36803
36847
|
* the browser's built-in scroll-into-view that accompanies focus.
|
|
36804
36848
|
* @param {boolean} [options.focusVisible]
|
|
36805
36849
|
* Passed as `focusVisible` to `element.focus()`.
|
|
36806
|
-
* @param {boolean} [options.
|
|
36850
|
+
* @param {boolean} [options.autoSelect]
|
|
36807
36851
|
* When true, also calls `element.select()` after focusing (useful for text inputs).
|
|
36808
|
-
* @returns {
|
|
36809
|
-
*
|
|
36852
|
+
* @returns {object} The attributes to spread on the element: `navi-autofocus`
|
|
36853
|
+
* (and `navi-autofocus-select`), with `autoFocus` set to undefined so the
|
|
36854
|
+
* native attribute never lands on the node.
|
|
36810
36855
|
*/
|
|
36811
|
-
const NO_ELEMENT_REF = { current: null };
|
|
36812
|
-
|
|
36813
36856
|
const useAutoFocus = (
|
|
36814
36857
|
focusableElementRef,
|
|
36815
36858
|
autoFocus,
|
|
@@ -37002,9 +37045,10 @@ document.body.addEventListener(
|
|
|
37002
37045
|
* and nothing about it stops someone from starting a run on every row before
|
|
37003
37046
|
* any of them comes back: a dozen requests in flight, a dozen rows waiting, and
|
|
37004
37047
|
* a server asked to do a dozen things at once because a list happened to be
|
|
37005
|
-
* long. The guard is what makes the surface say "not yet" instead — the
|
|
37006
|
-
* that
|
|
37007
|
-
* next press is possible again the moment one of the runs
|
|
37048
|
+
* long. The guard is what makes the surface say "not yet" instead — the
|
|
37049
|
+
* controls that would start another run go read-only and say what they are
|
|
37050
|
+
* waiting for, and the next press is possible again the moment one of the runs
|
|
37051
|
+
* comes back.
|
|
37008
37052
|
*
|
|
37009
37053
|
* It counts runs, not values — which is what separates it from
|
|
37010
37054
|
* `maxLengthGuard`, the one that says how many things a selection may HOLD.
|
|
@@ -37349,14 +37393,13 @@ const createControlGuard = (controller) => {
|
|
|
37349
37393
|
};
|
|
37350
37394
|
|
|
37351
37395
|
/**
|
|
37352
|
-
* Orchestrates the
|
|
37353
|
-
*
|
|
37354
|
-
* Instead of holding `controller.controlInteraction` + `controller.controlValidity`
|
|
37355
|
-
* independently, a controller now has a single `controller.rules` object:
|
|
37396
|
+
* Orchestrates the rule managers of a UI state controller, held together in
|
|
37397
|
+
* one `controller.rules` object:
|
|
37356
37398
|
*
|
|
37357
37399
|
* controller.rules.callout — shared callout display manager
|
|
37358
37400
|
* controller.rules.interaction — interactivity gate (disabled/readonly/busy)
|
|
37359
37401
|
* controller.rules.validation — value validity gate (required/pattern/etc.)
|
|
37402
|
+
* controller.rules.guard — keystroke guard (charGuard/maxLengthGuard)
|
|
37360
37403
|
*
|
|
37361
37404
|
* The callout manager is created first so both interaction and validation
|
|
37362
37405
|
* can use the same callout slot and the same lifecycle hooks.
|
|
@@ -39472,9 +39515,8 @@ const EMPTY_OBJECT = {};
|
|
|
39472
39515
|
* child changes → we update picker input → navi_ui_state_change fires →
|
|
39473
39516
|
* we see updatingRef=true → skip → no loop.
|
|
39474
39517
|
*
|
|
39475
|
-
*
|
|
39476
|
-
*
|
|
39477
|
-
* manually re-dispatch to inner controls.
|
|
39518
|
+
* So a control placed inside the picker popup needs no
|
|
39519
|
+
* `command="--navi-update"` to reach the picker input.
|
|
39478
39520
|
*/
|
|
39479
39521
|
const useUIFacadeStateController = (props, realUIStateController) => {
|
|
39480
39522
|
const firstChildControllerRef = useRef(null);
|
|
@@ -39886,9 +39928,10 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
39886
39928
|
* use `onInput` internally and route through `dispatchRequestAction` so the behavior
|
|
39887
39929
|
* is consistent regardless of input type.
|
|
39888
39930
|
*
|
|
39889
|
-
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce
|
|
39890
|
-
*
|
|
39891
|
-
* rather than each component having to manage its
|
|
39931
|
+
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce with
|
|
39932
|
+
* `actionDebounce` (ms). The input effect every field installs (input_effect.js)
|
|
39933
|
+
* handles the timing centrally rather than each component having to manage its
|
|
39934
|
+
* own debounce logic.
|
|
39892
39935
|
*/
|
|
39893
39936
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
39894
39937
|
|
|
@@ -39964,7 +40007,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
39964
40007
|
});
|
|
39965
40008
|
|
|
39966
40009
|
/**
|
|
39967
|
-
* Core hook for interactive field components (
|
|
40010
|
+
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
39968
40011
|
*
|
|
39969
40012
|
* Sets up the full field lifecycle:
|
|
39970
40013
|
* - Creates a UI state controller that manages state divergence between props and user interactions
|
|
@@ -39975,7 +40018,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
39975
40018
|
* - Handles constraint validation and message props
|
|
39976
40019
|
*
|
|
39977
40020
|
* All state changes route through DOM events on the field element so that
|
|
39978
|
-
* external subscribers (e.g.
|
|
40021
|
+
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
39979
40022
|
*
|
|
39980
40023
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
39981
40024
|
*/
|
|
@@ -41048,10 +41091,10 @@ const useReadOnlyUncontrolled = (props, controlInfo) => {
|
|
|
41048
41091
|
};
|
|
41049
41092
|
|
|
41050
41093
|
/**
|
|
41051
|
-
* Core hook for field group components (
|
|
41094
|
+
* Core hook for field group components (List selectable, CheckboxGroup, etc.).
|
|
41052
41095
|
* - Creates a UI group state controller that aggregates child states into one group state
|
|
41053
41096
|
* - Binds the group's action to the aggregated state signal
|
|
41054
|
-
* - Provides context to children: ParentUIStateController,
|
|
41097
|
+
* - Provides context to children: ParentUIStateController, ControlName, Disabled, ReadOnly,
|
|
41055
41098
|
* Required, Loading, Action, ActionRequester
|
|
41056
41099
|
* - Overrides `onnavi_reset_ui_state` to cascade resets to all monitored children
|
|
41057
41100
|
* by dispatching `navi_reset_ui_state` DOM events on each child's DOM element
|
|
@@ -43580,10 +43623,10 @@ const LoadingRectangleSvg = ({
|
|
|
43580
43623
|
}) => {
|
|
43581
43624
|
// The stroke is centered on the path, so half of it falls outside: drawing
|
|
43582
43625
|
// at half the stroke width puts its outer edge exactly on the box, whatever
|
|
43583
|
-
// the box.
|
|
43584
|
-
//
|
|
43585
|
-
//
|
|
43586
|
-
//
|
|
43626
|
+
// the box. Not a share of the smaller dimension (3%, floored at 2px): that
|
|
43627
|
+
// holds the line further and further inside as the box grows — a caller
|
|
43628
|
+
// placing the outline with `inset` gets the distance it asked for on a field
|
|
43629
|
+
// and something several pixels inward on a card. Nothing needs the
|
|
43587
43630
|
// extra room: the svg is `overflow: visible` and whoever positions the
|
|
43588
43631
|
// wrapper already reserves the half stroke.
|
|
43589
43632
|
const margin = strokeWidth / 2;
|
|
@@ -44655,7 +44698,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
44655
44698
|
* boldStable?: boolean,
|
|
44656
44699
|
* shrinkWrap?: boolean,
|
|
44657
44700
|
* capitalize?: boolean,
|
|
44658
|
-
* selectRange?: string | [number, number],
|
|
44701
|
+
* selectRange?: string | [number, number] | [string, string],
|
|
44659
44702
|
* childrenOutsideFlow?: import("ignore:preact").ComponentChildren,
|
|
44660
44703
|
* [key: string]: any,
|
|
44661
44704
|
* }>}
|
|
@@ -44723,9 +44766,10 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
44723
44766
|
* @param {boolean} [capitalize]
|
|
44724
44767
|
* Uppercases the first letter of the text content via CSS.
|
|
44725
44768
|
*
|
|
44726
|
-
* @param {string|[number,number]} [selectRange]
|
|
44727
|
-
* Selects a portion of the text on mount. Pass a substring to search for,
|
|
44728
|
-
*
|
|
44769
|
+
* @param {string|[number,number]|[string,string]} [selectRange]
|
|
44770
|
+
* Selects a portion of the text on mount. Pass a substring to search for, a
|
|
44771
|
+
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
44772
|
+
* substrings selecting from the first to the end of the second.
|
|
44729
44773
|
*
|
|
44730
44774
|
* @param {import("ignore:preact").ComponentChildren} [childrenOutsideFlow]
|
|
44731
44775
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
@@ -45595,7 +45639,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
45595
45639
|
* @param {object} props
|
|
45596
45640
|
* @param {string} [props.href] - Destination. Also the default `value`, and
|
|
45597
45641
|
* (when `hrefFallback`) the default visible text.
|
|
45598
|
-
* @param {
|
|
45642
|
+
* @param {object} [props.route] - Renders via `route`
|
|
45599
45643
|
* instead of a raw `href`: the URL is built from the route (see
|
|
45600
45644
|
* `routeParams`) and "current" is derived from whether the route matches.
|
|
45601
45645
|
* @param {object} [props.routeParams] - Params passed to `route.buildUrl`.
|
|
@@ -45619,14 +45663,14 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
45619
45663
|
* (`navi_value`); defaults to `href`.
|
|
45620
45664
|
* @param {boolean} [props.current] - Forces the "current" state on (otherwise
|
|
45621
45665
|
* derived from the href/route).
|
|
45622
|
-
* @param {
|
|
45666
|
+
* @param {object|object[]} [props.currentExcept] -
|
|
45623
45667
|
* Route(s) inside this link's own that are NOT it: while one of them matches,
|
|
45624
45668
|
* the link is not current even though its route still is. A bar entry
|
|
45625
45669
|
* standing for a whole section, and one place under that section the entry
|
|
45626
45670
|
* does not stand for — settings reached from everywhere and shown over
|
|
45627
45671
|
* whatever the reader was on. Nothing about the routing changes: the url is
|
|
45628
45672
|
* in the section, only the link stops claiming "you are here".
|
|
45629
|
-
* @param {
|
|
45673
|
+
* @param {object|object[]} [props.currentAlso] -
|
|
45630
45674
|
* Route(s) other than this link's own that are it: the link is current while
|
|
45631
45675
|
* one of them matches. The other side of `currentExcept` — an entry standing
|
|
45632
45676
|
* for more than the one page it opens. `currentExcept` wins over it.
|
|
@@ -46530,6 +46574,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
46530
46574
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
46531
46575
|
* currentIndicatorSlides?: boolean,
|
|
46532
46576
|
* slideContainer?: string,
|
|
46577
|
+
* panelPosition?: "before"|"after",
|
|
46533
46578
|
* }>}
|
|
46534
46579
|
* @param {boolean|"top"|"bottom"|"left"|"right"} [props.currentIndicator] - the
|
|
46535
46580
|
* bar that says which tab one is on, said once here rather than on every
|
|
@@ -46555,6 +46600,9 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
46555
46600
|
* container says which one is on screen, and pressing a tab travels there.
|
|
46556
46601
|
* Tabs that are places in the same screen rather than pages of their own —
|
|
46557
46602
|
* nothing is written to the URL and nothing is a link.
|
|
46603
|
+
* @param {"before"|"after"} [props.panelPosition] - turns the row into folder
|
|
46604
|
+
* tabs: the current tab and the panel it opens share one surface, and this
|
|
46605
|
+
* says which side of the row the panel sits on.
|
|
46558
46606
|
*/
|
|
46559
46607
|
const Nav = ({
|
|
46560
46608
|
children,
|
|
@@ -47677,6 +47725,8 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
47677
47725
|
* onChange?: (value: any) => void,
|
|
47678
47726
|
* tabsPosition?: "top"|"bottom"|"left"|"right",
|
|
47679
47727
|
* tabsAlign?: "start"|"center"|"end"|"stretch",
|
|
47728
|
+
* scrollablePage?: boolean,
|
|
47729
|
+
* maxLines?: number|false,
|
|
47680
47730
|
* borderWidth?: string|number,
|
|
47681
47731
|
* borderRadius?: string|number,
|
|
47682
47732
|
* tabBorderRadius?: string|number,
|
|
@@ -47856,7 +47906,7 @@ const Binder = ({
|
|
|
47856
47906
|
* position among its siblings.
|
|
47857
47907
|
* @param {import("ignore:preact").ComponentChildren} [props.label] - What the tab
|
|
47858
47908
|
* shows.
|
|
47859
|
-
* @param {
|
|
47909
|
+
* @param {object} [props.route] - Makes the tab a `Link`
|
|
47860
47910
|
* to that route, and the binder follows the url: the open item is the one
|
|
47861
47911
|
* whose route matches, no `value`/`onChange` needed.
|
|
47862
47912
|
* @param {string} [props.href] - Same, for a plain url.
|
|
@@ -48295,6 +48345,9 @@ const FixedBarStyleCSSVars = {
|
|
|
48295
48345
|
* `border={false}` for none.
|
|
48296
48346
|
* @param {string|number} [props.maxWidth] - Keeps the bar lined up with a
|
|
48297
48347
|
* content column narrower than the window (it stays centered).
|
|
48348
|
+
* @param {string|number} [props.padding=0] - Along the bar only: left/right on
|
|
48349
|
+
* a top or bottom bar, top/bottom on a side one. Across it, the size is
|
|
48350
|
+
* `height`/`width`.
|
|
48298
48351
|
*/
|
|
48299
48352
|
const FixedBar = ({
|
|
48300
48353
|
children,
|
|
@@ -49163,9 +49216,10 @@ const useExpandableContext = partName => {
|
|
|
49163
49216
|
* height too. `"idle"` builds it in a browser idle moment after load.
|
|
49164
49217
|
* `"while-opened"` throws the content away once the collapse
|
|
49165
49218
|
* settles — after the closing animation, so it still plays on real
|
|
49166
|
-
* content — and rebuilds it from scratch on every expansion.
|
|
49167
|
-
*
|
|
49168
|
-
* builds the content ahead of the click
|
|
49219
|
+
* content — and rebuilds it from scratch on every expansion. Under the
|
|
49220
|
+
* other three, intent on the UI part (pointer entering it, focus landing in
|
|
49221
|
+
* it) builds the content ahead of the click; `"while-opened"` content is
|
|
49222
|
+
* never warmed, it is built at the open.
|
|
49169
49223
|
*/
|
|
49170
49224
|
const Expandable = props => {
|
|
49171
49225
|
import.meta.css = [css$R, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
@@ -49942,7 +49996,8 @@ const DARK_CONTRAST_LIGHTNESS_THRESHOLD = 0.65;
|
|
|
49942
49996
|
* @param {string} accentColor - The accent color value. When it changes, attributes are recomputed.
|
|
49943
49997
|
* @param {object} [options]
|
|
49944
49998
|
* @param {string} [options.elementSelector] - CSS selector to find the element whose computed color is read.
|
|
49945
|
-
* Defaults to the root
|
|
49999
|
+
* Defaults to the root's own `data-visual-selector` attribute, else the root element itself.
|
|
50000
|
+
* Useful when the color is applied to a probe/child element.
|
|
49946
50001
|
* @param {string} [options.colorProperty="backgroundColor"] - Computed style property to read (e.g. "color", "borderColor").
|
|
49947
50002
|
*/
|
|
49948
50003
|
const useAccentColorAttributes = (
|
|
@@ -51981,16 +52036,18 @@ const css$L = /* css */`@layer navi {
|
|
|
51981
52036
|
/**
|
|
51982
52037
|
* Field — a semantic wrapper that connects a label to a form control.
|
|
51983
52038
|
*
|
|
51984
|
-
* It generates a stable `fieldId` (or accepts an explicit
|
|
52039
|
+
* It generates a stable `fieldId` (or accepts an explicit one) that is
|
|
51985
52040
|
* automatically forwarded to the `Label` inside the field as `htmlFor` and to
|
|
51986
|
-
*
|
|
51987
|
-
* label focuses the control without requiring manual wiring.
|
|
52041
|
+
* the labelable control inside it (Input, Select, Picker, Button) as its `id`,
|
|
52042
|
+
* so clicking the label focuses the control without requiring manual wiring.
|
|
51988
52043
|
*
|
|
51989
52044
|
* It also tracks the readOnly / disabled / interactive state reported by its
|
|
51990
52045
|
* child control and reflects it on the `Label` (dimmed color, cursor change).
|
|
51991
52046
|
*
|
|
51992
52047
|
* Props:
|
|
51993
|
-
*
|
|
52048
|
+
* fieldId — optional explicit field id instead of the auto-generated one (an `id`
|
|
52049
|
+
* is the wrapper's own DOM id)
|
|
52050
|
+
* as="label" — the field IS the label, wrapping its control: no id needed
|
|
51994
52051
|
* flex="y" — vertical layout; automatically sets alignX="start" and data-vertical
|
|
51995
52052
|
* children — any JSX; should contain a `Label` and a form control
|
|
51996
52053
|
* ...rest — forwarded to the wrapping element (className, style, flex, spacing, …)
|
|
@@ -52197,6 +52254,7 @@ const InputUnitSlot = ({
|
|
|
52197
52254
|
};
|
|
52198
52255
|
const InputSlot = ({
|
|
52199
52256
|
side,
|
|
52257
|
+
children,
|
|
52200
52258
|
...props
|
|
52201
52259
|
}) => {
|
|
52202
52260
|
const ctx = useContext(InputTextualContext);
|
|
@@ -52226,7 +52284,11 @@ const InputSlot = ({
|
|
|
52226
52284
|
e.preventDefault();
|
|
52227
52285
|
}
|
|
52228
52286
|
},
|
|
52229
|
-
...props
|
|
52287
|
+
...props,
|
|
52288
|
+
children: jsx(ControlIdContext.Provider, {
|
|
52289
|
+
value: undefined,
|
|
52290
|
+
children: children
|
|
52291
|
+
})
|
|
52230
52292
|
});
|
|
52231
52293
|
};
|
|
52232
52294
|
|
|
@@ -53691,9 +53753,9 @@ const InputWithListResolver = props => {
|
|
|
53691
53753
|
};
|
|
53692
53754
|
|
|
53693
53755
|
/**
|
|
53694
|
-
* InputWithList — connects an input to a
|
|
53756
|
+
* InputWithList — connects an input to a selectable List via its id.
|
|
53695
53757
|
*
|
|
53696
|
-
* Usage: <Input navi-list="my-list-id" /> next to <
|
|
53758
|
+
* Usage: <Input navi-list="my-list-id" /> next to <List selectable id="my-list-id">
|
|
53697
53759
|
*
|
|
53698
53760
|
* Behavior:
|
|
53699
53761
|
* - ArrowDown / ArrowUp move the list's "current item" without moving focus
|
|
@@ -54061,11 +54123,14 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
54061
54123
|
*
|
|
54062
54124
|
* - charGuard — restricts which characters can be typed, pasted, or set externally.
|
|
54063
54125
|
* Accepts a preset name or a raw regex character class:
|
|
54064
|
-
* "
|
|
54126
|
+
* true / "auto" → picked from the field: inputMode numeric/decimal, type tel/email
|
|
54127
|
+
* "numeric" → digits only, sets inputMode="numeric"
|
|
54128
|
+
* "decimal" → digits, minus, dot, comma, sets inputMode="decimal"
|
|
54065
54129
|
* "alpha" → letters only
|
|
54066
54130
|
* "alphanumeric" → letters and digits
|
|
54067
54131
|
* "uppercase" → uppercase letters only
|
|
54068
54132
|
* "tel" → phone chars (digits, +, -, parens, space), sets inputMode="tel"
|
|
54133
|
+
* "email" → what an email address is made of
|
|
54069
54134
|
* "card" → credit card (digits and spaces), sets inputMode="numeric"
|
|
54070
54135
|
* "hex" → hexadecimal digits
|
|
54071
54136
|
* "pin" → numeric PIN, sets inputMode="numeric"
|
|
@@ -55133,18 +55198,13 @@ const commitSubtree = (controller, e) => {
|
|
|
55133
55198
|
};
|
|
55134
55199
|
|
|
55135
55200
|
/**
|
|
55136
|
-
*
|
|
55137
|
-
*
|
|
55138
|
-
*
|
|
55139
|
-
*
|
|
55140
|
-
*
|
|
55141
|
-
*
|
|
55142
|
-
*
|
|
55143
|
-
* I could create an example where we would put a link on the page to let user see what he created
|
|
55144
|
-
* but by default user stays on the form allowing to create multiple resources at once
|
|
55145
|
-
* And an other where he is redirected to the resource he created
|
|
55146
|
-
* 3. If form submission fails ideally we should display this somewhere on the UI
|
|
55147
|
-
* right now it's just logged to the console I need to see how we can achieve this
|
|
55201
|
+
* <Form>: the <form> element as a navi control group. Its named fields
|
|
55202
|
+
* aggregate into one object; a submit checks their constraints, runs `action`
|
|
55203
|
+
* with that object when it changed (docs/form_changed.md), and `command` — or
|
|
55204
|
+
* the surface the form sits in — says what follows. The URL does not change on
|
|
55205
|
+
* a submit, and a failing action is drawn as an error callout on the button
|
|
55206
|
+
* that sent it (docs/error_handling.md). A form inside another form renders no
|
|
55207
|
+
* <form> element (FormNested).
|
|
55148
55208
|
*/
|
|
55149
55209
|
|
|
55150
55210
|
const Form = props => {
|
|
@@ -55764,9 +55824,9 @@ const offsetToPx = (offset, box) => {
|
|
|
55764
55824
|
|
|
55765
55825
|
// A press landing while the track is already travelling: what is playing is
|
|
55766
55826
|
// sent home in a fifth of the time it has left, and the press it could not take
|
|
55767
|
-
// yet is taken as soon as it lands. A press has to be FELT — nudging the
|
|
55768
|
-
// of a travel already in flight
|
|
55769
|
-
//
|
|
55827
|
+
// yet is taken as soon as it lands. A press has to be FELT — merely nudging the
|
|
55828
|
+
// pace of a travel already in flight reads as "nothing happened", because the
|
|
55829
|
+
// thing was moving before the click too. Getting there
|
|
55770
55830
|
// almost at once and setting off again is the click being answered.
|
|
55771
55831
|
// Played out fast rather than cut short: ending it on the spot would jump.
|
|
55772
55832
|
// Compounds, so two presses during one travel bring it home twice as sharply,
|
|
@@ -58337,13 +58397,12 @@ const SlideNavButton = ({
|
|
|
58337
58397
|
autoFocus: "last-resort",
|
|
58338
58398
|
icon: true,
|
|
58339
58399
|
variant: "discrete"
|
|
58340
|
-
// Takes the focus like any other button, on purpose.
|
|
58400
|
+
// Takes the focus like any other button, on purpose. Do not refuse it
|
|
58341
58401
|
// (mousedown.preventDefault) to keep the keyboard where the user had put
|
|
58342
|
-
// it
|
|
58343
|
-
//
|
|
58344
|
-
//
|
|
58345
|
-
//
|
|
58346
|
-
// page rather than from what is on screen.
|
|
58402
|
+
// it: the travel below only hands the focus to the slide arriving when it
|
|
58403
|
+
// was leaving a slide, so a press with nothing focused would arrive on
|
|
58404
|
+
// document.body, and the next Tab would start from the top of the page
|
|
58405
|
+
// rather than from what is on screen.
|
|
58347
58406
|
//
|
|
58348
58407
|
// Letting it focus is both the plain behaviour of a button and what makes
|
|
58349
58408
|
// the rest fall into place: the press lands on the chevron, the slide left
|
|
@@ -58360,12 +58419,6 @@ const SlideNavButton = ({
|
|
|
58360
58419
|
})
|
|
58361
58420
|
});
|
|
58362
58421
|
|
|
58363
|
-
/**
|
|
58364
|
-
* The way out of a slide, and the way into the next one. Nothing but the
|
|
58365
|
-
* command plus the chevron that matches the travel: a row goes left/right, a
|
|
58366
|
-
* column up/down — so the button points where the slide actually goes without
|
|
58367
|
-
* the caller having to keep the two in sync.
|
|
58368
|
-
*/
|
|
58369
58422
|
/**
|
|
58370
58423
|
* A way out pointing where it goes. It says a direction, not a slide: what is
|
|
58371
58424
|
* over there is the map's business, and moving a screen changes nothing here.
|
|
@@ -61415,7 +61468,7 @@ const css$E = /* css */`
|
|
|
61415
61468
|
* within its container (the viewport for `layer="top"`, the positioned
|
|
61416
61469
|
* ancestor for `layer="local"`) — Dialog is never anchored to a real
|
|
61417
61470
|
* element for positioning purposes. Same grammar as `Popover`'s own
|
|
61418
|
-
* `positionArea` (see `
|
|
61471
|
+
* `positionArea` (see `parsePositionArea` in @jsenv/dom): a single
|
|
61419
61472
|
* compass token — `top`/`top-start`/`top-end`/`top-left`/`top-right`,
|
|
61420
61473
|
* `right`/`right-start`/`right-end`, `bottom`/`bottom-start`/
|
|
61421
61474
|
* `bottom-end`/`bottom-left`/`bottom-right`, `left`/`left-start`/
|
|
@@ -61434,12 +61487,12 @@ const css$E = /* css */`
|
|
|
61434
61487
|
* between the dialog and the edges of its container, whatever its
|
|
61435
61488
|
* `positionArea`: it both caps the dialog's own size (via
|
|
61436
61489
|
* `--x-dialog-container-spacing`, written from this prop) and offsets a docked
|
|
61437
|
-
* one from the edge it docks to. Accepts a
|
|
61438
|
-
*
|
|
61439
|
-
*
|
|
61440
|
-
*
|
|
61441
|
-
*
|
|
61442
|
-
* meant to sit flush (a side panel).
|
|
61490
|
+
* one from the edge it docks to. Accepts a number of pixels, a viewport
|
|
61491
|
+
* length — "appw"/"apph" being the app's own screen (the visual viewport, or
|
|
61492
|
+
* the narrower one the app declared with --navi-app-max-width) and
|
|
61493
|
+
* "vvw"/"vvh" the visual viewport itself, which shrinks when the mobile
|
|
61494
|
+
* keyboard opens — or a container length ("3cqw", the `layer="local"`
|
|
61495
|
+
* default). Pass 0 for a dialog meant to sit flush (a side panel).
|
|
61443
61496
|
* @param {boolean} [props.backdrop=true] - Whether anything is laid between
|
|
61444
61497
|
* the dialog and the page at all — asked before any question of what an
|
|
61445
61498
|
* outside press does or how the backdrop is painted. `false` leaves the page
|
|
@@ -61452,10 +61505,12 @@ const css$E = /* css */`
|
|
|
61452
61505
|
* needs. What it gives up is what only a modal gets natively: the hardware
|
|
61453
61506
|
* back button no longer dismisses it. See docs/popup_backdrop.md.
|
|
61454
61507
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="close"]
|
|
61455
|
-
* - `"close"` closes the dialog on an outside press
|
|
61456
|
-
*
|
|
61457
|
-
*
|
|
61458
|
-
*
|
|
61508
|
+
* - `"close"` closes the dialog on an outside press, `"cancel"` closes it
|
|
61509
|
+
* as a cancel (what it holds reverts, as on Escape). `"capture"`/`"ignore"`
|
|
61510
|
+
* both just absorb the press without closing (`"capture"` paints the
|
|
61511
|
+
* blurred glass, `"ignore"` leaves the wall's paint alone) — a wall absorbs
|
|
61512
|
+
* the press whatever this prop says, natively for a modal and through the
|
|
61513
|
+
* sibling element for a local one.
|
|
61459
61514
|
* "Outside" is the dialog's own border box; a see-through dialog whose box is
|
|
61460
61515
|
* bigger than what it paints marks the difference with
|
|
61461
61516
|
* `data-navi-popup-outside` (see docs/popup_backdrop.md).
|
|
@@ -61530,11 +61585,10 @@ const css$E = /* css */`
|
|
|
61530
61585
|
* @param {string} [props.animationDuration] - Maps to
|
|
61531
61586
|
* `--popup-animation-duration`.
|
|
61532
61587
|
* @param {Element|{current: Element}|string} [props.anchor] - Never used for
|
|
61533
|
-
* positioning (see this file's top comment)
|
|
61534
|
-
* `
|
|
61535
|
-
* the dialog
|
|
61536
|
-
*
|
|
61537
|
-
* itself names none — an anchor carried by the opening event
|
|
61588
|
+
* positioning (see this file's top comment); read by `sizeFromAnchor` (the
|
|
61589
|
+
* `--anchor-width`/`--anchor-height` CSS vars), by `animation="lifting"`
|
|
61590
|
+
* (the box the dialog comes out of) and by `mount`'s warm-on-intent. Used
|
|
61591
|
+
* when the open itself names none — an anchor carried by the opening event
|
|
61538
61592
|
* (`e.detail.anchor`) is about that one opening and comes first; left out,
|
|
61539
61593
|
* whatever triggered the open (`e.detail.source`). A string is resolved via
|
|
61540
61594
|
* `document.getElementById` when the dialog opens — see popover.jsx's own
|
|
@@ -61596,6 +61650,9 @@ const css$E = /* css */`
|
|
|
61596
61650
|
* focusable of its own.
|
|
61597
61651
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
61598
61652
|
* held focus when it closed.
|
|
61653
|
+
* - `false` — the dialog element itself is never where the focus lands; the
|
|
61654
|
+
* opening still hands the focus to what the dialog holds. Unlike
|
|
61655
|
+
* `Popover`'s `false`, it does not leave the keyboard where it was.
|
|
61599
61656
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
61600
61657
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
61601
61658
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -61839,8 +61896,8 @@ const useDialogProps = props => {
|
|
|
61839
61896
|
// `backdrop` — see this file's top comment.
|
|
61840
61897
|
layer = "top",
|
|
61841
61898
|
dockedOnSmallTouchScreen,
|
|
61842
|
-
// Same grammar as Popover's own positionArea — see
|
|
61843
|
-
//
|
|
61899
|
+
// Same grammar as Popover's own positionArea — see parsePositionArea in
|
|
61900
|
+
// @jsenv/dom.
|
|
61844
61901
|
positionArea: positionAreaProp,
|
|
61845
61902
|
// A dialog docked against an edge must keep the same gap its own size cap
|
|
61846
61903
|
// already guarantees a centered one — so this drives both (see
|
|
@@ -61880,8 +61937,8 @@ const useDialogProps = props => {
|
|
|
61880
61937
|
sizing = "auto",
|
|
61881
61938
|
animation,
|
|
61882
61939
|
lift = "box",
|
|
61883
|
-
//
|
|
61884
|
-
//
|
|
61940
|
+
// Never positioned against (see this file's top comment): read by
|
|
61941
|
+
// sizeFromAnchor, by a lift, and by the warm-on-intent of `mount`.
|
|
61885
61942
|
anchor,
|
|
61886
61943
|
// Where a lift comes back to, when that is no longer the box it came out
|
|
61887
61944
|
// of. Read at the close, not kept from the opening — see
|
|
@@ -61891,8 +61948,8 @@ const useDialogProps = props => {
|
|
|
61891
61948
|
// See this prop's own JSDoc above for why a dialog does not follow its
|
|
61892
61949
|
// trigger's box by default.
|
|
61893
61950
|
sizeFromAnchor = false,
|
|
61894
|
-
// Same meaning as Popover's own prop, applied to
|
|
61895
|
-
//
|
|
61951
|
+
// Same meaning as Popover's own prop, applied to what an anchor does here:
|
|
61952
|
+
// sizing under sizeFromAnchor, and a lift.
|
|
61896
61953
|
anchorCustomEventDetail = "override",
|
|
61897
61954
|
// Makes the dialog itself a valid focus target so
|
|
61898
61955
|
// autoFocus="last-resort" below has somewhere to land when it contains
|
|
@@ -61948,8 +62005,8 @@ const useDialogProps = props => {
|
|
|
61948
62005
|
// narrower one) — and the positioned ancestor for a local one, where
|
|
61949
62006
|
// reading 3% of the screen gives an absurd gap inside a small box.
|
|
61950
62007
|
isTopLayer ? "3appw" : "3cqw");
|
|
61951
|
-
//
|
|
61952
|
-
//
|
|
62008
|
+
// `expand` is the shorthand for both axes; the docked default only applies
|
|
62009
|
+
// when neither was said.
|
|
61953
62010
|
const expandXUnset = expand === undefined && expandXProp === undefined;
|
|
61954
62011
|
const expandX = expandXUnset ? isDocked && DOCKED.expandX : Boolean(expand) || Boolean(expandXProp);
|
|
61955
62012
|
const scrollCapture = scrollCaptureProp ?? (isDocked ? DOCKED.scrollCapture : false);
|
|
@@ -62638,8 +62695,8 @@ const useDialogProps = props => {
|
|
|
62638
62695
|
// ("[navi-control='dialog'], [navi-control='popover']" — see openEffect
|
|
62639
62696
|
// above), and --navi-open/--navi-close resolve their target this way.
|
|
62640
62697
|
"navi-control": "dialog",
|
|
62641
|
-
// The protocol every command target answers
|
|
62642
|
-
//
|
|
62698
|
+
// The protocol every command target answers: a dialog is layout, not a
|
|
62699
|
+
// control, and still has to answer --navi-open,
|
|
62643
62700
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
62644
62701
|
// bubble.
|
|
62645
62702
|
"onnavi_command": e => {
|
|
@@ -62661,14 +62718,12 @@ const useDialogProps = props => {
|
|
|
62661
62718
|
"popover": isTopLayerPopover ? "manual" : undefined,
|
|
62662
62719
|
"baseClassName": "navi_dialog",
|
|
62663
62720
|
"pseudoClasses": DIALOG_PSEUDO_CLASSES,
|
|
62664
|
-
// Distinguishes the two renderers for the CSS above (position: fixed
|
|
62665
|
-
// vs. absolute) — positioning itself is entirely JS-driven now (see
|
|
62666
|
-
// openEffect's own positionDialog above), no data-position-area
|
|
62667
|
-
// attribute needed at all.
|
|
62668
62721
|
// A popup scrolls, and asking Box for that overflow is also what lets what
|
|
62669
62722
|
// it contains claim header/footer/body (see box.jsx) — a popup is always a
|
|
62670
62723
|
// scrolling area, so it says so once, here.
|
|
62671
62724
|
"overflow": "auto",
|
|
62725
|
+
// Tells the two renderers apart for the CSS above (position: fixed vs.
|
|
62726
|
+
// absolute); the placement itself is JS-driven (positionDialog).
|
|
62672
62727
|
"data-layer": layer,
|
|
62673
62728
|
// The sheet shape is live in CSS, not just a set of resolved defaults:
|
|
62674
62729
|
// it is what withdraws the caller's --dialog-max-width (see the stylesheet
|
|
@@ -63145,8 +63200,8 @@ const css$D = /* css */`
|
|
|
63145
63200
|
* whole viewport. A real `anchor` works with either.
|
|
63146
63201
|
* @param {string} [props.positionArea="bottom"] - Where to place the popover
|
|
63147
63202
|
* relative to its `anchor` (or its container, if there is none). Same
|
|
63148
|
-
* grammar as `Dialog`'s own `positionArea` (see `
|
|
63149
|
-
*
|
|
63203
|
+
* grammar as `Dialog`'s own `positionArea` (see `parsePositionArea` in
|
|
63204
|
+
* @jsenv/dom): a single compass token — `top`/`top-start`/
|
|
63150
63205
|
* `top-end`/`top-left`/`top-right`, `right`/`right-start`/`right-end`,
|
|
63151
63206
|
* `bottom`/`bottom-start`/`bottom-end`/`bottom-left`/`bottom-right`,
|
|
63152
63207
|
* `left`/`left-start`/`left-end`, or `center`. A bare token means no
|
|
@@ -63175,8 +63230,9 @@ const css$D = /* css */`
|
|
|
63175
63230
|
* See docs/popup_backdrop.md.
|
|
63176
63231
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="ignore"]
|
|
63177
63232
|
* - `"ignore"` (default): no backdrop at all, outside presses pass straight
|
|
63178
|
-
* through. `"close"` closes the popover on an outside press
|
|
63179
|
-
*
|
|
63233
|
+
* through. `"close"` closes the popover on an outside press, `"cancel"`
|
|
63234
|
+
* closes it as a cancel (what it holds reverts, as on Escape). `"capture"`
|
|
63235
|
+
* absorbs the press (blurred glass) without closing. Note this
|
|
63180
63236
|
* default differs from `Dialog`'s own (`"close"`) — a popover is
|
|
63181
63237
|
* typically a lightweight, non-modal affordance. "Outside" is the popover's
|
|
63182
63238
|
* own border box; a see-through popover whose box is bigger than what it
|
|
@@ -63210,9 +63266,11 @@ const css$D = /* css */`
|
|
|
63210
63266
|
* popover so the page/container behind it can't scroll while it's open.
|
|
63211
63267
|
* @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
|
|
63212
63268
|
* popover (see `focus_trap.js`).
|
|
63213
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`} [props.animation]
|
|
63214
|
-
* - `true`/`"auto"` resolves to
|
|
63215
|
-
*
|
|
63269
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`} [props.animation]
|
|
63270
|
+
* - `true`/`"auto"` resolves to `"scaling"` for a real anchor or a centered
|
|
63271
|
+
* `positionArea`, and to a concrete `"slide-from-*"` direction otherwise.
|
|
63272
|
+
* `"expanding"`/`"expand-*"` grow out of a real anchor (see popup_css.js).
|
|
63273
|
+
* Any other explicit value is used as-is.
|
|
63216
63274
|
* @param {string} [props.animationDuration] - Maps to
|
|
63217
63275
|
* `--popup-animation-duration`.
|
|
63218
63276
|
* @param {Element|{current: Element}|string} [props.anchor] - The element the
|
|
@@ -63324,7 +63382,7 @@ const css$D = /* css */`
|
|
|
63324
63382
|
* @param {object} [props.openController] - Advanced: an externally-owned
|
|
63325
63383
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
63326
63384
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
63327
|
-
* by `picker_custom.jsx
|
|
63385
|
+
* by `picker_custom.jsx`).
|
|
63328
63386
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
63329
63387
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
63330
63388
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
@@ -63634,12 +63692,10 @@ const usePopoverProps = props => {
|
|
|
63634
63692
|
unfreezeSize(popoverEl);
|
|
63635
63693
|
}
|
|
63636
63694
|
}, [sizing]);
|
|
63637
|
-
// The custom renderer
|
|
63638
|
-
//
|
|
63639
|
-
//
|
|
63640
|
-
//
|
|
63641
|
-
// leaving this to a layout effect meant an actual (if narrow) window
|
|
63642
|
-
// where the browser could paint it visible before this ever ran.
|
|
63695
|
+
// The custom renderer starts hidden from the stylesheet ([navi-hidden] on
|
|
63696
|
+
// .navi_popover/.navi_popover_backdrop above), never from a layout effect:
|
|
63697
|
+
// a plain div has no native hidden default the way [popover]/<dialog> do,
|
|
63698
|
+
// and an effect leaves a window where the browser paints it visible first.
|
|
63643
63699
|
// aria-expanded starts "false" the same way, but via a static literal
|
|
63644
63700
|
// JSX prop on contentProps/backdropProps below instead of a layout
|
|
63645
63701
|
// effect — see that prop's own comment for why a *constant* value there
|
|
@@ -63658,8 +63714,8 @@ const usePopoverProps = props => {
|
|
|
63658
63714
|
openController.getElement = () => ref.current;
|
|
63659
63715
|
openController.openEffect = e => {
|
|
63660
63716
|
const popoverEl = ref.current;
|
|
63661
|
-
// backdropEl is null when
|
|
63662
|
-
// the backdrop isn't rendered at all
|
|
63717
|
+
// backdropEl is null when there is no wall (pressOutside "ignore", or
|
|
63718
|
+
// backdrop={false}): the backdrop isn't rendered at all then.
|
|
63663
63719
|
const backdropEl = backdropRef.current;
|
|
63664
63720
|
if (!popoverEl) {
|
|
63665
63721
|
return undefined;
|
|
@@ -63714,10 +63770,8 @@ const usePopoverProps = props => {
|
|
|
63714
63770
|
// needed here.
|
|
63715
63771
|
const positionedAncestor = getPositionedParent(popoverEl);
|
|
63716
63772
|
// Drives the via-attribute renderer's own position: fixed/absolute
|
|
63717
|
-
// switch (see this file's top comment) — set here,
|
|
63718
|
-
// positioning/measurement runs, so
|
|
63719
|
-
// wrong (unlike the CSS this replaced, which keyed off the resolved
|
|
63720
|
-
// animation instead, known too late relative to the first measurement).
|
|
63773
|
+
// switch (see this file's top comment) — set here, before any
|
|
63774
|
+
// positioning/measurement runs, so the first measurement already sees it.
|
|
63721
63775
|
// True only for a real anchor that itself scrolls with the document —
|
|
63722
63776
|
// that's the one case `absolute` (scrolling in lockstep with it) is
|
|
63723
63777
|
// correct; not just "has an anchor at all", since an anchor that's
|
|
@@ -63767,10 +63821,9 @@ const usePopoverProps = props => {
|
|
|
63767
63821
|
disarmBackdropHideRef.current?.();
|
|
63768
63822
|
disarmBackdropHideRef.current = null;
|
|
63769
63823
|
// transitionProperty stays "none" here for both — reset later, in the
|
|
63770
|
-
// final commit step alongside popoverEl's own
|
|
63771
|
-
//
|
|
63772
|
-
//
|
|
63773
|
-
// aria-expanded flip, the same way it does for popoverEl.
|
|
63824
|
+
// final commit step alongside popoverEl's own, so a `silent` open (see
|
|
63825
|
+
// above) can keep both elements' transitions suppressed right up until
|
|
63826
|
+
// after their aria-expanded flip, the same way it does for popoverEl.
|
|
63774
63827
|
backdropEl.style.transitionProperty = "none";
|
|
63775
63828
|
if (isTopLayer) {
|
|
63776
63829
|
// Hidden first if a previous close's deferred hidePopover() (see
|
|
@@ -64195,16 +64248,10 @@ const usePopoverProps = props => {
|
|
|
64195
64248
|
// current unconditionally on every call, and treats their mere
|
|
64196
64249
|
// *presence* as "already positioned this session — stay on this
|
|
64197
64250
|
// side unless it no longer fits" (its own anti-oscillation guard for
|
|
64198
|
-
// repositioning mid-open, e.g. on scroll/resize).
|
|
64199
|
-
//
|
|
64200
|
-
//
|
|
64201
|
-
//
|
|
64202
|
-
// still-live purpose: without clearing them on close, a later reopen
|
|
64203
|
-
// sees a stale, "already positioned" value left over from a
|
|
64204
|
-
// *previous* open (e.g. the container-aligned collapse from an
|
|
64205
|
-
// anchorless silent open) and wrongly stays sticky to it instead of
|
|
64206
|
-
// resolving fresh from the current positionArea/anchor — a real,
|
|
64207
|
-
// reproduced bug, not just a leftover no-op.
|
|
64251
|
+
// repositioning mid-open, e.g. on scroll/resize). Cleared on close so a
|
|
64252
|
+
// later reopen resolves fresh from the current positionArea/anchor
|
|
64253
|
+
// instead of staying sticky to a side left over from a previous open
|
|
64254
|
+
// (e.g. the container-aligned collapse from an anchorless silent open).
|
|
64208
64255
|
popoverEl.removeAttribute("data-position-y-current");
|
|
64209
64256
|
popoverEl.removeAttribute("data-position-x-current");
|
|
64210
64257
|
cleanup();
|
|
@@ -64334,8 +64381,8 @@ const usePopoverProps = props => {
|
|
|
64334
64381
|
// ("[navi-control='dialog'], [navi-control='popover']"), and
|
|
64335
64382
|
// --navi-open/--navi-close resolve their target this way.
|
|
64336
64383
|
"navi-control": "popover",
|
|
64337
|
-
// The protocol every command target answers
|
|
64338
|
-
//
|
|
64384
|
+
// The protocol every command target answers: a popover is layout, not a
|
|
64385
|
+
// control, and still has to answer --navi-open,
|
|
64339
64386
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
64340
64387
|
// bubble.
|
|
64341
64388
|
"onnavi_command": e => {
|
|
@@ -64375,7 +64422,7 @@ const usePopoverProps = props => {
|
|
|
64375
64422
|
// already arrive here as plain props via ...rest (wired by
|
|
64376
64423
|
// UncontrolledPopover above, forwarded through ControlledPopover's own
|
|
64377
64424
|
// {...props} spread) — nothing extra to add here. A controlled caller
|
|
64378
|
-
// (picker_custom.jsx
|
|
64425
|
+
// (picker_custom.jsx) wires its own equivalent handling
|
|
64379
64426
|
// directly against its own openController instead.
|
|
64380
64427
|
});
|
|
64381
64428
|
return [hasBackdrop ? backdropProps : null, contentProps];
|
|
@@ -64407,9 +64454,6 @@ const POPUP_STYLE_CSS_VARS = {
|
|
|
64407
64454
|
maxHeight: "--popover-max-height"
|
|
64408
64455
|
};
|
|
64409
64456
|
|
|
64410
|
-
// parsePositionArea/POSITION_AREA_X/Y_VALUES moved to popup_shared.js — same
|
|
64411
|
-
// grammar Dialog's own layer="local"/"top" now shares.
|
|
64412
|
-
|
|
64413
64457
|
/**
|
|
64414
64458
|
* Shared by both renderers: parses `positionArea` and resolves
|
|
64415
64459
|
* `animation="auto"`/`true`. `animationAnchor` is the real anchor element
|
|
@@ -64437,15 +64481,6 @@ const resolvePositionAreaAndAnimationKind = ({
|
|
|
64437
64481
|
resolvedAnimationKind
|
|
64438
64482
|
};
|
|
64439
64483
|
};
|
|
64440
|
-
|
|
64441
|
-
// resolveDirectionValue/resolveAutoAnimationKind moved to popup_shared.js —
|
|
64442
|
-
// same logic Dialog's own auto-animation resolution now shares, since it
|
|
64443
|
-
// never has a real anchor either (always the "anchor === undefined" path).
|
|
64444
|
-
|
|
64445
|
-
// suppressPointerEventsDuringTransition/armPointerDownOutsideClose moved to
|
|
64446
|
-
// popup_shared.js — same helpers Dialog's own custom renderer needs, no
|
|
64447
|
-
// Popover-specific knowledge in either.
|
|
64448
|
-
|
|
64449
64484
|
Popover.Close = PopupClose;
|
|
64450
64485
|
|
|
64451
64486
|
installImportMetaCssBuild(import.meta);
|
|
@@ -64484,11 +64519,12 @@ const css$C = /* css */`@layer navi {
|
|
|
64484
64519
|
* @param {"top"|"local"} [props.layer] - Forwarded as-is to whichever of
|
|
64485
64520
|
* `Dialog`/`Popover` renders — see either component's own doc.
|
|
64486
64521
|
* @param {Element|{current: Element}} [props.anchor] - Forwarded as-is —
|
|
64487
|
-
* positioning for `Popover
|
|
64488
|
-
* `sizeFromAnchor`
|
|
64522
|
+
* positioning for `Popover`; for `Dialog`, the box it sizes from under
|
|
64523
|
+
* `sizeFromAnchor` and lifts out of under `animation="lifting"`. In both,
|
|
64524
|
+
* intent on the anchor warms the content (see `mount`).
|
|
64489
64525
|
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] - Forwarded
|
|
64490
64526
|
* as-is to both — what it governs differs (positioning for `Popover`,
|
|
64491
|
-
*
|
|
64527
|
+
* sizing and lifting for `Dialog`), but "ignore whatever anchor the
|
|
64492
64528
|
* triggering event carried" has to mean the same thing in either mode.
|
|
64493
64529
|
* @param {string} [props.marginWithAnchor] - **Popover-only**, destructured
|
|
64494
64530
|
* out so it can't leak onto the real `<dialog>` element as a stray DOM
|
|
@@ -64518,8 +64554,9 @@ const css$C = /* css */`@layer navi {
|
|
|
64518
64554
|
* the screen-size check happens to pick, which defeats the point of
|
|
64519
64555
|
* having one shared API in the first place. Note this only says what a
|
|
64520
64556
|
* press outside *does*; whether it reaches the page at all is `backdrop`
|
|
64521
|
-
* below
|
|
64522
|
-
*
|
|
64557
|
+
* below. `"capture"` is a wall that absorbs the press without closing, in
|
|
64558
|
+
* either mode. `"ignore"` is not the same in both: a `Dialog` keeps its
|
|
64559
|
+
* wall, a `Popover` lays none and the press reaches the page.
|
|
64523
64560
|
* @param {boolean} [props.backdrop] - Whether anything is laid between the
|
|
64524
64561
|
* popup and the page at all: `false` lets a press outside both dismiss the
|
|
64525
64562
|
* popup and reach whatever it landed on, in one gesture. Forwarded as-is and
|
|
@@ -64559,11 +64596,15 @@ const css$C = /* css */`@layer navi {
|
|
|
64559
64596
|
* `--x-popover-max-width`) instead of its content width — same meaning
|
|
64560
64597
|
* whichever mode the screen-size resolution picks.
|
|
64561
64598
|
* @param {boolean} [props.expandY] - Same, vertically (`data-expand-y`).
|
|
64562
|
-
* @param {boolean} [props.scrollCapture] - Forwarded
|
|
64599
|
+
* @param {boolean|"dialog"|"popover"} [props.scrollCapture] - Forwarded to
|
|
64600
|
+
* both; a mode name traps scroll only when that mode is the one rendered.
|
|
64563
64601
|
* @param {boolean} [props.open] - Forwarded as-is (controlled).
|
|
64564
|
-
* @param {import("@preact/signals").Signal<
|
|
64602
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] -
|
|
64565
64603
|
* Forwarded as-is: one binding to both drive the popup's open state and
|
|
64566
64604
|
* know where it is (see `Dialog`/`Popover`'s own `signal`).
|
|
64605
|
+
* @param {any} [props.value] - Forwarded as-is: what `signal` holds while
|
|
64606
|
+
* THIS popup is open, for several sharing one signal (see
|
|
64607
|
+
* `Dialog`/`Popover`'s own `value`).
|
|
64567
64608
|
* @param {boolean} [props.defaultOpen] - Forwarded as-is (uncontrolled,
|
|
64568
64609
|
* mount-only).
|
|
64569
64610
|
* @param {(event: Event) => void} [props.onClose] - Forwarded as-is.
|
|
@@ -64578,6 +64619,8 @@ const css$C = /* css */`@layer navi {
|
|
|
64578
64619
|
* them afterwards. `"always"` builds them right away, for content something
|
|
64579
64620
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
64580
64621
|
* surrounding form collects on submit, a size measured from outside.
|
|
64622
|
+
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
64623
|
+
* minus the cost on the critical render.
|
|
64581
64624
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
64582
64625
|
* content whose fresh state is its initial state: an uncontrolled field
|
|
64583
64626
|
* seeded from a `defaultValue` that changed while the popup was closed.
|
|
@@ -65163,7 +65206,7 @@ const PickerCustom = props => {
|
|
|
65163
65206
|
// Not on pickerProps (the trigger): commands.js's own
|
|
65164
65207
|
// resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
|
|
65165
65208
|
// find where to dispatch navi_request_open/navi_request_close — and
|
|
65166
|
-
// the popup itself
|
|
65209
|
+
// the popup itself carries its own aria-expanded (see
|
|
65167
65210
|
// popover.jsx/dialog.jsx), which is *closer* than the picker's own
|
|
65168
65211
|
// aria-expanded for anything dispatched from inside the popup's own
|
|
65169
65212
|
// content (e.g. a `command="--navi-close"` button rendered as
|
|
@@ -65442,12 +65485,8 @@ const PickerContentInsidePopup = props => {
|
|
|
65442
65485
|
mode,
|
|
65443
65486
|
pointerLock,
|
|
65444
65487
|
scrollCapture,
|
|
65445
|
-
// No default here
|
|
65446
|
-
//
|
|
65447
|
-
// Popover's real `focusCapture` prop (see this file's history), so
|
|
65448
|
-
// focus-trapping has never really been active for popover-mode pickers;
|
|
65449
|
-
// defaulting the now-correctly-named prop to `true` would be a real,
|
|
65450
|
-
// unintended behavior change riding along with the rename.
|
|
65488
|
+
// No default here: Popover's own `focusCapture` is inactive by default,
|
|
65489
|
+
// and a popover-mode picker traps the focus only when asked to.
|
|
65451
65490
|
focusCapture,
|
|
65452
65491
|
// Popup documents its own `layer` as forwarded as-is to Dialog/Popover,
|
|
65453
65492
|
// but popupProps is built explicitly here, so it only travels if named.
|
|
@@ -66084,6 +66123,8 @@ document.addEventListener(
|
|
|
66084
66123
|
{ capture: true, passive: true },
|
|
66085
66124
|
);
|
|
66086
66125
|
|
|
66126
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
66127
|
+
|
|
66087
66128
|
/**
|
|
66088
66129
|
* Calls back once the browser has painted what is committed now.
|
|
66089
66130
|
*
|
|
@@ -66095,10 +66136,9 @@ document.addEventListener(
|
|
|
66095
66136
|
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
66096
66137
|
* late, through the timeout.
|
|
66097
66138
|
*
|
|
66139
|
+
* @param {() => void} callback
|
|
66098
66140
|
* @returns {() => void} cancel
|
|
66099
66141
|
*/
|
|
66100
|
-
const FRAME_TIMEOUT_MS = 100;
|
|
66101
|
-
|
|
66102
66142
|
const afterPaint = (callback) => {
|
|
66103
66143
|
let called = false;
|
|
66104
66144
|
let timeoutId;
|
|
@@ -67645,7 +67685,7 @@ const SELECTABLE_INPUT_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-
|
|
|
67645
67685
|
const SelectableInputProxy = props => {
|
|
67646
67686
|
const selectableRealInputProps = useContext(SelectableRealInputContext);
|
|
67647
67687
|
if (!selectableRealInputProps) {
|
|
67648
|
-
throw new Error("
|
|
67688
|
+
throw new Error("SelectableInput must be used within a row of a selectable List");
|
|
67649
67689
|
}
|
|
67650
67690
|
|
|
67651
67691
|
// Reset FieldToInterfaceContext to ensure we don't read id or report our
|
|
@@ -67683,7 +67723,7 @@ const SelectableInput = SelectableInputProxy;
|
|
|
67683
67723
|
* background-color: var(--search-match-background-color);
|
|
67684
67724
|
* }
|
|
67685
67725
|
*
|
|
67686
|
-
* The `highlight`
|
|
67726
|
+
* The `highlight` argument of `useSearchHighlight` (a row's `matchInfo.matchRanges`) can be:
|
|
67687
67727
|
* - an array of [start, end] pairs — applied to all text nodes under the root element
|
|
67688
67728
|
* - an object { [domSelector]: [[start, end], …] } — applied to each sub-element
|
|
67689
67729
|
* matched by the selector (the format produced by createSearch)
|
|
@@ -71129,8 +71169,8 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
71129
71169
|
*
|
|
71130
71170
|
* All three are about the row as a thing the LIST holds, never about the
|
|
71131
71171
|
* selection: a selectable row taken while its list sends says so on its own
|
|
71132
|
-
* ("
|
|
71133
|
-
* that.
|
|
71172
|
+
* ("le choix est en cours d'enregistrement", "la sélection…" in a `multiple`
|
|
71173
|
+
* list), and needs no `loading` for that.
|
|
71134
71174
|
* @param {boolean} [props.readOnly]
|
|
71135
71175
|
* The row cannot be acted on: dimmed and click-through-proof, buttons inside
|
|
71136
71176
|
* it included.
|
|
@@ -72216,9 +72256,12 @@ const useItemStore = ({
|
|
|
72216
72256
|
* (accessible via aria-labelledby) and a <ul role="group"> for the items.
|
|
72217
72257
|
*
|
|
72218
72258
|
* Props:
|
|
72219
|
-
* label
|
|
72220
|
-
* labelProps
|
|
72221
|
-
*
|
|
72259
|
+
* label — group label content
|
|
72260
|
+
* labelProps — props forwarded to the label <span>
|
|
72261
|
+
* hiddenWhileEmpty — the group leaves the flow (`display: none`) while it
|
|
72262
|
+
* holds no real row — a search that emptied it, rows not
|
|
72263
|
+
* arrived yet
|
|
72264
|
+
* ...rest — forwarded to the outer <li role="presentation">
|
|
72222
72265
|
*/
|
|
72223
72266
|
const ListItemGroup = ({
|
|
72224
72267
|
label,
|
|
@@ -72330,7 +72373,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
72330
72373
|
* uiAction?: (value: any) => void,
|
|
72331
72374
|
* popover?: boolean,
|
|
72332
72375
|
* role?: string,
|
|
72333
|
-
* renderBudget?: number | string,
|
|
72376
|
+
* renderBudget?: number | string | {initial?: number, after: number},
|
|
72334
72377
|
* renderBudgetSkipCheck?: boolean,
|
|
72335
72378
|
* virtualItemSize?: number,
|
|
72336
72379
|
* onListVisibleItemsChange?: (visibleItems: any[]) => void,
|
|
@@ -72359,6 +72402,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
72359
72402
|
* alignX?: string,
|
|
72360
72403
|
* alignY?: string,
|
|
72361
72404
|
* flexWrap?: boolean,
|
|
72405
|
+
* overflow?: string,
|
|
72362
72406
|
* expandX?: boolean,
|
|
72363
72407
|
* expandY?: boolean,
|
|
72364
72408
|
* expand?: boolean,
|
|
@@ -72545,10 +72589,10 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
72545
72589
|
* @param {number} [props.parallelGuard=4]
|
|
72546
72590
|
* How many runs the rows may have in flight at once, for a list whose rows
|
|
72547
72591
|
* carry their own `action` (a button per row). While that many are out, every
|
|
72548
|
-
*
|
|
72549
|
-
* the next press is possible again as soon as one comes back.
|
|
72550
|
-
* lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
72551
|
-
* says how many things the selection may hold.
|
|
72592
|
+
* control that would start another run goes read-only and says how many it is
|
|
72593
|
+
* waiting on; the next press is possible again as soon as one comes back.
|
|
72594
|
+
* `Infinity` lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
72595
|
+
* the one that says how many things the selection may hold.
|
|
72552
72596
|
* @param {boolean} [props.standalone]
|
|
72553
72597
|
* This list answers for itself: it does not register with the control group
|
|
72554
72598
|
* or picker around it, so its selection stays out of that value and nothing
|
|
@@ -75275,7 +75319,7 @@ const TRAVELS_NONE = [];
|
|
|
75275
75319
|
* fallbackValue?: any,
|
|
75276
75320
|
* valueAtStep: (value: any, count: number) => any,
|
|
75277
75321
|
* compareValues?: (a: any, b: any) => number,
|
|
75278
|
-
* renderValue?: (value: any) => import("ignore:preact").ComponentChildren,
|
|
75322
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("ignore:preact").ComponentChildren,
|
|
75279
75323
|
* controlProps?: object,
|
|
75280
75324
|
* duration?: number,
|
|
75281
75325
|
* vertical?: boolean,
|
|
@@ -76750,7 +76794,8 @@ const css$q = /* css */`.navi_input_duration {
|
|
|
76750
76794
|
* @param {boolean} [props.required]
|
|
76751
76795
|
* @param {boolean} [props.loading]
|
|
76752
76796
|
* @param {Function} [props.uiAction] - Called on every change with the ISO 8601 value
|
|
76753
|
-
* @param {Function} [props.action] -
|
|
76797
|
+
* @param {Function} [props.action] - Runs with the ISO 8601 value when it changes,
|
|
76798
|
+
* like any control's `action` (busy state, error callout)
|
|
76754
76799
|
* @param {preact.ComponentChild} [props.unitHour] - Custom label for the hour sub-field
|
|
76755
76800
|
* @param {"auto"|"left"|"center"|"right"} [props.textAlign="auto"] - Text alignment of sub-inputs.
|
|
76756
76801
|
* "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
|
|
@@ -77758,11 +77803,12 @@ const splitPopupProps = props => {
|
|
|
77758
77803
|
* multi-word, one word at start (all match) 0.75
|
|
77759
77804
|
* phrase / word at word boundary 0.625
|
|
77760
77805
|
* phrase / words mid-word 0.5
|
|
77806
|
+
* acronym (2+ chars, each the first letter of a word, in order) 0.4, 0.45 at start
|
|
77761
77807
|
* + case-exact bonus +0.125
|
|
77762
77808
|
* multi-word partial: score × (matched/total)
|
|
77763
77809
|
*
|
|
77764
77810
|
* matchRanges: [start, end] pairs (exclusive end) for CSS Highlight API.
|
|
77765
|
-
* Intended to be passed to
|
|
77811
|
+
* Intended to be passed to useSearchText as the matchFn parameter.
|
|
77766
77812
|
*/
|
|
77767
77813
|
const applySearch = (searchText, value) => {
|
|
77768
77814
|
if (!searchText) {
|
|
@@ -77972,7 +78018,7 @@ const mergeRanges = (ranges) => {
|
|
|
77972
78018
|
};
|
|
77973
78019
|
|
|
77974
78020
|
/**
|
|
77975
|
-
* createSearch — builds a matchFn compatible with
|
|
78021
|
+
* createSearch — builds a matchFn compatible with useSearchText that searches
|
|
77976
78022
|
* across multiple named fields of an item, each with its own DOM selector
|
|
77977
78023
|
* and optional priority weight.
|
|
77978
78024
|
*
|
|
@@ -77990,7 +78036,7 @@ const mergeRanges = (ranges) => {
|
|
|
77990
78036
|
* },
|
|
77991
78037
|
* });
|
|
77992
78038
|
*
|
|
77993
|
-
* const [orderedItems, getItemMatchInfo] =
|
|
78039
|
+
* const [orderedItems, getItemMatchInfo] = useSearchText(search, items, searchPerson);
|
|
77994
78040
|
* // getItemMatchInfo(item).matchRanges is { ".name": [[start,end],…], ".address": [[start,end],…] }
|
|
77995
78041
|
* // Pass the whole thing: <ListItem matchInfo={getItemMatchInfo(item)} />
|
|
77996
78042
|
* // — ListItem handles the per-selector object format for matchRanges.
|
|
@@ -78057,7 +78103,7 @@ const useSearchText = (searchText, items, matchFn = applySearch) => {
|
|
|
78057
78103
|
);
|
|
78058
78104
|
}
|
|
78059
78105
|
if (items === undefined) {
|
|
78060
|
-
throw new TypeError("
|
|
78106
|
+
throw new TypeError("useSearchText: items is undefined");
|
|
78061
78107
|
}
|
|
78062
78108
|
const { orderedItems, matchInfoMap } = useMemo(() => {
|
|
78063
78109
|
const { scoreEntries, nonMatched, matchInfoMap } = buildMatchInfo(
|
|
@@ -78182,17 +78228,19 @@ const buildMatchInfo = (searchText, items, matchFn) => {
|
|
|
78182
78228
|
* - orderedKeys: number[] of visible item keys sorted by explicit order
|
|
78183
78229
|
* - keyToOrderedIndex: Map key → orderedKeys index, gives O(1) indexOf equivalent
|
|
78184
78230
|
* - keyToExplicitOrder: Map key → explicitly passed index, used to maintain sort order
|
|
78185
|
-
* -
|
|
78186
|
-
* - visibleItemsSignal: signal(array),
|
|
78231
|
+
* - itemsSignal: signal(array), all items including hidden, ordered by explicit index
|
|
78232
|
+
* - visibleItemsSignal: signal(array), items neither hidden nor filtered
|
|
78187
78233
|
* - countSignal: signal(number), count of all items including hidden
|
|
78188
78234
|
* - visibleCountSignal: signal(number), updated in microtask batch, only when count changes
|
|
78189
|
-
* -
|
|
78235
|
+
* - noMatchCountSignal: signal(number), items registered with `match: false`
|
|
78190
78236
|
* - onChangeRef: holds the latest onChange callback, called once per microtask batch
|
|
78191
78237
|
*
|
|
78192
|
-
* useTrackItem(
|
|
78193
|
-
*
|
|
78194
|
-
* correct order and passes it directly — no render-sequence deduction
|
|
78195
|
-
*
|
|
78238
|
+
* useTrackItem(data): registers the item under `data.id`, at the position
|
|
78239
|
+
* `data.index` gives it among its siblings. The caller (e.g. items.map) knows
|
|
78240
|
+
* the correct order and passes it directly — no render-sequence deduction
|
|
78241
|
+
* needed. `data.role === "presentation"` keeps it out entirely.
|
|
78242
|
+
* Returns the visible rank (position among visible items), or -1 when hidden
|
|
78243
|
+
* or filtered.
|
|
78196
78244
|
* Signals and onChange are deferred to a microtask so multiple items updating
|
|
78197
78245
|
* in one commit cause only one notification.
|
|
78198
78246
|
*
|
|
@@ -78960,7 +79008,7 @@ const WheelGroupContext = createContext(null);
|
|
|
78960
79008
|
* [key: string]: any,
|
|
78961
79009
|
* }>}
|
|
78962
79010
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
78963
|
-
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (
|
|
79011
|
+
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (1.8em).
|
|
78964
79012
|
* @param {number|string} [props.itemWidth] - Main-axis size of a cell when horizontal (number = px). Defaults to the CSS var (3.5ch).
|
|
78965
79013
|
* @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.
|
|
78966
79014
|
* @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).
|
|
@@ -80448,7 +80496,7 @@ Wheel.Item = WheelItem;
|
|
|
80448
80496
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
80449
80497
|
* @param {boolean} [props.glass] - Frost every wheel's neighbouring rows (see Wheel's glass prop) with one prop for the whole group.
|
|
80450
80498
|
* @param {boolean} [props.frameBorder] - Line every wheel's center-window edges with a faint frame (off by default; independent of glass).
|
|
80451
|
-
* @param {boolean} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
80499
|
+
* @param {boolean|number} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
80452
80500
|
*/
|
|
80453
80501
|
const WheelGroup = props => {
|
|
80454
80502
|
import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
|
|
@@ -81233,8 +81281,8 @@ const stringifyTableSelectionValue = (type, value) => {
|
|
|
81233
81281
|
|
|
81234
81282
|
/**
|
|
81235
81283
|
* Check if a specific cell is selected
|
|
81236
|
-
* @param {
|
|
81237
|
-
* @param {{
|
|
81284
|
+
* @param {string[]} selection - The table's `selection` array
|
|
81285
|
+
* @param {{columnId: string, rowId: string}} cell - The cell's column and row ids
|
|
81238
81286
|
* @returns {boolean} True if the cell is selected
|
|
81239
81287
|
*/
|
|
81240
81288
|
const isCellSelected = (selection, { columnId, rowId }) => {
|
|
@@ -81247,8 +81295,8 @@ const isCellSelected = (selection, { columnId, rowId }) => {
|
|
|
81247
81295
|
|
|
81248
81296
|
/**
|
|
81249
81297
|
* Check if a specific row is selected
|
|
81250
|
-
* @param {
|
|
81251
|
-
* @param {
|
|
81298
|
+
* @param {string[]} selection - The table's `selection` array
|
|
81299
|
+
* @param {string} rowId - The row's id (the `id` of its `<Tr>`)
|
|
81252
81300
|
* @returns {boolean} True if the row is selected
|
|
81253
81301
|
*/
|
|
81254
81302
|
const isRowSelected = (selection, rowId) => {
|
|
@@ -81258,8 +81306,8 @@ const isRowSelected = (selection, rowId) => {
|
|
|
81258
81306
|
|
|
81259
81307
|
/**
|
|
81260
81308
|
* Check if a specific column is selected
|
|
81261
|
-
* @param {
|
|
81262
|
-
* @param {
|
|
81309
|
+
* @param {string[]} selection - The table's `selection` array
|
|
81310
|
+
* @param {string} columnId - The column's id (the `id` of its `<Col>`)
|
|
81263
81311
|
* @returns {boolean} True if the column is selected
|
|
81264
81312
|
*/
|
|
81265
81313
|
const isColumnSelected = (selection, columnId) => {
|
|
@@ -81794,7 +81842,7 @@ const initDragTableColumnViaPointer = (pointerdownEvent, {
|
|
|
81794
81842
|
/**
|
|
81795
81843
|
* Creates a MutationObserver that syncs attribute changes from original table to clone
|
|
81796
81844
|
* @param {HTMLElement} table - The original table element
|
|
81797
|
-
* @param {HTMLElement}
|
|
81845
|
+
* @param {HTMLElement} tableClone - The cloned table element
|
|
81798
81846
|
* @returns {MutationObserver} The observer instance with disconnect method
|
|
81799
81847
|
*/
|
|
81800
81848
|
const createTableAttributeSync = (table, tableClone) => {
|
|
@@ -83759,9 +83807,10 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
83759
83807
|
* KEY PRINCIPLES:
|
|
83760
83808
|
* - Use inset box-shadow to ensure borders appear above table cell backgrounds
|
|
83761
83809
|
* - Use ::before pseudo-elements with position: absolute for flexible positioning
|
|
83762
|
-
* - Each cell draws its own borders
|
|
83810
|
+
* - Each cell draws its own borders; `borderCollapse` (on by default) keeps only
|
|
83811
|
+
* the right and bottom ones, plus top on the first row and left on the first column
|
|
83763
83812
|
* - Selection borders override table borders using higher CSS specificity
|
|
83764
|
-
* - Sticky borders use thicker box-shadows in accent color
|
|
83813
|
+
* - Sticky borders use thicker box-shadows in an accent color
|
|
83765
83814
|
*
|
|
83766
83815
|
* TECHNICAL IMPLEMENTATION:
|
|
83767
83816
|
* - All borders use inset box-shadow with specific directional mapping:
|
|
@@ -83769,9 +83818,8 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
83769
83818
|
* * Right: inset -1px 0 0 0
|
|
83770
83819
|
* * Bottom: inset 0 -1px 0 0
|
|
83771
83820
|
* * Left: inset 1px 0 0 0
|
|
83772
|
-
* - Selection borders
|
|
83821
|
+
* - Selection borders override table borders in the same pseudo-element
|
|
83773
83822
|
* - Sticky borders replace regular borders with thicker colored variants
|
|
83774
|
-
* - Border-collapse mode available as optional feature for future use
|
|
83775
83823
|
*
|
|
83776
83824
|
* Note how border disappear for sticky elements when using border-collapse (https://bugzilla.mozilla.org/show_bug.cgi?id=1727594)
|
|
83777
83825
|
*
|
|
@@ -84905,11 +84953,18 @@ const BadgeCountOverflow = () => jsx("span", {
|
|
|
84905
84953
|
});
|
|
84906
84954
|
const MAX_CHAR_AS_CIRCLE = 3;
|
|
84907
84955
|
const MAX_FOR_CIRCLE = 99;
|
|
84956
|
+
|
|
84957
|
+
/**
|
|
84958
|
+
* @param {boolean} [circle] Drawn round rather than as a pill. A circle holds
|
|
84959
|
+
* three characters at most, so `max` defaults to 99 with it ("99+"); a `max`
|
|
84960
|
+
* above 99, or `"none"`, lets a bigger count turn the circle into a pill as
|
|
84961
|
+
* it grows.
|
|
84962
|
+
* @param {number|"none"} [max] The count above which `max` and a "+" are
|
|
84963
|
+
* written in its place. 99 under `circle`, no cap otherwise.
|
|
84964
|
+
*/
|
|
84908
84965
|
const BadgeCount = ({
|
|
84909
84966
|
children,
|
|
84910
84967
|
maxElement = jsx(BadgeCountOverflow, {}),
|
|
84911
|
-
// When you use max="none" (or max > 99) it might be a good idea to force ellipse
|
|
84912
|
-
// so that visually the interface do not suddently switch from circle to ellipse depending on the count
|
|
84913
84968
|
circle,
|
|
84914
84969
|
max = circle ? MAX_FOR_CIRCLE : Infinity,
|
|
84915
84970
|
integer,
|
|
@@ -86092,12 +86147,13 @@ const css$7 = /* css */`.navi_text_box {
|
|
|
86092
86147
|
* - Displays optional icon(s) before and/or after the text
|
|
86093
86148
|
* - Shrinks its width to fit the longest rendered text line (no trailing blank space)
|
|
86094
86149
|
* - Wraps long text by default (overflow-wrap: anywhere)
|
|
86095
|
-
* - Shows ellipsis
|
|
86150
|
+
* - Shows an ellipsis instead under `singleLine`
|
|
86096
86151
|
*
|
|
86097
86152
|
* Props:
|
|
86098
86153
|
* iconBefore — element shown to the left of the text
|
|
86099
86154
|
* iconAfter — element shown to the right of the text (stays on the same line)
|
|
86100
|
-
*
|
|
86155
|
+
* singleLine — keeps the text on one line, cut with an ellipsis (no width shrinking)
|
|
86156
|
+
* maxHeight — CSS max-height string; content past it is clipped
|
|
86101
86157
|
* children — the text content
|
|
86102
86158
|
*/
|
|
86103
86159
|
const TextBox = ({
|
|
@@ -86276,6 +86332,41 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
86276
86332
|
stroke-opacity: 1 !important;
|
|
86277
86333
|
}
|
|
86278
86334
|
`;
|
|
86335
|
+
|
|
86336
|
+
/**
|
|
86337
|
+
* SVGMaskOverlay
|
|
86338
|
+
*
|
|
86339
|
+
* Creates composite SVGs by combining independent SVG elements with masking.
|
|
86340
|
+
*
|
|
86341
|
+
* This component solves the challenge of combining independently created SVGs into
|
|
86342
|
+
* a single visual composition. Each SVG can have its own coordinate system, viewBox,
|
|
86343
|
+
* and styling, allowing for maximum reusability of individual icons or graphics.
|
|
86344
|
+
*
|
|
86345
|
+
* When overlaying SVGs, each subsequent overlay "cuts out" its portion from the base SVG,
|
|
86346
|
+
* creating a seamless integration where SVGs appear to interact with each other visually.
|
|
86347
|
+
*
|
|
86348
|
+
* Key benefits:
|
|
86349
|
+
* - Maintains each SVG's independence - use them individually elsewhere
|
|
86350
|
+
* - Handles different viewBox dimensions automatically
|
|
86351
|
+
* - Works with any SVG components regardless of internal implementation
|
|
86352
|
+
* - Supports unlimited overlay elements
|
|
86353
|
+
* - Creates proper masking between elements for visual integration
|
|
86354
|
+
*
|
|
86355
|
+
* Usage example combining two independent icon components:
|
|
86356
|
+
* ```jsx
|
|
86357
|
+
* <SVGMaskOverlay viewBox="0 0 24 24">
|
|
86358
|
+
* <DatabaseSvg />
|
|
86359
|
+
* <svg x="12" y="12" width="16" height="16" overflow="visible">
|
|
86360
|
+
* <PlusSvg />
|
|
86361
|
+
* </svg>
|
|
86362
|
+
* </SVGMaskOverlay>
|
|
86363
|
+
* ```
|
|
86364
|
+
*
|
|
86365
|
+
* @param {Object} props - Component properties
|
|
86366
|
+
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
86367
|
+
* @param {import("ignore:preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
86368
|
+
* @returns {import("ignore:preact").VNode} A composed SVG with all elements properly masked
|
|
86369
|
+
*/
|
|
86279
86370
|
const SVGMaskOverlay = ({
|
|
86280
86371
|
viewBox,
|
|
86281
86372
|
children
|
|
@@ -86288,7 +86379,7 @@ const SVGMaskOverlay = ({
|
|
|
86288
86379
|
return children[0];
|
|
86289
86380
|
}
|
|
86290
86381
|
if (!viewBox) {
|
|
86291
|
-
console.error("
|
|
86382
|
+
console.error("SVGMaskOverlay requires an explicit viewBox");
|
|
86292
86383
|
return null;
|
|
86293
86384
|
}
|
|
86294
86385
|
|
|
@@ -87229,7 +87320,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
87229
87320
|
* interaction only makes sense paired with not letting focus silently
|
|
87230
87321
|
* leave the panel first. A box of the page whose press must not close the
|
|
87231
87322
|
* panel (a card that fills it) names the panel:
|
|
87232
|
-
* `data-navi-popup-inside={id}
|
|
87323
|
+
* `data-navi-popup-inside={id}`, read when the panel has no wall
|
|
87324
|
+
* (`backdrop={false}`) — see docs/popup_backdrop.md.
|
|
87233
87325
|
* @param {boolean} [props.closeByDrag=true] - Pushing the panel back
|
|
87234
87326
|
* towards the edge it is docked to closes it: the panel follows the
|
|
87235
87327
|
* pointer and finishes leaving (or comes back to rest) when it is
|
|
@@ -87244,8 +87336,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
87244
87336
|
* doc).
|
|
87245
87337
|
* @param {import("ignore:preact").ComponentChildren} props.children - No built-in
|
|
87246
87338
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
87247
|
-
* plain `<Button command="--navi-close">`
|
|
87248
|
-
* `
|
|
87339
|
+
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
87340
|
+
* `closeByPressOutside`/Escape instead.
|
|
87249
87341
|
* A form sent inside the panel closes it, as in any popup: a panel one
|
|
87250
87342
|
* keeps editing in says `command="--navi-void"` on that form (see
|
|
87251
87343
|
* docs/form_changed.md).
|
|
@@ -87336,9 +87428,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
87336
87428
|
* Stuck to the top of the panel's own scrollable area (`position: sticky`)
|
|
87337
87429
|
* regardless of `side` — only the panel's content in between scrolls. No
|
|
87338
87430
|
* built-in padding or close button — add a `<Button command="--navi-close">`
|
|
87339
|
-
*
|
|
87340
|
-
* float-right utility this file's own CSS still provides) wherever it makes
|
|
87341
|
-
* sense for the layout.
|
|
87431
|
+
* wherever it makes sense for the layout.
|
|
87342
87432
|
*
|
|
87343
87433
|
* @param {object} props
|
|
87344
87434
|
* @param {string} [props.className] - Merged with the shared
|