@jsenv/navi 0.29.485 → 0.29.486

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.
@@ -11489,6 +11489,31 @@ const resolveEffectiveDocumentState = (
11489
11489
  return { ...state, ...sharedState };
11490
11490
  };
11491
11491
 
11492
+ /**
11493
+ * What a press on a link handed to the navigation it starts (see the
11494
+ * integrations' `carryByLinkNavigation`): each write is an `editState` applied
11495
+ * to the entry that navigation writes, and an `otherwise` that makes the write
11496
+ * where it would have been made, for a press that starts no navigation here.
11497
+ */
11498
+ const applyPressWrites = (state, pressWrites) => {
11499
+ if (!pressWrites) {
11500
+ return state;
11501
+ }
11502
+ let stateWritten = state;
11503
+ for (const { editState } of pressWrites) {
11504
+ stateWritten = editState(stateWritten);
11505
+ }
11506
+ return stateWritten;
11507
+ };
11508
+ const writePressWritesHere = (pressWrites) => {
11509
+ if (!pressWrites) {
11510
+ return;
11511
+ }
11512
+ for (const { otherwise } of pressWrites) {
11513
+ otherwise();
11514
+ }
11515
+ };
11516
+
11492
11517
  // Preact's own useId() (see preact/hooks) returns "P<mask0>-<mask1>", where the
11493
11518
  // mask is derived from render order within the nearest root/async boundary:
11494
11519
  // stable across re-renders of the same mount, but not across two mounts on the
