Skip to content

Commit a6613df

Browse files
syedszeeshanchrisolsen
authored andcommitted
feat(#2492): add onblur for textarea
1 parent 1f5b634 commit a6613df

File tree

8 files changed

+142
-18
lines changed

8 files changed

+142
-18
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,4 @@ devenv.local.nix
6767
vite.config.*.timestamp*
6868
vitest.config.*.timestamp*
6969
.cursor/rules/nx-rules.mdc
70-
.github/instructions/nx.instructions.md
70+
.github/instructions/nx.instructions.md

libs/angular-components/src/lib/components/textarea/textarea.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { fireEvent } from "@testing-library/dom";
2222
[mb]="mb"
2323
[ml]="ml"
2424
(onChange)="onChange()"
25+
(onBlur)="onBlur()"
2526
></goab-textarea>
2627
`,
2728
})
@@ -46,6 +47,10 @@ class TestTextareaComponent {
4647
onChange() {
4748
/** do nothing **/
4849
}
50+
51+
onBlur() {
52+
/** do nothing **/
53+
}
4954
}
5055

5156
describe("GoABTextArea", () => {
@@ -104,4 +109,18 @@ describe("GoABTextArea", () => {
104109

105110
expect(onChange).toBeCalledTimes(1);
106111
});
112+
113+
it("should dispatch onBlur", () => {
114+
const onBlur = jest.spyOn(component, "onBlur");
115+
116+
const el = fixture.nativeElement.querySelector("goa-textarea");
117+
fireEvent(
118+
el,
119+
new CustomEvent("_blur", {
120+
detail: { name: "textarea-name", value: "test value" },
121+
}),
122+
);
123+
124+
expect(onBlur).toBeCalledTimes(1);
125+
});
107126
});

libs/angular-components/src/lib/components/textarea/textarea.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
GoabTextAreaCountBy,
33
GoabTextAreaOnChangeDetail,
44
GoabTextAreaOnKeyPressDetail,
5+
GoabTextAreaOnBlurDetail,
56
} from "@abgov/ui-components-common";
67
import {
78
CUSTOM_ELEMENTS_SCHEMA,
@@ -41,6 +42,7 @@ import { GoabControlValueAccessor } from "../base.component";
4142
[attr.mr]="mr"
4243
(_change)="_onChange($event)"
4344
(_keyPress)="_onKeyPress($event)"
45+
(_blur)="_onBlur($event)"
4446
>
4547
</goa-textarea>
4648
`,
@@ -67,6 +69,7 @@ export class GoabTextArea extends GoabControlValueAccessor {
6769

6870
@Output() onChange = new EventEmitter<GoabTextAreaOnChangeDetail>();
6971
@Output() onKeyPress = new EventEmitter<GoabTextAreaOnKeyPressDetail>();
72+
@Output() onBlur = new EventEmitter<GoabTextAreaOnBlurDetail>();
7073

7174
_onChange(e: Event) {
7275
const detail = (e as CustomEvent<GoabTextAreaOnChangeDetail>).detail;
@@ -80,4 +83,10 @@ export class GoabTextArea extends GoabControlValueAccessor {
8083
this.markAsTouched();
8184
this.onKeyPress.emit(detail);
8285
}
86+
87+
_onBlur(e: Event) {
88+
const detail = (e as CustomEvent<GoabTextAreaOnBlurDetail>).detail;
89+
this.markAsTouched();
90+
this.onBlur.emit(detail);
91+
}
8392
}

libs/common/src/lib/common.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ export type GoabTextAreaOnKeyPressDetail = {
193193
key: string;
194194
};
195195

196+
export type GoabTextAreaOnBlurDetail = {
197+
name: string;
198+
value: string;
199+
};
200+
196201
// Tabs
197202

198203
export interface GoabTabsProps {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { render } from "vitest-browser-react";
2+
import { GoabTextArea, GoabInput } from "../src";
3+
import { expect, describe, it, vi } from "vitest";
4+
import { useState } from "react";
5+
6+
describe("TextArea", () => {
7+
it("should trigger onBlur event when focus leaves the textarea", async () => {
8+
const onBlurSpy = vi.fn();
9+
10+
const Component = () => {
11+
const [value, setValue] = useState("");
12+
13+
return (
14+
<div>
15+
<GoabTextArea
16+
testId="test-textarea"
17+
name="test-textarea"
18+
value={value}
19+
onChange={(detail) => setValue(detail.value)}
20+
onBlur={onBlurSpy}
21+
/>
22+
<GoabInput
23+
type="text"
24+
testId="focus-target"
25+
name="focus-input"
26+
// eslint-disable-next-line @typescript-eslint/no-empty-function
27+
onChange={() => {}}
28+
/>
29+
</div>
30+
);
31+
};
32+
33+
const result = render(<Component />);
34+
const textareaEl = result.getByTestId("test-textarea");
35+
const inputEl = result.getByTestId("focus-target");
36+
37+
await vi.waitFor(async () => {
38+
const textarea = textareaEl.element() as HTMLTextAreaElement;
39+
expect(textarea).toBeTruthy();
40+
textarea.focus();
41+
textarea.value = "Test content for blur";
42+
});
43+
44+
// Trigger blur by focusing on the input element
45+
await vi.waitFor(() => {
46+
const input = inputEl.element() as HTMLInputElement;
47+
expect(input).toBeTruthy();
48+
input.focus();
49+
});
50+
51+
// Verify onBlur was called with correct values
52+
await vi.waitFor(() => {
53+
expect(onBlurSpy).toHaveBeenCalledTimes(1);
54+
expect(onBlurSpy).toHaveBeenCalledWith({
55+
name: "test-textarea",
56+
value: "Test content for blur",
57+
});
58+
});
59+
});
60+
});

libs/react-components/src/lib/textarea/textarea.tsx

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
GoabTextAreaCountBy,
33
GoabTextAreaOnChangeDetail,
44
GoabTextAreaOnKeyPressDetail,
5+
GoabTextAreaOnBlurDetail,
56
Margins,
67
} from "@abgov/ui-components-common";
78
import { useEffect, useRef, type JSX } from "react";
@@ -52,6 +53,7 @@ export interface GoabTextAreaProps extends Margins {
5253

5354
onChange?: (event: GoabTextAreaOnChangeDetail) => void;
5455
onKeyPress?: (event: GoabTextAreaOnKeyPressDetail) => void;
56+
onBlur?: (event: GoabTextAreaOnBlurDetail) => void;
5557
}
5658

5759
export function GoabTextArea({
@@ -75,6 +77,7 @@ export function GoabTextArea({
7577
autoComplete,
7678
onChange,
7779
onKeyPress,
80+
onBlur,
7881
}: GoabTextAreaProps): JSX.Element {
7982
const el = useRef<HTMLTextAreaElement>(null);
8083

@@ -83,36 +86,32 @@ export function GoabTextArea({
8386
return;
8487
}
8588
const current = el.current;
86-
const listener: EventListener = (e: Event) => {
87-
const detail = (e as CustomEvent<GoabTextAreaOnChangeDetail>).detail;
8889

90+
const changeListener: EventListener = (e: Event) => {
91+
const detail = (e as CustomEvent<GoabTextAreaOnChangeDetail>).detail;
8992
onChange?.(detail);
9093
};
91-
if (onChange) {
92-
current.addEventListener("_change", listener);
93-
}
94-
return () => {
95-
if (onChange) {
96-
current.removeEventListener("_change", listener);
97-
}
98-
};
99-
}, [el, onChange]);
10094

101-
useEffect(() => {
102-
if (!el.current) {
103-
return;
104-
}
105-
const current = el.current;
10695
const keypressListener = (e: unknown) => {
10796
const detail = (e as CustomEvent<GoabTextAreaOnKeyPressDetail>).detail;
10897
onKeyPress?.(detail);
10998
};
11099

100+
const blurListener = (e: unknown) => {
101+
const detail = (e as CustomEvent<GoabTextAreaOnBlurDetail>).detail;
102+
onBlur?.(detail);
103+
};
104+
105+
current.addEventListener("_change", changeListener);
111106
current.addEventListener("_keyPress", keypressListener);
107+
current.addEventListener("_blur", blurListener);
108+
112109
return () => {
110+
current.removeEventListener("_change", changeListener);
113111
current.removeEventListener("_keyPress", keypressListener);
112+
current.removeEventListener("_blur", blurListener);
114113
};
115-
}, [el, onKeyPress]);
114+
}, [el, onChange, onKeyPress, onBlur]);
116115

117116
return (
118117
<goa-textarea

libs/web-components/src/components/text-area/TextArea.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ describe("GoATextArea", () => {
7878
});
7979
});
8080

81+
it("handles the blur event", async () => {
82+
const onBlur = vi.fn();
83+
const result = render(GoATextArea, {
84+
name: "name",
85+
value: "test value",
86+
testid: "blur-test",
87+
});
88+
89+
const textarea = result.queryByTestId("blur-test");
90+
textarea.addEventListener("_blur", (e: CustomEvent) => {
91+
expect(e.detail.name).toBe("name");
92+
expect(e.detail.value).toBe("test value");
93+
onBlur();
94+
});
95+
96+
await fireEvent.blur(textarea);
97+
98+
await waitFor(() => {
99+
expect(onBlur).toBeCalledTimes(1);
100+
});
101+
});
102+
81103
it("can be readonly", async () => {
82104
const result = render(GoATextArea, {
83105
name: "name",

libs/web-components/src/components/text-area/TextArea.svelte

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@
152152
function onFocus(_e: Event) {
153153
dispatch(_rootEl, "help-text::announce", undefined, { bubbles: true });
154154
}
155+
156+
function onBlur(_e: Event) {
157+
dispatch(
158+
_textareaEl,
159+
"_blur",
160+
{ name, value: _textareaEl.value },
161+
{ bubbles: true },
162+
);
163+
}
155164
</script>
156165

157166
<!-- HTML -->
@@ -183,6 +192,7 @@
183192
on:keyup={onKeyPress}
184193
on:change={onChange}
185194
on:focus={onFocus}
195+
on:blur={onBlur}
186196
/>
187197

188198
{#if maxcount > 0 && !isDisabled}

0 commit comments

Comments
 (0)