@remit/web-client 0.0.112 → 0.0.113

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/web-client",
3
- "version": "0.0.112",
3
+ "version": "0.0.113",
4
4
  "type": "module",
5
5
  "description": "Remit web client, published as composable primitives — the app shell, auth shells, and runtime config. A distributor imports what it composes and bundles it.",
6
6
  "exports": {
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The touch row is not an anchor (#116).
3
+ *
4
+ * An `<a href>` is what the OS long-press callout and the link context menu
5
+ * fire on, and they raced the row's own long-press-to-select gesture. Nothing
6
+ * an anchor buys — middle-click, open in new tab, copy link address — exists
7
+ * without a mouse, and the swipe row is the touch row.
8
+ */
9
+
10
+ import assert from "node:assert/strict";
11
+ import { afterEach, describe, it } from "node:test";
12
+ import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
13
+ import {
14
+ type AnyRouter,
15
+ createMemoryHistory,
16
+ createRootRoute,
17
+ createRoute,
18
+ createRouter,
19
+ RouterContextProvider,
20
+ } from "@tanstack/react-router";
21
+ import { createElement } from "react";
22
+ import { createDomHarness, type DomHarness } from "../../test-support/dom";
23
+ import { SwipeableMessageRow } from "./SwipeableMessageRow";
24
+
25
+ const PHONE_WIDTH = 390;
26
+
27
+ let harness: DomHarness | undefined;
28
+
29
+ afterEach(() => {
30
+ harness?.close();
31
+ harness = undefined;
32
+ });
33
+
34
+ const thread = {
35
+ threadMessageId: "tm-1",
36
+ threadId: "th-1",
37
+ messageId: "msg-1",
38
+ accountId: "acc-1",
39
+ accountConfigId: "acc-1",
40
+ mailboxId: "mbx-1",
41
+ subject: "Q3 planning notes",
42
+ fromName: "Alex Rivera",
43
+ fromEmail: "alex@example.com",
44
+ snippet: "Notes from the planning session.",
45
+ sentDate: 0,
46
+ isRead: false,
47
+ hasAttachment: false,
48
+ hasStars: false,
49
+ star: "None",
50
+ isDeleted: false,
51
+ senderTrust: "unknown",
52
+ createdAt: 0,
53
+ updatedAt: 0,
54
+ } as unknown as RemitImapThreadMessageResponse;
55
+
56
+ // The router reads `self` at construction; the shared jsdom globals stop at
57
+ // `window`.
58
+ (globalThis as { self?: typeof globalThis }).self ??= globalThis;
59
+
60
+ const rootRoute = createRootRoute();
61
+ const mailRoute = createRoute({
62
+ getParentRoute: () => rootRoute,
63
+ path: "/mail/$mailboxId",
64
+ });
65
+
66
+ const testRouter = (): AnyRouter =>
67
+ createRouter({
68
+ routeTree: rootRoute.addChildren([mailRoute]),
69
+ history: createMemoryHistory({ initialEntries: ["/mail/mbx-1"] }),
70
+ }) as unknown as AnyRouter;
71
+
72
+ const renderRow = (): DomHarness => {
73
+ const created = createDomHarness({ viewportWidth: PHONE_WIDTH });
74
+ created.render(
75
+ createElement(RouterContextProvider, {
76
+ router: testRouter(),
77
+ // biome-ignore lint/correctness/noChildrenProp: RouterContextProvider types `children` as a required prop, which createElement's rest-argument form does not satisfy
78
+ children: createElement(SwipeableMessageRow, {
79
+ thread,
80
+ mailboxId: "mbx-1",
81
+ isSelected: false,
82
+ isChecked: false,
83
+ onToggleCheck: () => undefined,
84
+ onRowSelect: () => false,
85
+ isMultiSelectMode: false,
86
+ onLongPress: () => undefined,
87
+ isDesktop: false,
88
+ onDelete: () => undefined,
89
+ onToggleRead: () => undefined,
90
+ }),
91
+ }),
92
+ );
93
+ return created;
94
+ };
95
+
96
+ describe("SwipeableMessageRow — the touch row's open affordance", () => {
97
+ it("renders no anchor at all", () => {
98
+ harness = renderRow();
99
+
100
+ assert.equal(harness.queryAll("a").length, 0);
101
+ });
102
+
103
+ it("opens through a button carrying the row marker", () => {
104
+ harness = renderRow();
105
+
106
+ const row = harness.query("button[data-message-row]");
107
+ assert.ok(row, "the row's open affordance is not a button");
108
+ assert.match(row.textContent ?? "", /Q3 planning notes/);
109
+ });
110
+ });
@@ -6,12 +6,11 @@ import {
6
6
  type SwipePeek,
7
7
  type ThreadRowData,
8
8
  } from "@remit/ui";
9
- import { Link } from "@tanstack/react-router";
9
+ import { useNavigate } from "@tanstack/react-router";
10
10
  import { useCallback, useState } from "react";
11
11
  import { toDisplayCategory } from "@/lib/display-category";
12
12
  import { formatEmailDate } from "@/lib/format";
13
13
  import { MessageListItem } from "./MessageListItem";
14
- import { useModifierSelect } from "./useModifierSelect";
15
14
 
16
15
  interface MailboxLinkSearch {
17
16
  selectedMessageId?: string;
@@ -78,6 +77,7 @@ export const SwipeableMessageRow = ({
78
77
  density,
79
78
  }: SwipeableMessageRowProps) => {
80
79
  const [peek, setPeek] = useState<SwipePeek>("none");
80
+ const navigate = useNavigate();
81
81
 
82
82
  const handleAct = useCallback(
83
83
  (side: "leading" | "trailing") => {
@@ -100,10 +100,16 @@ export const SwipeableMessageRow = ({
100
100
  onToggleCheck(thread.messageId);
101
101
  }, [onToggleCheck, thread.messageId]);
102
102
 
103
- // The swipe row is the layout tier's row, not the input device's: a mouse at
104
- // a narrow width still gets it, and a shift or cmd click on it belongs to
105
- // selection rather than to the anchor's own new-window/new-tab gesture.
106
- const modifierSelect = useModifierSelect(thread.messageId, onRowSelect);
103
+ const handleOpen = useCallback(() => {
104
+ navigate({
105
+ to: "/mail/$mailboxId",
106
+ params: { mailboxId },
107
+ search: (prev: MailboxLinkSearch) => ({
108
+ ...prev,
109
+ selectedMessageId: thread.messageId,
110
+ }),
111
+ });
112
+ }, [navigate, mailboxId, thread.messageId]);
107
113
 
108
114
  if (isDesktop || isMultiSelectMode) {
109
115
  return (
@@ -135,28 +141,8 @@ export const SwipeableMessageRow = ({
135
141
  onPeek={setPeek}
136
142
  onToggleCheck={handleToggleCheck}
137
143
  onLongPress={handleLongPress}
138
- onOpen={() => undefined}
144
+ onOpen={handleOpen}
139
145
  onAct={handleAct}
140
- linkComponent={({ onOpenClick, children, ...rowProps }) => (
141
- <Link
142
- {...rowProps}
143
- to="/mail/$mailboxId"
144
- params={{ mailboxId }}
145
- search={(prev: MailboxLinkSearch) => ({
146
- ...prev,
147
- selectedMessageId: thread.messageId,
148
- })}
149
- data-message-row
150
- onMouseDown={modifierSelect.onMouseDown}
151
- onContextMenu={modifierSelect.onContextMenu}
152
- onClick={(e) => {
153
- if (modifierSelect.claimClick(e)) return;
154
- onOpenClick(e);
155
- }}
156
- >
157
- {children}
158
- </Link>
159
- )}
160
146
  />
161
147
  );
162
148
  };