@jsenv/navi 0.29.108 → 0.29.109
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/jsenv_navi.js +143 -78
- package/dist/jsenv_navi.js.map +11 -10
- package/docs/popup_open.md +3 -1
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { installImportMetaCssBuild, windowHeightSignal, windowWidthSignal, visualViewportHeightSignal, visualViewportWidthSignal, getAppHeight, getAppWidth, coarsePointerSignal, smallTouchScreenSignal } from "./jsenv_navi_side_effects.js";
|
|
6
6
|
export { disableVirtualKeyboardOverlay } from "./jsenv_navi_side_effects.js";
|
|
7
|
-
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition,
|
|
7
|
+
import { elementIsFocusable, createIterableWeakSet, dispatchInternalCustomEvent, dispatchCustomEvent, getVisuallyVisibleInfo, getFirstVisuallyVisibleAncestor, getElementSignature, createPubSub, findEvent, createValueEffect, findFocusDelegateTarget, findFocusable, allowWheelThrough, dispatchPublicCustomEvent, resolveCSSColor, ELEMENT_SIZE_CHANGE, findSelfOrAncestorFixedPosition, visibleRectEffect, pickPositionRelativeTo, getBorderSizes, getPaddingSizes, applyNewPosition, measureLongestVisualLineWidth, chainEvent, waitForPressHeld, suppressClickAfterGesture, startDragToTravel, markDragSource, startDragTo, createEventGroupLogger, getKeyboardEventDefaultAction, activeElementSignal, normalizeStyle, mergeOneStyle, getPositionedParent, normalizeStyles, createGroupTransitionController, getBorderRadius, preventIntermediateScrollbar, createOpacityTransition, watchWheelTravel, scrollRoomTowards, closestOpenableAncestor, isAncestorOpen, observeAncestorOpenState, getAncestorOpenType, findBefore, findAfter, resolveCSSSize, hasCSSSizeUnit, initFocusGroup, scrollIntoViewScoped, stringifyStyle as stringifyStyle$1, resolveOklchLightness, contrastColor, isTouchDrivenEvent, parsePositionArea, snapToPixel, trapFocusInside, trapScrollInside, onAncestorReopen, getScrollContainer, canScroll, measureWidestChildRow, performTabNavigation, wheelGestureIsTakenFrom, releaseWheelGesture, claimWheelGesture, dragAfterIntent, stickyAsRelativeCoords, createDragToMoveGestureController, getDropTargetInfo, setStyles, useActiveElement } from "@jsenv/dom";
|
|
8
8
|
export { clickIsSuppressed, contrastColor, findEvent, startDragTo } from "@jsenv/dom";
|
|
9
9
|
import { signal, computed, effect, batch, untracked, useSignal } from "@preact/signals";
|
|
10
10
|
import { isValidElement, createContext, render, h, Fragment, toChildArray, options, cloneElement } from "preact";
|
|
@@ -25330,75 +25330,85 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
25330
25330
|
return requestedResult;
|
|
25331
25331
|
};
|
|
25332
25332
|
|
|
25333
|
-
//
|
|
25333
|
+
// A click on a link is answered on the link itself, after the link's own
|
|
25334
|
+
// listeners: a navi control decides there whether the press is allowed — it
|
|
25335
|
+
// refuses with preventDefault from its click reaction — and runs its command
|
|
25336
|
+
// and its action; only then does the routing take the browser's place. This
|
|
25337
|
+
// is the order the Navigation API integration gets for free, where the
|
|
25338
|
+
// navigation starts once the click has been dispatched.
|
|
25339
|
+
const onLinkClick = (e) => {
|
|
25340
|
+
if (e.button !== 0) {
|
|
25341
|
+
// Ignore non-left clicks
|
|
25342
|
+
return;
|
|
25343
|
+
}
|
|
25344
|
+
if (e.metaKey) {
|
|
25345
|
+
// Ignore clicks with meta key (e.g. open in new tab)
|
|
25346
|
+
return;
|
|
25347
|
+
}
|
|
25348
|
+
if (e.defaultPrevented) {
|
|
25349
|
+
// Refused — by the link itself, or by whoever came before it.
|
|
25350
|
+
return;
|
|
25351
|
+
}
|
|
25352
|
+
const linkElement = e.currentTarget;
|
|
25353
|
+
if (linkElement.hasAttribute("data-readonly")) {
|
|
25354
|
+
return;
|
|
25355
|
+
}
|
|
25356
|
+
const href = linkElement.href;
|
|
25357
|
+
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
25358
|
+
getHrefTargetInfo(href);
|
|
25359
|
+
if (isEmpty || !isSameOrigin) {
|
|
25360
|
+
// Let link to other origins be handled by the browser
|
|
25361
|
+
return;
|
|
25362
|
+
}
|
|
25363
|
+
if (isAnchor) {
|
|
25364
|
+
// Fragment navigation belongs to the browser: it owns the indicated
|
|
25365
|
+
// part of the document, and taking it over would cost `:target` and the
|
|
25366
|
+
// focus handling that come with it.
|
|
25367
|
+
if (isCurrent) {
|
|
25368
|
+
// Except this one, which the browser answers with a scroll and
|
|
25369
|
+
// nothing else: same pathname, same hash, so no event and no url
|
|
25370
|
+
// change reaches whoever is waiting on the designated element.
|
|
25371
|
+
rearmUrlTarget();
|
|
25372
|
+
}
|
|
25373
|
+
return;
|
|
25374
|
+
}
|
|
25375
|
+
// Nothing here declared a route, so there is nothing to route to: the
|
|
25376
|
+
// page is a plain document and a link in it is a plain link. Taking it
|
|
25377
|
+
// over anyway would push the url and then have nothing to show for it —
|
|
25378
|
+
// the address bar moves and the page does not (see applyRouting's own
|
|
25379
|
+
// "not called yet" branch, which is where that used to end up).
|
|
25380
|
+
if (!isRouting()) {
|
|
25381
|
+
return;
|
|
25382
|
+
}
|
|
25383
|
+
e.preventDefault();
|
|
25384
|
+
handleRoutingTask(href, {
|
|
25385
|
+
reason: `"click" on a[href="${href}"]`,
|
|
25386
|
+
// A link that takes the place of the current entry instead of stacking
|
|
25387
|
+
// on it says so on itself (see link_replace.js).
|
|
25388
|
+
navigationType: linkAsksForReplace(linkElement) ? "replace" : "push",
|
|
25389
|
+
// Who started it. Announced with the navigation because a press
|
|
25390
|
+
// carries things the url does not: what a link asks of a route
|
|
25391
|
+
// transition is the first of them (see route_transition.jsx). Read by
|
|
25392
|
+
// whoever knows what to do with it, and it is the anchor itself —
|
|
25393
|
+
// resolved here, where it already is.
|
|
25394
|
+
element: linkElement,
|
|
25395
|
+
});
|
|
25396
|
+
};
|
|
25397
|
+
// Wired from the capture phase on window, the first place a click is seen,
|
|
25398
|
+
// so that a link is answered whatever handler stops the click on its way up
|
|
25399
|
+
// (a card's own onClick). Wired on every click: a listener an element
|
|
25400
|
+
// already has is not added twice, and a link seen for the first time is
|
|
25401
|
+
// wired before the click reaches it. The click a gesture leaves behind
|
|
25402
|
+
// never gets this far — its suppressor (click_suppression.js in
|
|
25403
|
+
// @jsenv/dom) stops it on window, before anything below.
|
|
25334
25404
|
window.addEventListener(
|
|
25335
25405
|
"click",
|
|
25336
25406
|
(e) => {
|
|
25337
|
-
if (e.button !== 0) {
|
|
25338
|
-
// Ignore non-left clicks
|
|
25339
|
-
return;
|
|
25340
|
-
}
|
|
25341
|
-
if (e.metaKey) {
|
|
25342
|
-
// Ignore clicks with meta key (e.g. open in new tab)
|
|
25343
|
-
return;
|
|
25344
|
-
}
|
|
25345
|
-
if (e.defaultPrevented) {
|
|
25346
|
-
return;
|
|
25347
|
-
}
|
|
25348
|
-
if (clickIsSuppressed()) {
|
|
25349
|
-
// The click that ends a gesture (click_suppression.js in @jsenv/dom).
|
|
25350
|
-
// Its suppressor also listens on window in capture, so whichever
|
|
25351
|
-
// module registered first runs first — asked explicitly, the order
|
|
25352
|
-
// stops mattering.
|
|
25353
|
-
return;
|
|
25354
|
-
}
|
|
25355
25407
|
const linkElement = e.target.closest("a");
|
|
25356
25408
|
if (!linkElement) {
|
|
25357
25409
|
return;
|
|
25358
25410
|
}
|
|
25359
|
-
|
|
25360
|
-
return;
|
|
25361
|
-
}
|
|
25362
|
-
const href = linkElement.href;
|
|
25363
|
-
const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
|
|
25364
|
-
getHrefTargetInfo(href);
|
|
25365
|
-
if (isEmpty || !isSameOrigin) {
|
|
25366
|
-
// Let link to other origins be handled by the browser
|
|
25367
|
-
return;
|
|
25368
|
-
}
|
|
25369
|
-
if (isAnchor) {
|
|
25370
|
-
// Fragment navigation belongs to the browser: it owns the indicated
|
|
25371
|
-
// part of the document, and taking it over would cost `:target` and the
|
|
25372
|
-
// focus handling that come with it.
|
|
25373
|
-
if (isCurrent) {
|
|
25374
|
-
// Except this one, which the browser answers with a scroll and
|
|
25375
|
-
// nothing else: same pathname, same hash, so no event and no url
|
|
25376
|
-
// change reaches whoever is waiting on the designated element.
|
|
25377
|
-
rearmUrlTarget();
|
|
25378
|
-
}
|
|
25379
|
-
return;
|
|
25380
|
-
}
|
|
25381
|
-
// Nothing here declared a route, so there is nothing to route to: the
|
|
25382
|
-
// page is a plain document and a link in it is a plain link. Taking it
|
|
25383
|
-
// over anyway would push the url and then have nothing to show for it —
|
|
25384
|
-
// the address bar moves and the page does not (see applyRouting's own
|
|
25385
|
-
// "not called yet" branch, which is where that used to end up).
|
|
25386
|
-
if (!isRouting()) {
|
|
25387
|
-
return;
|
|
25388
|
-
}
|
|
25389
|
-
e.preventDefault();
|
|
25390
|
-
handleRoutingTask(href, {
|
|
25391
|
-
reason: `"click" on a[href="${href}"]`,
|
|
25392
|
-
// A link that takes the place of the current entry instead of stacking
|
|
25393
|
-
// on it says so on itself (see link_replace.js).
|
|
25394
|
-
navigationType: linkAsksForReplace(linkElement) ? "replace" : "push",
|
|
25395
|
-
// Who started it. Announced with the navigation because a press
|
|
25396
|
-
// carries things the url does not: what a link asks of a route
|
|
25397
|
-
// transition is the first of them (see route_transition.jsx). Read by
|
|
25398
|
-
// whoever knows what to do with it, and it is the anchor itself —
|
|
25399
|
-
// resolved here, where it already is.
|
|
25400
|
-
element: linkElement,
|
|
25401
|
-
});
|
|
25411
|
+
linkElement.addEventListener("click", onLinkClick);
|
|
25402
25412
|
},
|
|
25403
25413
|
{ capture: true },
|
|
25404
25414
|
);
|
|
@@ -29838,7 +29848,11 @@ registerNaviCommand("--navi-open", (source, event, { anchor, value } = {}) => {
|
|
|
29838
29848
|
},
|
|
29839
29849
|
};
|
|
29840
29850
|
});
|
|
29841
|
-
|
|
29851
|
+
// "--navi-close:all" closes every expandable above the source, nearest first —
|
|
29852
|
+
// a link leaving from a badge shown over a sheet leaves both. A surface that
|
|
29853
|
+
// refuses (a form asking about its changes) keeps what is above it open too:
|
|
29854
|
+
// one cannot be out of the sheet while still in the badge.
|
|
29855
|
+
registerNaviCommand("--navi-close", (source, event, { argument }) => {
|
|
29842
29856
|
const target =
|
|
29843
29857
|
resolveExplicitTarget(source) || resolveClosestExpandable(source);
|
|
29844
29858
|
if (!target) {
|
|
@@ -29847,13 +29861,30 @@ registerNaviCommand("--navi-close", (source, event) => {
|
|
|
29847
29861
|
return {
|
|
29848
29862
|
target,
|
|
29849
29863
|
implementation: () => {
|
|
29850
|
-
|
|
29851
|
-
|
|
29852
|
-
|
|
29853
|
-
}
|
|
29864
|
+
const detail = { event, source: resolveCommandProxySource(source) };
|
|
29865
|
+
if (argument === "all") {
|
|
29866
|
+
return requestCloseUpward(target, detail);
|
|
29867
|
+
}
|
|
29868
|
+
return dispatchCustomEvent(target, "navi_request_close", detail);
|
|
29854
29869
|
},
|
|
29855
29870
|
};
|
|
29856
29871
|
});
|
|
29872
|
+
const requestCloseUpward = (target, detail) => {
|
|
29873
|
+
let expandable = target;
|
|
29874
|
+
while (expandable) {
|
|
29875
|
+
const closing = dispatchCustomEvent(
|
|
29876
|
+
expandable,
|
|
29877
|
+
"navi_request_close",
|
|
29878
|
+
detail,
|
|
29879
|
+
);
|
|
29880
|
+
if (!closing) {
|
|
29881
|
+
return false;
|
|
29882
|
+
}
|
|
29883
|
+
const parent = expandable.parentElement;
|
|
29884
|
+
expandable = parent ? parent.closest("[aria-expanded]") : null;
|
|
29885
|
+
}
|
|
29886
|
+
return true;
|
|
29887
|
+
};
|
|
29857
29888
|
registerNaviCommand("--navi-cancel", (source, event) => {
|
|
29858
29889
|
const target =
|
|
29859
29890
|
resolveExplicitTarget(source) || resolveClosestExpandable(source);
|
|
@@ -33573,6 +33604,13 @@ const useControlProps = (props, {
|
|
|
33573
33604
|
}
|
|
33574
33605
|
};
|
|
33575
33606
|
}
|
|
33607
|
+
if (getKeyboardEventDefaultAction(e) === "activate") {
|
|
33608
|
+
// Enter: the browser presses the link itself, with a click that
|
|
33609
|
+
// follows this keydown — and the click reaction below is where
|
|
33610
|
+
// the press is answered, once. A tab for a slide (no href) gets
|
|
33611
|
+
// no such click; Link answers Enter on its own there.
|
|
33612
|
+
return null;
|
|
33613
|
+
}
|
|
33576
33614
|
return keyDownDefault(e);
|
|
33577
33615
|
},
|
|
33578
33616
|
click: e => {
|
|
@@ -39127,9 +39165,12 @@ const LinkPlain = props => {
|
|
|
39127
39165
|
replace: undefined,
|
|
39128
39166
|
"data-navi-route-transition-request": routeTransitionRequest,
|
|
39129
39167
|
...replaceRequest,
|
|
39168
|
+
// The control's own handlers first — the interaction gate, the caller's
|
|
39169
|
+
// onClick/onKeyDown, the command and the action — then what only a link
|
|
39170
|
+
// does. Written over the spread above, so they have to be called here.
|
|
39130
39171
|
onClick: e => {
|
|
39131
|
-
onClick
|
|
39132
|
-
if (slide) {
|
|
39172
|
+
controlHostProps.onClick(e);
|
|
39173
|
+
if (slide && !e.defaultPrevented) {
|
|
39133
39174
|
goToSlide(e.currentTarget, e);
|
|
39134
39175
|
}
|
|
39135
39176
|
if (preventDefault) {
|
|
@@ -39142,7 +39183,7 @@ const LinkPlain = props => {
|
|
|
39142
39183
|
,
|
|
39143
39184
|
|
|
39144
39185
|
onKeyDown: e => {
|
|
39145
|
-
|
|
39186
|
+
controlHostProps.onKeyDown(e);
|
|
39146
39187
|
if (!slide || e.defaultPrevented) {
|
|
39147
39188
|
return;
|
|
39148
39189
|
}
|
|
@@ -45772,6 +45813,9 @@ const COMMAND_DEFAULT_PROPS_FACTORIES = {
|
|
|
45772
45813
|
* `--navi-nav-to` command — by TAKING THE PLACE of the current history entry
|
|
45773
45814
|
* rather than stacking on it: what `<Link replace>` says, for a press drawn
|
|
45774
45815
|
* as a button.
|
|
45816
|
+
* @param {Function} [action] On a button with an `href` or a `route`, the
|
|
45817
|
+
* same order as a Link's: it runs on the press, before the navigation, and
|
|
45818
|
+
* the navigation does not wait for it (see Link's `action`).
|
|
45775
45819
|
* @param {boolean} [emojiAsIcon=true] Renders the emoji of the label as icons
|
|
45776
45820
|
* so the button keeps the height of its text — `Text`'s prop, on by default
|
|
45777
45821
|
* here. Pass `false` to let an emoji draw at its natural size.
|
|
@@ -51781,7 +51825,8 @@ const FOCUS_DELAY_ON_KEYBOARD_MS = 250;
|
|
|
51781
51825
|
* - `open()`: requests opening — calls the caller's `onOpen` (see below), then
|
|
51782
51826
|
* `mountContent`/`openEffect`, then `openHandler`.
|
|
51783
51827
|
* - `requestClose()`: requests closing — calls `onRequestClose` then `onClose`,
|
|
51784
|
-
* stopping after the first if denied. The popup may choose to stay open
|
|
51828
|
+
* stopping after the first if denied. The popup may choose to stay open,
|
|
51829
|
+
* which is what a `false` return says (`true`: closed, or closed already).
|
|
51785
51830
|
* - `close()`: closes for real — calls only `onClose`, skipping
|
|
51786
51831
|
* `onRequestClose` entirely. Used when there really is no choice (e.g. the
|
|
51787
51832
|
* popup unmounting).
|
|
@@ -52074,7 +52119,7 @@ const createOpenController = (
|
|
|
52074
52119
|
detail,
|
|
52075
52120
|
) => {
|
|
52076
52121
|
if (!controller.opened) {
|
|
52077
|
-
return;
|
|
52122
|
+
return true;
|
|
52078
52123
|
}
|
|
52079
52124
|
const requestCloseEvent = new CustomEvent("navi_request_close", {
|
|
52080
52125
|
detail: { event: e, ...detail },
|
|
@@ -52089,9 +52134,10 @@ const createOpenController = (
|
|
|
52089
52134
|
if (nativeCancelEvent) {
|
|
52090
52135
|
nativeCancelEvent.preventDefault();
|
|
52091
52136
|
}
|
|
52092
|
-
return;
|
|
52137
|
+
return false;
|
|
52093
52138
|
}
|
|
52094
52139
|
performClose(requestCloseEvent);
|
|
52140
|
+
return true;
|
|
52095
52141
|
},
|
|
52096
52142
|
close: (e = new CustomEvent("programmatic", { detail: {} }), detail) => {
|
|
52097
52143
|
if (!controller.opened) {
|
|
@@ -53658,10 +53704,14 @@ const UncontrolledDialog = props => {
|
|
|
53658
53704
|
});
|
|
53659
53705
|
},
|
|
53660
53706
|
onnavi_request_close: e => {
|
|
53661
|
-
openController.requestClose(e, {
|
|
53707
|
+
const closing = openController.requestClose(e, {
|
|
53662
53708
|
isCancel: e.detail?.isCancel,
|
|
53663
53709
|
requester: e.detail?.source
|
|
53664
53710
|
});
|
|
53711
|
+
if (!closing) {
|
|
53712
|
+
// Said back to whoever asked: --navi-close:all stops climbing here.
|
|
53713
|
+
e.preventDefault();
|
|
53714
|
+
}
|
|
53665
53715
|
}
|
|
53666
53716
|
});
|
|
53667
53717
|
};
|
|
@@ -55108,9 +55158,13 @@ const UncontrolledPopover = props => {
|
|
|
55108
55158
|
});
|
|
55109
55159
|
},
|
|
55110
55160
|
onnavi_request_close: e => {
|
|
55111
|
-
openController.requestClose(e, {
|
|
55161
|
+
const closing = openController.requestClose(e, {
|
|
55112
55162
|
isCancel: e.detail?.isCancel
|
|
55113
55163
|
});
|
|
55164
|
+
if (!closing) {
|
|
55165
|
+
// Said back to whoever asked: --navi-close:all stops climbing here.
|
|
55166
|
+
e.preventDefault();
|
|
55167
|
+
}
|
|
55114
55168
|
}
|
|
55115
55169
|
});
|
|
55116
55170
|
};
|
|
@@ -56433,6 +56487,9 @@ const PickerCustomResolver = props => {
|
|
|
56433
56487
|
if (circle) {
|
|
56434
56488
|
props.variant = "icon";
|
|
56435
56489
|
}
|
|
56490
|
+
// A door, never a field (see `allowNameless`): the form around it expects
|
|
56491
|
+
// no value from it, and no name.
|
|
56492
|
+
props.allowNameless = true;
|
|
56436
56493
|
// A word in a sentence asks for a plain tooltip — no icon in the callout,
|
|
56437
56494
|
// no status color; an icon one presses is the callout's own status icon,
|
|
56438
56495
|
// and says "info" like the callout it opens.
|
|
@@ -56782,7 +56839,10 @@ const PickerCustom = props => {
|
|
|
56782
56839
|
dispatchCustomEvent(popupRef.current, "navi_request_open", e.detail);
|
|
56783
56840
|
},
|
|
56784
56841
|
"onnavi_request_close": e => {
|
|
56785
|
-
dispatchCustomEvent(popupRef.current, "navi_request_close", e.detail);
|
|
56842
|
+
const closing = dispatchCustomEvent(popupRef.current, "navi_request_close", e.detail);
|
|
56843
|
+
if (!closing) {
|
|
56844
|
+
e.preventDefault();
|
|
56845
|
+
}
|
|
56786
56846
|
},
|
|
56787
56847
|
children
|
|
56788
56848
|
});
|
|
@@ -56834,12 +56894,17 @@ const PickerCustom = props => {
|
|
|
56834
56894
|
event: e,
|
|
56835
56895
|
intent: "read",
|
|
56836
56896
|
allowed: () => {
|
|
56837
|
-
requestClose(e, {
|
|
56897
|
+
const closing = requestClose(e, {
|
|
56838
56898
|
isCancel: e.detail.isCancel
|
|
56839
56899
|
});
|
|
56900
|
+
if (!closing) {
|
|
56901
|
+
e.preventDefault();
|
|
56902
|
+
}
|
|
56840
56903
|
},
|
|
56841
56904
|
prevented: () => {
|
|
56842
56905
|
confirmEventRef.current = null;
|
|
56906
|
+
// Not closing either way; said back to whoever asked.
|
|
56907
|
+
e.preventDefault();
|
|
56843
56908
|
}
|
|
56844
56909
|
});
|
|
56845
56910
|
},
|