Skip to content

Commit 4fa88a6

Browse files
committed
feat: add acitivty related fields into the form data
1 parent ab274ed commit 4fa88a6

File tree

6 files changed

+125
-2
lines changed

6 files changed

+125
-2
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="/test-harness.js"></script>
6+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
7+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
8+
<style>
9+
form {
10+
display: flex;
11+
flex-flow: column nowrap;
12+
gap: 4px
13+
}
14+
15+
form output {
16+
white-space: pre-wrap;
17+
padding: 4px;
18+
}
19+
</style>
20+
</head>
21+
22+
<body>
23+
<main onsubmit="handleSubmit(event)" id="webchat"></main>
24+
<script>
25+
function handleSubmit(event) {
26+
const form = event.target;
27+
event.preventDefault();
28+
const data = Object.fromEntries(new FormData(form).entries());
29+
form.elements[1].value = `${'webchat:activity-key' in data} ${data['webchat:activity-id']}`;
30+
}
31+
32+
run(async function () {
33+
const {
34+
WebChat: { renderWebChat }
35+
} = window; // Imports in UMD fashion.
36+
37+
const allowFormElementsTransform = () => next => request =>
38+
next({
39+
...request,
40+
allowedTags: Object.freeze(
41+
new Map(request.allowedTags)
42+
.set('form', Object.freeze({ attributes: Object.freeze([]) }))
43+
.set('button', Object.freeze({ attributes: Object.freeze([]) }))
44+
.set('output', Object.freeze({ attributes: Object.freeze([]) }))
45+
)
46+
});
47+
48+
const { directLine, store } = testHelpers.createDirectLineEmulator();
49+
50+
renderWebChat(
51+
{ directLine, htmlContentTransformMiddleware: [allowFormElementsTransform], store },
52+
document.getElementById('webchat')
53+
);
54+
55+
await pageConditions.uiConnected();
56+
57+
await directLine.emulateIncomingActivity({
58+
id: 'my-activity-id',
59+
text: `<form><button>Send form</button><output></output></form>`,
60+
type: 'message'
61+
});
62+
63+
64+
await pageConditions.numActivitiesShown(1);
65+
66+
pageElements.sendBoxTextBox().focus();
67+
68+
// WHEN: Clicking on Send Form button
69+
await host.sendShiftTab(2);
70+
await host.sendKeys('ARROW_UP');
71+
await host.sendKeys('ENTER');
72+
await host.sendKeys('ENTER');
73+
74+
// THEN: The form with activity related fields is shown
75+
await host.snapshot('local');
76+
77+
// WHEN: Stolen form is submitted
78+
const form = document.querySelector('article form').cloneNode(true)
79+
window.webchat.prepend(form);
80+
form.requestSubmit();
81+
82+
// THEN: No activity related fields are present
83+
await host.snapshot('local');
84+
});
85+
</script>
86+
</body>
87+
</html>
Loading
Loading

packages/component/src/Transcript/ActivityRow.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const { useActivityKeysByRead, useGetHasAcknowledgedByActivityKey, useGetKeyByAc
2020

2121
type ActivityRowProps = PropsWithChildren<{ activity: WebChatActivity }>;
2222

23-
const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, children }, ref) => {
23+
const ActivityRow = forwardRef<HTMLElement, ActivityRowProps>(({ activity, children }, ref) => {
2424
const [activeDescendantId] = useActiveDescendantId();
2525
const [readActivityKeys] = useActivityKeysByRead();
2626
const bodyRef = useRef<HTMLDivElement>();
@@ -65,6 +65,42 @@ const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, chi
6565
[bodyRef, children]
6666
);
6767

68+
const activityIdRef = useRefFrom(activity.id);
69+
70+
const handleFormData = useCallback(
71+
(event: FormDataEvent) => {
72+
event.formData.set('webchat:activity-key', activityKeyRef.current);
73+
activityIdRef.current && event.formData.set('webchat:activity-id', activityIdRef.current);
74+
},
75+
76+
[activityKeyRef, activityIdRef]
77+
);
78+
79+
const prevArticleRef = useRef<HTMLElement>(null);
80+
81+
const wrappedRef = useCallback(
82+
(el: HTMLElement | null) => {
83+
if (prevArticleRef.current) {
84+
el.removeEventListener('formdata', handleFormData);
85+
}
86+
87+
if (el) {
88+
el.addEventListener('formdata', handleFormData);
89+
}
90+
91+
if (ref) {
92+
if (ref instanceof Function) {
93+
ref(el);
94+
} else {
95+
ref.current = el;
96+
}
97+
}
98+
99+
prevArticleRef.current = el;
100+
},
101+
[handleFormData, ref]
102+
);
103+
68104
return (
69105
// TODO: [P2] Add `aria-roledescription="message"` for better AX, need localization strings.
70106
<article
@@ -74,7 +110,7 @@ const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, chi
74110
})}
75111
// When NVDA is in browse mode, using up/down arrow key to "browse" will dispatch "click" and "mousedown" events for <article> element (inside <LiveRegionActivity>).
76112
onMouseDownCapture={handleMouseDownCapture}
77-
ref={ref}
113+
ref={wrappedRef}
78114
>
79115
{/* TODO: [P1] File a crbug for TalkBack. It should not able to read the content twice when scanning. */}
80116

0 commit comments

Comments
 (0)