@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.
|
|
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 {
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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={
|
|
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
|
};
|