@protobase/ui 0.58.0 → 0.58.2

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.
@@ -6,9 +6,15 @@ export declare const isFramed: (win?: {
6
6
  top: unknown;
7
7
  self: unknown;
8
8
  }) => boolean;
9
- /** Whether the page has been in front for `delayMs` (`armAfterFocus`), as React state. */
9
+ /**
10
+ * Whether the page has been in front for `delayMs` (`armAfterFocus`), as React state: it waits again after a press while
11
+ * unarmed and after a back-forward cache restore, and a disarm renders synchronously, so the next press already sees it.
12
+ */
10
13
  export declare const useArmedAfterFocus: (delayMs: number) => boolean;
11
- /** The handlers of a button guarded by `armed`: its press must begin while armed for its click to act (`pressGate`). */
14
+ /**
15
+ * The handlers of a button guarded by `armed`: its press must begin while armed for its click to act (`pressGate`).
16
+ * With `armed` from `useArmedAfterFocus`, it is never stale after a disarm, which renders synchronously.
17
+ */
12
18
  export declare const useGuardedPress: (armed: boolean, onPress: () => void) => {
13
19
  onPointerDown: () => void;
14
20
  onKeyDown: (event: {
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
+ import { flushSync } from "react-dom";
2
3
  //#region src/connect/interaction-guard.ts
3
4
  /** How long the connect page must have been in front, visible and focused, before Allow takes a click. */
4
5
  const allowDelayMs = 500;
@@ -6,9 +7,13 @@ const allowDelayMs = 500;
6
7
  const isFramed = (win = window) => win.top !== win.self;
7
8
  /**
8
9
  * Calls `onChange(true)` once the document has been visible and focused for `delayMs` without a break, and
9
- * `onChange(false)` as soon as the window loses focus or the document is hidden, waiting again from the next focus. A
10
- * click aimed at another page, such as the second half of a double-click on a page that just gave way to this one,
11
- * lands before then. Returns the cleanup that removes its listeners and timer.
10
+ * `onChange(false)` as soon as the window loses focus, the document is hidden or the page is left (`pagehide`), waiting
11
+ * again from the next focus. A click aimed at another page, such as the second half of a double-click on a page that
12
+ * just gave way to this one, lands before then. While unarmed, a press anywhere on the page (`pointerdown` or `keydown`,
13
+ * of any pointer, button or key) or a restore from the back-forward cache starts the wait again, so pressing as fast as
14
+ * one can over the spot never lands an armed press. `pointerdown` is the only pointer event that still reaches the
15
+ * document from a disabled button. A document without a window never arms, since it cannot hear blur or a restore.
16
+ * Returns the cleanup that removes its listeners and timer.
12
17
  */
13
18
  const armAfterFocus = (doc, delayMs, onChange) => {
14
19
  let armed = false;
@@ -24,18 +29,32 @@ const armAfterFocus = (doc, delayMs, onChange) => {
24
29
  };
25
30
  const wait = () => {
26
31
  disarm();
27
- if (doc.visibilityState === "visible" && doc.hasFocus()) timer = setTimeout(() => set(true), delayMs);
32
+ if (doc.defaultView && doc.visibilityState === "visible" && doc.hasFocus()) timer = setTimeout(() => set(true), delayMs);
28
33
  };
29
34
  const visibility = () => doc.visibilityState === "visible" ? wait() : disarm();
35
+ const pressed = () => {
36
+ if (!armed) wait();
37
+ };
38
+ const shown = (event) => {
39
+ if ("persisted" in event && event.persisted) wait();
40
+ };
30
41
  doc.defaultView?.addEventListener("focus", wait);
31
42
  doc.defaultView?.addEventListener("blur", disarm);
43
+ doc.defaultView?.addEventListener("pagehide", disarm);
44
+ doc.defaultView?.addEventListener("pageshow", shown);
32
45
  doc.addEventListener("visibilitychange", visibility);
46
+ doc.addEventListener("pointerdown", pressed, true);
47
+ doc.addEventListener("keydown", pressed, true);
33
48
  wait();
34
49
  return () => {
35
50
  clearTimeout(timer);
36
51
  doc.defaultView?.removeEventListener("focus", wait);
37
52
  doc.defaultView?.removeEventListener("blur", disarm);
53
+ doc.defaultView?.removeEventListener("pagehide", disarm);
54
+ doc.defaultView?.removeEventListener("pageshow", shown);
38
55
  doc.removeEventListener("visibilitychange", visibility);
56
+ doc.removeEventListener("pointerdown", pressed, true);
57
+ doc.removeEventListener("keydown", pressed, true);
39
58
  };
40
59
  };
41
60
  /**
@@ -58,13 +77,21 @@ const pressGate = () => {
58
77
  }
59
78
  };
60
79
  };
61
- /** Whether the page has been in front for `delayMs` (`armAfterFocus`), as React state. */
80
+ /** `armAfterFocus`'s onChange as React state: a disarm renders at once (`flush`), so the very next press sees it. */
81
+ const armedState = (set, flush) => (armed) => armed ? set(true) : flush(() => set(false));
82
+ /**
83
+ * Whether the page has been in front for `delayMs` (`armAfterFocus`), as React state: it waits again after a press while
84
+ * unarmed and after a back-forward cache restore, and a disarm renders synchronously, so the next press already sees it.
85
+ */
62
86
  const useArmedAfterFocus = (delayMs) => {
63
87
  const [armed, setArmed] = useState(false);
64
- useEffect(() => armAfterFocus(document, delayMs, setArmed), [delayMs]);
88
+ useEffect(() => armAfterFocus(document, delayMs, armedState(setArmed, flushSync)), [delayMs]);
65
89
  return armed;
66
90
  };
67
- /** The handlers of a button guarded by `armed`: its press must begin while armed for its click to act (`pressGate`). */
91
+ /**
92
+ * The handlers of a button guarded by `armed`: its press must begin while armed for its click to act (`pressGate`).
93
+ * With `armed` from `useArmedAfterFocus`, it is never stale after a disarm, which renders synchronously.
94
+ */
68
95
  const useGuardedPress = (armed, onPress) => {
69
96
  const gate = useRef(pressGate());
70
97
  return {
@@ -76,4 +103,4 @@ const useGuardedPress = (armed, onPress) => {
76
103
  };
77
104
  };
78
105
  //#endregion
79
- export { allowDelayMs, armAfterFocus, isFramed, pressGate, useArmedAfterFocus, useGuardedPress };
106
+ export { allowDelayMs, armAfterFocus, armedState, isFramed, pressGate, useArmedAfterFocus, useGuardedPress };
package/dist/package.js CHANGED
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.58.0";
2
+ var version = "0.58.2";
3
3
  var protobase = { "pluginApi": 1 };
4
4
  //#endregion
5
5
  export { protobase, version };