@rapidmx/web-client 0.17.0 → 0.17.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.
@@ -9,7 +9,7 @@ import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shar
9
9
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
10
10
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
11
11
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
12
- import { useLocation } from "@rapidrest/react/client";
12
+ import { useLocation } from "../../../navigation/index.js";
13
13
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
14
14
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
15
15
 
@@ -10,8 +10,7 @@ import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shar
10
10
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
11
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
12
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
- import { useLocation } from "@rapidrest/react/client";
14
- import { useNavigate } from "../../../navigation/index.js";
13
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
15
14
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
16
15
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
17
16
 
@@ -10,8 +10,7 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
10
10
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
11
11
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
12
12
  import FloatingActionButton from "../../layout/FloatingActionButton.js";
13
- import { useLocation } from "@rapidrest/react/client";
14
- import { useNavigate } from "../../../navigation/index.js";
13
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
15
14
  import KeyEnrollmentGate from "../../layout/KeyEnrollmentGate.js";
16
15
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
17
16
  import { prefetchComposeWindow, useCompose } from "../compose/ComposeContext.js";
@@ -10,8 +10,7 @@ import { Mailbox, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
10
10
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
11
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
12
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
- import { useLocation } from "@rapidrest/react/client";
14
- import { useNavigate } from "../../../navigation/index.js";
13
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
15
14
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
16
15
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
17
16
  import { mergePluginNavItems, PluginNav } from "../../../plugins/pluginNav.js";
@@ -10,8 +10,7 @@ import { Folder, Mailbox, listFolders, listMailboxes } from "@rapidmx/react-shar
10
10
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
11
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
12
  import AppShell, { AppShellProps } from "../../layout/AppShell.js";
13
- import { useLocation } from "@rapidrest/react/client";
14
- import { useNavigate } from "../../../navigation/index.js";
13
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
15
14
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
16
15
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
17
16
 
@@ -7,8 +7,8 @@
7
7
  * route, the app's `_shell.tsx` as the persistent frame - see `apps/www/_shell.tsx`); this is the one thing on top of it: a
8
8
  * `navigate()` that keeps the page when only the query string changes.
9
9
  */
10
- import { useCallback, useRef } from "react";
11
- import { useRouter } from "@rapidrest/react/client";
10
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
11
+ import { useLocation as useRouterLocation, useRouter } from "@rapidrest/react/client";
12
12
 
13
13
  export interface NavigateOptions {
14
14
  /** Replaces the current history entry instead of adding one (a redirect, or a state change that isn't a "place"). */
@@ -41,3 +41,30 @@ export function useNavigate(): NavigateFn {
41
41
  void go(href, { replace: options.replace, shallow });
42
42
  }, []);
43
43
  }
