@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/dev/jsenv_navi.js
CHANGED
|
@@ -559,7 +559,9 @@ effect(() => {
|
|
|
559
559
|
* Built-in key namespaces, all overridable — the registrations below are the
|
|
560
560
|
* exhaustive list, read them to find the exact key to override:
|
|
561
561
|
* - `"button.*"` — Clear, Reset, Send, Open, Close, Cancel, Confirm…
|
|
562
|
-
* - `"
|
|
562
|
+
* - `"confirm.*"` — the question a confirmation asks by default
|
|
563
|
+
* - `"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
|
|
564
|
+
* - `"time_range.*"` — how the two ends of a span are named ("From", "to")
|
|
563
565
|
* - `"spin.*"` — the ends of a steppable range
|
|
564
566
|
* - `"list.*"` — empty/no-match/failed-rows messages
|
|
565
567
|
* - `"badge_list.*"` — the "+[count] more" overflow badge
|
|
@@ -1572,11 +1574,10 @@ const errorIsDisplayed = (error) => {
|
|
|
1572
1574
|
*
|
|
1573
1575
|
* Whatever the reader does with it is already covered without any deadline: it
|
|
1574
1576
|
* displays it (and marks it), or it throws it, and a thrown error either finds a
|
|
1575
|
-
* boundary that displays it or reaches window on its own —
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1578
|
-
*
|
|
1579
|
-
* happened.
|
|
1577
|
+
* boundary that displays it or reaches window on its own — preact re-throws
|
|
1578
|
+
* what no boundary handled. Reporting it here as well would be a second voice
|
|
1579
|
+
* saying the same thing, always the wrong one, since this module cannot see
|
|
1580
|
+
* which of those happened.
|
|
1580
1581
|
*/
|
|
1581
1582
|
const errorTakenByRenderSet = new WeakSet();
|
|
1582
1583
|
const markErrorAsTakenByRender = (error) => {
|
|
@@ -1666,7 +1667,7 @@ const errorIsAccountedFor = (error) => {
|
|
|
1666
1667
|
/**
|
|
1667
1668
|
* Rethrown rather than logged: an error nobody took is an unhandled error, and
|
|
1668
1669
|
* the runtime already knows what to do with those (window "error" event, jsenv
|
|
1669
|
-
* overlay in dev). Same
|
|
1670
|
+
* overlay in dev). Same answer preact gives an error no boundary handled.
|
|
1670
1671
|
*/
|
|
1671
1672
|
const errorReportedSet = new WeakSet();
|
|
1672
1673
|
const reportErrorIfNobodyDisplaysIt = (error, { action } = {}) => {
|
|
@@ -2187,7 +2188,7 @@ const compareTwoJsValues = (
|
|
|
2187
2188
|
}
|
|
2188
2189
|
return true;
|
|
2189
2190
|
}
|
|
2190
|
-
// Ordered array comparison
|
|
2191
|
+
// Ordered array comparison
|
|
2191
2192
|
let i = 0;
|
|
2192
2193
|
while (i < a.length) {
|
|
2193
2194
|
const aValue = a[i];
|
|
@@ -3370,6 +3371,8 @@ const actionWeakMap = new WeakMap();
|
|
|
3370
3371
|
* long is this still true?" belongs to the question: a court list is good for
|
|
3371
3372
|
* the session, a count of people watching grows on its own and is worth a few
|
|
3372
3373
|
* minutes, a payment status is worth nothing at all.
|
|
3374
|
+
* @param {{ verb?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" }} [rootOptions.meta] -
|
|
3375
|
+
* `verb` puts the action under the network policy (docs/network_policy.md).
|
|
3373
3376
|
*/
|
|
3374
3377
|
const createAction = (callback, rootOptions = {}) => {
|
|
3375
3378
|
const existing = actionWeakMap.get(callback);
|
|
@@ -4484,7 +4487,7 @@ const runUnwatched = (startRun) => {
|
|
|
4484
4487
|
/**
|
|
4485
4488
|
* Reactively runs an action whenever the params derived from signals change.
|
|
4486
4489
|
*
|
|
4487
|
-
* @param {object} action - The action to run.
|
|
4490
|
+
* @param {object | Function} action - The action to run, or a function made into one.
|
|
4488
4491
|
* @param {Function} deriveActionParamsFromSignals - A function that reads signals and returns
|
|
4489
4492
|
* the params to pass to the action. It is re-evaluated automatically whenever a signal it
|
|
4490
4493
|
* read changes. Return `false`/`null`/`undefined` to skip running the action.
|
|
@@ -4507,6 +4510,8 @@ const runUnwatched = (startRun) => {
|
|
|
4507
4510
|
* ```
|
|
4508
4511
|
* The action will not fire while the user is actively changing filters; it fires once
|
|
4509
4512
|
* they pause for half a second.
|
|
4513
|
+
* @returns {object} the instance bound to the derived params: the one to read
|
|
4514
|
+
* (`useAsyncData`), since the answer lands there and never on `action` itself.
|
|
4510
4515
|
*/
|
|
4511
4516
|
// The run is not awaited here, and a rejection nobody waits for is an unhandled
|
|
4512
4517
|
// one — in dev, an error overlay thrown over a page that is already saying what
|
|
@@ -5021,11 +5026,12 @@ const isNumberInput = (type, naviInputType) => {
|
|
|
5021
5026
|
*/
|
|
5022
5027
|
/**
|
|
5023
5028
|
* A bound signal that carries a default of its own says the same thing on every
|
|
5024
|
-
* control: the control starts there and
|
|
5025
|
-
*
|
|
5026
|
-
*
|
|
5027
|
-
*
|
|
5028
|
-
*
|
|
5029
|
+
* control: the control starts there, uncontrolled, and a reset goes back to it.
|
|
5030
|
+
* Uncontrolled here is about where the control starts, not about whether it
|
|
5031
|
+
* follows the signal — the binding stays two-way either way (see
|
|
5032
|
+
* stateFromSignal in control_hooks.jsx) — nor about what a form counts as
|
|
5033
|
+
* held: that is the signal's content (isUIStateHeld in held_ui_state.js), and a
|
|
5034
|
+
* stateSignal holds its default. Written once and used by everything
|
|
5029
5035
|
* that takes a `signal`, so one signal cannot mean two different things
|
|
5030
5036
|
* depending on which control it was handed to.
|
|
5031
5037
|
*/
|
|
@@ -5085,8 +5091,7 @@ const resolveInputProps = (props, { controlType = "input" } = {}) => {
|
|
|
5085
5091
|
// If no explicit defaultChecked, derive it from the signal's default
|
|
5086
5092
|
// value so that resetUIState restores to the original default.
|
|
5087
5093
|
// Only a stateSignal carries a default of its own; a plain signal has
|
|
5088
|
-
// no `options` at all,
|
|
5089
|
-
// the same optional read every other branch here already does.
|
|
5094
|
+
// no `options` at all, hence the optional read.
|
|
5090
5095
|
const defaultVal = signalOptions?.getDefaultValue(false);
|
|
5091
5096
|
if (defaultVal === undefined) ; else if (props.type === "radio") {
|
|
5092
5097
|
if (defaultVal === true) {
|
|
@@ -9169,8 +9174,8 @@ const isLanguageSupported = (lang, supportedLanguages) => {
|
|
|
9169
9174
|
* supported), falls back to `supportedLanguagesSignal` itself so callers
|
|
9170
9175
|
* still get *something* usable rather than an empty array.
|
|
9171
9176
|
*
|
|
9172
|
-
* Consumers that accept either a single lang or an ordered array
|
|
9173
|
-
*
|
|
9177
|
+
* Consumers that accept either a single lang or an ordered array
|
|
9178
|
+
* (@jsenv/humanize's `createI18n` and formatters, and native `Intl.NumberFormat`/
|
|
9174
9179
|
* `Intl.DateTimeFormat`) can pass this straight through: anything not
|
|
9175
9180
|
* covered by the first entry falls through to the next, rather than
|
|
9176
9181
|
* jumping straight to an unrelated default like "en".
|
|
@@ -10153,15 +10158,15 @@ const readOnlyMessage = (field) => {
|
|
|
10153
10158
|
* - Action dispatch + validity: `control_action.js` / `dispatchRequestAction`
|
|
10154
10159
|
* - Validity checking: `control_validation.js`
|
|
10155
10160
|
*
|
|
10156
|
-
* Each UI state controller gets its own `
|
|
10157
|
-
* `createControlInteraction`)
|
|
10161
|
+
* Each UI state controller gets its own instance, `controller.rules.interaction`
|
|
10162
|
+
* (created by `createControlInteraction`), beside `controller.rules.validation`.
|
|
10158
10163
|
*
|
|
10159
10164
|
* Flow:
|
|
10160
10165
|
* user interaction
|
|
10161
10166
|
* → dispatchRequestInteraction
|
|
10162
10167
|
* → "navi_request_interaction" event
|
|
10163
10168
|
* → onRequestInteraction
|
|
10164
|
-
* → check disabled / read-only / busy (via controller.
|
|
10169
|
+
* → check disabled / read-only / busy (via controller.rules.interaction)
|
|
10165
10170
|
* against the interaction's `intent` ("write" by default, "read" for one
|
|
10166
10171
|
* that only shows what is already there — see READONLY_CONSTRAINT)
|
|
10167
10172
|
* → if blocked → prevented()
|
|
@@ -11004,7 +11009,8 @@ const interactionsDisputeThePress = (interactions) => {
|
|
|
11004
11009
|
* control (a Button) is its own; a Box inside one, or wrapping exactly one,
|
|
11005
11010
|
* reaches it; a Box with no control anywhere near it — or laying out several,
|
|
11006
11011
|
* which belongs to none of them — still answers with a callback of the caller's,
|
|
11007
|
-
* and only "request_action"
|
|
11012
|
+
* and only the two requests ("request_action", "request_ui_action") have
|
|
11013
|
+
* nothing to ask.
|
|
11008
11014
|
*
|
|
11009
11015
|
* Set up once per element rather than on every render, which is what lets a
|
|
11010
11016
|
* detector be a plain `setup`/teardown pair. So the interactions themselves are
|
|
@@ -11173,7 +11179,7 @@ const useInteractionsEffect = (ref, interactionsRef) => {
|
|
|
11173
11179
|
cancelable: true,
|
|
11174
11180
|
});
|
|
11175
11181
|
// The event the interaction was read from stays reachable from it:
|
|
11176
|
-
// `findEvent(actionEvent, "pointerdown")` still finds the press a
|
|
11182
|
+
// `findEvent(actionEvent, "pointerdown")` still finds the press a hold
|
|
11177
11183
|
// was made of.
|
|
11178
11184
|
chainEvent(interactionEvent, originalEvent);
|
|
11179
11185
|
if (!element.dispatchEvent(interactionEvent)) {
|
|
@@ -11939,7 +11945,7 @@ const swipeTypeOf = (axis, pulled) => {
|
|
|
11939
11945
|
* …
|
|
11940
11946
|
* }}}
|
|
11941
11947
|
*
|
|
11942
|
-
* `toId`
|
|
11948
|
+
* `toId` names an element and is never null: a copy over nothing is a release that meant
|
|
11943
11949
|
* nothing, and the interaction does not happen at all — unless the element
|
|
11944
11950
|
* declares `leave` (see below), which gives that release a meaning of its own.
|
|
11945
11951
|
*
|
|
@@ -16403,7 +16409,7 @@ const debug$3 = (...args) => {
|
|
|
16403
16409
|
* @param {string} name - resource name, used in action names and error messages
|
|
16404
16410
|
* @param {Object} restCallbacks - `{ idKey, uniqueKeys, rerunOn, dependencies, GET, GET_MANY, GET_RANGE, POST, POST_MANY, PUT, PUT_MANY, PATCH, PATCH_MANY, DELETE, DELETE_MANY }`
|
|
16405
16411
|
* @param {string} [restCallbacks.idKey] - primary key property, defaults to `"id"`
|
|
16406
|
-
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
16412
|
+
* @param {string[]} [restCallbacks.uniqueKeys] - alternate keys the store can find an item by (e.g. `"username"`)
|
|
16407
16413
|
* @see docs/resource.md — relationships, callback return contracts, decision table
|
|
16408
16414
|
*
|
|
16409
16415
|
* @example
|
|
@@ -16630,7 +16636,7 @@ const createResource = (
|
|
|
16630
16636
|
* identical parameters, preventing cross-contamination between different parameter sets.
|
|
16631
16637
|
*
|
|
16632
16638
|
* @param {Object} params - Parameters to bind to all actions of this resource (required)
|
|
16633
|
-
* @param {Object} options -
|
|
16639
|
+
* @param {{ rerunOn?: Object, dependencies?: Object[] }} [options] - reruns of that scope; left out, the scope inherits the resource's
|
|
16634
16640
|
* @returns {Object} A new resource instance with parameter-bound actions and isolated lifecycle
|
|
16635
16641
|
* @see docs/resource.md — what a scope isolates, and `dependencies`
|
|
16636
16642
|
*
|
|
@@ -16648,7 +16654,7 @@ const createResource = (
|
|
|
16648
16654
|
* const ROLE_WITH_OWNERSHIP = role.withParams({ owners: true }, {
|
|
16649
16655
|
* dependencies: [role, database, tables],
|
|
16650
16656
|
* });
|
|
16651
|
-
* // ROLE_WITH_OWNERSHIP.GET_MANY
|
|
16657
|
+
* // ROLE_WITH_OWNERSHIP.GET_MANY reruns after any write (POST/PUT/PATCH/DELETE) on a role, a database or a table
|
|
16652
16658
|
*/
|
|
16653
16659
|
const withParams = (
|
|
16654
16660
|
paramsToInject,
|
|
@@ -16694,13 +16700,14 @@ const createResource = (
|
|
|
16694
16700
|
* Callback return contracts:
|
|
16695
16701
|
* - GET / PUT → the parent object with the relationship nested inside:
|
|
16696
16702
|
* `async ({ id }) => ({ id, session: { id: 10, token: "abc" } })`; `null` for no relationship
|
|
16697
|
-
* - DELETE → the parent id
|
|
16703
|
+
* - DELETE → the parent id; the property is set to `null`
|
|
16698
16704
|
*
|
|
16699
16705
|
* The backend may also embed the child inline in a parent GET/POST response — the
|
|
16700
16706
|
* setter on the property upserts the nested object into the child store.
|
|
16701
16707
|
*
|
|
16702
|
-
* Returns the
|
|
16703
|
-
* `
|
|
16708
|
+
* Returns the relationship resource: its actions (`USER_SESSION.GET`, `.PUT`,
|
|
16709
|
+
* `.DELETE`) write the parent's property. A relation of the child itself is
|
|
16710
|
+
* declared on the child resource (`SESSION.one("device", DEVICE)`).
|
|
16704
16711
|
*
|
|
16705
16712
|
* @param {string} propertyName - property holding the child on each parent item
|
|
16706
16713
|
* @param {Object} childResource - the independent resource created by `resource()`
|
|
@@ -17861,7 +17868,7 @@ Received an object with keys: ${keys.join(", ")}.`,
|
|
|
17861
17868
|
* });
|
|
17862
17869
|
*
|
|
17863
17870
|
* syncResourceToSignals(USER, { username: usernameSignal });
|
|
17864
|
-
* //
|
|
17871
|
+
* // When a user item's username is updated via USER.PUT,
|
|
17865
17872
|
* // usernameSignal.value is set to the new username,
|
|
17866
17873
|
* // which in turn triggers the route Signal->URL sync and updates the browser URL.
|
|
17867
17874
|
*/
|
|
@@ -17874,6 +17881,17 @@ const syncResourceToSignals = (resource, propertyToSignalMap) => {
|
|
|
17874
17881
|
syncStoreToSignals(resource.store, propertyToSignalMap);
|
|
17875
17882
|
};
|
|
17876
17883
|
|
|
17884
|
+
/**
|
|
17885
|
+
* The same, for a `scopedOne`/`scopedMany` resource: its items live in one store
|
|
17886
|
+
* per owner, so the store to watch is the one of the owner `ownerSignal` names
|
|
17887
|
+
* (by id or by any unique key), and it is switched when the signal changes.
|
|
17888
|
+
* Nothing is synced while `ownerSignal` holds `null`/`undefined` or names an
|
|
17889
|
+
* owner nothing has been loaded for yet.
|
|
17890
|
+
*
|
|
17891
|
+
* @param {Object} resource - a resource made by `.scopedOne()` / `.scopedMany()`
|
|
17892
|
+
* @param {import("@preact/signals").Signal} ownerSignal - the owner whose children are watched
|
|
17893
|
+
* @param {Object} propertyToSignalMap - `{ [propertyName]: signal }`, as for `syncResourceToSignals`
|
|
17894
|
+
*/
|
|
17877
17895
|
const syncOwnedResourceToSignals = (
|
|
17878
17896
|
resource,
|
|
17879
17897
|
ownerSignal,
|
|
@@ -17921,7 +17939,7 @@ const generateSignalId = () => {
|
|
|
17921
17939
|
* 1. Initially takes value from the default signal
|
|
17922
17940
|
* 2. When explicitly set (programmatically or via localStorage), the explicit value takes precedence
|
|
17923
17941
|
* 3. When default signal changes, it only updates if no explicit value was ever set
|
|
17924
|
-
* 4.
|
|
17942
|
+
* 4. Setting it to undefined makes the signal use the dynamic default again
|
|
17925
17943
|
* 5. If dynamic default is undefined and options.default is provided, uses the static fallback
|
|
17926
17944
|
*
|
|
17927
17945
|
* This is useful for:
|
|
@@ -17935,7 +17953,9 @@ const generateSignalId = () => {
|
|
|
17935
17953
|
* @param {string|number} [options.id] - Custom ID for the signal. If not provided, an auto-generated ID will be used. Used for localStorage key and route pattern detection.
|
|
17936
17954
|
* @param {any} [options.default] - Static fallback value used when defaultValue is a signal and that signal's value is undefined
|
|
17937
17955
|
* @param {boolean} [options.persists=false] - Whether to persist the signal value in localStorage using the signal ID as key
|
|
17938
|
-
* @param {
|
|
17956
|
+
* @param {string} [options.type] - Type the value is coerced to, and read back as from localStorage and the url:
|
|
17957
|
+
* any @jsenv/validity type (`"string"`, `"number"`, `"boolean"`, `"array"`, `"object"`, `"integer"`, `"date"`,
|
|
17958
|
+
* `"time"`…). Without it the value is not coerced.
|
|
17939
17959
|
* @param {"string" | "number" | "boolean"} [options.itemType] - For array type: type of the array items.
|
|
17940
17960
|
* Used when reading the value back from a url search param, where everything is a string:
|
|
17941
17961
|
* `?level=3,4` becomes `[3, 4]` instead of `["3", "4"]`. Without it items stay strings.
|
|
@@ -19105,14 +19125,12 @@ const LoadingFallback = ({
|
|
|
19105
19125
|
* Two things it gets right that a hand-written boundary rarely does, both
|
|
19106
19126
|
* explained in docs/error_handling.md:
|
|
19107
19127
|
*
|
|
19108
|
-
* - It marks the error as displayed ONLY when it actually displays it.
|
|
19109
|
-
*
|
|
19110
|
-
*
|
|
19111
|
-
*
|
|
19112
|
-
*
|
|
19113
|
-
*
|
|
19114
|
-
* silent one. Without a `fallback` there is nothing to display, so the error is
|
|
19115
|
-
* left alone and continues up.
|
|
19128
|
+
* - It marks the error as displayed ONLY when it actually displays it. The
|
|
19129
|
+
* jsenv overlay skips an error carrying `__handled_by__`, so a mark set before
|
|
19130
|
+
* knowing whether anything is rendered mutes an error that then continues up:
|
|
19131
|
+
* a TypeError in a component becomes a blank page AND a silent one. Without a
|
|
19132
|
+
* `fallback` there is nothing to display, so the error is left alone and
|
|
19133
|
+
* continues up.
|
|
19116
19134
|
*
|
|
19117
19135
|
* - It resets on navigation, not only on rerun. Rerunning the failed action is
|
|
19118
19136
|
* one way out; going somewhere else is the common one. Without a reset on the
|
|
@@ -22222,9 +22240,48 @@ const PSEUDO_STATE_CHILD_PROP_SET = new Set(["tabIndex", "tabindex"]);
|
|
|
22222
22240
|
* separator?: import("ignore:preact").ComponentChildren | ((index: number) => import("ignore:preact").ComponentChildren),
|
|
22223
22241
|
* selfInteractions?: string,
|
|
22224
22242
|
* interactions?: { [type: string]: "request_action" | "request_ui_action" | "refuse" | ((event: Event) => void) | false | null | undefined },
|
|
22243
|
+
* alignX?: string,
|
|
22244
|
+
* alignY?: string,
|
|
22245
|
+
* spacing?: string | number,
|
|
22246
|
+
* overflow?: string,
|
|
22247
|
+
* header?: boolean,
|
|
22248
|
+
* footer?: boolean,
|
|
22249
|
+
* body?: boolean,
|
|
22250
|
+
* sticky?: boolean,
|
|
22225
22251
|
* children?: import("ignore:preact").ComponentChildren,
|
|
22226
22252
|
* [key: string]: any,
|
|
22227
22253
|
* }>}
|
|
22254
|
+
* @param {"x"|"y"|boolean} [flex] A flex container: `"x"` (or `true`) side by
|
|
22255
|
+
* side, `"y"` stacked. Named by axis because `flex-direction: column` stacks
|
|
22256
|
+
* items vertically, the opposite of what "column" suggests.
|
|
22257
|
+
* @param {boolean} [inline] The inline form of the box's display
|
|
22258
|
+
* (`inline-flex`, `inline-grid`…). An inline box given a `width`/`height`
|
|
22259
|
+
* becomes an inline flex row, since an inline box ignores both.
|
|
22260
|
+
* @param {string} [alignX] Horizontal alignment of the content whatever the
|
|
22261
|
+
* flex direction: `justify-content` or `align-items` is picked from it
|
|
22262
|
+
* (`text-align` outside flex and grid).
|
|
22263
|
+
* @param {string} [alignY] Vertical alignment, the same way.
|
|
22264
|
+
* @param {string|number} [spacing] The gap between children in a flex or grid
|
|
22265
|
+
* box (`spacingX`/`spacingY` per axis) — there is no `gap` prop. Spacing
|
|
22266
|
+
* props (`spacing`, `margin*`, `padding*`) take the scale `"xxs"`…`"xxl"`
|
|
22267
|
+
* (`--navi-xxs`…), `"-s"` for a negative, or any CSS value.
|
|
22268
|
+
* @param {object|string} [style] CSS properties, plus pseudo keys holding
|
|
22269
|
+
* their own: `style={{ color: "blue", ":hover": { color: "red" } }}`
|
|
22270
|
+
* (`":-navi-pressed"`, `"::before"`…). A pseudo class used there is tracked
|
|
22271
|
+
* on the element; nothing re-renders when it changes.
|
|
22272
|
+
* @param {string} [overflow] `"auto"`/`"scroll"` (`overflowX`/`overflowY`
|
|
22273
|
+
* too) makes the box a scrolling area, which is what gives `header`/`body`/
|
|
22274
|
+
* `footer` their meaning; such a box is also `isolation: isolate` and gets a
|
|
22275
|
+
* `scroll-padding` (see docs/scroll.md).
|
|
22276
|
+
* @param {boolean} [header] A part that stays put at the top of the scrolling
|
|
22277
|
+
* box it is a direct child of — nothing at all without that box's overflow.
|
|
22278
|
+
* Rendered as `<header>` unless `as` says otherwise.
|
|
22279
|
+
* @param {boolean} [footer] The same at the bottom, rendered as `<footer>`.
|
|
22280
|
+
* @param {boolean} [body] The only part of its scrolling box that scrolls,
|
|
22281
|
+
* `header`/`footer` sitting outside it. It shrinks, it never grows.
|
|
22282
|
+
* @param {boolean} [sticky] `position: sticky` in the sticky z-index band
|
|
22283
|
+
* (`--navi-z-index-sticky`), stuck or not; an explicit `zIndex`, `"auto"`
|
|
22284
|
+
* included, wins (see docs/z_index.md). `top`/`bottom`/… take `true` for 0.
|
|
22228
22285
|
* @param {string} [background] Any CSS background, plus two keywords for the
|
|
22229
22286
|
* planes an app paints: `"surface"` is the paper content sits on,
|
|
22230
22287
|
* `"chrome"` the frame around a screen — a top bar, a side menu, a toolbar.
|
|
@@ -23968,21 +24025,16 @@ const createUITransitionController = (
|
|
|
23968
24025
|
/**
|
|
23969
24026
|
* UITransition
|
|
23970
24027
|
*
|
|
23971
|
-
* A Preact component that
|
|
23972
|
-
*
|
|
24028
|
+
* A Preact component that animates the change between its children: the
|
|
24029
|
+
* outgoing content fades out while the incoming one fades in, and the box
|
|
24030
|
+
* resizes from the one to the other (the mechanics are in ui_transition.js).
|
|
23973
24031
|
*
|
|
23974
|
-
*
|
|
23975
|
-
* -
|
|
23976
|
-
*
|
|
23977
|
-
*
|
|
23978
|
-
* -
|
|
23979
|
-
*
|
|
23980
|
-
*
|
|
23981
|
-
* Usage:
|
|
23982
|
-
* - Wrap dynamic content in <UITransition> to animate between states
|
|
23983
|
-
* - Set a unique `data-content-id` on your rendered content to identify each content variant
|
|
23984
|
-
* - Use `data-content-phase` to mark loading/error states for phase transitions
|
|
23985
|
-
* - Configure transition types and durations for both content and phase changes
|
|
24032
|
+
* Two kinds of change are told apart, from attributes on the rendered content:
|
|
24033
|
+
* - a content transition, when the content key changes — `data-content-key` on
|
|
24034
|
+
* the rendered element (a user id, a search), or `useUITransitionContentId`
|
|
24035
|
+
* called from a component inside (what `<Route>` does with its url pattern);
|
|
24036
|
+
* - a phase transition, when `data-content-phase` (a loading or error state)
|
|
24037
|
+
* changes for the same content key.
|
|
23986
24038
|
*
|
|
23987
24039
|
* Example:
|
|
23988
24040
|
*
|
|
@@ -23991,9 +24043,6 @@ const createUITransitionController = (
|
|
|
23991
24043
|
* ? <Spinner data-content-key={userId} data-content-phase />
|
|
23992
24044
|
* : <UserProfile user={user} data-content-key={userId} />}
|
|
23993
24045
|
* </UITransition>
|
|
23994
|
-
*
|
|
23995
|
-
* When `data-content-id` changes, UITransition animates content transitions.
|
|
23996
|
-
* When `data-content-phase` changes for the same key, it animates phase transitions.
|
|
23997
24046
|
*/
|
|
23998
24047
|
|
|
23999
24048
|
const UITransitionContentIdContext = createContext();
|
|
@@ -24115,16 +24164,12 @@ const UITransition = ({
|
|
|
24115
24164
|
};
|
|
24116
24165
|
|
|
24117
24166
|
/**
|
|
24118
|
-
*
|
|
24119
|
-
*
|
|
24120
|
-
*
|
|
24121
|
-
* When the key changes, UITransition will be able to detect that and consider the content
|
|
24122
|
-
* as changed even if the component is still the same
|
|
24123
|
-
*
|
|
24124
|
-
* This is used by <Route> to set the content key to the route path
|
|
24125
|
-
* When the route becomes inactive it will call useUITransitionContentId(undefined)
|
|
24126
|
-
* And if a sibling route becones active it will call useUITransitionContentId with its own path
|
|
24167
|
+
* Names the content a component renders, for the <UITransition> around it:
|
|
24168
|
+
* everything rendered inside is identified by that key, and the key changing
|
|
24169
|
+
* is read as a content change even though the component is the same.
|
|
24127
24170
|
*
|
|
24171
|
+
* <Route> calls it with the url pattern of the branch it renders; the branch
|
|
24172
|
+
* unmounting removes its part, and a sibling branch mounting adds its own.
|
|
24128
24173
|
*/
|
|
24129
24174
|
const useUITransitionContentId = value => {
|
|
24130
24175
|
const contentId = useContext(UITransitionContentIdContext);
|
|
@@ -24149,8 +24194,8 @@ const useUITransitionContentId = value => {
|
|
|
24149
24194
|
};
|
|
24150
24195
|
|
|
24151
24196
|
/**
|
|
24152
|
-
* Custom route pattern matching system
|
|
24153
|
-
*
|
|
24197
|
+
* Custom route pattern matching system.
|
|
24198
|
+
* Not built on URLPattern: matching here is simpler and more predictable.
|
|
24154
24199
|
*/
|
|
24155
24200
|
|
|
24156
24201
|
|
|
@@ -27482,7 +27527,7 @@ const useRouteStatus = (route) => {
|
|
|
27482
27527
|
const assertRoute = (route) => {
|
|
27483
27528
|
if (!route.isRoute) {
|
|
27484
27529
|
throw new Error(
|
|
27485
|
-
"The route prop must be a route object created with
|
|
27530
|
+
"The route prop must be a route object created with route()",
|
|
27486
27531
|
);
|
|
27487
27532
|
}
|
|
27488
27533
|
if (!setupRoutesCalled) {
|
|
@@ -31453,8 +31498,9 @@ const RouteTransitionArea = ({
|
|
|
31453
31498
|
* Declare how a pair of routes moves against each other — or how one route is
|
|
31454
31499
|
* entered, from wherever its door happens to be.
|
|
31455
31500
|
*
|
|
31456
|
-
* @param {object|null} from - a route
|
|
31457
|
-
* a param of a route rather than a route
|
|
31501
|
+
* @param {object|null} from - a route (or the `routeFallback()` page), or
|
|
31502
|
+
* `{ route, params }` when the page is a param of a route rather than a route
|
|
31503
|
+
* of its own. `null` says the page is
|
|
31458
31504
|
* reached FROM ANYWHERE: its door is in the furniture (a gear in the top
|
|
31459
31505
|
* bar, a "+" in the tab bar) rather than on a screen, so there is no pair to
|
|
31460
31506
|
* write it on. Arriving at `to` from any page then plays forward and leaving
|
|
@@ -31502,8 +31548,7 @@ const RouteTransitionArea = ({
|
|
|
31502
31548
|
* Whatever is written here is what EVERY crossing of the pair plays. One
|
|
31503
31549
|
* crossing can ask for something else — `<Link routeTransition>`, or
|
|
31504
31550
|
* navTo(url, { routeTransition }) — which overrides this field by field, for
|
|
31505
|
-
* that
|
|
31506
|
-
* navigation alone.
|
|
31551
|
+
* that navigation alone.
|
|
31507
31552
|
* @returns {() => void} remove this relation.
|
|
31508
31553
|
*/
|
|
31509
31554
|
const defineRouteTransition = (from, to, transition) => {
|
|
@@ -31586,9 +31631,8 @@ const normalizeTransition = transition => {
|
|
|
31586
31631
|
*
|
|
31587
31632
|
* Two mouths, one meaning: the element being pressed wears it (a `<Link
|
|
31588
31633
|
* routeTransition>`, or the attribute by hand on any anchor), or navTo() is
|
|
31589
|
-
* handed
|
|
31590
|
-
* it
|
|
31591
|
-
* writes anything (see before_routing.js).
|
|
31634
|
+
* handed it. Both arrive here through the announcement the navigation makes
|
|
31635
|
+
* before it writes anything (see before_routing.js).
|
|
31592
31636
|
*
|
|
31593
31637
|
* A request answers FIELD BY FIELD: what it does not say, the relation — or
|
|
31594
31638
|
* the default — still answers for. So `{ direction: "back" }` keeps the pair's
|
|
@@ -32835,10 +32879,10 @@ const css$11 = /* css */`.navi_route_travel {
|
|
|
32835
32879
|
* The pages are cut at the edge of this box while they travel, and at the app's
|
|
32836
32880
|
* safe area the box runs under, which is written on the transition's own
|
|
32837
32881
|
* pseudo-elements — no overflow of the document reaches pictures drawn in the
|
|
32838
|
-
* top layer. It needs nothing of the browser beyond view
|
|
32839
|
-
*
|
|
32840
|
-
*
|
|
32841
|
-
*
|
|
32882
|
+
* top layer. It needs nothing of the browser beyond view transitions
|
|
32883
|
+
* themselves: a browser without them navigates without the movement, and the
|
|
32884
|
+
* gesture applies its change on release instead of dragging a picture that does
|
|
32885
|
+
* not exist.
|
|
32842
32886
|
*
|
|
32843
32887
|
* While a travel plays, the rest of the page is taken as a picture too — this
|
|
32844
32888
|
* box asks for `view-transition-name: root` back for that time, so an
|
|
@@ -34429,9 +34473,6 @@ const onNaviCommand = (e, { debugCommand = () => {} } = {}) => {
|
|
|
34429
34473
|
`targeting`,
|
|
34430
34474
|
commandTarget,
|
|
34431
34475
|
);
|
|
34432
|
-
// Timed once, for the wheel-in-dialog "Définir feels frozen on mobile" case;
|
|
34433
|
-
// the line is gone now that the answer is known — a command that runs in 1ms
|
|
34434
|
-
// said nothing, and it said it on every single interaction.
|
|
34435
34476
|
return implementation();
|
|
34436
34477
|
};
|
|
34437
34478
|
|
|
@@ -35076,8 +35117,8 @@ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
|
|
|
35076
35117
|
target,
|
|
35077
35118
|
implementation: () => {
|
|
35078
35119
|
// The popup reads `anchor` first and falls back to the source (see
|
|
35079
|
-
// onnavi_request_open in popover.jsx)
|
|
35080
|
-
//
|
|
35120
|
+
// onnavi_request_open in popover.jsx): without an `anchor`, the element
|
|
35121
|
+
// asking is the element opened against.
|
|
35081
35122
|
return dispatchCustomEvent(target, "navi_request_open", {
|
|
35082
35123
|
event,
|
|
35083
35124
|
source: resolveCommandProxySource(source),
|
|
@@ -37548,8 +37589,8 @@ const usePopupContentMount = (
|
|
|
37548
37589
|
* are no-ops.
|
|
37549
37590
|
*
|
|
37550
37591
|
* Behavior:
|
|
37551
|
-
* - No <dialog>/<details>/[popover] ancestor → runs like a
|
|
37552
|
-
* useLayoutEffect with the provided deps.
|
|
37592
|
+
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor → runs like a
|
|
37593
|
+
* normal useLayoutEffect with the provided deps.
|
|
37553
37594
|
* - Inside a closed/hidden ancestor → skips the initial run; instead runs
|
|
37554
37595
|
* the callback once the ancestor opens — see @jsenv/dom's own
|
|
37555
37596
|
* observeAncestorOpenState for exactly how that's detected, and why it
|
|
@@ -37563,8 +37604,9 @@ const usePopupContentMount = (
|
|
|
37563
37604
|
* reveals nothing about them, so it does not re-run them either. See
|
|
37564
37605
|
* isDisplayedDespiteClosedAncestor in @jsenv/dom.
|
|
37565
37606
|
*
|
|
37566
|
-
* The callback
|
|
37567
|
-
*
|
|
37607
|
+
* The callback is called with the element (`ref.current`), and a
|
|
37608
|
+
* `navi_displayed` CustomEvent as its second argument, with
|
|
37609
|
+
* `detail: { ancestor, ancestorType, becauseAncestorOpened }`:
|
|
37568
37610
|
* - No <dialog>/<details>/[popover]/[aria-expanded] ancestor at all →
|
|
37569
37611
|
* `{ ancestor: document, ancestorType: "document" }`.
|
|
37570
37612
|
* - Otherwise → `{ ancestor: <the matched element>, ancestorType: "dialog"
|
|
@@ -37729,6 +37771,8 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
37729
37771
|
// see also https://github.com/preactjs/preact/issues/1255
|
|
37730
37772
|
|
|
37731
37773
|
|
|
37774
|
+
const NO_ELEMENT_REF = { current: null };
|
|
37775
|
+
|
|
37732
37776
|
/**
|
|
37733
37777
|
* Programmatic autofocus that runs after Preact layout effects are flushed.
|
|
37734
37778
|
*
|
|
@@ -37781,13 +37825,12 @@ const createDisplayedEvent = (ancestor, becauseAncestorOpened) => {
|
|
|
37781
37825
|
* the browser's built-in scroll-into-view that accompanies focus.
|
|
37782
37826
|
* @param {boolean} [options.focusVisible]
|
|
37783
37827
|
* Passed as `focusVisible` to `element.focus()`.
|
|
37784
|
-
* @param {boolean} [options.
|
|
37828
|
+
* @param {boolean} [options.autoSelect]
|
|
37785
37829
|
* When true, also calls `element.select()` after focusing (useful for text inputs).
|
|
37786
|
-
* @returns {
|
|
37787
|
-
*
|
|
37830
|
+
* @returns {object} The attributes to spread on the element: `navi-autofocus`
|
|
37831
|
+
* (and `navi-autofocus-select`), with `autoFocus` set to undefined so the
|
|
37832
|
+
* native attribute never lands on the node.
|
|
37788
37833
|
*/
|
|
37789
|
-
const NO_ELEMENT_REF = { current: null };
|
|
37790
|
-
|
|
37791
37834
|
const useAutoFocus = (
|
|
37792
37835
|
focusableElementRef,
|
|
37793
37836
|
autoFocus,
|
|
@@ -37982,9 +38025,10 @@ document.body.addEventListener(
|
|
|
37982
38025
|
* and nothing about it stops someone from starting a run on every row before
|
|
37983
38026
|
* any of them comes back: a dozen requests in flight, a dozen rows waiting, and
|
|
37984
38027
|
* a server asked to do a dozen things at once because a list happened to be
|
|
37985
|
-
* long. The guard is what makes the surface say "not yet" instead — the
|
|
37986
|
-
* that
|
|
37987
|
-
* next press is possible again the moment one of the runs
|
|
38028
|
+
* long. The guard is what makes the surface say "not yet" instead — the
|
|
38029
|
+
* controls that would start another run go read-only and say what they are
|
|
38030
|
+
* waiting for, and the next press is possible again the moment one of the runs
|
|
38031
|
+
* comes back.
|
|
37988
38032
|
*
|
|
37989
38033
|
* It counts runs, not values — which is what separates it from
|
|
37990
38034
|
* `maxLengthGuard`, the one that says how many things a selection may HOLD.
|
|
@@ -38329,14 +38373,13 @@ const createControlGuard = (controller) => {
|
|
|
38329
38373
|
};
|
|
38330
38374
|
|
|
38331
38375
|
/**
|
|
38332
|
-
* Orchestrates the
|
|
38333
|
-
*
|
|
38334
|
-
* Instead of holding `controller.controlInteraction` + `controller.controlValidity`
|
|
38335
|
-
* independently, a controller now has a single `controller.rules` object:
|
|
38376
|
+
* Orchestrates the rule managers of a UI state controller, held together in
|
|
38377
|
+
* one `controller.rules` object:
|
|
38336
38378
|
*
|
|
38337
38379
|
* controller.rules.callout — shared callout display manager
|
|
38338
38380
|
* controller.rules.interaction — interactivity gate (disabled/readonly/busy)
|
|
38339
38381
|
* controller.rules.validation — value validity gate (required/pattern/etc.)
|
|
38382
|
+
* controller.rules.guard — keystroke guard (charGuard/maxLengthGuard)
|
|
38340
38383
|
*
|
|
38341
38384
|
* The callout manager is created first so both interaction and validation
|
|
38342
38385
|
* can use the same callout slot and the same lifecycle hooks.
|
|
@@ -40487,9 +40530,8 @@ const EMPTY_OBJECT = {};
|
|
|
40487
40530
|
* child changes → we update picker input → navi_ui_state_change fires →
|
|
40488
40531
|
* we see updatingRef=true → skip → no loop.
|
|
40489
40532
|
*
|
|
40490
|
-
*
|
|
40491
|
-
*
|
|
40492
|
-
* manually re-dispatch to inner controls.
|
|
40533
|
+
* So a control placed inside the picker popup needs no
|
|
40534
|
+
* `command="--navi-update"` to reach the picker input.
|
|
40493
40535
|
*/
|
|
40494
40536
|
const useUIFacadeStateController = (props, realUIStateController) => {
|
|
40495
40537
|
const firstChildControllerRef = useRef(null);
|
|
@@ -40981,9 +41023,10 @@ const uiStateHoldsNothing = (uiState) => {
|
|
|
40981
41023
|
* use `onInput` internally and route through `dispatchRequestAction` so the behavior
|
|
40982
41024
|
* is consistent regardless of input type.
|
|
40983
41025
|
*
|
|
40984
|
-
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce
|
|
40985
|
-
*
|
|
40986
|
-
* rather than each component having to manage its
|
|
41026
|
+
* 2. Any field (text, checkbox, radio, picker…) can opt into debounce with
|
|
41027
|
+
* `actionDebounce` (ms). The input effect every field installs (input_effect.js)
|
|
41028
|
+
* handles the timing centrally rather than each component having to manage its
|
|
41029
|
+
* own debounce logic.
|
|
40987
41030
|
*/
|
|
40988
41031
|
const NO_ACTION_YET = Symbol("no_action_yet");
|
|
40989
41032
|
|
|
@@ -41059,7 +41102,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
41059
41102
|
});
|
|
41060
41103
|
|
|
41061
41104
|
/**
|
|
41062
|
-
* Core hook for interactive field components (
|
|
41105
|
+
* Core hook for interactive field components (InputTextual, InputCheckbox, etc.).
|
|
41063
41106
|
*
|
|
41064
41107
|
* Sets up the full field lifecycle:
|
|
41065
41108
|
* - Creates a UI state controller that manages state divergence between props and user interactions
|
|
@@ -41070,7 +41113,7 @@ const ControlgroupChildrenWrapper = ({
|
|
|
41070
41113
|
* - Handles constraint validation and message props
|
|
41071
41114
|
*
|
|
41072
41115
|
* All state changes route through DOM events on the field element so that
|
|
41073
|
-
* external subscribers (e.g.
|
|
41116
|
+
* external subscribers (e.g. useControlUIState, Selectable) receive every update.
|
|
41074
41117
|
*
|
|
41075
41118
|
* @returns {[controlRootProps, controlHostProps, { uiStateController }]}
|
|
41076
41119
|
*/
|
|
@@ -42163,10 +42206,10 @@ const useReadOnlyUncontrolled = (props, controlInfo) => {
|
|
|
42163
42206
|
};
|
|
42164
42207
|
|
|
42165
42208
|
/**
|
|
42166
|
-
* Core hook for field group components (
|
|
42209
|
+
* Core hook for field group components (List selectable, CheckboxGroup, etc.).
|
|
42167
42210
|
* - Creates a UI group state controller that aggregates child states into one group state
|
|
42168
42211
|
* - Binds the group's action to the aggregated state signal
|
|
42169
|
-
* - Provides context to children: ParentUIStateController,
|
|
42212
|
+
* - Provides context to children: ParentUIStateController, ControlName, Disabled, ReadOnly,
|
|
42170
42213
|
* Required, Loading, Action, ActionRequester
|
|
42171
42214
|
* - Overrides `onnavi_reset_ui_state` to cascade resets to all monitored children
|
|
42172
42215
|
* by dispatching `navi_reset_ui_state` DOM events on each child's DOM element
|
|
@@ -44701,10 +44744,10 @@ const LoadingRectangleSvg = ({
|
|
|
44701
44744
|
}) => {
|
|
44702
44745
|
// The stroke is centered on the path, so half of it falls outside: drawing
|
|
44703
44746
|
// at half the stroke width puts its outer edge exactly on the box, whatever
|
|
44704
|
-
// the box.
|
|
44705
|
-
//
|
|
44706
|
-
//
|
|
44707
|
-
//
|
|
44747
|
+
// the box. Not a share of the smaller dimension (3%, floored at 2px): that
|
|
44748
|
+
// holds the line further and further inside as the box grows — a caller
|
|
44749
|
+
// placing the outline with `inset` gets the distance it asked for on a field
|
|
44750
|
+
// and something several pixels inward on a card. Nothing needs the
|
|
44708
44751
|
// extra room: the svg is `overflow: visible` and whoever positions the
|
|
44709
44752
|
// wrapper already reserves the half stroke.
|
|
44710
44753
|
const margin = strokeWidth / 2;
|
|
@@ -45776,7 +45819,7 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
45776
45819
|
* boldStable?: boolean,
|
|
45777
45820
|
* shrinkWrap?: boolean,
|
|
45778
45821
|
* capitalize?: boolean,
|
|
45779
|
-
* selectRange?: string | [number, number],
|
|
45822
|
+
* selectRange?: string | [number, number] | [string, string],
|
|
45780
45823
|
* childrenOutsideFlow?: import("ignore:preact").ComponentChildren,
|
|
45781
45824
|
* [key: string]: any,
|
|
45782
45825
|
* }>}
|
|
@@ -45844,9 +45887,10 @@ const shouldInjectSpacingBetween = (left, right) => {
|
|
|
45844
45887
|
* @param {boolean} [capitalize]
|
|
45845
45888
|
* Uppercases the first letter of the text content via CSS.
|
|
45846
45889
|
*
|
|
45847
|
-
* @param {string|[number,number]} [selectRange]
|
|
45848
|
-
* Selects a portion of the text on mount. Pass a substring to search for,
|
|
45849
|
-
*
|
|
45890
|
+
* @param {string|[number,number]|[string,string]} [selectRange]
|
|
45891
|
+
* Selects a portion of the text on mount. Pass a substring to search for, a
|
|
45892
|
+
* `[start, end]` character-offset tuple, or a `[startText, endText]` pair of
|
|
45893
|
+
* substrings selecting from the first to the end of the second.
|
|
45850
45894
|
*
|
|
45851
45895
|
* @param {import("ignore:preact").ComponentChildren} [childrenOutsideFlow]
|
|
45852
45896
|
* Rendered after children but outside the text spacing/flow logic. Used
|
|
@@ -46716,7 +46760,7 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
46716
46760
|
* @param {object} props
|
|
46717
46761
|
* @param {string} [props.href] - Destination. Also the default `value`, and
|
|
46718
46762
|
* (when `hrefFallback`) the default visible text.
|
|
46719
|
-
* @param {
|
|
46763
|
+
* @param {object} [props.route] - Renders via `route`
|
|
46720
46764
|
* instead of a raw `href`: the URL is built from the route (see
|
|
46721
46765
|
* `routeParams`) and "current" is derived from whether the route matches.
|
|
46722
46766
|
* @param {object} [props.routeParams] - Params passed to `route.buildUrl`.
|
|
@@ -46740,14 +46784,14 @@ Object.assign(PSEUDO_CLASSES, {
|
|
|
46740
46784
|
* (`navi_value`); defaults to `href`.
|
|
46741
46785
|
* @param {boolean} [props.current] - Forces the "current" state on (otherwise
|
|
46742
46786
|
* derived from the href/route).
|
|
46743
|
-
* @param {
|
|
46787
|
+
* @param {object|object[]} [props.currentExcept] -
|
|
46744
46788
|
* Route(s) inside this link's own that are NOT it: while one of them matches,
|
|
46745
46789
|
* the link is not current even though its route still is. A bar entry
|
|
46746
46790
|
* standing for a whole section, and one place under that section the entry
|
|
46747
46791
|
* does not stand for — settings reached from everywhere and shown over
|
|
46748
46792
|
* whatever the reader was on. Nothing about the routing changes: the url is
|
|
46749
46793
|
* in the section, only the link stops claiming "you are here".
|
|
46750
|
-
* @param {
|
|
46794
|
+
* @param {object|object[]} [props.currentAlso] -
|
|
46751
46795
|
* Route(s) other than this link's own that are it: the link is current while
|
|
46752
46796
|
* one of them matches. The other side of `currentExcept` — an entry standing
|
|
46753
46797
|
* for more than the one page it opens. `currentExcept` wins over it.
|
|
@@ -47660,6 +47704,7 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
47660
47704
|
* currentIndicator?: boolean|"top"|"bottom"|"left"|"right",
|
|
47661
47705
|
* currentIndicatorSlides?: boolean,
|
|
47662
47706
|
* slideContainer?: string,
|
|
47707
|
+
* panelPosition?: "before"|"after",
|
|
47663
47708
|
* }>}
|
|
47664
47709
|
* @param {boolean|"top"|"bottom"|"left"|"right"} [props.currentIndicator] - the
|
|
47665
47710
|
* bar that says which tab one is on, said once here rather than on every
|
|
@@ -47685,6 +47730,9 @@ const positionOfCurrentIndicator = (currentIndicator, vertical) => {
|
|
|
47685
47730
|
* container says which one is on screen, and pressing a tab travels there.
|
|
47686
47731
|
* Tabs that are places in the same screen rather than pages of their own —
|
|
47687
47732
|
* nothing is written to the URL and nothing is a link.
|
|
47733
|
+
* @param {"before"|"after"} [props.panelPosition] - turns the row into folder
|
|
47734
|
+
* tabs: the current tab and the panel it opens share one surface, and this
|
|
47735
|
+
* says which side of the row the panel sits on.
|
|
47688
47736
|
*/
|
|
47689
47737
|
const Nav = ({
|
|
47690
47738
|
children,
|
|
@@ -48807,6 +48855,8 @@ const TABS_ALIGN_TO_JUSTIFY_CONTENT = {
|
|
|
48807
48855
|
* onChange?: (value: any) => void,
|
|
48808
48856
|
* tabsPosition?: "top"|"bottom"|"left"|"right",
|
|
48809
48857
|
* tabsAlign?: "start"|"center"|"end"|"stretch",
|
|
48858
|
+
* scrollablePage?: boolean,
|
|
48859
|
+
* maxLines?: number|false,
|
|
48810
48860
|
* borderWidth?: string|number,
|
|
48811
48861
|
* borderRadius?: string|number,
|
|
48812
48862
|
* tabBorderRadius?: string|number,
|
|
@@ -48986,7 +49036,7 @@ const Binder = ({
|
|
|
48986
49036
|
* position among its siblings.
|
|
48987
49037
|
* @param {import("ignore:preact").ComponentChildren} [props.label] - What the tab
|
|
48988
49038
|
* shows.
|
|
48989
|
-
* @param {
|
|
49039
|
+
* @param {object} [props.route] - Makes the tab a `Link`
|
|
48990
49040
|
* to that route, and the binder follows the url: the open item is the one
|
|
48991
49041
|
* whose route matches, no `value`/`onChange` needed.
|
|
48992
49042
|
* @param {string} [props.href] - Same, for a plain url.
|
|
@@ -49425,6 +49475,9 @@ const FixedBarStyleCSSVars = {
|
|
|
49425
49475
|
* `border={false}` for none.
|
|
49426
49476
|
* @param {string|number} [props.maxWidth] - Keeps the bar lined up with a
|
|
49427
49477
|
* content column narrower than the window (it stays centered).
|
|
49478
|
+
* @param {string|number} [props.padding=0] - Along the bar only: left/right on
|
|
49479
|
+
* a top or bottom bar, top/bottom on a side one. Across it, the size is
|
|
49480
|
+
* `height`/`width`.
|
|
49428
49481
|
*/
|
|
49429
49482
|
const FixedBar = ({
|
|
49430
49483
|
children,
|
|
@@ -50293,9 +50346,10 @@ const useExpandableContext = partName => {
|
|
|
50293
50346
|
* height too. `"idle"` builds it in a browser idle moment after load.
|
|
50294
50347
|
* `"while-opened"` throws the content away once the collapse
|
|
50295
50348
|
* settles — after the closing animation, so it still plays on real
|
|
50296
|
-
* content — and rebuilds it from scratch on every expansion.
|
|
50297
|
-
*
|
|
50298
|
-
* builds the content ahead of the click
|
|
50349
|
+
* content — and rebuilds it from scratch on every expansion. Under the
|
|
50350
|
+
* other three, intent on the UI part (pointer entering it, focus landing in
|
|
50351
|
+
* it) builds the content ahead of the click; `"while-opened"` content is
|
|
50352
|
+
* never warmed, it is built at the open.
|
|
50299
50353
|
*/
|
|
50300
50354
|
const Expandable = props => {
|
|
50301
50355
|
import.meta.css = [css$R, "@jsenv/navi/src/control/expandable/expandable.jsx"];
|
|
@@ -51072,7 +51126,8 @@ const DARK_CONTRAST_LIGHTNESS_THRESHOLD = 0.65;
|
|
|
51072
51126
|
* @param {string} accentColor - The accent color value. When it changes, attributes are recomputed.
|
|
51073
51127
|
* @param {object} [options]
|
|
51074
51128
|
* @param {string} [options.elementSelector] - CSS selector to find the element whose computed color is read.
|
|
51075
|
-
* Defaults to the root
|
|
51129
|
+
* Defaults to the root's own `data-visual-selector` attribute, else the root element itself.
|
|
51130
|
+
* Useful when the color is applied to a probe/child element.
|
|
51076
51131
|
* @param {string} [options.colorProperty="backgroundColor"] - Computed style property to read (e.g. "color", "borderColor").
|
|
51077
51132
|
*/
|
|
51078
51133
|
const useAccentColorAttributes = (
|
|
@@ -53117,16 +53172,18 @@ const css$L = /* css */`@layer navi {
|
|
|
53117
53172
|
/**
|
|
53118
53173
|
* Field — a semantic wrapper that connects a label to a form control.
|
|
53119
53174
|
*
|
|
53120
|
-
* It generates a stable `fieldId` (or accepts an explicit
|
|
53175
|
+
* It generates a stable `fieldId` (or accepts an explicit one) that is
|
|
53121
53176
|
* automatically forwarded to the `Label` inside the field as `htmlFor` and to
|
|
53122
|
-
*
|
|
53123
|
-
* label focuses the control without requiring manual wiring.
|
|
53177
|
+
* the labelable control inside it (Input, Select, Picker, Button) as its `id`,
|
|
53178
|
+
* so clicking the label focuses the control without requiring manual wiring.
|
|
53124
53179
|
*
|
|
53125
53180
|
* It also tracks the readOnly / disabled / interactive state reported by its
|
|
53126
53181
|
* child control and reflects it on the `Label` (dimmed color, cursor change).
|
|
53127
53182
|
*
|
|
53128
53183
|
* Props:
|
|
53129
|
-
*
|
|
53184
|
+
* fieldId — optional explicit field id instead of the auto-generated one (an `id`
|
|
53185
|
+
* is the wrapper's own DOM id)
|
|
53186
|
+
* as="label" — the field IS the label, wrapping its control: no id needed
|
|
53130
53187
|
* flex="y" — vertical layout; automatically sets alignX="start" and data-vertical
|
|
53131
53188
|
* children — any JSX; should contain a `Label` and a form control
|
|
53132
53189
|
* ...rest — forwarded to the wrapping element (className, style, flex, spacing, …)
|
|
@@ -53333,6 +53390,7 @@ const InputUnitSlot = ({
|
|
|
53333
53390
|
};
|
|
53334
53391
|
const InputSlot = ({
|
|
53335
53392
|
side,
|
|
53393
|
+
children,
|
|
53336
53394
|
...props
|
|
53337
53395
|
}) => {
|
|
53338
53396
|
const ctx = useContext(InputTextualContext);
|
|
@@ -53362,7 +53420,11 @@ const InputSlot = ({
|
|
|
53362
53420
|
e.preventDefault();
|
|
53363
53421
|
}
|
|
53364
53422
|
},
|
|
53365
|
-
...props
|
|
53423
|
+
...props,
|
|
53424
|
+
children: jsx(ControlIdContext.Provider, {
|
|
53425
|
+
value: undefined,
|
|
53426
|
+
children: children
|
|
53427
|
+
})
|
|
53366
53428
|
});
|
|
53367
53429
|
};
|
|
53368
53430
|
|
|
@@ -54827,9 +54889,9 @@ const InputWithListResolver = props => {
|
|
|
54827
54889
|
};
|
|
54828
54890
|
|
|
54829
54891
|
/**
|
|
54830
|
-
* InputWithList — connects an input to a
|
|
54892
|
+
* InputWithList — connects an input to a selectable List via its id.
|
|
54831
54893
|
*
|
|
54832
|
-
* Usage: <Input navi-list="my-list-id" /> next to <
|
|
54894
|
+
* Usage: <Input navi-list="my-list-id" /> next to <List selectable id="my-list-id">
|
|
54833
54895
|
*
|
|
54834
54896
|
* Behavior:
|
|
54835
54897
|
* - ArrowDown / ArrowUp move the list's "current item" without moving focus
|
|
@@ -55197,11 +55259,14 @@ const useAutoSelectReadOnly = (props) => {
|
|
|
55197
55259
|
*
|
|
55198
55260
|
* - charGuard — restricts which characters can be typed, pasted, or set externally.
|
|
55199
55261
|
* Accepts a preset name or a raw regex character class:
|
|
55200
|
-
* "
|
|
55262
|
+
* true / "auto" → picked from the field: inputMode numeric/decimal, type tel/email
|
|
55263
|
+
* "numeric" → digits only, sets inputMode="numeric"
|
|
55264
|
+
* "decimal" → digits, minus, dot, comma, sets inputMode="decimal"
|
|
55201
55265
|
* "alpha" → letters only
|
|
55202
55266
|
* "alphanumeric" → letters and digits
|
|
55203
55267
|
* "uppercase" → uppercase letters only
|
|
55204
55268
|
* "tel" → phone chars (digits, +, -, parens, space), sets inputMode="tel"
|
|
55269
|
+
* "email" → what an email address is made of
|
|
55205
55270
|
* "card" → credit card (digits and spaces), sets inputMode="numeric"
|
|
55206
55271
|
* "hex" → hexadecimal digits
|
|
55207
55272
|
* "pin" → numeric PIN, sets inputMode="numeric"
|
|
@@ -56272,18 +56337,13 @@ const commitSubtree = (controller, e) => {
|
|
|
56272
56337
|
};
|
|
56273
56338
|
|
|
56274
56339
|
/**
|
|
56275
|
-
*
|
|
56276
|
-
*
|
|
56277
|
-
*
|
|
56278
|
-
*
|
|
56279
|
-
*
|
|
56280
|
-
*
|
|
56281
|
-
*
|
|
56282
|
-
* I could create an example where we would put a link on the page to let user see what he created
|
|
56283
|
-
* but by default user stays on the form allowing to create multiple resources at once
|
|
56284
|
-
* And an other where he is redirected to the resource he created
|
|
56285
|
-
* 3. If form submission fails ideally we should display this somewhere on the UI
|
|
56286
|
-
* right now it's just logged to the console I need to see how we can achieve this
|
|
56340
|
+
* <Form>: the <form> element as a navi control group. Its named fields
|
|
56341
|
+
* aggregate into one object; a submit checks their constraints, runs `action`
|
|
56342
|
+
* with that object when it changed (docs/form_changed.md), and `command` — or
|
|
56343
|
+
* the surface the form sits in — says what follows. The URL does not change on
|
|
56344
|
+
* a submit, and a failing action is drawn as an error callout on the button
|
|
56345
|
+
* that sent it (docs/error_handling.md). A form inside another form renders no
|
|
56346
|
+
* <form> element (FormNested).
|
|
56287
56347
|
*/
|
|
56288
56348
|
|
|
56289
56349
|
const Form = props => {
|
|
@@ -56923,9 +56983,9 @@ const offsetToPx = (offset, box) => {
|
|
|
56923
56983
|
|
|
56924
56984
|
// A press landing while the track is already travelling: what is playing is
|
|
56925
56985
|
// sent home in a fifth of the time it has left, and the press it could not take
|
|
56926
|
-
// yet is taken as soon as it lands. A press has to be FELT — nudging the
|
|
56927
|
-
// of a travel already in flight
|
|
56928
|
-
//
|
|
56986
|
+
// yet is taken as soon as it lands. A press has to be FELT — merely nudging the
|
|
56987
|
+
// pace of a travel already in flight reads as "nothing happened", because the
|
|
56988
|
+
// thing was moving before the click too. Getting there
|
|
56929
56989
|
// almost at once and setting off again is the click being answered.
|
|
56930
56990
|
// Played out fast rather than cut short: ending it on the spot would jump.
|
|
56931
56991
|
// Compounds, so two presses during one travel bring it home twice as sharply,
|
|
@@ -59523,13 +59583,12 @@ const SlideNavButton = ({
|
|
|
59523
59583
|
autoFocus: "last-resort",
|
|
59524
59584
|
icon: true,
|
|
59525
59585
|
variant: "discrete"
|
|
59526
|
-
// Takes the focus like any other button, on purpose.
|
|
59586
|
+
// Takes the focus like any other button, on purpose. Do not refuse it
|
|
59527
59587
|
// (mousedown.preventDefault) to keep the keyboard where the user had put
|
|
59528
|
-
// it
|
|
59529
|
-
//
|
|
59530
|
-
//
|
|
59531
|
-
//
|
|
59532
|
-
// page rather than from what is on screen.
|
|
59588
|
+
// it: the travel below only hands the focus to the slide arriving when it
|
|
59589
|
+
// was leaving a slide, so a press with nothing focused would arrive on
|
|
59590
|
+
// document.body, and the next Tab would start from the top of the page
|
|
59591
|
+
// rather than from what is on screen.
|
|
59533
59592
|
//
|
|
59534
59593
|
// Letting it focus is both the plain behaviour of a button and what makes
|
|
59535
59594
|
// the rest fall into place: the press lands on the chevron, the slide left
|
|
@@ -59546,12 +59605,6 @@ const SlideNavButton = ({
|
|
|
59546
59605
|
})
|
|
59547
59606
|
});
|
|
59548
59607
|
|
|
59549
|
-
/**
|
|
59550
|
-
* The way out of a slide, and the way into the next one. Nothing but the
|
|
59551
|
-
* command plus the chevron that matches the travel: a row goes left/right, a
|
|
59552
|
-
* column up/down — so the button points where the slide actually goes without
|
|
59553
|
-
* the caller having to keep the two in sync.
|
|
59554
|
-
*/
|
|
59555
59608
|
/**
|
|
59556
59609
|
* A way out pointing where it goes. It says a direction, not a slide: what is
|
|
59557
59610
|
* over there is the map's business, and moving a screen changes nothing here.
|
|
@@ -62663,7 +62716,7 @@ const css$E = /* css */`
|
|
|
62663
62716
|
* within its container (the viewport for `layer="top"`, the positioned
|
|
62664
62717
|
* ancestor for `layer="local"`) — Dialog is never anchored to a real
|
|
62665
62718
|
* element for positioning purposes. Same grammar as `Popover`'s own
|
|
62666
|
-
* `positionArea` (see `
|
|
62719
|
+
* `positionArea` (see `parsePositionArea` in @jsenv/dom): a single
|
|
62667
62720
|
* compass token — `top`/`top-start`/`top-end`/`top-left`/`top-right`,
|
|
62668
62721
|
* `right`/`right-start`/`right-end`, `bottom`/`bottom-start`/
|
|
62669
62722
|
* `bottom-end`/`bottom-left`/`bottom-right`, `left`/`left-start`/
|
|
@@ -62682,12 +62735,12 @@ const css$E = /* css */`
|
|
|
62682
62735
|
* between the dialog and the edges of its container, whatever its
|
|
62683
62736
|
* `positionArea`: it both caps the dialog's own size (via
|
|
62684
62737
|
* `--x-dialog-container-spacing`, written from this prop) and offsets a docked
|
|
62685
|
-
* one from the edge it docks to. Accepts a
|
|
62686
|
-
*
|
|
62687
|
-
*
|
|
62688
|
-
*
|
|
62689
|
-
*
|
|
62690
|
-
* meant to sit flush (a side panel).
|
|
62738
|
+
* one from the edge it docks to. Accepts a number of pixels, a viewport
|
|
62739
|
+
* length — "appw"/"apph" being the app's own screen (the visual viewport, or
|
|
62740
|
+
* the narrower one the app declared with --navi-app-max-width) and
|
|
62741
|
+
* "vvw"/"vvh" the visual viewport itself, which shrinks when the mobile
|
|
62742
|
+
* keyboard opens — or a container length ("3cqw", the `layer="local"`
|
|
62743
|
+
* default). Pass 0 for a dialog meant to sit flush (a side panel).
|
|
62691
62744
|
* @param {boolean} [props.backdrop=true] - Whether anything is laid between
|
|
62692
62745
|
* the dialog and the page at all — asked before any question of what an
|
|
62693
62746
|
* outside press does or how the backdrop is painted. `false` leaves the page
|
|
@@ -62700,10 +62753,12 @@ const css$E = /* css */`
|
|
|
62700
62753
|
* needs. What it gives up is what only a modal gets natively: the hardware
|
|
62701
62754
|
* back button no longer dismisses it. See docs/popup_backdrop.md.
|
|
62702
62755
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="close"]
|
|
62703
|
-
* - `"close"` closes the dialog on an outside press
|
|
62704
|
-
*
|
|
62705
|
-
*
|
|
62706
|
-
*
|
|
62756
|
+
* - `"close"` closes the dialog on an outside press, `"cancel"` closes it
|
|
62757
|
+
* as a cancel (what it holds reverts, as on Escape). `"capture"`/`"ignore"`
|
|
62758
|
+
* both just absorb the press without closing (`"capture"` paints the
|
|
62759
|
+
* blurred glass, `"ignore"` leaves the wall's paint alone) — a wall absorbs
|
|
62760
|
+
* the press whatever this prop says, natively for a modal and through the
|
|
62761
|
+
* sibling element for a local one.
|
|
62707
62762
|
* "Outside" is the dialog's own border box; a see-through dialog whose box is
|
|
62708
62763
|
* bigger than what it paints marks the difference with
|
|
62709
62764
|
* `data-navi-popup-outside` (see docs/popup_backdrop.md).
|
|
@@ -62778,11 +62833,10 @@ const css$E = /* css */`
|
|
|
62778
62833
|
* @param {string} [props.animationDuration] - Maps to
|
|
62779
62834
|
* `--popup-animation-duration`.
|
|
62780
62835
|
* @param {Element|{current: Element}|string} [props.anchor] - Never used for
|
|
62781
|
-
* positioning (see this file's top comment)
|
|
62782
|
-
* `
|
|
62783
|
-
* the dialog
|
|
62784
|
-
*
|
|
62785
|
-
* itself names none — an anchor carried by the opening event
|
|
62836
|
+
* positioning (see this file's top comment); read by `sizeFromAnchor` (the
|
|
62837
|
+
* `--anchor-width`/`--anchor-height` CSS vars), by `animation="lifting"`
|
|
62838
|
+
* (the box the dialog comes out of) and by `mount`'s warm-on-intent. Used
|
|
62839
|
+
* when the open itself names none — an anchor carried by the opening event
|
|
62786
62840
|
* (`e.detail.anchor`) is about that one opening and comes first; left out,
|
|
62787
62841
|
* whatever triggered the open (`e.detail.source`). A string is resolved via
|
|
62788
62842
|
* `document.getElementById` when the dialog opens — see popover.jsx's own
|
|
@@ -62844,6 +62898,9 @@ const css$E = /* css */`
|
|
|
62844
62898
|
* focusable of its own.
|
|
62845
62899
|
* - `"restore"` — the dialog stays out of the opening focus chain unless it
|
|
62846
62900
|
* held focus when it closed.
|
|
62901
|
+
* - `false` — the dialog element itself is never where the focus lands; the
|
|
62902
|
+
* opening still hands the focus to what the dialog holds. Unlike
|
|
62903
|
+
* `Popover`'s `false`, it does not leave the keyboard where it was.
|
|
62847
62904
|
* Wherever the keyboard is a virtual one (a touch device), the surface is
|
|
62848
62905
|
* already what one arrives on: a popup is read before it is reached there, so
|
|
62849
62906
|
* the focus only leaves it for something that asked by name (`autoFocus` on
|
|
@@ -63089,8 +63146,8 @@ const useDialogProps = props => {
|
|
|
63089
63146
|
// `backdrop` — see this file's top comment.
|
|
63090
63147
|
layer = "top",
|
|
63091
63148
|
dockedOnSmallTouchScreen,
|
|
63092
|
-
// Same grammar as Popover's own positionArea — see
|
|
63093
|
-
//
|
|
63149
|
+
// Same grammar as Popover's own positionArea — see parsePositionArea in
|
|
63150
|
+
// @jsenv/dom.
|
|
63094
63151
|
positionArea: positionAreaProp,
|
|
63095
63152
|
// A dialog docked against an edge must keep the same gap its own size cap
|
|
63096
63153
|
// already guarantees a centered one — so this drives both (see
|
|
@@ -63130,8 +63187,8 @@ const useDialogProps = props => {
|
|
|
63130
63187
|
sizing = "auto",
|
|
63131
63188
|
animation,
|
|
63132
63189
|
lift = "box",
|
|
63133
|
-
//
|
|
63134
|
-
//
|
|
63190
|
+
// Never positioned against (see this file's top comment): read by
|
|
63191
|
+
// sizeFromAnchor, by a lift, and by the warm-on-intent of `mount`.
|
|
63135
63192
|
anchor,
|
|
63136
63193
|
// Where a lift comes back to, when that is no longer the box it came out
|
|
63137
63194
|
// of. Read at the close, not kept from the opening — see
|
|
@@ -63141,8 +63198,8 @@ const useDialogProps = props => {
|
|
|
63141
63198
|
// See this prop's own JSDoc above for why a dialog does not follow its
|
|
63142
63199
|
// trigger's box by default.
|
|
63143
63200
|
sizeFromAnchor = false,
|
|
63144
|
-
// Same meaning as Popover's own prop, applied to
|
|
63145
|
-
//
|
|
63201
|
+
// Same meaning as Popover's own prop, applied to what an anchor does here:
|
|
63202
|
+
// sizing under sizeFromAnchor, and a lift.
|
|
63146
63203
|
anchorCustomEventDetail = "override",
|
|
63147
63204
|
// Makes the dialog itself a valid focus target so
|
|
63148
63205
|
// autoFocus="last-resort" below has somewhere to land when it contains
|
|
@@ -63201,8 +63258,8 @@ const useDialogProps = props => {
|
|
|
63201
63258
|
// narrower one) — and the positioned ancestor for a local one, where
|
|
63202
63259
|
// reading 3% of the screen gives an absurd gap inside a small box.
|
|
63203
63260
|
isTopLayer ? "3appw" : "3cqw");
|
|
63204
|
-
//
|
|
63205
|
-
//
|
|
63261
|
+
// `expand` is the shorthand for both axes; the docked default only applies
|
|
63262
|
+
// when neither was said.
|
|
63206
63263
|
const expandXUnset = expand === undefined && expandXProp === undefined;
|
|
63207
63264
|
const expandX = expandXUnset ? isDocked && DOCKED.expandX : Boolean(expand) || Boolean(expandXProp);
|
|
63208
63265
|
const scrollCapture = scrollCaptureProp ?? (isDocked ? DOCKED.scrollCapture : false);
|
|
@@ -63941,8 +63998,8 @@ const useDialogProps = props => {
|
|
|
63941
63998
|
// ("[navi-control='dialog'], [navi-control='popover']" — see openEffect
|
|
63942
63999
|
// above), and --navi-open/--navi-close resolve their target this way.
|
|
63943
64000
|
"navi-control": "dialog",
|
|
63944
|
-
// The protocol every command target answers
|
|
63945
|
-
//
|
|
64001
|
+
// The protocol every command target answers: a dialog is layout, not a
|
|
64002
|
+
// control, and still has to answer --navi-open,
|
|
63946
64003
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
63947
64004
|
// bubble.
|
|
63948
64005
|
"onnavi_command": e => {
|
|
@@ -63964,14 +64021,12 @@ const useDialogProps = props => {
|
|
|
63964
64021
|
"popover": isTopLayerPopover ? "manual" : undefined,
|
|
63965
64022
|
"baseClassName": "navi_dialog",
|
|
63966
64023
|
"pseudoClasses": DIALOG_PSEUDO_CLASSES,
|
|
63967
|
-
// Distinguishes the two renderers for the CSS above (position: fixed
|
|
63968
|
-
// vs. absolute) — positioning itself is entirely JS-driven now (see
|
|
63969
|
-
// openEffect's own positionDialog above), no data-position-area
|
|
63970
|
-
// attribute needed at all.
|
|
63971
64024
|
// A popup scrolls, and asking Box for that overflow is also what lets what
|
|
63972
64025
|
// it contains claim header/footer/body (see box.jsx) — a popup is always a
|
|
63973
64026
|
// scrolling area, so it says so once, here.
|
|
63974
64027
|
"overflow": "auto",
|
|
64028
|
+
// Tells the two renderers apart for the CSS above (position: fixed vs.
|
|
64029
|
+
// absolute); the placement itself is JS-driven (positionDialog).
|
|
63975
64030
|
"data-layer": layer,
|
|
63976
64031
|
// The sheet shape is live in CSS, not just a set of resolved defaults:
|
|
63977
64032
|
// it is what withdraws the caller's --dialog-max-width (see the stylesheet
|
|
@@ -64475,8 +64530,8 @@ const css$D = /* css */`
|
|
|
64475
64530
|
* whole viewport. A real `anchor` works with either.
|
|
64476
64531
|
* @param {string} [props.positionArea="bottom"] - Where to place the popover
|
|
64477
64532
|
* relative to its `anchor` (or its container, if there is none). Same
|
|
64478
|
-
* grammar as `Dialog`'s own `positionArea` (see `
|
|
64479
|
-
*
|
|
64533
|
+
* grammar as `Dialog`'s own `positionArea` (see `parsePositionArea` in
|
|
64534
|
+
* @jsenv/dom): a single compass token — `top`/`top-start`/
|
|
64480
64535
|
* `top-end`/`top-left`/`top-right`, `right`/`right-start`/`right-end`,
|
|
64481
64536
|
* `bottom`/`bottom-start`/`bottom-end`/`bottom-left`/`bottom-right`,
|
|
64482
64537
|
* `left`/`left-start`/`left-end`, or `center`. A bare token means no
|
|
@@ -64505,8 +64560,9 @@ const css$D = /* css */`
|
|
|
64505
64560
|
* See docs/popup_backdrop.md.
|
|
64506
64561
|
* @param {"close"|"cancel"|"capture"|"ignore"} [props.pressOutside="ignore"]
|
|
64507
64562
|
* - `"ignore"` (default): no backdrop at all, outside presses pass straight
|
|
64508
|
-
* through. `"close"` closes the popover on an outside press
|
|
64509
|
-
*
|
|
64563
|
+
* through. `"close"` closes the popover on an outside press, `"cancel"`
|
|
64564
|
+
* closes it as a cancel (what it holds reverts, as on Escape). `"capture"`
|
|
64565
|
+
* absorbs the press (blurred glass) without closing. Note this
|
|
64510
64566
|
* default differs from `Dialog`'s own (`"close"`) — a popover is
|
|
64511
64567
|
* typically a lightweight, non-modal affordance. "Outside" is the popover's
|
|
64512
64568
|
* own border box; a see-through popover whose box is bigger than what it
|
|
@@ -64540,9 +64596,11 @@ const css$D = /* css */`
|
|
|
64540
64596
|
* popover so the page/container behind it can't scroll while it's open.
|
|
64541
64597
|
* @param {boolean} [props.focusCapture] - Traps Tab navigation inside the
|
|
64542
64598
|
* popover (see `focus_trap.js`).
|
|
64543
|
-
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`} [props.animation]
|
|
64544
|
-
* - `true`/`"auto"` resolves to
|
|
64545
|
-
*
|
|
64599
|
+
* @param {boolean|"auto"|"fading"|"scaling"|"sliding"|`slide-from-${string}`|"expanding"|`expand-${string}`} [props.animation]
|
|
64600
|
+
* - `true`/`"auto"` resolves to `"scaling"` for a real anchor or a centered
|
|
64601
|
+
* `positionArea`, and to a concrete `"slide-from-*"` direction otherwise.
|
|
64602
|
+
* `"expanding"`/`"expand-*"` grow out of a real anchor (see popup_css.js).
|
|
64603
|
+
* Any other explicit value is used as-is.
|
|
64546
64604
|
* @param {string} [props.animationDuration] - Maps to
|
|
64547
64605
|
* `--popup-animation-duration`.
|
|
64548
64606
|
* @param {Element|{current: Element}|string} [props.anchor] - The element the
|
|
@@ -64654,7 +64712,7 @@ const css$D = /* css */`
|
|
|
64654
64712
|
* @param {object} [props.openController] - Advanced: an externally-owned
|
|
64655
64713
|
* open controller (see `open_controller.js`) for a caller that wants to
|
|
64656
64714
|
* drive open/close itself instead of `open`/`defaultOpen`/`onClose` (used
|
|
64657
|
-
* by `picker_custom.jsx
|
|
64715
|
+
* by `picker_custom.jsx`).
|
|
64658
64716
|
* @param {"always"|"idle"|"from-first-open"|"while-opened"} [props.mount] - When
|
|
64659
64717
|
* `children` are built and thrown away (see popup_content_mount.js).
|
|
64660
64718
|
* `"from-first-open"` (the default) builds them on the first open and keeps
|
|
@@ -64966,12 +65024,10 @@ const usePopoverProps = props => {
|
|
|
64966
65024
|
unfreezeSize(popoverEl);
|
|
64967
65025
|
}
|
|
64968
65026
|
}, [sizing]);
|
|
64969
|
-
// The custom renderer
|
|
64970
|
-
//
|
|
64971
|
-
//
|
|
64972
|
-
//
|
|
64973
|
-
// leaving this to a layout effect meant an actual (if narrow) window
|
|
64974
|
-
// where the browser could paint it visible before this ever ran.
|
|
65027
|
+
// The custom renderer starts hidden from the stylesheet ([navi-hidden] on
|
|
65028
|
+
// .navi_popover/.navi_popover_backdrop above), never from a layout effect:
|
|
65029
|
+
// a plain div has no native hidden default the way [popover]/<dialog> do,
|
|
65030
|
+
// and an effect leaves a window where the browser paints it visible first.
|
|
64975
65031
|
// aria-expanded starts "false" the same way, but via a static literal
|
|
64976
65032
|
// JSX prop on contentProps/backdropProps below instead of a layout
|
|
64977
65033
|
// effect — see that prop's own comment for why a *constant* value there
|
|
@@ -64990,8 +65046,8 @@ const usePopoverProps = props => {
|
|
|
64990
65046
|
openController.getElement = () => ref.current;
|
|
64991
65047
|
openController.openEffect = e => {
|
|
64992
65048
|
const popoverEl = ref.current;
|
|
64993
|
-
// backdropEl is null when
|
|
64994
|
-
// the backdrop isn't rendered at all
|
|
65049
|
+
// backdropEl is null when there is no wall (pressOutside "ignore", or
|
|
65050
|
+
// backdrop={false}): the backdrop isn't rendered at all then.
|
|
64995
65051
|
const backdropEl = backdropRef.current;
|
|
64996
65052
|
if (!popoverEl) {
|
|
64997
65053
|
{
|
|
@@ -65052,10 +65108,8 @@ const usePopoverProps = props => {
|
|
|
65052
65108
|
// needed here.
|
|
65053
65109
|
const positionedAncestor = getPositionedParent(popoverEl);
|
|
65054
65110
|
// Drives the via-attribute renderer's own position: fixed/absolute
|
|
65055
|
-
// switch (see this file's top comment) — set here,
|
|
65056
|
-
// positioning/measurement runs, so
|
|
65057
|
-
// wrong (unlike the CSS this replaced, which keyed off the resolved
|
|
65058
|
-
// animation instead, known too late relative to the first measurement).
|
|
65111
|
+
// switch (see this file's top comment) — set here, before any
|
|
65112
|
+
// positioning/measurement runs, so the first measurement already sees it.
|
|
65059
65113
|
// True only for a real anchor that itself scrolls with the document —
|
|
65060
65114
|
// that's the one case `absolute` (scrolling in lockstep with it) is
|
|
65061
65115
|
// correct; not just "has an anchor at all", since an anchor that's
|
|
@@ -65105,10 +65159,9 @@ const usePopoverProps = props => {
|
|
|
65105
65159
|
disarmBackdropHideRef.current?.();
|
|
65106
65160
|
disarmBackdropHideRef.current = null;
|
|
65107
65161
|
// transitionProperty stays "none" here for both — reset later, in the
|
|
65108
|
-
// final commit step alongside popoverEl's own
|
|
65109
|
-
//
|
|
65110
|
-
//
|
|
65111
|
-
// aria-expanded flip, the same way it does for popoverEl.
|
|
65162
|
+
// final commit step alongside popoverEl's own, so a `silent` open (see
|
|
65163
|
+
// above) can keep both elements' transitions suppressed right up until
|
|
65164
|
+
// after their aria-expanded flip, the same way it does for popoverEl.
|
|
65112
65165
|
backdropEl.style.transitionProperty = "none";
|
|
65113
65166
|
if (isTopLayer) {
|
|
65114
65167
|
// Hidden first if a previous close's deferred hidePopover() (see
|
|
@@ -65534,16 +65587,10 @@ const usePopoverProps = props => {
|
|
|
65534
65587
|
// current unconditionally on every call, and treats their mere
|
|
65535
65588
|
// *presence* as "already positioned this session — stay on this
|
|
65536
65589
|
// side unless it no longer fits" (its own anti-oscillation guard for
|
|
65537
|
-
// repositioning mid-open, e.g. on scroll/resize).
|
|
65538
|
-
//
|
|
65539
|
-
//
|
|
65540
|
-
//
|
|
65541
|
-
// still-live purpose: without clearing them on close, a later reopen
|
|
65542
|
-
// sees a stale, "already positioned" value left over from a
|
|
65543
|
-
// *previous* open (e.g. the container-aligned collapse from an
|
|
65544
|
-
// anchorless silent open) and wrongly stays sticky to it instead of
|
|
65545
|
-
// resolving fresh from the current positionArea/anchor — a real,
|
|
65546
|
-
// reproduced bug, not just a leftover no-op.
|
|
65590
|
+
// repositioning mid-open, e.g. on scroll/resize). Cleared on close so a
|
|
65591
|
+
// later reopen resolves fresh from the current positionArea/anchor
|
|
65592
|
+
// instead of staying sticky to a side left over from a previous open
|
|
65593
|
+
// (e.g. the container-aligned collapse from an anchorless silent open).
|
|
65547
65594
|
popoverEl.removeAttribute("data-position-y-current");
|
|
65548
65595
|
popoverEl.removeAttribute("data-position-x-current");
|
|
65549
65596
|
cleanup();
|
|
@@ -65673,8 +65720,8 @@ const usePopoverProps = props => {
|
|
|
65673
65720
|
// ("[navi-control='dialog'], [navi-control='popover']"), and
|
|
65674
65721
|
// --navi-open/--navi-close resolve their target this way.
|
|
65675
65722
|
"navi-control": "popover",
|
|
65676
|
-
// The protocol every command target answers
|
|
65677
|
-
//
|
|
65723
|
+
// The protocol every command target answers: a popover is layout, not a
|
|
65724
|
+
// control, and still has to answer --navi-open,
|
|
65678
65725
|
// --navi-close and --navi-toggle, which are dispatched here and do not
|
|
65679
65726
|
// bubble.
|
|
65680
65727
|
"onnavi_command": e => {
|
|
@@ -65714,7 +65761,7 @@ const usePopoverProps = props => {
|
|
|
65714
65761
|
// already arrive here as plain props via ...rest (wired by
|
|
65715
65762
|
// UncontrolledPopover above, forwarded through ControlledPopover's own
|
|
65716
65763
|
// {...props} spread) — nothing extra to add here. A controlled caller
|
|
65717
|
-
// (picker_custom.jsx
|
|
65764
|
+
// (picker_custom.jsx) wires its own equivalent handling
|
|
65718
65765
|
// directly against its own openController instead.
|
|
65719
65766
|
});
|
|
65720
65767
|
return [hasBackdrop ? backdropProps : null, contentProps];
|
|
@@ -65746,9 +65793,6 @@ const POPUP_STYLE_CSS_VARS = {
|
|
|
65746
65793
|
maxHeight: "--popover-max-height"
|
|
65747
65794
|
};
|
|
65748
65795
|
|
|
65749
|
-
// parsePositionArea/POSITION_AREA_X/Y_VALUES moved to popup_shared.js — same
|
|
65750
|
-
// grammar Dialog's own layer="local"/"top" now shares.
|
|
65751
|
-
|
|
65752
65796
|
/**
|
|
65753
65797
|
* Shared by both renderers: parses `positionArea` and resolves
|
|
65754
65798
|
* `animation="auto"`/`true`. `animationAnchor` is the real anchor element
|
|
@@ -65776,15 +65820,6 @@ const resolvePositionAreaAndAnimationKind = ({
|
|
|
65776
65820
|
resolvedAnimationKind
|
|
65777
65821
|
};
|
|
65778
65822
|
};
|
|
65779
|
-
|
|
65780
|
-
// resolveDirectionValue/resolveAutoAnimationKind moved to popup_shared.js —
|
|
65781
|
-
// same logic Dialog's own auto-animation resolution now shares, since it
|
|
65782
|
-
// never has a real anchor either (always the "anchor === undefined" path).
|
|
65783
|
-
|
|
65784
|
-
// suppressPointerEventsDuringTransition/armPointerDownOutsideClose moved to
|
|
65785
|
-
// popup_shared.js — same helpers Dialog's own custom renderer needs, no
|
|
65786
|
-
// Popover-specific knowledge in either.
|
|
65787
|
-
|
|
65788
65823
|
Popover.Close = PopupClose;
|
|
65789
65824
|
|
|
65790
65825
|
installImportMetaCssBuild(import.meta);
|
|
@@ -65823,11 +65858,12 @@ const css$C = /* css */`@layer navi {
|
|
|
65823
65858
|
* @param {"top"|"local"} [props.layer] - Forwarded as-is to whichever of
|
|
65824
65859
|
* `Dialog`/`Popover` renders — see either component's own doc.
|
|
65825
65860
|
* @param {Element|{current: Element}} [props.anchor] - Forwarded as-is —
|
|
65826
|
-
* positioning for `Popover
|
|
65827
|
-
* `sizeFromAnchor`
|
|
65861
|
+
* positioning for `Popover`; for `Dialog`, the box it sizes from under
|
|
65862
|
+
* `sizeFromAnchor` and lifts out of under `animation="lifting"`. In both,
|
|
65863
|
+
* intent on the anchor warms the content (see `mount`).
|
|
65828
65864
|
* @param {"override"|"ignore"} [props.anchorCustomEventDetail] - Forwarded
|
|
65829
65865
|
* as-is to both — what it governs differs (positioning for `Popover`,
|
|
65830
|
-
*
|
|
65866
|
+
* sizing and lifting for `Dialog`), but "ignore whatever anchor the
|
|
65831
65867
|
* triggering event carried" has to mean the same thing in either mode.
|
|
65832
65868
|
* @param {string} [props.marginWithAnchor] - **Popover-only**, destructured
|
|
65833
65869
|
* out so it can't leak onto the real `<dialog>` element as a stray DOM
|
|
@@ -65857,8 +65893,9 @@ const css$C = /* css */`@layer navi {
|
|
|
65857
65893
|
* the screen-size check happens to pick, which defeats the point of
|
|
65858
65894
|
* having one shared API in the first place. Note this only says what a
|
|
65859
65895
|
* press outside *does*; whether it reaches the page at all is `backdrop`
|
|
65860
|
-
* below
|
|
65861
|
-
*
|
|
65896
|
+
* below. `"capture"` is a wall that absorbs the press without closing, in
|
|
65897
|
+
* either mode. `"ignore"` is not the same in both: a `Dialog` keeps its
|
|
65898
|
+
* wall, a `Popover` lays none and the press reaches the page.
|
|
65862
65899
|
* @param {boolean} [props.backdrop] - Whether anything is laid between the
|
|
65863
65900
|
* popup and the page at all: `false` lets a press outside both dismiss the
|
|
65864
65901
|
* popup and reach whatever it landed on, in one gesture. Forwarded as-is and
|
|
@@ -65898,11 +65935,15 @@ const css$C = /* css */`@layer navi {
|
|
|
65898
65935
|
* `--x-popover-max-width`) instead of its content width — same meaning
|
|
65899
65936
|
* whichever mode the screen-size resolution picks.
|
|
65900
65937
|
* @param {boolean} [props.expandY] - Same, vertically (`data-expand-y`).
|
|
65901
|
-
* @param {boolean} [props.scrollCapture] - Forwarded
|
|
65938
|
+
* @param {boolean|"dialog"|"popover"} [props.scrollCapture] - Forwarded to
|
|
65939
|
+
* both; a mode name traps scroll only when that mode is the one rendered.
|
|
65902
65940
|
* @param {boolean} [props.open] - Forwarded as-is (controlled).
|
|
65903
|
-
* @param {import("@preact/signals").Signal<
|
|
65941
|
+
* @param {import("@preact/signals").Signal<any>} [props.signal] -
|
|
65904
65942
|
* Forwarded as-is: one binding to both drive the popup's open state and
|
|
65905
65943
|
* know where it is (see `Dialog`/`Popover`'s own `signal`).
|
|
65944
|
+
* @param {any} [props.value] - Forwarded as-is: what `signal` holds while
|
|
65945
|
+
* THIS popup is open, for several sharing one signal (see
|
|
65946
|
+
* `Dialog`/`Popover`'s own `value`).
|
|
65906
65947
|
* @param {boolean} [props.defaultOpen] - Forwarded as-is (uncontrolled,
|
|
65907
65948
|
* mount-only).
|
|
65908
65949
|
* @param {(event: Event) => void} [props.onClose] - Forwarded as-is.
|
|
@@ -65917,6 +65958,8 @@ const css$C = /* css */`@layer navi {
|
|
|
65917
65958
|
* them afterwards. `"always"` builds them right away, for content something
|
|
65918
65959
|
* depends on while the popup is still closed: a value read off it, fields a
|
|
65919
65960
|
* surrounding form collects on submit, a size measured from outside.
|
|
65961
|
+
* `"idle"` builds them in a browser idle moment after load — `"always"`
|
|
65962
|
+
* minus the cost on the critical render.
|
|
65920
65963
|
* `"while-opened"` throws them away once the popup has finished closing, for
|
|
65921
65964
|
* content whose fresh state is its initial state: an uncontrolled field
|
|
65922
65965
|
* seeded from a `defaultValue` that changed while the popup was closed.
|
|
@@ -66515,7 +66558,7 @@ const PickerCustom = props => {
|
|
|
66515
66558
|
// Not on pickerProps (the trigger): commands.js's own
|
|
66516
66559
|
// resolveClosestExpandable() does `el.closest("[aria-expanded]")` to
|
|
66517
66560
|
// find where to dispatch navi_request_open/navi_request_close — and
|
|
66518
|
-
// the popup itself
|
|
66561
|
+
// the popup itself carries its own aria-expanded (see
|
|
66519
66562
|
// popover.jsx/dialog.jsx), which is *closer* than the picker's own
|
|
66520
66563
|
// aria-expanded for anything dispatched from inside the popup's own
|
|
66521
66564
|
// content (e.g. a `command="--navi-close"` button rendered as
|
|
@@ -66795,12 +66838,8 @@ const PickerContentInsidePopup = props => {
|
|
|
66795
66838
|
mode,
|
|
66796
66839
|
pointerLock,
|
|
66797
66840
|
scrollCapture,
|
|
66798
|
-
// No default here
|
|
66799
|
-
//
|
|
66800
|
-
// Popover's real `focusCapture` prop (see this file's history), so
|
|
66801
|
-
// focus-trapping has never really been active for popover-mode pickers;
|
|
66802
|
-
// defaulting the now-correctly-named prop to `true` would be a real,
|
|
66803
|
-
// unintended behavior change riding along with the rename.
|
|
66841
|
+
// No default here: Popover's own `focusCapture` is inactive by default,
|
|
66842
|
+
// and a popover-mode picker traps the focus only when asked to.
|
|
66804
66843
|
focusCapture,
|
|
66805
66844
|
// Popup documents its own `layer` as forwarded as-is to Dialog/Popover,
|
|
66806
66845
|
// but popupProps is built explicitly here, so it only travels if named.
|
|
@@ -67437,6 +67476,8 @@ document.addEventListener(
|
|
|
67437
67476
|
{ capture: true, passive: true },
|
|
67438
67477
|
);
|
|
67439
67478
|
|
|
67479
|
+
const FRAME_TIMEOUT_MS = 100;
|
|
67480
|
+
|
|
67440
67481
|
/**
|
|
67441
67482
|
* Calls back once the browser has painted what is committed now.
|
|
67442
67483
|
*
|
|
@@ -67448,10 +67489,9 @@ document.addEventListener(
|
|
|
67448
67489
|
* has to survive. A frame not coming at all (a background tab) still answers,
|
|
67449
67490
|
* late, through the timeout.
|
|
67450
67491
|
*
|
|
67492
|
+
* @param {() => void} callback
|
|
67451
67493
|
* @returns {() => void} cancel
|
|
67452
67494
|
*/
|
|
67453
|
-
const FRAME_TIMEOUT_MS = 100;
|
|
67454
|
-
|
|
67455
67495
|
const afterPaint = (callback) => {
|
|
67456
67496
|
let called = false;
|
|
67457
67497
|
let timeoutId;
|
|
@@ -68998,7 +69038,7 @@ const SELECTABLE_INPUT_PSEUDO_CLASSES = [":hover", ":active", ":focus", ":focus-
|
|
|
68998
69038
|
const SelectableInputProxy = props => {
|
|
68999
69039
|
const selectableRealInputProps = useContext(SelectableRealInputContext);
|
|
69000
69040
|
if (!selectableRealInputProps) {
|
|
69001
|
-
throw new Error("
|
|
69041
|
+
throw new Error("SelectableInput must be used within a row of a selectable List");
|
|
69002
69042
|
}
|
|
69003
69043
|
|
|
69004
69044
|
// Reset FieldToInterfaceContext to ensure we don't read id or report our
|
|
@@ -69036,7 +69076,7 @@ const SelectableInput = SelectableInputProxy;
|
|
|
69036
69076
|
* background-color: var(--search-match-background-color);
|
|
69037
69077
|
* }
|
|
69038
69078
|
*
|
|
69039
|
-
* The `highlight`
|
|
69079
|
+
* The `highlight` argument of `useSearchHighlight` (a row's `matchInfo.matchRanges`) can be:
|
|
69040
69080
|
* - an array of [start, end] pairs — applied to all text nodes under the root element
|
|
69041
69081
|
* - an object { [domSelector]: [[start, end], …] } — applied to each sub-element
|
|
69042
69082
|
* matched by the selector (the format produced by createSearch)
|
|
@@ -72584,8 +72624,8 @@ const LIST_ITEM_STYLE_CSS_VARS = {
|
|
|
72584
72624
|
*
|
|
72585
72625
|
* All three are about the row as a thing the LIST holds, never about the
|
|
72586
72626
|
* selection: a selectable row taken while its list sends says so on its own
|
|
72587
|
-
* ("
|
|
72588
|
-
* that.
|
|
72627
|
+
* ("le choix est en cours d'enregistrement", "la sélection…" in a `multiple`
|
|
72628
|
+
* list), and needs no `loading` for that.
|
|
72589
72629
|
* @param {boolean} [props.readOnly]
|
|
72590
72630
|
* The row cannot be acted on: dimmed and click-through-proof, buttons inside
|
|
72591
72631
|
* it included.
|
|
@@ -73680,9 +73720,12 @@ const useItemStore = ({
|
|
|
73680
73720
|
* (accessible via aria-labelledby) and a <ul role="group"> for the items.
|
|
73681
73721
|
*
|
|
73682
73722
|
* Props:
|
|
73683
|
-
* label
|
|
73684
|
-
* labelProps
|
|
73685
|
-
*
|
|
73723
|
+
* label — group label content
|
|
73724
|
+
* labelProps — props forwarded to the label <span>
|
|
73725
|
+
* hiddenWhileEmpty — the group leaves the flow (`display: none`) while it
|
|
73726
|
+
* holds no real row — a search that emptied it, rows not
|
|
73727
|
+
* arrived yet
|
|
73728
|
+
* ...rest — forwarded to the outer <li role="presentation">
|
|
73686
73729
|
*/
|
|
73687
73730
|
const ListItemGroup = ({
|
|
73688
73731
|
label,
|
|
@@ -73794,7 +73837,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
73794
73837
|
* uiAction?: (value: any) => void,
|
|
73795
73838
|
* popover?: boolean,
|
|
73796
73839
|
* role?: string,
|
|
73797
|
-
* renderBudget?: number | string,
|
|
73840
|
+
* renderBudget?: number | string | {initial?: number, after: number},
|
|
73798
73841
|
* renderBudgetSkipCheck?: boolean,
|
|
73799
73842
|
* virtualItemSize?: number,
|
|
73800
73843
|
* onListVisibleItemsChange?: (visibleItems: any[]) => void,
|
|
@@ -73823,6 +73866,7 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
73823
73866
|
* alignX?: string,
|
|
73824
73867
|
* alignY?: string,
|
|
73825
73868
|
* flexWrap?: boolean,
|
|
73869
|
+
* overflow?: string,
|
|
73826
73870
|
* expandX?: boolean,
|
|
73827
73871
|
* expandY?: boolean,
|
|
73828
73872
|
* expand?: boolean,
|
|
@@ -74009,10 +74053,10 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
74009
74053
|
* @param {number} [props.parallelGuard=4]
|
|
74010
74054
|
* How many runs the rows may have in flight at once, for a list whose rows
|
|
74011
74055
|
* carry their own `action` (a button per row). While that many are out, every
|
|
74012
|
-
*
|
|
74013
|
-
* the next press is possible again as soon as one comes back.
|
|
74014
|
-
* lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
74015
|
-
* says how many things the selection may hold.
|
|
74056
|
+
* control that would start another run goes read-only and says how many it is
|
|
74057
|
+
* waiting on; the next press is possible again as soon as one comes back.
|
|
74058
|
+
* `Infinity` lifts it. Counts runs, not values — `maxLengthGuard` above is
|
|
74059
|
+
* the one that says how many things the selection may hold.
|
|
74016
74060
|
* @param {boolean} [props.standalone]
|
|
74017
74061
|
* This list answers for itself: it does not register with the control group
|
|
74018
74062
|
* or picker around it, so its selection stays out of that value and nothing
|
|
@@ -76547,7 +76591,7 @@ const PickerFirstResolver = props => {
|
|
|
76547
76591
|
* popupTestId?: string,
|
|
76548
76592
|
* confirmTestId?: string,
|
|
76549
76593
|
* cancelTestId?: string,
|
|
76550
|
-
* mount?: "always" | "from-first-open" | "while-opened",
|
|
76594
|
+
* mount?: "always" | "idle" | "from-first-open" | "while-opened",
|
|
76551
76595
|
* dialogExpand?: boolean,
|
|
76552
76596
|
* dialogExpandX?: boolean,
|
|
76553
76597
|
* dialogExpandY?: boolean,
|
|
@@ -76984,8 +77028,8 @@ const PickerFirstResolver = props => {
|
|
|
76984
77028
|
* itself names the trigger (see docs/testid.md); this one is for a test that
|
|
76985
77029
|
* needs the popup's own box — its surface, its position, its backdrop — and
|
|
76986
77030
|
* not only what it holds, which a testid inside the children already names.
|
|
76987
|
-
*
|
|
76988
|
-
*
|
|
77031
|
+
* Under `mode="callout"` it names the callout element — the control's own,
|
|
77032
|
+
* shared with its constraint messages — while the children are shown in it.
|
|
76989
77033
|
* @param {string} [confirmTestId] `type="confirm"`, default body: the
|
|
76990
77034
|
* `data-testid` of the yes button; `cancelTestId` that of the no button.
|
|
76991
77035
|
* Both are elements navi builds, which a test may not name by class (see
|
|
@@ -77373,7 +77417,7 @@ const TRAVELS_NONE = [];
|
|
|
77373
77417
|
* fallbackValue?: any,
|
|
77374
77418
|
* valueAtStep: (value: any, count: number) => any,
|
|
77375
77419
|
* compareValues?: (a: any, b: any) => number,
|
|
77376
|
-
* renderValue?: (value: any) => import("ignore:preact").ComponentChildren,
|
|
77420
|
+
* renderValue?: (value: any, context: { maxLines?: number }) => import("ignore:preact").ComponentChildren,
|
|
77377
77421
|
* controlProps?: object,
|
|
77378
77422
|
* duration?: number,
|
|
77379
77423
|
* vertical?: boolean,
|
|
@@ -78848,7 +78892,8 @@ const css$q = /* css */`.navi_input_duration {
|
|
|
78848
78892
|
* @param {boolean} [props.required]
|
|
78849
78893
|
* @param {boolean} [props.loading]
|
|
78850
78894
|
* @param {Function} [props.uiAction] - Called on every change with the ISO 8601 value
|
|
78851
|
-
* @param {Function} [props.action] -
|
|
78895
|
+
* @param {Function} [props.action] - Runs with the ISO 8601 value when it changes,
|
|
78896
|
+
* like any control's `action` (busy state, error callout)
|
|
78852
78897
|
* @param {preact.ComponentChild} [props.unitHour] - Custom label for the hour sub-field
|
|
78853
78898
|
* @param {"auto"|"left"|"center"|"right"} [props.textAlign="auto"] - Text alignment of sub-inputs.
|
|
78854
78899
|
* "auto" aligns each field toward its neighbouring separator (first→right, last→left, middle/solo→center).
|
|
@@ -79859,11 +79904,12 @@ const splitPopupProps = props => {
|
|
|
79859
79904
|
* multi-word, one word at start (all match) 0.75
|
|
79860
79905
|
* phrase / word at word boundary 0.625
|
|
79861
79906
|
* phrase / words mid-word 0.5
|
|
79907
|
+
* acronym (2+ chars, each the first letter of a word, in order) 0.4, 0.45 at start
|
|
79862
79908
|
* + case-exact bonus +0.125
|
|
79863
79909
|
* multi-word partial: score × (matched/total)
|
|
79864
79910
|
*
|
|
79865
79911
|
* matchRanges: [start, end] pairs (exclusive end) for CSS Highlight API.
|
|
79866
|
-
* Intended to be passed to
|
|
79912
|
+
* Intended to be passed to useSearchText as the matchFn parameter.
|
|
79867
79913
|
*/
|
|
79868
79914
|
const applySearch = (searchText, value) => {
|
|
79869
79915
|
if (!searchText) {
|
|
@@ -80073,7 +80119,7 @@ const mergeRanges = (ranges) => {
|
|
|
80073
80119
|
};
|
|
80074
80120
|
|
|
80075
80121
|
/**
|
|
80076
|
-
* createSearch — builds a matchFn compatible with
|
|
80122
|
+
* createSearch — builds a matchFn compatible with useSearchText that searches
|
|
80077
80123
|
* across multiple named fields of an item, each with its own DOM selector
|
|
80078
80124
|
* and optional priority weight.
|
|
80079
80125
|
*
|
|
@@ -80091,7 +80137,7 @@ const mergeRanges = (ranges) => {
|
|
|
80091
80137
|
* },
|
|
80092
80138
|
* });
|
|
80093
80139
|
*
|
|
80094
|
-
* const [orderedItems, getItemMatchInfo] =
|
|
80140
|
+
* const [orderedItems, getItemMatchInfo] = useSearchText(search, items, searchPerson);
|
|
80095
80141
|
* // getItemMatchInfo(item).matchRanges is { ".name": [[start,end],…], ".address": [[start,end],…] }
|
|
80096
80142
|
* // Pass the whole thing: <ListItem matchInfo={getItemMatchInfo(item)} />
|
|
80097
80143
|
* // — ListItem handles the per-selector object format for matchRanges.
|
|
@@ -80158,7 +80204,7 @@ const useSearchText = (searchText, items, matchFn = applySearch) => {
|
|
|
80158
80204
|
);
|
|
80159
80205
|
}
|
|
80160
80206
|
if (items === undefined) {
|
|
80161
|
-
throw new TypeError("
|
|
80207
|
+
throw new TypeError("useSearchText: items is undefined");
|
|
80162
80208
|
}
|
|
80163
80209
|
const { orderedItems, matchInfoMap } = useMemo(() => {
|
|
80164
80210
|
const { scoreEntries, nonMatched, matchInfoMap } = buildMatchInfo(
|
|
@@ -80283,17 +80329,19 @@ const buildMatchInfo = (searchText, items, matchFn) => {
|
|
|
80283
80329
|
* - orderedKeys: number[] of visible item keys sorted by explicit order
|
|
80284
80330
|
* - keyToOrderedIndex: Map key → orderedKeys index, gives O(1) indexOf equivalent
|
|
80285
80331
|
* - keyToExplicitOrder: Map key → explicitly passed index, used to maintain sort order
|
|
80286
|
-
* -
|
|
80287
|
-
* - visibleItemsSignal: signal(array),
|
|
80332
|
+
* - itemsSignal: signal(array), all items including hidden, ordered by explicit index
|
|
80333
|
+
* - visibleItemsSignal: signal(array), items neither hidden nor filtered
|
|
80288
80334
|
* - countSignal: signal(number), count of all items including hidden
|
|
80289
80335
|
* - visibleCountSignal: signal(number), updated in microtask batch, only when count changes
|
|
80290
|
-
* -
|
|
80336
|
+
* - noMatchCountSignal: signal(number), items registered with `match: false`
|
|
80291
80337
|
* - onChangeRef: holds the latest onChange callback, called once per microtask batch
|
|
80292
80338
|
*
|
|
80293
|
-
* useTrackItem(
|
|
80294
|
-
*
|
|
80295
|
-
* correct order and passes it directly — no render-sequence deduction
|
|
80296
|
-
*
|
|
80339
|
+
* useTrackItem(data): registers the item under `data.id`, at the position
|
|
80340
|
+
* `data.index` gives it among its siblings. The caller (e.g. items.map) knows
|
|
80341
|
+
* the correct order and passes it directly — no render-sequence deduction
|
|
80342
|
+
* needed. `data.role === "presentation"` keeps it out entirely.
|
|
80343
|
+
* Returns the visible rank (position among visible items), or -1 when hidden
|
|
80344
|
+
* or filtered.
|
|
80297
80345
|
* Signals and onChange are deferred to a microtask so multiple items updating
|
|
80298
80346
|
* in one commit cause only one notification.
|
|
80299
80347
|
*
|
|
@@ -81061,7 +81109,7 @@ const WheelGroupContext = createContext(null);
|
|
|
81061
81109
|
* [key: string]: any,
|
|
81062
81110
|
* }>}
|
|
81063
81111
|
* @param {number} [props.visibleCount=3] - Odd number of rows visible in the viewport (the center one is the selection).
|
|
81064
|
-
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (
|
|
81112
|
+
* @param {number|string} [props.itemHeight] - Main-axis size of a row when vertical (number = px). Defaults to the CSS var (1.8em).
|
|
81065
81113
|
* @param {number|string} [props.itemWidth] - Main-axis size of a cell when horizontal (number = px). Defaults to the CSS var (3.5ch).
|
|
81066
81114
|
* @param {number|string} [props.size] - Font size of the wheel: a size token ("s", "m", "l", "xl", …), a number (px), or a CSS length. Scales the digits and, since the row size is em-based, the row height too — a simple way to make the whole wheel bigger. An explicit itemHeight/itemWidth still overrides the row size.
|
|
81067
81115
|
* @param {boolean} [props.bounded] - Give the wheel fixed ends instead of wrapping: it stops at the first/last value. By default the wheel loops endlessly (past the last value the first reappears, and vice-versa).
|
|
@@ -82557,7 +82605,7 @@ Wheel.Item = WheelItem;
|
|
|
82557
82605
|
* @param {boolean} [props.horizontal] - Stack the (horizontal) wheels vertically instead of in a row.
|
|
82558
82606
|
* @param {boolean} [props.glass] - Frost every wheel's neighbouring rows (see Wheel's glass prop) with one prop for the whole group.
|
|
82559
82607
|
* @param {boolean} [props.frameBorder] - Line every wheel's center-window edges with a faint frame (off by default; independent of glass).
|
|
82560
|
-
* @param {boolean} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
82608
|
+
* @param {boolean|number} [props.zoom] - Enlarge the centered value of every wheel (see Wheel's zoom prop) with one prop for the whole group.
|
|
82561
82609
|
*/
|
|
82562
82610
|
const WheelGroup = props => {
|
|
82563
82611
|
import.meta.css = [css$m, "@jsenv/navi/src/control/wheel/wheel.jsx"];
|
|
@@ -83342,8 +83390,8 @@ const stringifyTableSelectionValue = (type, value) => {
|
|
|
83342
83390
|
|
|
83343
83391
|
/**
|
|
83344
83392
|
* Check if a specific cell is selected
|
|
83345
|
-
* @param {
|
|
83346
|
-
* @param {{
|
|
83393
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83394
|
+
* @param {{columnId: string, rowId: string}} cell - The cell's column and row ids
|
|
83347
83395
|
* @returns {boolean} True if the cell is selected
|
|
83348
83396
|
*/
|
|
83349
83397
|
const isCellSelected = (selection, { columnId, rowId }) => {
|
|
@@ -83356,8 +83404,8 @@ const isCellSelected = (selection, { columnId, rowId }) => {
|
|
|
83356
83404
|
|
|
83357
83405
|
/**
|
|
83358
83406
|
* Check if a specific row is selected
|
|
83359
|
-
* @param {
|
|
83360
|
-
* @param {
|
|
83407
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83408
|
+
* @param {string} rowId - The row's id (the `id` of its `<Tr>`)
|
|
83361
83409
|
* @returns {boolean} True if the row is selected
|
|
83362
83410
|
*/
|
|
83363
83411
|
const isRowSelected = (selection, rowId) => {
|
|
@@ -83367,8 +83415,8 @@ const isRowSelected = (selection, rowId) => {
|
|
|
83367
83415
|
|
|
83368
83416
|
/**
|
|
83369
83417
|
* Check if a specific column is selected
|
|
83370
|
-
* @param {
|
|
83371
|
-
* @param {
|
|
83418
|
+
* @param {string[]} selection - The table's `selection` array
|
|
83419
|
+
* @param {string} columnId - The column's id (the `id` of its `<Col>`)
|
|
83372
83420
|
* @returns {boolean} True if the column is selected
|
|
83373
83421
|
*/
|
|
83374
83422
|
const isColumnSelected = (selection, columnId) => {
|
|
@@ -83903,7 +83951,7 @@ const initDragTableColumnViaPointer = (pointerdownEvent, {
|
|
|
83903
83951
|
/**
|
|
83904
83952
|
* Creates a MutationObserver that syncs attribute changes from original table to clone
|
|
83905
83953
|
* @param {HTMLElement} table - The original table element
|
|
83906
|
-
* @param {HTMLElement}
|
|
83954
|
+
* @param {HTMLElement} tableClone - The cloned table element
|
|
83907
83955
|
* @returns {MutationObserver} The observer instance with disconnect method
|
|
83908
83956
|
*/
|
|
83909
83957
|
const createTableAttributeSync = (table, tableClone) => {
|
|
@@ -85868,9 +85916,10 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
85868
85916
|
* KEY PRINCIPLES:
|
|
85869
85917
|
* - Use inset box-shadow to ensure borders appear above table cell backgrounds
|
|
85870
85918
|
* - Use ::before pseudo-elements with position: absolute for flexible positioning
|
|
85871
|
-
* - Each cell draws its own borders
|
|
85919
|
+
* - Each cell draws its own borders; `borderCollapse` (on by default) keeps only
|
|
85920
|
+
* the right and bottom ones, plus top on the first row and left on the first column
|
|
85872
85921
|
* - Selection borders override table borders using higher CSS specificity
|
|
85873
|
-
* - Sticky borders use thicker box-shadows in accent color
|
|
85922
|
+
* - Sticky borders use thicker box-shadows in an accent color
|
|
85874
85923
|
*
|
|
85875
85924
|
* TECHNICAL IMPLEMENTATION:
|
|
85876
85925
|
* - All borders use inset box-shadow with specific directional mapping:
|
|
@@ -85878,9 +85927,8 @@ const TableUI = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
85878
85927
|
* * Right: inset -1px 0 0 0
|
|
85879
85928
|
* * Bottom: inset 0 -1px 0 0
|
|
85880
85929
|
* * Left: inset 1px 0 0 0
|
|
85881
|
-
* - Selection borders
|
|
85930
|
+
* - Selection borders override table borders in the same pseudo-element
|
|
85882
85931
|
* - Sticky borders replace regular borders with thicker colored variants
|
|
85883
|
-
* - Border-collapse mode available as optional feature for future use
|
|
85884
85932
|
*
|
|
85885
85933
|
* Note how border disappear for sticky elements when using border-collapse (https://bugzilla.mozilla.org/show_bug.cgi?id=1727594)
|
|
85886
85934
|
*
|
|
@@ -87014,11 +87062,18 @@ const BadgeCountOverflow = () => jsx("span", {
|
|
|
87014
87062
|
});
|
|
87015
87063
|
const MAX_CHAR_AS_CIRCLE = 3;
|
|
87016
87064
|
const MAX_FOR_CIRCLE = 99;
|
|
87065
|
+
|
|
87066
|
+
/**
|
|
87067
|
+
* @param {boolean} [circle] Drawn round rather than as a pill. A circle holds
|
|
87068
|
+
* three characters at most, so `max` defaults to 99 with it ("99+"); a `max`
|
|
87069
|
+
* above 99, or `"none"`, lets a bigger count turn the circle into a pill as
|
|
87070
|
+
* it grows.
|
|
87071
|
+
* @param {number|"none"} [max] The count above which `max` and a "+" are
|
|
87072
|
+
* written in its place. 99 under `circle`, no cap otherwise.
|
|
87073
|
+
*/
|
|
87017
87074
|
const BadgeCount = ({
|
|
87018
87075
|
children,
|
|
87019
87076
|
maxElement = jsx(BadgeCountOverflow, {}),
|
|
87020
|
-
// When you use max="none" (or max > 99) it might be a good idea to force ellipse
|
|
87021
|
-
// so that visually the interface do not suddently switch from circle to ellipse depending on the count
|
|
87022
87077
|
circle,
|
|
87023
87078
|
max = circle ? MAX_FOR_CIRCLE : Infinity,
|
|
87024
87079
|
integer,
|
|
@@ -88201,12 +88256,13 @@ const css$7 = /* css */`.navi_text_box {
|
|
|
88201
88256
|
* - Displays optional icon(s) before and/or after the text
|
|
88202
88257
|
* - Shrinks its width to fit the longest rendered text line (no trailing blank space)
|
|
88203
88258
|
* - Wraps long text by default (overflow-wrap: anywhere)
|
|
88204
|
-
* - Shows ellipsis
|
|
88259
|
+
* - Shows an ellipsis instead under `singleLine`
|
|
88205
88260
|
*
|
|
88206
88261
|
* Props:
|
|
88207
88262
|
* iconBefore — element shown to the left of the text
|
|
88208
88263
|
* iconAfter — element shown to the right of the text (stays on the same line)
|
|
88209
|
-
*
|
|
88264
|
+
* singleLine — keeps the text on one line, cut with an ellipsis (no width shrinking)
|
|
88265
|
+
* maxHeight — CSS max-height string; content past it is clipped
|
|
88210
88266
|
* children — the text content
|
|
88211
88267
|
*/
|
|
88212
88268
|
const TextBox = ({
|
|
@@ -88385,6 +88441,41 @@ const css$4 = /* css */`.svg_mask_content * {
|
|
|
88385
88441
|
stroke-opacity: 1 !important;
|
|
88386
88442
|
}
|
|
88387
88443
|
`;
|
|
88444
|
+
|
|
88445
|
+
/**
|
|
88446
|
+
* SVGMaskOverlay
|
|
88447
|
+
*
|
|
88448
|
+
* Creates composite SVGs by combining independent SVG elements with masking.
|
|
88449
|
+
*
|
|
88450
|
+
* This component solves the challenge of combining independently created SVGs into
|
|
88451
|
+
* a single visual composition. Each SVG can have its own coordinate system, viewBox,
|
|
88452
|
+
* and styling, allowing for maximum reusability of individual icons or graphics.
|
|
88453
|
+
*
|
|
88454
|
+
* When overlaying SVGs, each subsequent overlay "cuts out" its portion from the base SVG,
|
|
88455
|
+
* creating a seamless integration where SVGs appear to interact with each other visually.
|
|
88456
|
+
*
|
|
88457
|
+
* Key benefits:
|
|
88458
|
+
* - Maintains each SVG's independence - use them individually elsewhere
|
|
88459
|
+
* - Handles different viewBox dimensions automatically
|
|
88460
|
+
* - Works with any SVG components regardless of internal implementation
|
|
88461
|
+
* - Supports unlimited overlay elements
|
|
88462
|
+
* - Creates proper masking between elements for visual integration
|
|
88463
|
+
*
|
|
88464
|
+
* Usage example combining two independent icon components:
|
|
88465
|
+
* ```jsx
|
|
88466
|
+
* <SVGMaskOverlay viewBox="0 0 24 24">
|
|
88467
|
+
* <DatabaseSvg />
|
|
88468
|
+
* <svg x="12" y="12" width="16" height="16" overflow="visible">
|
|
88469
|
+
* <PlusSvg />
|
|
88470
|
+
* </svg>
|
|
88471
|
+
* </SVGMaskOverlay>
|
|
88472
|
+
* ```
|
|
88473
|
+
*
|
|
88474
|
+
* @param {Object} props - Component properties
|
|
88475
|
+
* @param {string} props.viewBox - The main viewBox for the composition (required as soon as there is an overlay)
|
|
88476
|
+
* @param {import("ignore:preact").ComponentChildren} props.children - SVG elements (first is base, rest are overlays)
|
|
88477
|
+
* @returns {import("ignore:preact").VNode} A composed SVG with all elements properly masked
|
|
88478
|
+
*/
|
|
88388
88479
|
const SVGMaskOverlay = ({
|
|
88389
88480
|
viewBox,
|
|
88390
88481
|
children
|
|
@@ -88397,7 +88488,7 @@ const SVGMaskOverlay = ({
|
|
|
88397
88488
|
return children[0];
|
|
88398
88489
|
}
|
|
88399
88490
|
if (!viewBox) {
|
|
88400
|
-
console.error("
|
|
88491
|
+
console.error("SVGMaskOverlay requires an explicit viewBox");
|
|
88401
88492
|
return null;
|
|
88402
88493
|
}
|
|
88403
88494
|
|
|
@@ -89338,7 +89429,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
89338
89429
|
* interaction only makes sense paired with not letting focus silently
|
|
89339
89430
|
* leave the panel first. A box of the page whose press must not close the
|
|
89340
89431
|
* panel (a card that fills it) names the panel:
|
|
89341
|
-
* `data-navi-popup-inside={id}
|
|
89432
|
+
* `data-navi-popup-inside={id}`, read when the panel has no wall
|
|
89433
|
+
* (`backdrop={false}`) — see docs/popup_backdrop.md.
|
|
89342
89434
|
* @param {boolean} [props.closeByDrag=true] - Pushing the panel back
|
|
89343
89435
|
* towards the edge it is docked to closes it: the panel follows the
|
|
89344
89436
|
* pointer and finishes leaving (or comes back to rest) when it is
|
|
@@ -89353,8 +89445,8 @@ const css = /* css */`.navi_side_panel {
|
|
|
89353
89445
|
* doc).
|
|
89354
89446
|
* @param {import("ignore:preact").ComponentChildren} props.children - No built-in
|
|
89355
89447
|
* close button — add one wherever it makes sense for the layout (e.g. a
|
|
89356
|
-
* plain `<Button command="--navi-close">`
|
|
89357
|
-
* `
|
|
89448
|
+
* plain `<Button command="--navi-close">` in `SidePanel.Head`), or rely on
|
|
89449
|
+
* `closeByPressOutside`/Escape instead.
|
|
89358
89450
|
* A form sent inside the panel closes it, as in any popup: a panel one
|
|
89359
89451
|
* keeps editing in says `command="--navi-void"` on that form (see
|
|
89360
89452
|
* docs/form_changed.md).
|
|
@@ -89445,9 +89537,7 @@ const toCssLength = (value, propertyName) => value === undefined || value === nu
|
|
|
89445
89537
|
* Stuck to the top of the panel's own scrollable area (`position: sticky`)
|
|
89446
89538
|
* regardless of `side` — only the panel's content in between scrolls. No
|
|
89447
89539
|
* built-in padding or close button — add a `<Button command="--navi-close">`
|
|
89448
|
-
*
|
|
89449
|
-
* float-right utility this file's own CSS still provides) wherever it makes
|
|
89450
|
-
* sense for the layout.
|
|
89540
|
+
* wherever it makes sense for the layout.
|
|
89451
89541
|
*
|
|
89452
89542
|
* @param {object} props
|
|
89453
89543
|
* @param {string} [props.className] - Merged with the shared
|