@@ -13133,6 +13158,7 @@ const setupBrowserIntegrationViaHistory = ({
13133
13158
  // Asked to go where we already are: an address that redirects is never
13134
13159
  // the one being displayed, so there is nothing to go to and nothing to
13135
13160
  // stack on the history.
13161
+ writePressWritesHere(options.pressWrites);
13136
13162
  return undefined;
13137
13163
  }
13138
13164
  return handleRoutingTask(redirectionUrl, {
@@ -13144,10 +13170,13 @@ const setupBrowserIntegrationViaHistory = ({
13144
13170
  // Decided before anything is announced: an elided push IS the traversal it
13145
13171
  // becomes, and the traversal will make its own announcements when the
13146
13172
  // browser answers — a before/after cycle here would be about a navigation
13147
- // that never happens.
13173
+ // that never happens. A push with something of its own for its entry — a
13174
+ // state, what the press handed over (see carryByLinkNavigation) — is a
13175
+ // new entry, never one standing there already.
13148
13176
  if (
13149
13177
  options.navigationType === "push" &&
13150
13178
  options.state === undefined &&
13179
+ !options.pressWrites &&
13151
13180
  url !== readAddress()
13152
13181
  ) {
13153
13182
  const delta = adjacentEntryDelta(url);
@@ -13197,6 +13226,7 @@ const setupBrowserIntegrationViaHistory = ({
13197
13226
  redirected,
13198
13227
  landOn,
13199
13228
  debounce,
13229
+ pressWrites,
13200
13230
  } = options;
13201
13231
  let { state } = options;
13202
13232
 
@@ -13207,15 +13237,18 @@ const setupBrowserIntegrationViaHistory = ({
13207
13237
 
13208
13238
  if (navigationType === "push" || navigationType === "replace") {
13209
13239
  markUrlAsVisited(url);
13210
- const effectiveState = resolveEffectiveDocumentState(state, {
13211
- navigationType,
13212
- isArrival: isArrival(url, { from: urlLeft }),
13213
- currentState: getDocumentState(),
13214
- sharedState: {
13215
- jsenv_visited_urls: Array.from(visitedUrlSet),
13216
- [NAV_DEPTH_STATE_KEY]: getNavDepth(),
13217
- },
13218
- });
13240
+ const effectiveState = applyPressWrites(
13241
+ resolveEffectiveDocumentState(state, {
13242
+ navigationType,
13243
+ isArrival: isArrival(url, { from: urlLeft }),
13244
+ currentState: getDocumentState(),
13245
+ sharedState: {
13246
+ jsenv_visited_urls: Array.from(visitedUrlSet),
13247
+ [NAV_DEPTH_STATE_KEY]: getNavDepth(),
13248
+ },
13249
+ }),
13250
+ pressWrites,
13251
+ );
13219
13252
  if (navigationType === "replace" && debounce && state === undefined) {
13220
13253
  // Nothing of its own for the entry: the address moves, the state is
13221
13254
  // carried over. The one kind of write allowed to lag, and only when
@@ -13315,6 +13348,36 @@ const setupBrowserIntegrationViaHistory = ({
13315
13348
  return requestedResult;
13316
13349
  };
13317
13350
 
13351
+ // A press on a link is one navigation, and what the press asks of the
13352
+ // history entry is part of it: a popup the link's own command closes is
13353
+ // closed on the entry the link leads to, while the one it leaves keeps what
13354
+ // the reader was looking at — a back finds the popup open. The link's
13355
+ // listeners run before the routing is asked (below), so a write they make
13356
+ // is handed over, keyed by the click, rather than written onto the entry
13357
+ // being left. Once the link has been answered there is nothing to hand
13358
+ // over to: the navigation, when there was one, has already written its
13359
+ // entry, and a write lands there by itself.
13360
+ const linkPressWritesMap = new WeakMap();
13361
+ const answeredLinkClickSet = new WeakSet();
13362
+ const carryByLinkNavigation = (event, pressWrite) => {
13363
+ const clickEvent = findEvent(event, "click");
13364
+ if (
13365
+ !clickEvent ||
13366
+ clickEvent.eventPhase === Event.NONE ||
13367
+ answeredLinkClickSet.has(clickEvent) ||
13368
+ !clickEvent.target.closest("a")
13369
+ ) {
13370
+ return false;
13371
+ }
13372
+ const pressWrites = linkPressWritesMap.get(clickEvent);
13373
+ if (pressWrites) {
13374
+ pressWrites.push(pressWrite);
13375
+ } else {
13376
+ linkPressWritesMap.set(clickEvent, [pressWrite]);
13377
+ }
13378
+ return true;
13379
+ };
13380
+
13318
13381
  // A click on a link is answered on the link itself, after the link's own
13319
13382
  // listeners: a navi control decides there whether the press is allowed — it
13320
13383
  // refuses with preventDefault from its click reaction — and runs its command
@@ -13322,34 +13385,42 @@ const setupBrowserIntegrationViaHistory = ({
13322
13385
  // is the order the Navigation API integration gets for free, where the
13323
13386
  // navigation starts once the click has been dispatched.
13324
13387
  const onLinkClick = (e) => {
13388
+ answeredLinkClickSet.add(e);
13389
+ const pressWrites = linkPressWritesMap.get(e);
13390
+ linkPressWritesMap.delete(e);
13391
+ if (!routeLinkClick(e, pressWrites)) {
13392
+ writePressWritesHere(pressWrites);
13393
+ }
13394
+ };
13395
+ const routeLinkClick = (e, pressWrites) => {
13325
13396
  if (e.button !== 0) {
13326
13397
  // Ignore non-left clicks
13327
- return;
13398
+ return false;
13328
13399
  }
13329
13400
  if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
13330
13401
  // A modifier asks the browser for something other than this tab: a new
13331
13402
  // tab (meta on macOS, ctrl elsewhere), a new window (shift), a download
13332
13403
  // (alt).
13333
- return;
13404
+ return false;
13334
13405
  }
13335
13406
  if (e.defaultPrevented) {
13336
13407
  // Refused — by the link itself, or by whoever came before it.
13337
- return;
13408
+ return false;
13338
13409
  }
13339
13410
  const linkElement = e.currentTarget;
13340
13411
  if (linkElement.hasAttribute("data-readonly")) {
13341
- return;
13412
+ return false;
13342
13413
  }
13343
13414
  if (linkOpensElsewhere(linkElement)) {
13344
13415
  // Another tab or window, or a download (see link_elsewhere.js).
13345
- return;
13416
+ return false;
13346
13417
  }
13347
13418
  const href = linkElement.href;
13348
13419
  const { isEmpty, isCurrent, isSameOrigin, isAnchor } =
13349
13420
  getHrefTargetInfo(href);
13350
13421
  if (isEmpty || !isSameOrigin) {
13351
13422
  // Let link to other origins be handled by the browser
13352
- return;
13423
+ return false;
13353
13424
  }
13354
13425
  if (isAnchor) {
13355
13426
  // Fragment navigation belongs to the browser: it owns the indicated
@@ -13361,13 +13432,13 @@ const setupBrowserIntegrationViaHistory = ({
13361
13432
  // change reaches whoever is waiting on the designated element.
13362
13433
  rearmUrlTarget();
13363
13434
  }
13364
- return;
13435
+ return false;
13365
13436
  }
13366
13437
  if (linkAsksForDocument(linkElement)) {
13367
13438
  // The link says its address is another document of this origin (see
13368
13439
  // link_document.js). Routing to it would land on the fallback route
13369
13440
  // instead of loading the page.
13370
- return;
13441
+ return false;
13371
13442
  }
13372
13443
  // Nothing here declared a route, so there is nothing to route to: the
13373
13444
  // page is a plain document and a link in it is a plain link. Taking it
@@ -13375,7 +13446,7 @@ const setupBrowserIntegrationViaHistory = ({
13375
13446
  // the address bar moves and the page does not (see applyRouting's own
13376
13447
  // "not called yet" branch, which is where that used to end up).
13377
13448
  if (!isRouting()) {
13378
- return;
13449
+ return false;
13379
13450
  }
13380
13451
  e.preventDefault();
13381
13452
  handleRoutingTask(href, {
@@ -13389,7 +13460,9 @@ const setupBrowserIntegrationViaHistory = ({
13389
13460
  // whoever knows what to do with it, and it is the anchor itself —
13390
13461
  // resolved here, where it already is.
13391
13462
  element: linkElement,
13463
+ pressWrites,
13392
13464
  });
13465
+ return true;
13393
13466
  };
13394
13467
  // Wired from the capture phase on window, the first place a click is seen,
13395
13468
  // so that a link is answered whatever handler stops the click on its way up
@@ -13569,6 +13642,7 @@ const setupBrowserIntegrationViaHistory = ({
13569
13642
  navBack,
13570
13643
  navForward,
13571
13644
  getDocumentState,
13645
+ carryByLinkNavigation,
13572
13646
  isVisited,
13573
13647
  visitedUrlsSignal,
13574
13648
  };
@@ -13968,8 +14042,32 @@ const useNavStateBasic = (
13968
14042
  // that param for gone. Only with nothing of this document behind (the state
13969
14043
  // was entered on a cold-loaded url, navBack has nowhere to go) does it
13970
14044
  // rewrite in place.
13971
- const leave = ({ isBack } = {}) => {
14045
+ //
14046
+ // event: what asked for it. A leave the press of a link asks for (its
14047
+ // --navi-close) is made by the navigation that link starts: the entry the
14048
+ // link leads to does not hold the key, and every entry behind is left as
14049
+ // the reader saw it — a back finds the state still entered.
14050
+ const leave = ({ isBack, event } = {}) => {
13972
14051
  enteredRef.current = false;
14052
+ if (!Object.hasOwn(browserIntegration.getDocumentState() || {}, id)) {
14053
+ return;
14054
+ }
14055
+ if (
14056
+ event &&
14057
+ browserIntegration.carryByLinkNavigation(event, {
14058
+ editState: (state) => {
14059
+ const stateWithoutKey = { ...state };
14060
+ delete stateWithoutKey[id];
14061
+ return stateWithoutKey;
14062
+ },
14063
+ otherwise: () => leaveEntry({ isBack }),
14064
+ })
14065
+ ) {
14066
+ return;
14067
+ }
14068
+ leaveEntry({ isBack });
14069
+ };
14070
+ const leaveEntry = ({ isBack }) => {
13973
14071
  const currentStateCopy = browserIntegration.getDocumentState() || {};
13974
14072
  if (!Object.hasOwn(currentStateCopy, id)) {
13975
14073
  return;
@@ -14027,6 +14125,9 @@ const useNavStateBasic = (
14027
14125
  * the value allows associating extra data when needed.
14028
14126
  * - `leave()`: navigate AWAY FROM this state (removes `id` from document state,
14029
14127
  * or goes back in history when `type` is "push").
14128
+ * `leave({ event })` with the event that asked for it: asked by the press
14129
+ * of a link that navigates, the key is left out of the entry the link
14130
+ * leads to, and the entry it leaves keeps it.
14030
14131
  */
14031
14132
  const useNavState = useNavStateBasic;
14032
14133
 
@@ -17210,8 +17311,13 @@ const useOpenPropsEffectOnOpenController = (
17210
17311
  } else {
17211
17312
  // Under type "push" a cancel discards everything written while
17212
17313
  // the popup was open — it goes back with the entry; a confirmed
17213
- // close keeps those writes (see useNavState's own leave()).
17214
- leaveNavState({ isBack: Boolean(event?.detail?.isCancel) });
17314
+ // close keeps those writes (see useNavState's own leave()). A
17315
+ // close a link's press asked for is written by the navigation
17316
+ // the link starts.
17317
+ leaveNavState({
17318
+ isBack: Boolean(event?.detail?.isCancel),
17319
+ event,
17320
+ });
17215
17321
  }
17216
17322
  }
17217
17323
  if (signal) {
@@ -53123,7 +53229,9 @@ const LinkPseudoElements = ["::-navi-loader", "::-navi-indicator"];
53123
53229
  * fetching on a hover.
53124
53230
  * @param {string} [props.command] - What the press asks of a control around
53125
53231
  * the link — `"--navi-close"` on a link that leaves the sheet it is in.
53126
- * Triggered on the press, before the navigation.
53232
+ * Triggered on the press, before the navigation. A popup it closes that
53233
+ * keeps its open state in the history entry (`navState`) is closed on the
53234
+ * entry the navigation leads to: Back finds it open.
53127
53235
  * @param {Function} [props.action] - Work the press runs, before the
53128
53236
  * navigation. Nothing waits for it — not the navigation, not `command`: what
53129
53237
  * the next page must find has to be written synchronously (a draft in a