@remit/web-client 0.0.309 → 0.0.311
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.311",
|
|
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": {
|
|
@@ -327,57 +327,67 @@ const WiredComposeHeader = ({
|
|
|
327
327
|
// the keyboard down with it and started the cycle over.
|
|
328
328
|
const [expandedFor, setExpandedFor] = useState<number | undefined>(undefined);
|
|
329
329
|
const expanded = expandedFor === documentGeneration;
|
|
330
|
+
const [focusWithin, setFocusWithin] = useState(false);
|
|
330
331
|
|
|
331
332
|
return (
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
onPendingChange={onToPendingChange}
|
|
354
|
-
ref={fieldHandles.to}
|
|
355
|
-
/>
|
|
356
|
-
}
|
|
357
|
-
cc={
|
|
358
|
-
showCc ? (
|
|
359
|
-
<AddressField
|
|
360
|
-
label="Cc"
|
|
361
|
-
addresses={ccAddresses}
|
|
362
|
-
onChange={setCcAddresses}
|
|
363
|
-
ref={fieldHandles.cc}
|
|
333
|
+
<div
|
|
334
|
+
className="contents"
|
|
335
|
+
onFocusCapture={() => setFocusWithin(true)}
|
|
336
|
+
onBlurCapture={(event) => {
|
|
337
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
338
|
+
setFocusWithin(false);
|
|
339
|
+
}}
|
|
340
|
+
>
|
|
341
|
+
<ComposeHeader
|
|
342
|
+
collapsed={!isDesktop && isKeyboardOpen && !expanded && !focusWithin}
|
|
343
|
+
onExpand={() => setExpandedFor(documentGeneration)}
|
|
344
|
+
summary={composeHeaderSummary({
|
|
345
|
+
to: toAddresses,
|
|
346
|
+
cc: ccAddresses,
|
|
347
|
+
bcc: bccAddresses,
|
|
348
|
+
subject,
|
|
349
|
+
})}
|
|
350
|
+
from={
|
|
351
|
+
<FromSelector
|
|
352
|
+
selectedAccountId={selectedAccountId}
|
|
353
|
+
onSelect={onAccountChange}
|
|
364
354
|
/>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
bcc={
|
|
368
|
-
showBcc ? (
|
|
355
|
+
}
|
|
356
|
+
to={
|
|
369
357
|
<AddressField
|
|
370
|
-
label="
|
|
371
|
-
addresses={
|
|
372
|
-
onChange={
|
|
373
|
-
|
|
358
|
+
label="To"
|
|
359
|
+
addresses={toAddresses}
|
|
360
|
+
onChange={setToAddresses}
|
|
361
|
+
placeholder="Recipients"
|
|
362
|
+
onPendingChange={onToPendingChange}
|
|
363
|
+
ref={fieldHandles.to}
|
|
374
364
|
/>
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
365
|
+
}
|
|
366
|
+
cc={
|
|
367
|
+
showCc ? (
|
|
368
|
+
<AddressField
|
|
369
|
+
label="Cc"
|
|
370
|
+
addresses={ccAddresses}
|
|
371
|
+
onChange={setCcAddresses}
|
|
372
|
+
ref={fieldHandles.cc}
|
|
373
|
+
/>
|
|
374
|
+
) : undefined
|
|
375
|
+
}
|
|
376
|
+
bcc={
|
|
377
|
+
showBcc ? (
|
|
378
|
+
<AddressField
|
|
379
|
+
label="Bcc"
|
|
380
|
+
addresses={bccAddresses}
|
|
381
|
+
onChange={setBccAddresses}
|
|
382
|
+
ref={fieldHandles.bcc}
|
|
383
|
+
/>
|
|
384
|
+
) : undefined
|
|
385
|
+
}
|
|
386
|
+
subject={<ComposeSubjectField value={subject} onChange={setSubject} />}
|
|
387
|
+
onShowCc={() => setShowCc(true)}
|
|
388
|
+
onShowBcc={() => setShowBcc(true)}
|
|
389
|
+
/>
|
|
390
|
+
</div>
|
|
381
391
|
);
|
|
382
392
|
};
|
|
383
393
|
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
import assert from "node:assert/strict";
|
|
16
16
|
import { afterEach, describe, it } from "node:test";
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
RemitImapAccountResponse,
|
|
19
|
+
RemitImapDescribeMessageResponse,
|
|
20
|
+
} from "@remit/api-http-client/types.gen.ts";
|
|
18
21
|
import {
|
|
19
22
|
type AnyRouter,
|
|
20
23
|
createMemoryHistory,
|
|
@@ -44,6 +47,20 @@ const account = {
|
|
|
44
47
|
smtpEnabled: true,
|
|
45
48
|
} as unknown as RemitImapAccountResponse;
|
|
46
49
|
|
|
50
|
+
const sourceMessage = {
|
|
51
|
+
message: { messageId: "msg-1" },
|
|
52
|
+
envelope: {
|
|
53
|
+
subject: "Lunch",
|
|
54
|
+
messageIdValue: "<m1@example.com>",
|
|
55
|
+
from: [{ normalizedEmail: "them@example.com", displayName: "Them" }],
|
|
56
|
+
replyTo: [],
|
|
57
|
+
to: [],
|
|
58
|
+
cc: [],
|
|
59
|
+
},
|
|
60
|
+
references: [],
|
|
61
|
+
bodyParts: [],
|
|
62
|
+
} as unknown as RemitImapDescribeMessageResponse;
|
|
63
|
+
|
|
47
64
|
interface VisualViewportStub {
|
|
48
65
|
height: number;
|
|
49
66
|
addEventListener: (type: string, listener: () => void) => void;
|
|
@@ -97,15 +114,22 @@ const testRouter = (): AnyRouter =>
|
|
|
97
114
|
history: createMemoryHistory({ initialEntries: ["/mail/mbx-1"] }),
|
|
98
115
|
}) as unknown as AnyRouter;
|
|
99
116
|
|
|
100
|
-
const Opened = () =>
|
|
117
|
+
const Opened = ({ mode }: { mode: "new" | "reply" }) =>
|
|
101
118
|
createElement(ComposeForm, {
|
|
102
|
-
mode
|
|
119
|
+
mode,
|
|
103
120
|
account,
|
|
121
|
+
sourceMessage: mode === "reply" ? sourceMessage : undefined,
|
|
104
122
|
onDraftCreated: () => {},
|
|
105
123
|
onClose: () => {},
|
|
106
124
|
});
|
|
107
125
|
|
|
108
|
-
const mount = async (
|
|
126
|
+
const mount = async ({
|
|
127
|
+
mode = "new",
|
|
128
|
+
keyboardOpen = true,
|
|
129
|
+
}: {
|
|
130
|
+
mode?: "new" | "reply";
|
|
131
|
+
keyboardOpen?: boolean;
|
|
132
|
+
} = {}): Promise<void> => {
|
|
109
133
|
http = mockFetch(async (call) => {
|
|
110
134
|
if (call.path.endsWith("/config")) return { accounts: [account] };
|
|
111
135
|
return { items: [] };
|
|
@@ -115,7 +139,9 @@ const mount = async (): Promise<void> => {
|
|
|
115
139
|
configurable: true,
|
|
116
140
|
value: viewport,
|
|
117
141
|
});
|
|
118
|
-
viewport.height =
|
|
142
|
+
viewport.height = keyboardOpen
|
|
143
|
+
? globalThis.window.innerHeight - KEYBOARD_HEIGHT
|
|
144
|
+
: globalThis.window.innerHeight;
|
|
119
145
|
|
|
120
146
|
harness = createDomHarness(PHONE);
|
|
121
147
|
harness.renderApp(
|
|
@@ -125,7 +151,7 @@ const mount = async (): Promise<void> => {
|
|
|
125
151
|
children: createElement(
|
|
126
152
|
ComposeProvider,
|
|
127
153
|
null,
|
|
128
|
-
createElement(Opened,
|
|
154
|
+
createElement(Opened, { mode }),
|
|
129
155
|
),
|
|
130
156
|
}),
|
|
131
157
|
);
|
|
@@ -171,4 +197,33 @@ describe("the phone compose header and the software keyboard", () => {
|
|
|
171
197
|
"the header did not collapse over the field being typed into",
|
|
172
198
|
);
|
|
173
199
|
});
|
|
200
|
+
|
|
201
|
+
it("keeps To mounted when a reply brings the keyboard up over it", async () => {
|
|
202
|
+
await mount({ mode: "reply", keyboardOpen: false });
|
|
203
|
+
|
|
204
|
+
const input = recipientInput();
|
|
205
|
+
assert.ok(input, "the rows are there while the keyboard is down");
|
|
206
|
+
|
|
207
|
+
act(() => {
|
|
208
|
+
input.focus();
|
|
209
|
+
});
|
|
210
|
+
setKeyboard(true);
|
|
211
|
+
await harness?.flush();
|
|
212
|
+
|
|
213
|
+
assert.equal(
|
|
214
|
+
recipientInput(),
|
|
215
|
+
input,
|
|
216
|
+
"the recipient field stayed mounted under the keyboard",
|
|
217
|
+
);
|
|
218
|
+
assert.equal(
|
|
219
|
+
globalThis.document.activeElement,
|
|
220
|
+
input,
|
|
221
|
+
"the recipient field kept focus",
|
|
222
|
+
);
|
|
223
|
+
assert.equal(
|
|
224
|
+
collapsedBar(),
|
|
225
|
+
null,
|
|
226
|
+
"the header did not collapse over the field being typed into",
|
|
227
|
+
);
|
|
228
|
+
});
|
|
174
229
|
});
|
|
@@ -105,6 +105,10 @@ export interface CalendarSuggestionAnswers {
|
|
|
105
105
|
suggestionId: string,
|
|
106
106
|
calendarId: string,
|
|
107
107
|
) => Promise<SuggestionAnswer>;
|
|
108
|
+
tentative: (
|
|
109
|
+
suggestionId: string,
|
|
110
|
+
calendarId: string,
|
|
111
|
+
) => Promise<SuggestionAnswer>;
|
|
108
112
|
decline: (suggestionId: string) => Promise<SuggestionAnswer>;
|
|
109
113
|
dismiss: (
|
|
110
114
|
suggestionId: string,
|
|
@@ -144,6 +148,17 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
144
148
|
.catch(refused),
|
|
145
149
|
[accept],
|
|
146
150
|
);
|
|
151
|
+
const tentativeSuggestion = useCallback(
|
|
152
|
+
(suggestionId: string, calendarId: string) =>
|
|
153
|
+
accept
|
|
154
|
+
.mutateAsync({
|
|
155
|
+
path: { suggestionId },
|
|
156
|
+
body: { calendarId, answer: "Tentative" },
|
|
157
|
+
})
|
|
158
|
+
.then(() => ANSWERED)
|
|
159
|
+
.catch(refused),
|
|
160
|
+
[accept],
|
|
161
|
+
);
|
|
147
162
|
const declineSuggestion = useCallback(
|
|
148
163
|
(suggestionId: string) =>
|
|
149
164
|
decline
|
|
@@ -164,12 +179,14 @@ export function useCalendarSuggestionAnswers(): CalendarSuggestionAnswers {
|
|
|
164
179
|
return useMemo(
|
|
165
180
|
() => ({
|
|
166
181
|
accept: acceptSuggestion,
|
|
182
|
+
tentative: tentativeSuggestion,
|
|
167
183
|
decline: declineSuggestion,
|
|
168
184
|
dismiss: dismissSuggestion,
|
|
169
185
|
isAnswering: accept.isPending || decline.isPending || dismiss.isPending,
|
|
170
186
|
}),
|
|
171
187
|
[
|
|
172
188
|
acceptSuggestion,
|
|
189
|
+
tentativeSuggestion,
|
|
173
190
|
declineSuggestion,
|
|
174
191
|
dismissSuggestion,
|
|
175
192
|
accept.isPending,
|
|
@@ -245,6 +245,12 @@ export function useIntelligenceCalendar(
|
|
|
245
245
|
answer(invitationId, "add this to your calendar", () =>
|
|
246
246
|
answers.accept(invitationId, defaultCalendarId),
|
|
247
247
|
),
|
|
248
|
+
onTentativeInvite: cancellation
|
|
249
|
+
? undefined
|
|
250
|
+
: () =>
|
|
251
|
+
answer(invitationId, "add this as a maybe", () =>
|
|
252
|
+
answers.tentative(invitationId, defaultCalendarId),
|
|
253
|
+
),
|
|
248
254
|
onDeclineInvite: () =>
|
|
249
255
|
answer(invitationId, "decline this", () => answers.decline(invitationId)),
|
|
250
256
|
onMuteInvite: () =>
|