@jsenv/navi 0.29.225 → 0.29.227
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 +2 -0
- package/dist/dev/jsenv_navi.js +168 -115
- package/dist/dev/jsenv_navi.js.map +13 -12
- package/dist/jsenv_navi.js +168 -115
- package/dist/jsenv_navi.js.map +13 -12
- package/docs/AI_INSTRUCTIONS.md +8 -6
- package/docs/dynamic_import.md +118 -69
- package/docs/interactions.md +7 -0
- package/docs/popup_open.md +31 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,8 @@ How to declare routes, render them, and turn them into tabs — including `Route
|
|
|
14
14
|
|
|
15
15
|
Actions are async operations with lifecycle management — pending, success, error. You can declare actions that run when navigating to a route, and any component can subscribe to them via `useAsyncData` to reflect what is happening: loading states, results, errors. No manual wiring.
|
|
16
16
|
|
|
17
|
+
Code loaded on demand is the same thing: a page's `import()` is a route action, started with the page's data on the url change and prefetched when a link to it is hovered or focused; a component inside a page that stays reads its own with `useAsyncData(() => import(…))`. One wait, one failure, no lazy-shaped API. See [docs/dynamic_import.md](./docs/dynamic_import.md).
|
|
18
|
+
|
|
17
19
|
## REST state
|
|
18
20
|
|
|
19
21
|
`resource()` turns a REST endpoint into a reactive store: one action per verb, a
|
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -5249,6 +5249,9 @@ const CONTROL_PROP_SET = new Set([
|
|
|
5249
5249
|
// The wait this control's action creates is its own: it renders busy and
|
|
5250
5250
|
// refuses a second press, and nothing above it is told (see BUSY_CONSTRAINT).
|
|
5251
5251
|
"actionStandalone",
|
|
5252
|
+
// The wait this control's action creates may be called off by the person
|
|
5253
|
+
// waiting: closing the popup it holds gives up on the run (see popup_busy.js).
|
|
5254
|
+
"actionAbortable",
|
|
5252
5255
|
|
|
5253
5256
|
"charGuard",
|
|
5254
5257
|
"maxLengthGuard",
|
|
@@ -8013,11 +8016,10 @@ const BUSY_CONSTRAINT = {
|
|
|
8013
8016
|
// Unless it says the wait is its own (`actionStandalone`): then the refusal
|
|
8014
8017
|
// stays on the element and every ancestor reads it as free — the group above
|
|
8015
8018
|
// (see getInteractionBlockingControls) and the popup around it (see
|
|
8016
|
-
//
|
|
8017
|
-
// `ignoredByParents`).
|
|
8019
|
+
// popup_busy.js, which filters on `ignoredByParents`).
|
|
8018
8020
|
check: (field, { intent } = {}) => {
|
|
8019
|
-
const
|
|
8020
|
-
if (!
|
|
8021
|
+
const busySource = findBusySource(field);
|
|
8022
|
+
if (!busySource) {
|
|
8021
8023
|
return null;
|
|
8022
8024
|
}
|
|
8023
8025
|
|
|
@@ -8038,11 +8040,31 @@ const BUSY_CONSTRAINT = {
|
|
|
8038
8040
|
message,
|
|
8039
8041
|
status: "info",
|
|
8040
8042
|
ignoredByParents: Boolean(field.actionStandalone),
|
|
8043
|
+
// Read off the control the run belongs to, not off this one: a field or a
|
|
8044
|
+
// submit button inherits its form's wait, so it inherits with it whether
|
|
8045
|
+
// the person waiting is allowed to call that wait off (see
|
|
8046
|
+
// abortControlRun, and `actionAbortable` in control_hooks.jsx).
|
|
8047
|
+
abortable: Boolean(busySource.action && busySource.field.actionAbortable),
|
|
8041
8048
|
};
|
|
8042
8049
|
},
|
|
8043
8050
|
};
|
|
8044
8051
|
CONSTRAINT_ATTRIBUTE_SET.add("data-busy");
|
|
8045
8052
|
|
|
8053
|
+
/**
|
|
8054
|
+
* Give up on the run that makes `field` busy — the person waiting deciding the
|
|
8055
|
+
* answer is not coming. The work is not undone: aborting frees the client and
|
|
8056
|
+
* nothing more, the server may already have done it (see docs/actions.md,
|
|
8057
|
+
* "Aborting saves resources, it does not undo"). What ends is the wait, and
|
|
8058
|
+
* with it every refusal held by it.
|
|
8059
|
+
*/
|
|
8060
|
+
const abortControlRun = (field, reason) => {
|
|
8061
|
+
const busySource = findBusySource(field);
|
|
8062
|
+
if (!busySource || !busySource.action) {
|
|
8063
|
+
return false;
|
|
8064
|
+
}
|
|
8065
|
+
return busySource.action.abort(reason);
|
|
8066
|
+
};
|
|
8067
|
+
|
|
8046
8068
|
// Asked source by source rather than off the rendered `aria-busy`, which
|
|
8047
8069
|
// conflates them and is a frame behind: that attribute is written during
|
|
8048
8070
|
// render, so it still says "true" for the whole tick in which an action
|
|
@@ -8054,9 +8076,15 @@ CONSTRAINT_ATTRIBUTE_SET.add("data-busy");
|
|
|
8054
8076
|
// control busy only because the group above is waiting has no state of its own
|
|
8055
8077
|
// to read — the group's answer IS its answer, so it asks upward and inherits
|
|
8056
8078
|
// the same live reading.
|
|
8057
|
-
|
|
8079
|
+
//
|
|
8080
|
+
// What comes back is the control the wait BELONGS to, and the running action
|
|
8081
|
+
// when there is one, because two questions are asked of it: whether this
|
|
8082
|
+
// control is busy at all, and what giving up on that wait would mean.
|
|
8083
|
+
const findBusySource = (field) => {
|
|
8058
8084
|
if (field.loadingFromOwnProp) {
|
|
8059
|
-
|
|
8085
|
+
// A `loading` prop is a wait the app draws itself; there is no run behind
|
|
8086
|
+
// it to call off.
|
|
8087
|
+
return { field, action: null };
|
|
8060
8088
|
}
|
|
8061
8089
|
const { boundAction } = field;
|
|
8062
8090
|
// An optimistic control stays interactive while its bound action runs:
|
|
@@ -8074,14 +8102,14 @@ const isControlBusy = (field) => {
|
|
|
8074
8102
|
// cannot move mid-run, this very gate blocks it.
|
|
8075
8103
|
const liveAction = boundAction.getCurrentAction?.() ?? boundAction;
|
|
8076
8104
|
if (liveAction.runningStateSignal.value === RUNNING) {
|
|
8077
|
-
return
|
|
8105
|
+
return { field, action: liveAction };
|
|
8078
8106
|
}
|
|
8079
8107
|
}
|
|
8080
8108
|
if (field.loadingFromAbove) {
|
|
8081
8109
|
const parent = field.parentUIStateController;
|
|
8082
|
-
return parent ?
|
|
8110
|
+
return parent ? findBusySource(parent) : null;
|
|
8083
8111
|
}
|
|
8084
|
-
return
|
|
8112
|
+
return null;
|
|
8085
8113
|
};
|
|
8086
8114
|
|
|
8087
8115
|
const DISABLED_CONSTRAINT = {
|
|
@@ -23944,50 +23972,30 @@ const parsePattern = (
|
|
|
23944
23972
|
};
|
|
23945
23973
|
|
|
23946
23974
|
/**
|
|
23947
|
-
*
|
|
23975
|
+
* A literal segment the url stops short of is optional when an ancestor
|
|
23976
|
+
* pattern has a path param AT THAT SAME POSITION whose default is this word:
|
|
23977
|
+
* "/admin/settings/:tab" is the page "/admin" opens on when "/admin/:section"
|
|
23978
|
+
* defaults to "settings". Only the connection occupying the segment's index
|
|
23979
|
+
* may excuse it — a query param, or a param sitting elsewhere, says nothing
|
|
23980
|
+
* about this position, so a state defaulting to "games" must not turn
|
|
23981
|
+
* "/places/:slug/games" into a match for "/places/:slug".
|
|
23948
23982
|
*/
|
|
23949
23983
|
const checkIfLiteralCanBeOptionalWithPatternObj = (
|
|
23950
|
-
|
|
23984
|
+
literalSegment,
|
|
23951
23985
|
patternObj,
|
|
23952
23986
|
) => {
|
|
23953
|
-
|
|
23954
|
-
|
|
23955
|
-
|
|
23956
|
-
|
|
23957
|
-
|
|
23958
|
-
|
|
23959
|
-
if (connection.isDefaultValue(literalValue)) {
|
|
23960
|
-
return true;
|
|
23961
|
-
}
|
|
23962
|
-
}
|
|
23963
|
-
|
|
23964
|
-
// Check parent pattern's connections
|
|
23965
|
-
let currentParent = patternObj.parent;
|
|
23966
|
-
while (currentParent) {
|
|
23967
|
-
for (const connection of currentParent.connections) {
|
|
23968
|
-
if (connection.isDefaultValue(literalValue)) {
|
|
23987
|
+
let ancestor = patternObj ? patternObj.parent : null;
|
|
23988
|
+
while (ancestor) {
|
|
23989
|
+
const ancestorSegment = ancestor.pattern.segments[literalSegment.index];
|
|
23990
|
+
if (ancestorSegment && ancestorSegment.type === "param") {
|
|
23991
|
+
const connection = ancestor.pathConnectionMap.get(ancestorSegment.name);
|
|
23992
|
+
if (connection && connection.isDefaultValue(literalSegment.value)) {
|
|
23969
23993
|
return true;
|
|
23970
23994
|
}
|
|
23971
23995
|
}
|
|
23972
|
-
|
|
23996
|
+
ancestor = ancestor.parent;
|
|
23973
23997
|
}
|
|
23974
|
-
|
|
23975
|
-
// Check children pattern's connections
|
|
23976
|
-
const checkChildrenRecursively = (pattern) => {
|
|
23977
|
-
for (const child of pattern.children || []) {
|
|
23978
|
-
for (const connection of child.connections) {
|
|
23979
|
-
if (connection.isDefaultValue(literalValue)) {
|
|
23980
|
-
return true;
|
|
23981
|
-
}
|
|
23982
|
-
}
|
|
23983
|
-
if (checkChildrenRecursively(child)) {
|
|
23984
|
-
return true;
|
|
23985
|
-
}
|
|
23986
|
-
}
|
|
23987
|
-
return false;
|
|
23988
|
-
};
|
|
23989
|
-
|
|
23990
|
-
return checkChildrenRecursively(patternObj);
|
|
23998
|
+
return false;
|
|
23991
23999
|
};
|
|
23992
24000
|
|
|
23993
24001
|
/**
|
|
@@ -24132,9 +24140,8 @@ const matchUrl = (
|
|
|
24132
24140
|
// Check if URL has this segment
|
|
24133
24141
|
if (urlSegmentIndex >= urlSegments.length) {
|
|
24134
24142
|
// URL is too short for this literal segment
|
|
24135
|
-
// Check if this literal segment can be treated as optional based on pattern hierarchy
|
|
24136
24143
|
const canBeOptional = checkIfLiteralCanBeOptionalWithPatternObj(
|
|
24137
|
-
patternSeg
|
|
24144
|
+
patternSeg,
|
|
24138
24145
|
patternObj,
|
|
24139
24146
|
);
|
|
24140
24147
|
if (canBeOptional) {
|
|
@@ -31284,22 +31291,23 @@ const routeAction = (
|
|
|
31284
31291
|
|
|
31285
31292
|
// The instance the effect above runs on the match is the one bound to `true`
|
|
31286
31293
|
// (the default params), so a prerun here is what the arrival promotes to a
|
|
31287
|
-
// run. A prefetch that fails is
|
|
31288
|
-
//
|
|
31294
|
+
// run. A prefetch that fails is not reported: nothing on screen asked for it,
|
|
31295
|
+
// and the failure stays on the instance, where a run asks again — a FAILED
|
|
31296
|
+
// action is run, only RUNNING and COMPLETED are left alone — and the arrival
|
|
31297
|
+
// shows its own. It is left FAILED on purpose, not reset: the press that
|
|
31298
|
+
// brings the arrival also focuses the link, so a prerun can be in flight when
|
|
31299
|
+
// the arrival promotes it, and a reset landing after that would pull the
|
|
31300
|
+
// answer from under the page that is reading it.
|
|
31289
31301
|
const prefetchParamless = (action) => {
|
|
31290
31302
|
const instance = action.bindParams(true);
|
|
31291
|
-
const forget = () => {
|
|
31292
|
-
instance.reset({ reason: "prefetch failed" });
|
|
31293
|
-
};
|
|
31294
31303
|
let result;
|
|
31295
31304
|
try {
|
|
31296
31305
|
result = instance.prerun({ reason: "intent" });
|
|
31297
31306
|
} catch {
|
|
31298
|
-
forget();
|
|
31299
31307
|
return;
|
|
31300
31308
|
}
|
|
31301
31309
|
if (result && typeof result.catch === "function") {
|
|
31302
|
-
result.catch(
|
|
31310
|
+
result.catch(() => {});
|
|
31303
31311
|
}
|
|
31304
31312
|
};
|
|
31305
31313
|
|
|
@@ -37627,7 +37635,8 @@ const useInteractiveProps = (props, {
|
|
|
37627
37635
|
readOnly,
|
|
37628
37636
|
loading,
|
|
37629
37637
|
optimistic,
|
|
37630
|
-
actionStandalone
|
|
37638
|
+
actionStandalone,
|
|
37639
|
+
actionAbortable
|
|
37631
37640
|
} = props;
|
|
37632
37641
|
|
|
37633
37642
|
// `whenSelfInteractionsBlocked="ignore"`: an affordance that writes nothing
|
|
@@ -37691,6 +37700,12 @@ const useInteractiveProps = (props, {
|
|
|
37691
37700
|
// above it is told, so a form still submits and a popup still closes over a
|
|
37692
37701
|
// run that was meant to be left going.
|
|
37693
37702
|
uiStateController.actionStandalone = Boolean(actionStandalone);
|
|
37703
|
+
// Read by BUSY_CONSTRAINT: the wait holds what is around it as always, and
|
|
37704
|
+
// the person waiting may end it — closing the popup this control holds
|
|
37705
|
+
// calls the run off instead of being refused (see popup_busy.js). Read off
|
|
37706
|
+
// the control the run belongs to, so a field or a submit inheriting a
|
|
37707
|
+
// form's wait inherits this with it.
|
|
37708
|
+
uiStateController.actionAbortable = Boolean(actionAbortable);
|
|
37694
37709
|
// What the interaction rule last refused is only true while the control is
|
|
37695
37710
|
// held; the state it was read from moves here (see refreshReport).
|
|
37696
37711
|
useLayoutEffect(() => {
|
|
@@ -48351,6 +48366,7 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48351
48366
|
* whenSelfInteractionsBlocked?: "hide" | "refuse" | "ignore",
|
|
48352
48367
|
* replace?: boolean,
|
|
48353
48368
|
* actionStandalone?: boolean,
|
|
48369
|
+
* actionAbortable?: boolean,
|
|
48354
48370
|
* [key: string]: any,
|
|
48355
48371
|
* }>}
|
|
48356
48372
|
* @param {boolean} [replace] Go where the press leads — an `href`, a
|
|
@@ -48394,6 +48410,14 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
48394
48410
|
* running, which the app watches from somewhere else; never for one holding
|
|
48395
48411
|
* an answer the screen is the only place to read (see
|
|
48396
48412
|
* docs/interactions.md#the-fourth-question-whose-wait-is-it).
|
|
48413
|
+
* @param {boolean} [actionAbortable] The person waiting may give up on this
|
|
48414
|
+
* button's action: closing the popup the run holds calls it off and goes
|
|
48415
|
+
* through, instead of being refused. For a run whose answer may never come —
|
|
48416
|
+
* a request over a network that stops answering — where a popup with no way
|
|
48417
|
+
* out is worse than an answer lost. Aborting frees the client and nothing
|
|
48418
|
+
* more: the work may already have happened on the other side (see
|
|
48419
|
+
* docs/actions.md#aborting-saves-resources-it-does-not-undo), so say it only
|
|
48420
|
+
* where the screen can be re-opened on what is actually there.
|
|
48397
48421
|
* @param {string} [contentDisplay] The display of the frame the button draws
|
|
48398
48422
|
* around its children. It follows the button's own by default — its display
|
|
48399
48423
|
* and, a display alone saying nothing about direction, the rest of its flow
|
|
@@ -57341,6 +57365,66 @@ const getAvailableWidth = (layer, element) => {
|
|
|
57341
57365
|
return visualViewportWidthSignal.peek();
|
|
57342
57366
|
};
|
|
57343
57367
|
|
|
57368
|
+
/**
|
|
57369
|
+
* What a popup asks before it closes: is anything inside it mid-action, and may
|
|
57370
|
+
* the person waiting call that wait off.
|
|
57371
|
+
*
|
|
57372
|
+
* A popup carries no state of its own — it is layout (see dialog.jsx's top
|
|
57373
|
+
* comment) — so it walks the controls it contains and asks each one. Asked
|
|
57374
|
+
* rather than read off `aria-busy`, which is a render snapshot: BUSY_CONSTRAINT
|
|
57375
|
+
* answers live.
|
|
57376
|
+
*
|
|
57377
|
+
* `Dialog` and `Popover` both read this way and read it identically, so it
|
|
57378
|
+
* lives here rather than in each of them: the answer decides whether a close
|
|
57379
|
+
* request goes through, and the two must not drift on that.
|
|
57380
|
+
*/
|
|
57381
|
+
|
|
57382
|
+
|
|
57383
|
+
/**
|
|
57384
|
+
* The controls keeping `popupEl` open: mid-action, and not saying the wait is
|
|
57385
|
+
* their own. `ignoredByParents` is that saying (`actionStandalone`) — a popup
|
|
57386
|
+
* is one more ancestor such a control does not hold, the same reading a group
|
|
57387
|
+
* makes of it (see control_interaction.js).
|
|
57388
|
+
*/
|
|
57389
|
+
const findControlsHoldingPopup = (popupEl) => {
|
|
57390
|
+
const controlsHolding = [];
|
|
57391
|
+
for (const element of popupEl.querySelectorAll("[navi-control-host]")) {
|
|
57392
|
+
const controller = element.__uiStateController__;
|
|
57393
|
+
if (!controller) {
|
|
57394
|
+
continue;
|
|
57395
|
+
}
|
|
57396
|
+
const busyInfo = BUSY_CONSTRAINT.check(controller);
|
|
57397
|
+
if (busyInfo && !busyInfo.ignoredByParents) {
|
|
57398
|
+
controlsHolding.push({
|
|
57399
|
+
element,
|
|
57400
|
+
controller,
|
|
57401
|
+
abortable: busyInfo.abortable,
|
|
57402
|
+
});
|
|
57403
|
+
}
|
|
57404
|
+
}
|
|
57405
|
+
return controlsHolding;
|
|
57406
|
+
};
|
|
57407
|
+
|
|
57408
|
+
/**
|
|
57409
|
+
* Give up on every run holding the popup, so that closing may go through.
|
|
57410
|
+
*
|
|
57411
|
+
* All or nothing: calling off one run while another still keeps the popup shut
|
|
57412
|
+
* would cost an answer and change nothing on screen. Several of these controls
|
|
57413
|
+
* usually share one run — a form and the fields and submit inside it — and the
|
|
57414
|
+
* ones asked after it has been called off simply find nothing to call off.
|
|
57415
|
+
*/
|
|
57416
|
+
const giveUpOnControlsHoldingPopup = (controlsHolding, reason) => {
|
|
57417
|
+
for (const controlHolding of controlsHolding) {
|
|
57418
|
+
if (!controlHolding.abortable) {
|
|
57419
|
+
return false;
|
|
57420
|
+
}
|
|
57421
|
+
}
|
|
57422
|
+
for (const controlHolding of controlsHolding) {
|
|
57423
|
+
abortControlRun(controlHolding.controller, reason);
|
|
57424
|
+
}
|
|
57425
|
+
return true;
|
|
57426
|
+
};
|
|
57427
|
+
|
|
57344
57428
|
/**
|
|
57345
57429
|
* Entry/exit animation CSS shared by Popover and Dialog.
|
|
57346
57430
|
*
|
|
@@ -58130,7 +58214,7 @@ const PopupClose = ({
|
|
|
58130
58214
|
// and what is opened has to be closable. Whether closing is allowed at
|
|
58131
58215
|
// this instant is the popup's own question, answered by the popup — a
|
|
58132
58216
|
// dialog holds the close while an action inside it runs (see
|
|
58133
|
-
//
|
|
58217
|
+
// popup_busy.js).
|
|
58134
58218
|
,
|
|
58135
58219
|
whenSelfInteractionsBlocked: "ignore"
|
|
58136
58220
|
// The cross is drawn at the control size, which is a few millimetres
|
|
@@ -58879,14 +58963,21 @@ const UncontrolledDialog = props => {
|
|
|
58879
58963
|
// dialog has no such state of its own to consult (it is layout — see
|
|
58880
58964
|
// this file's top comment), so it asks what it contains, and lets that
|
|
58881
58965
|
// control report why the way it would to anyone else.
|
|
58882
|
-
const
|
|
58883
|
-
if (
|
|
58884
|
-
|
|
58885
|
-
|
|
58886
|
-
|
|
58887
|
-
|
|
58888
|
-
|
|
58966
|
+
const controlsHolding = findControlsHoldingPopup(dialogEl);
|
|
58967
|
+
if (controlsHolding.length === 0) {
|
|
58968
|
+
return;
|
|
58969
|
+
}
|
|
58970
|
+
// Unless every one of those runs was declared givable-up
|
|
58971
|
+
// (`actionAbortable`): the answer is not coming, closing is how the
|
|
58972
|
+
// person waiting says so, and the runs are called off on the way out.
|
|
58973
|
+
if (giveUpOnControlsHoldingPopup(controlsHolding, "the dialog holding the run was closed")) {
|
|
58974
|
+
return;
|
|
58889
58975
|
}
|
|
58976
|
+
dispatchRequestInteraction(controlsHolding[0].element, {
|
|
58977
|
+
event: requestCloseEvent,
|
|
58978
|
+
name: "dialog request close"
|
|
58979
|
+
});
|
|
58980
|
+
requestCloseEvent.preventDefault();
|
|
58890
58981
|
},
|
|
58891
58982
|
onClose: closeEvent => {
|
|
58892
58983
|
props.onClose?.(closeEvent);
|
|
@@ -59006,29 +59097,6 @@ const DOCKED = {
|
|
|
59006
59097
|
// no header and nothing marked is not pushed down at all; it is closed by its
|
|
59007
59098
|
// own controls, by the backdrop and by Escape.
|
|
59008
59099
|
const DOCKED_SWIPE_GRIP = "[data-header],[data-swipe-grip]";
|
|
59009
|
-
|
|
59010
|
-
// The first control inside `dialogEl` that is mid-action, if any. Walks the
|
|
59011
|
-
// controls rather than reading an attribute off the dialog: a dialog carries no
|
|
59012
|
-
// state of its own (see this file's top comment), and `aria-busy` on the
|
|
59013
|
-
// controls is a render snapshot — BUSY_CONSTRAINT reads the live answer.
|
|
59014
|
-
// Same as Popover's own; kept in both rather than shared, since each file reads
|
|
59015
|
-
// on its own — what changes here changes there too.
|
|
59016
|
-
const findBusyElementInside$1 = dialogEl => {
|
|
59017
|
-
for (const element of dialogEl.querySelectorAll("[navi-control-host]")) {
|
|
59018
|
-
const controller = element.__uiStateController__;
|
|
59019
|
-
if (!controller) {
|
|
59020
|
-
continue;
|
|
59021
|
-
}
|
|
59022
|
-
const busyInfo = BUSY_CONSTRAINT.check(controller);
|
|
59023
|
-
// `ignoredByParents`: the control says the wait is its own
|
|
59024
|
-
// (`actionStandalone`), so a popup is one more ancestor it does not hold —
|
|
59025
|
-
// the same reading a group makes of it (see control_interaction.js).
|
|
59026
|
-
if (busyInfo && !busyInfo.ignoredByParents) {
|
|
59027
|
-
return element;
|
|
59028
|
-
}
|
|
59029
|
-
}
|
|
59030
|
-
return null;
|
|
59031
|
-
};
|
|
59032
59100
|
const useDialogProps = props => {
|
|
59033
59101
|
const backdropProps = {};
|
|
59034
59102
|
const contentProps = {};
|
|
@@ -60490,14 +60558,21 @@ const UncontrolledPopover = props => {
|
|
|
60490
60558
|
debugPopup(openEvent, `popover opened`);
|
|
60491
60559
|
return {
|
|
60492
60560
|
onRequestClose: requestCloseEvent => {
|
|
60493
|
-
const
|
|
60494
|
-
if (
|
|
60495
|
-
|
|
60496
|
-
|
|
60497
|
-
|
|
60498
|
-
|
|
60499
|
-
|
|
60561
|
+
const controlsHolding = findControlsHoldingPopup(popoverEl);
|
|
60562
|
+
if (controlsHolding.length === 0) {
|
|
60563
|
+
return;
|
|
60564
|
+
}
|
|
60565
|
+
// Unless every one of those runs was declared givable-up
|
|
60566
|
+
// (`actionAbortable`): the answer is not coming, closing is how the
|
|
60567
|
+
// person waiting says so, and the runs are called off on the way out.
|
|
60568
|
+
if (giveUpOnControlsHoldingPopup(controlsHolding, "the popover holding the run was closed")) {
|
|
60569
|
+
return;
|
|
60500
60570
|
}
|
|
60571
|
+
dispatchRequestInteraction(controlsHolding[0].element, {
|
|
60572
|
+
event: requestCloseEvent,
|
|
60573
|
+
name: "popover request close"
|
|
60574
|
+
});
|
|
60575
|
+
requestCloseEvent.preventDefault();
|
|
60501
60576
|
},
|
|
60502
60577
|
onClose: closeEvent => {
|
|
60503
60578
|
props.onClose?.(closeEvent);
|
|
@@ -60606,28 +60681,6 @@ const PopoverCustom = props => {
|
|
|
60606
60681
|
* contentProps]` — two plain prop objects ready to spread onto a
|
|
60607
60682
|
* backdrop/content element each.
|
|
60608
60683
|
*/
|
|
60609
|
-
// The first control inside `popupEl` that is mid-action, if any. Walks the
|
|
60610
|
-
// controls rather than reading an attribute off the popup: a popup carries no
|
|
60611
|
-
// state of its own (see this file's top comment), and `aria-busy` on the
|
|
60612
|
-
// controls is a render snapshot — BUSY_CONSTRAINT reads the live answer.
|
|
60613
|
-
// Same as Dialog's own; kept in both rather than shared, since each file reads
|
|
60614
|
-
// on its own — what changes here changes there too.
|
|
60615
|
-
const findBusyElementInside = popupEl => {
|
|
60616
|
-
for (const element of popupEl.querySelectorAll("[navi-control-host]")) {
|
|
60617
|
-
const controller = element.__uiStateController__;
|
|
60618
|
-
if (!controller) {
|
|
60619
|
-
continue;
|
|
60620
|
-
}
|
|
60621
|
-
const busyInfo = BUSY_CONSTRAINT.check(controller);
|
|
60622
|
-
// `ignoredByParents`: the control says the wait is its own
|
|
60623
|
-
// (`actionStandalone`), so a popup is one more ancestor it does not hold —
|
|
60624
|
-
// the same reading a group makes of it (see control_interaction.js).
|
|
60625
|
-
if (busyInfo && !busyInfo.ignoredByParents) {
|
|
60626
|
-
return element;
|
|
60627
|
-
}
|
|
60628
|
-
}
|
|
60629
|
-
return null;
|
|
60630
|
-
};
|
|
60631
60684
|
const usePopoverProps = props => {
|
|
60632
60685
|
const backdropProps = {};
|
|
60633
60686
|
const contentProps = {};
|