@jsenv/navi 0.29.151 → 0.29.152

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.
@@ -15,6 +15,45 @@ import { parseDuration, durationContainsNaN, compareTwoDurations, durationToSeco
15
15
  export { compareTwoDurations, durationContainsNaN, durationToHours, durationToISOString, durationToMinutes, durationToNumber, durationToSeconds, durationToString, parseDuration } from "@jsenv/validity";
16
16
  import { Suspense, createPortal, forwardRef } from "preact/compat";
17
17
 
18
+ /**
19
+ * One page, one navi.
20
+ *
21
+ * navi keeps contexts, registries and singletons at module scope, so a second
22
+ * copy of the library in the same page is a second of each, and nothing
23
+ * crosses between them. Every symptom of that lands far from the cause and
24
+ * reads as an app bug: a <Loading> mounted by one copy is invisible to the
25
+ * hook reading the other's context and reports itself missing, an action
26
+ * registered in one is unknown to the other, focus and z-index each get a
27
+ * second owner. So the copies are detected here, when the second one is
28
+ * imported, and that import fails — the first thing that goes wrong says what
29
+ * is wrong.
30
+ *
31
+ * A copy is a module evaluation, which is what the object added to the set
32
+ * stands for; the urls are only there to tell the reader which two files to
33
+ * compare. Two urls for one file on disk is the usual shape (a dev server
34
+ * stamping a package version into specifiers, a bundle embedding navi next to
35
+ * a copy resolved separately); two versions installed in node_modules is the
36
+ * other.
37
+ *
38
+ * This module must stay in the entry chunk: the whole point is that it is
39
+ * evaluated once per copy of navi, and a shared chunk would be evaluated once
40
+ * for both.
41
+ */
42
+
43
+ // Symbol.for so the set is the same one across copies — a copy cannot see the
44
+ // other's module scope, which is the very thing being detected.
45
+ const NAVI_INSTANCE_SET = Symbol.for("navi_instance_set");
46
+
47
+ const instanceSet = globalThis[NAVI_INSTANCE_SET] || new Set();
48
+ globalThis[NAVI_INSTANCE_SET] = instanceSet;
49
+ instanceSet.add({ url: import.meta.url });
50
+ if (instanceSet.size > 1) {
51
+ const urlList = Array.from(instanceSet, ({ url }) => `- ${url}`).join("\n");
52
+ throw new Error(`@jsenv/navi is loaded ${instanceSet.size} times in this page:
53
+ ${urlList}
54
+ Each one builds its own contexts, stores and registries, so what one holds is invisible to the components using another.`);
55
+ }
56
+
18
57
  installImportMetaCssBuild(import.meta);/**
19
58
  * Every z-index navi can be seen competing on, in one place, ordered.
20
59
  *
@@ -30169,16 +30208,40 @@ const anyMatchingRouteSignal = (routes) => {
30169
30208
  };
30170
30209
 
30171
30210
  /**
30211
+ * Runs a navi command from JS. It is the LAST RESORT: the attributes say the
30212
+ * same thing declaratively and go through this very function, so a press that
30213
+ * opens, closes, sends, updates or travels needs none of this.
30214
+ *
30215
+ * ```html
30216
+ * <button command="--navi-open" commandfor="note-dialog" value="42">
30217
+ * ```
30218
+ *
30219
+ * `command` names it (`--navi-x:argument` when it needs an argument),
30220
+ * `commandfor` / `navi-command-target` says to whom, the source's own `value`
30221
+ * says what it is about (`command-value` when the source has a value meaning
30222
+ * something else). Reach for `triggerNaviCommand` only when what decides is not
30223
+ * a press on the element carrying those attributes: a long press, the end of a
30224
+ * drag, a double-click, a keyboard shortcut, a server answer, an observer.
30225
+ *
30172
30226
  * @param {Element} element The element asking — the command's source, and the
30173
30227
  * anchor a popup opens on unless `anchor` says otherwise.
30174
30228
  * @param {string} command
30175
30229
  * @param {Event} event What caused this. Mandatory: it is what makes a command
30176
30230
  * traceable back to its origin — the debug panel groups everything a gesture
30177
30231
  * set off under it, and the gates below read it to know whether the default
30178
- * was already prevented and which mouse button was pressed. When nothing was
30179
- * handed over — a timer firing, an action settling, a signal changing — build
30180
- * a `CustomEvent` that names what happened and chain it to whatever preceded
30181
- * it, rather than leaving the origin unsaid:
30232
+ * was already prevented and which mouse button was pressed.
30233
+ *
30234
+ * **Forward the event you were handed**, down through every function between
30235
+ * the handler and this call, so what arrives here is the real gesture. Almost
30236
+ * every command has one: a person did something to the page, or the browser
30237
+ * did. Building a `CustomEvent` at the call site instead of taking `event` as
30238
+ * a parameter looks like it satisfies the requirement, and throws away
30239
+ * exactly what makes it useful — the chain a popup reads to give the focus
30240
+ * back, the button that was pressed, a default already prevented.
30241
+ *
30242
+ * The `CustomEvent` is for the genuinely originless case — a timer firing, an
30243
+ * action settling, a signal changing — and even there it names what happened
30244
+ * and chains to whatever preceded it:
30182
30245
  * const expiredEvent = new CustomEvent("session_expired");
30183
30246
  * chainEvent(expiredEvent, causeEvent); // when something did precede it
30184
30247
  * triggerNaviCommand(dialogEl, "--navi-open", expiredEvent);
@@ -30200,7 +30263,7 @@ const triggerNaviCommand = (
30200
30263
  ) => {
30201
30264
  if (!event) {
30202
30265
  throw new Error(
30203
- `"${command}" triggered without an event: it is mandatory, a command must say what caused it. Pass the gesture, or a CustomEvent naming the cause when no gesture did — see triggerNaviCommand's jsdoc.`,
30266
+ `"${command}" triggered without an event: it is mandatory, a command must say what caused it. Forward the gesture that led here, or a CustomEvent naming the cause when no gesture did — see triggerNaviCommand's jsdoc.`,
30204
30267
  );
30205
30268
  }
30206
30269
  const naviCommand =