@gcforms/tag-input 1.0.4 → 1.0.6
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/CHANGELOG.md +7 -2
- package/package.json +1 -1
- package/src/TagInput.tsx +10 -0
- package/src/tests/TagInput.cy.tsx +0 -239
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.6] - 2026-05-07
|
|
9
|
+
|
|
10
|
+
- Allow passing onBlur enabling flows to mark valid and invalid addresses without requiring the user to press Enter
|
|
11
|
+
|
|
12
|
+
## [1.0.5] - 2025-12-05
|
|
13
|
+
|
|
14
|
+
- Remove cypress test over to vitest outside of package directory
|
|
8
15
|
|
|
9
16
|
## [1.0.4] - 2025-10-01
|
|
10
17
|
|
|
@@ -32,6 +39,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
39
|
|
|
33
40
|
## [1.0.0] - 2025-05-14
|
|
34
41
|
|
|
35
|
-
### Added
|
|
36
|
-
|
|
37
42
|
- Initial release
|
package/package.json
CHANGED
package/src/TagInput.tsx
CHANGED
|
@@ -28,6 +28,7 @@ export const TagInput = ({
|
|
|
28
28
|
restrictDuplicates = true,
|
|
29
29
|
allowSpacesInTags = true,
|
|
30
30
|
maxTags,
|
|
31
|
+
onBlur,
|
|
31
32
|
onTagAdd,
|
|
32
33
|
onTagRemove,
|
|
33
34
|
validateTag,
|
|
@@ -42,6 +43,10 @@ export const TagInput = ({
|
|
|
42
43
|
restrictDuplicates?: boolean;
|
|
43
44
|
allowSpacesInTags?: boolean;
|
|
44
45
|
maxTags?: number;
|
|
46
|
+
onBlur?: (
|
|
47
|
+
event: React.FocusEvent<HTMLInputElement>,
|
|
48
|
+
helpers: { addTag: (tag: string) => void }
|
|
49
|
+
) => void;
|
|
45
50
|
onTagAdd?: (tag: string) => void;
|
|
46
51
|
onTagRemove?: (tag: string) => void;
|
|
47
52
|
validateTag?: (tag: string) => {
|
|
@@ -214,6 +219,10 @@ export const TagInput = ({
|
|
|
214
219
|
}
|
|
215
220
|
};
|
|
216
221
|
|
|
222
|
+
const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
223
|
+
onBlur?.(event, { addTag: handleAddTag });
|
|
224
|
+
};
|
|
225
|
+
|
|
217
226
|
return (
|
|
218
227
|
<div className="gc-tag-input-container" onClick={() => tagInputRef.current?.focus()}>
|
|
219
228
|
<label htmlFor={id} className="gc-tag-input-label">
|
|
@@ -244,6 +253,7 @@ export const TagInput = ({
|
|
|
244
253
|
name={name}
|
|
245
254
|
type="text"
|
|
246
255
|
placeholder={placeholder}
|
|
256
|
+
onBlur={handleBlur}
|
|
247
257
|
onKeyDown={handleKeyDown}
|
|
248
258
|
ref={tagInputRef}
|
|
249
259
|
/>
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React from "react";
|
|
3
|
-
|
|
4
|
-
import { TagInput } from "../TagInput";
|
|
5
|
-
|
|
6
|
-
describe("<TagInput />", () => {
|
|
7
|
-
it("renders without crashing", () => {
|
|
8
|
-
cy.mount(
|
|
9
|
-
<div>
|
|
10
|
-
<TagInput initialTags={[]} />
|
|
11
|
-
</div>
|
|
12
|
-
);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("accepts initial tags", () => {
|
|
16
|
-
cy.mount(
|
|
17
|
-
<div>
|
|
18
|
-
<TagInput
|
|
19
|
-
initialTags={["Tag one", "Tag two", "Tag three"]}
|
|
20
|
-
onTagAdd={() => {}}
|
|
21
|
-
onTagRemove={() => {}}
|
|
22
|
-
/>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
cy.get(".gc-tag").should("have.length", 3);
|
|
27
|
-
cy.get(".gc-tag").should("contain", "Tag one");
|
|
28
|
-
cy.get(".gc-tag").should("contain", "Tag two");
|
|
29
|
-
cy.get(".gc-tag").should("contain", "Tag three");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("sets the name attribute", () => {
|
|
33
|
-
cy.mount(
|
|
34
|
-
<div>
|
|
35
|
-
<TagInput initialTags={[]} name="test-name" />
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
cy.get("[data-testid='tag-input']").should("have.attr", "name", "test-name");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("adds a custom label", () => {
|
|
43
|
-
cy.mount(
|
|
44
|
-
<div>
|
|
45
|
-
<TagInput initialTags={[]} label="Custom Label" />
|
|
46
|
-
</div>
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
cy.get(".gc-tag-input-label").should("contain", "Custom Label");
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("adds a custom description", () => {
|
|
53
|
-
cy.mount(
|
|
54
|
-
<div>
|
|
55
|
-
<TagInput initialTags={[]} description="Custom Description" />
|
|
56
|
-
</div>
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
cy.get(".gc-tag-input-description").should("contain", "Custom Description");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it("adds a tag", () => {
|
|
63
|
-
cy.mount(
|
|
64
|
-
<div>
|
|
65
|
-
<TagInput initialTags={[]} />
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
cy.get("[data-testid='tag-input']").type("New Tag{enter}");
|
|
70
|
-
cy.get("[data-testid='tag-input']").should("have.value", "");
|
|
71
|
-
cy.get(".gc-tag").should("contain", "New Tag");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("announces that a tag was added", () => {
|
|
75
|
-
cy.mount(
|
|
76
|
-
<div>
|
|
77
|
-
<TagInput initialTags={[]} />
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
cy.get("[data-testid='tag-input']").type("New Tag{enter}");
|
|
81
|
-
cy.get("#tag-input-live-region").should("exist").and("contain", `Tag "New Tag" added`);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("restricts duplicates", () => {
|
|
85
|
-
cy.mount(
|
|
86
|
-
<div>
|
|
87
|
-
<TagInput initialTags={["Tag 1"]} restrictDuplicates={true} />
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
cy.get("[data-testid='tag-input']").type("Tag 1{enter}");
|
|
92
|
-
cy.get("[data-testid='tag-input']").should("have.value", "");
|
|
93
|
-
cy.get(".gc-tag").should("have.length", 1);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("announces that a duplicate tag was added", () => {
|
|
97
|
-
cy.mount(
|
|
98
|
-
<div>
|
|
99
|
-
<TagInput restrictDuplicates={true} />
|
|
100
|
-
</div>
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
cy.get("[data-testid='tag-input']").type("Tag 1{enter}Tag 1{enter}");
|
|
104
|
-
cy.get("#tag-input-live-region").should("exist").and("contain", `"Tag 1" is a duplicate tag`);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("allows duplicates", () => {
|
|
108
|
-
cy.mount(
|
|
109
|
-
<div>
|
|
110
|
-
<TagInput initialTags={["Tag 1"]} restrictDuplicates={false} />
|
|
111
|
-
</div>
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
cy.get("[data-testid='tag-input']").type("Tag 1{enter}");
|
|
115
|
-
cy.get("[data-testid='tag-input']").should("have.value", "");
|
|
116
|
-
cy.get(".gc-tag").should("have.length", 2);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("removes a tag", () => {
|
|
120
|
-
const onTagRemove = cy.stub().as("onTagRemove");
|
|
121
|
-
|
|
122
|
-
cy.mount(
|
|
123
|
-
<div>
|
|
124
|
-
<TagInput initialTags={["Tag 1", "Tag 2"]} onTagAdd={() => {}} onTagRemove={onTagRemove} />
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
cy.get(".gc-tag").should("contain", "Tag 2").should("contain", "Tag 1");
|
|
129
|
-
cy.get("#tag-0 button").click();
|
|
130
|
-
cy.get(".gc-tag").should("contain", "Tag 2").should("not.contain", "Tag 1");
|
|
131
|
-
cy.get("@onTagRemove").should("have.been.calledWith", "Tag 1");
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it("removes a selected tag", () => {
|
|
135
|
-
cy.mount(
|
|
136
|
-
<div>
|
|
137
|
-
<TagInput initialTags={["one", "two", "three", "four", "five", "six"]} />
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
cy.get("[data-testid='tag-input']").type("{leftarrow}{leftarrow}{leftarrow}{leftarrow}{del}");
|
|
142
|
-
cy.get("#tag-input-live-region").should("exist").and("contain", `Tag "three" removed`);
|
|
143
|
-
cy.get(".gc-tag").should("not.contain", "three");
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("announces when a tag is removed", () => {
|
|
147
|
-
cy.mount(
|
|
148
|
-
<div>
|
|
149
|
-
<TagInput initialTags={["Tag 1"]} onTagAdd={() => {}} onTagRemove={() => {}} />
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
cy.get(".gc-tag").should("contain", "Tag 1");
|
|
154
|
-
cy.get(".gc-tag button").click();
|
|
155
|
-
cy.get("#tag-input-live-region").should("exist").and("contain", `Tag "Tag 1" removed`);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("calls onTagAdd handler when adding a tag", () => {
|
|
159
|
-
const onTagAdd = cy.stub().as("onTagAdd");
|
|
160
|
-
|
|
161
|
-
cy.mount(
|
|
162
|
-
<div>
|
|
163
|
-
<TagInput initialTags={[]} onTagAdd={onTagAdd} onTagRemove={() => {}} />
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
|
|
167
|
-
cy.get("[data-testid='tag-input']").type("New Tag{enter}");
|
|
168
|
-
cy.get("[data-testid='tag-input']").should("have.value", "");
|
|
169
|
-
cy.get(".gc-tag").should("contain", "New Tag");
|
|
170
|
-
cy.get("@onTagAdd").should("have.been.calledWith", "New Tag");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it("calls onTagRemove handler when removing a tag", () => {
|
|
174
|
-
const onTagRemove = cy.stub().as("onTagRemove");
|
|
175
|
-
|
|
176
|
-
cy.mount(
|
|
177
|
-
<div>
|
|
178
|
-
<TagInput initialTags={["Tag one", "Tag two"]} onTagRemove={onTagRemove} />
|
|
179
|
-
</div>
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
cy.get(".gc-tag").should("contain", "Tag one");
|
|
183
|
-
cy.get(".gc-tag").first().find("button").click();
|
|
184
|
-
cy.get("[data-testid='tag-input']").should("not.contain", "Tag one");
|
|
185
|
-
cy.get("@onTagRemove").should("have.been.calledWith", "Tag one");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("validates the tag according to the validation function", () => {
|
|
189
|
-
const validateTag = (tag: string) => {
|
|
190
|
-
const errors: string[] = [];
|
|
191
|
-
|
|
192
|
-
if (tag.length < 3) {
|
|
193
|
-
errors.push("Tag must be at least 3 characters long");
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (/\d/.test(tag)) {
|
|
197
|
-
errors.push("Tag must not include numbers");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (tag.length > 10) {
|
|
201
|
-
errors.push("Tag must be at most 10 characters long");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
isValid: errors.length === 0,
|
|
206
|
-
errors: errors,
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
cy.mount(
|
|
211
|
-
<div>
|
|
212
|
-
<TagInput validateTag={validateTag} />
|
|
213
|
-
</div>
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
cy.get("[data-testid='tag-input']").type("ab{enter}");
|
|
217
|
-
cy.get("[data-testid='tag-input-error'] div").should("have.length", 1);
|
|
218
|
-
cy.get("[data-testid='tag-input-error']").should(
|
|
219
|
-
"contain",
|
|
220
|
-
"Tag must be at least 3 characters long"
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
cy.get("[data-testid='tag-input']").type("abcdefghijklmnopqrstuvwxy{enter}");
|
|
224
|
-
cy.get("[data-testid='tag-input-error'] div").should("have.length", 1);
|
|
225
|
-
cy.get("[data-testid='tag-input-error']").should(
|
|
226
|
-
"contain",
|
|
227
|
-
"Tag must be at most 10 characters long"
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// Multiple errors
|
|
231
|
-
cy.get("[data-testid='tag-input']").type("T1{enter}");
|
|
232
|
-
cy.get("[data-testid='tag-input-error'] div").should("have.length", 2);
|
|
233
|
-
cy.get("[data-testid='tag-input-error']").should(
|
|
234
|
-
"contain",
|
|
235
|
-
"Tag must be at least 3 characters long"
|
|
236
|
-
);
|
|
237
|
-
cy.get("[data-testid='tag-input-error']").should("contain", "Tag must not include numbers");
|
|
238
|
-
});
|
|
239
|
-
});
|