44
+
45
+ function subscribeToHistory(onChange: () => void): () => void {
46
+ window.addEventListener("popstate", onChange);
47
+ return () => window.removeEventListener("popstate", onChange);
48
+ }
49
+
50
+ // Joined, not added: a test's stand-in for `window.location` may have no `search`, and `undefined + undefined` is `NaN`.
51
+ const currentAddress = (): string => [window.location.pathname, window.location.search, window.location.hash].join("");
52
+ const noAddress = (): string => "";
53
+
54
+ /**
55
+ * Where the page is, as `{ pathname, search, hash }`: the router's location when the page is under a router, else the browser's.
56
+ * A page a plugin renders has no router (`router = false`), where `@rapidrest/react`'s `useLocation()` is empty and the query a
57
+ * shell reads its mailbox from (`?mailboxUid=`) would be lost. Without one, the address is the browser's - and empty on the
58
+ * server and while hydrating, so the page hydrates as it was rendered and then follows the address.
59
+ */
60
+ export function useLocation(): { pathname: string; search: string; hash: string } {
61
+ const routed = useRouterLocation();
62
+ const own = useSyncExternalStore(subscribeToHistory, currentAddress, noAddress);
63
+ const ownLocation = useMemo(() => {
64
+ // Split by hand, not with `URL`: a page that stubs the global `URL` (for `createObjectURL`) must still render.
65
+ const [beforeHash, ...fragment] = own.split("#");
66
+ const [pathname, ...query] = beforeHash.split("?");
67
+ return { pathname, search: query.length ? `?${query.join("?")}` : "", hash: fragment.length ? `#${fragment.join("#")}` : "" };
68
+ }, [own]);
69
+ return routed.pathname !== "" ? routed : ownLocation;
70
+ }
@@ -10,7 +10,7 @@ import { listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.j
10
10
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
11
11
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
12
12
  import AppShell from "../../layout/AppShell.js";
13
- import { useLocation } from "@rapidrest/react/client";
13
+ import { useLocation } from "../../../navigation/index.js";
14
14
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
15
15
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
16
16
  const CalendarShellContext = createContext({
@@ -11,8 +11,7 @@ import { listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.j
11
11
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
12
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
13
13
  import AppShell from "../../layout/AppShell.js";
14
- import { useLocation } from "@rapidrest/react/client";
15
- import { useNavigate } from "../../../navigation/index.js";
14
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
16
15
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
17
16
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
18
17
  const ContactsShellContext = createContext({ mailboxes: [] });
@@ -10,8 +10,7 @@ import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
10
10
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
11
11
  import AppShell from "../../layout/AppShell.js";
12
12
  import FloatingActionButton from "../../layout/FloatingActionButton.js";
13
- import { useLocation } from "@rapidrest/react/client";
14
- import { useNavigate } from "../../../navigation/index.js";
13
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
15
14
  import KeyEnrollmentGate from "../../layout/KeyEnrollmentGate.js";
16
15
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
17
16
  import { prefetchComposeWindow, useCompose } from "../compose/ComposeContext.js";
@@ -11,8 +11,7 @@ import { listMailboxes } from "@rapidmx/react-shared/mail/mailApi.js";
11
11
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
12
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
13
13
  import AppShell from "../../layout/AppShell.js";
14
- import { useLocation } from "@rapidrest/react/client";
15
- import { useNavigate } from "../../../navigation/index.js";
14
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
16
15
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
17
16
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
18
17
  import { mergePluginNavItems } from "../../../plugins/pluginNav.js";
@@ -11,8 +11,7 @@ import { listFolders, listMailboxes } from "@rapidmx/react-shared/mail/mailApi.j
11
11
  import Alert from "@rapidmx/react-shared/components/feedback/Alert.js";
12
12
  import Skeleton, { SkeletonList } from "@rapidmx/react-shared/components/feedback/Skeleton.js";
13
13
  import AppShell from "../../layout/AppShell.js";
14
- import { useLocation } from "@rapidrest/react/client";
15
- import { useNavigate } from "../../../navigation/index.js";
14
+ import { useLocation, useNavigate } from "../../../navigation/index.js";
16
15
  import MailboxProvisioning from "../../layout/MailboxProvisioning.js";
17
16
  import { orderMailboxes, primaryMailboxUid } from "../../../mail/primaryMailbox.js";
18
17
  const TasksShellContext = createContext({ mailboxes: [] });
@@ -14,3 +14,14 @@ export type NavigateFn = (href: string, options?: NavigateOptions) => void;
14
14
  * stable across renders.
15
15
  */
16
16
  export declare function useNavigate(): NavigateFn;
17
+ /**
18
+ * Where the page is, as `{ pathname, search, hash }`: the router's location when the page is under a router, else the browser's.
19
+ * A page a plugin renders has no router (`router = false`), where `@rapidrest/react`'s `useLocation()` is empty and the query a
20
+ * shell reads its mailbox from (`?mailboxUid=`) would be lost. Without one, the address is the browser's - and empty on the
21
+ * server and while hydrating, so the page hydrates as it was rendered and then follows the address.
22
+ */
23
+ export declare function useLocation(): {
24
+ pathname: string;
25
+ search: string;
26
+ hash: string;
27
+ };
@@ -7,8 +7,8 @@
7
7
  * route, the app's `_shell.tsx` as the persistent frame - see `apps/www/_shell.tsx`); this is the one thing on top of it: a
8
8
  * `navigate()` that keeps the page when only the query string changes.
9
9
  */
10
- import { useCallback, useRef } from "react";
11
- import { useRouter } from "@rapidrest/react/client";
10
+ import { useCallback, useMemo, useRef, useSyncExternalStore } from "react";
11
+ import { useLocation as useRouterLocation, useRouter } from "@rapidrest/react/client";
12
12
  /**
13
13
  * The function to go somewhere with, for what code decides (after a save, a select, a folder change). `navigate("/calendar")`
14
14
  * changes the page and the URL without a page load when the router knows the page (Mail, Calendar, Contacts, Tasks, Settings
@@ -32,3 +32,27 @@ export function useNavigate() {
32
32
  void go(href, { replace: options.replace, shallow });
33
33
  }, []);
34
34
  }
35
+ function subscribeToHistory(onChange) {
36
+ window.addEventListener("popstate", onChange);
37
+ return () => window.removeEventListener("popstate", onChange);
38
+ }
39
+ // Joined, not added: a test's stand-in for `window.location` may have no `search`, and `undefined + undefined` is `NaN`.
40
+ const currentAddress = () => [window.location.pathname, window.location.search, window.location.hash].join("");
41
+ const noAddress = () => "";
42
+ /**
43
+ * Where the page is, as `{ pathname, search, hash }`: the router's location when the page is under a router, else the browser's.
44
+ * A page a plugin renders has no router (`router = false`), where `@rapidrest/react`'s `useLocation()` is empty and the query a
45
+ * shell reads its mailbox from (`?mailboxUid=`) would be lost. Without one, the address is the browser's - and empty on the
46
+ * server and while hydrating, so the page hydrates as it was rendered and then follows the address.
47
+ */
48
+ export function useLocation() {
49
+ const routed = useRouterLocation();
50
+ const own = useSyncExternalStore(subscribeToHistory, currentAddress, noAddress);
51
+ const ownLocation = useMemo(() => {
52
+ // Split by hand, not with `URL`: a page that stubs the global `URL` (for `createObjectURL`) must still render.
53
+ const [beforeHash, ...fragment] = own.split("#");
54
+ const [pathname, ...query] = beforeHash.split("?");
55
+ return { pathname, search: query.length ? `?${query.join("?")}` : "", hash: fragment.length ? `#${fragment.join("#")}` : "" };
56
+ }, [own]);
57
+ return routed.pathname !== "" ? routed : ownLocation;
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rapidmx/web-client",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "RapidMX's webmail and admin console React UI for web-based clients.",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/rapidmx/web-client",