@gregcm999/strapi-plugin-tagsinput 2.0.3-beta.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Sumita Canopas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ <p align="center"><a href="https://canopas.com/contact"><img src="./assets/banner.png"></a></p>
2
+
3
+ <h1><strong>Tagsinput plugin for strapi with suggestions</strong></h1>
4
+
5
+ <h3>🚀<strong>Strapi 5 supported from <a href="https://github.com/canopas/strapi-plugin-tagsinput/releases/tag/2.0.2">2.0.2</a> </strong>🚀</h3>
6
+
7
+ This plugin is used to add tagsinput in your strapi admin panel.
8
+ Read more about it at [tagsinput guidence](https://blog.canopas.com/the-simple-guidance-how-to-add-tagsinput-customfield-plugin-in-strapi-b5d2b5af7c3b).
9
+
10
+ <img src="./assets/demo.png">
11
+
12
+ ## How to Install
13
+
14
+ Using npm,
15
+
16
+ ```
17
+ npm i strapi-plugin-tagsinput
18
+ ```
19
+
20
+ Using yarn,
21
+
22
+ ```
23
+ yarn add strapi-plugin-tagsinput
24
+ ```
25
+
26
+ ## How to use
27
+
28
+ After installation, you can add tagsinput as a custom field.
29
+
30
+ #### Suggestions for tag
31
+
32
+ While adding tagsInput, you will see the `API URL` field.
33
+
34
+ If you want to use REST API for suggestions, then add your API url in this field.
35
+
36
+ **Notes:**
37
+
38
+ - If the API domain is different, then a full API URL is required. i.e. `http://localhost:1337/api/tags?fields[0]=name` (Make sure API CORS are enabled for your strapi domain in this case).
39
+ - Otherwise, add only the path of API i.e `/api/tags?fields[0]=name`
40
+ - You need to add custom logic for adding created tags in `Tags` collection.
41
+
42
+ ## Showcase
43
+
44
+ How to use tagsinput?
45
+
46
+ <img src="./assets/showcase.gif">
47
+
48
+ ## Issues
49
+
50
+ If you face any issues, feel free to submit them with detailed information.
51
+
52
+ ## Contribution
53
+
54
+ The Canopas team enthusiastically welcomes contributions and project participation! There are a bunch of things you can do if you want to contribute! The [Contributor Guide](CONTRIBUTING.md) has all the information you need for everything from reporting bugs to contributing entire new features. Please don't hesitate to jump in if you'd like to, or even ask us questions if something isn't clear.
55
+
56
+ ## Show your support ⭐️
57
+
58
+ Add a star if this project helped you.
59
+
60
+ ## Credits
61
+
62
+ This repository is owned and maintained by the [Canopas team](https://canopas.com/). If you are interested in building web apps, plugins or designing products, please let us know. We'd love to hear from you!
63
+
64
+ <a href="https://canopas.com/contact"><img src="./assets/cta.png" width=300></a>
65
+
66
+ ## Licence
67
+
68
+ This repository is licenced under [MIT](https://github.com/canopas/strapi-plugin-tagsinput/blob/master/LICENSE).
69
+
70
+ ## Custom Fix Purpose
71
+
72
+ - **Plugin Tags Input**
73
+ - Fork from `strapi-plugin-tagsinput` to `gregcm999/strapi-plugin-tagsinput`
74
+ - **Improve theme handling**: Added system preference support for dark/light mode ([PR #45](https://github.com/canopas/strapi-plugin-tagsinput/pull/45))
75
+ - **Fix deletion error**: Resolved error when removing the last tag from the input field ([PR #44](https://github.com/canopas/strapi-plugin-tagsinput/pull/44))
76
+ - **Better field labels**: Updated custom tag fields to use their specific labels instead of a generic "Tags" label ([PR #42](https://github.com/canopas/strapi-plugin-tagsinput/pull/42))
@@ -0,0 +1,329 @@
1
+ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState, useEffect } from "react";
3
+ import PropTypes from "prop-types";
4
+ import axios from "axios";
5
+ import { Field, Flex } from "@strapi/design-system";
6
+ import { useIntl } from "react-intl";
7
+ import TagsInput from "react-tagsinput";
8
+ import Autosuggest from "react-autosuggest";
9
+ import { createGlobalStyle, css } from "styled-components";
10
+ const light = css`
11
+ :root {
12
+ --primary: #7b79ff;
13
+ --secondary: rgb(255, 255, 255);
14
+ --text: #32324d;
15
+ --input-background: #ffffff;
16
+ --input-border: #dcdce4;
17
+ --tag-background: #f0f0ff;
18
+ --tag-text: #4945ff;
19
+ --suggestion-background: #ffffff;
20
+ --suggestion-hover: #f6f6f9;
21
+ }
22
+ `;
23
+ const dark = css`
24
+ :root {
25
+ --primary: #7b79ff;
26
+ --secondary: rgb(33, 33, 52);
27
+ --text: #ffffff;
28
+ --input-background: #181826;
29
+ --input-border: #4a4a6a;
30
+ --tag-background: #7b79ff;
31
+ --tag-text: #ffffff;
32
+ --suggestion-background: #181826;
33
+ --suggestion-hover: #212134;
34
+ }
35
+ `;
36
+ const styles = css`
37
+ .react-tagsinput {
38
+ width: 100%;
39
+ border: 1px solid var(--input-border);
40
+ border-radius: 4px;
41
+ overflow: hidden;
42
+ padding-left: 5px;
43
+ padding-top: 5px;
44
+ }
45
+
46
+ .react-tagsinput--focused {
47
+ outline: 3px solid var(--primary);
48
+ }
49
+
50
+ .react-tagsinput-tag {
51
+ background-color: var(--tag-background);
52
+ border-radius: 2px;
53
+ border: 1px solid var(--tag-background);
54
+ color: var(--tag-text);
55
+ display: inline-block;
56
+ font-family: sans-serif;
57
+ font-size: 13px;
58
+ font-weight: 400;
59
+ margin-bottom: 5px;
60
+ margin-right: 5px;
61
+ padding: 5px;
62
+ }
63
+
64
+ .react-tagsinput-remove {
65
+ cursor: pointer;
66
+ font-weight: bold;
67
+ }
68
+
69
+ .react-tagsinput-tag a::before {
70
+ content: " ×";
71
+ }
72
+
73
+ .react-tagsinput-input {
74
+ background: transparent;
75
+ border: 0;
76
+ color: var(--text);
77
+ font-family: sans-serif;
78
+ font-size: 13px;
79
+ font-weight: 400;
80
+ margin-bottom: 6px;
81
+ margin-top: 1px;
82
+ outline: none;
83
+ padding: 5px;
84
+ width: 100%;
85
+ }
86
+
87
+ .react-tagsinput > span {
88
+ display: flex;
89
+ flex-flow: wrap;
90
+ }
91
+
92
+ .react-autosuggest__container {
93
+ display: flex;
94
+ flex-direction: column;
95
+ flex: auto;
96
+ }
97
+
98
+ .react-autosuggest__suggestions-container {
99
+ position: absolute;
100
+ z-index: 200;
101
+ width: 280px;
102
+ margin: 0;
103
+ padding: 0;
104
+ list-style-type: none;
105
+ background-color: var(--suggestion-background);
106
+ }
107
+
108
+ .react-autosuggest__suggestions-container--open {
109
+ border: 1px solid var(--input-border);
110
+ }
111
+
112
+ .react-autosuggest__suggestion {
113
+ cursor: pointer;
114
+ padding: 10px 20px;
115
+ }
116
+
117
+ .react-autosuggest__suggestion > span {
118
+ font-size: 13px;
119
+ font-weight: 400;
120
+ }
121
+
122
+ .react-autosuggest__suggestion--highlighted,
123
+ .react-autosuggest__suggestion--focused {
124
+ background-color: var(--suggestion-hover);
125
+ }
126
+ `;
127
+ const getStyling = (theme) => {
128
+ let themeStyle = light;
129
+ switch (theme) {
130
+ case "dark":
131
+ themeStyle = dark;
132
+ break;
133
+ default:
134
+ themeStyle = light;
135
+ }
136
+ return createGlobalStyle`
137
+ ${themeStyle}
138
+ ${styles}
139
+ `;
140
+ };
141
+ const ThemeStyle = getStyling(getCurrentTheme());
142
+ const Tags = ({
143
+ attribute,
144
+ description,
145
+ error,
146
+ label,
147
+ labelAction,
148
+ name,
149
+ onChange,
150
+ required,
151
+ value
152
+ }) => {
153
+ const { formatMessage } = useIntl();
154
+ const apiUrl = attribute?.options?.apiUrl || "";
155
+ const attrName = apiUrl.slice(apiUrl.lastIndexOf("=") + 1) || "name";
156
+ const inputEle = useRef(null);
157
+ const [tags, setTags] = useState(() => {
158
+ try {
159
+ const values = typeof value === "string" ? JSON.parse(value) : value;
160
+ return Array.isArray(values) ? values.map((v) => v[attrName] || v.name || v) : [];
161
+ } catch (e) {
162
+ return [];
163
+ }
164
+ });
165
+ const [suggestions, setSuggestions] = useState([]);
166
+ useEffect(() => {
167
+ const suggestionsContainer = document.querySelector(
168
+ ".react-autosuggest__suggestions-container"
169
+ );
170
+ if (suggestionsContainer && inputEle.current) {
171
+ suggestionsContainer.style.top = `${inputEle.current.offsetHeight + 5}px`;
172
+ }
173
+ const handleClickOutside = (event) => {
174
+ const tagsInput = document.querySelector(".react-tagsinput");
175
+ if (tagsInput) {
176
+ tagsInput.classList.toggle(
177
+ "react-tagsinput--focused",
178
+ inputEle.current?.contains(event.target)
179
+ );
180
+ }
181
+ };
182
+ document.addEventListener("mousedown", handleClickOutside);
183
+ return () => document.removeEventListener("mousedown", handleClickOutside);
184
+ }, []);
185
+ const handleTagsChange = async (newTags) => {
186
+ const lastTag = newTags[newTags.length - 1];
187
+ const suggestionsArray = suggestions.data || [];
188
+ const existingTag = suggestionsArray.find(
189
+ (s) => s[attrName]?.toLowerCase() === lastTag?.toLowerCase()
190
+ );
191
+ if (lastTag && !existingTag) {
192
+ if (apiUrl) {
193
+ try {
194
+ const response = await axios.post(apiUrl, {
195
+ data: {
196
+ [attrName]: lastTag
197
+ }
198
+ });
199
+ setSuggestions((prevSuggestions) => {
200
+ const newSuggestionsData = [...prevSuggestions.data || [], response.data.data];
201
+ return { ...prevSuggestions, data: newSuggestionsData };
202
+ });
203
+ newTags[newTags.length - 1] = response.data.data[attrName];
204
+ } catch (error2) {
205
+ console.error("Error creating new tag:", error2);
206
+ }
207
+ } else {
208
+ setSuggestions((prevSuggestions) => {
209
+ const newSuggestion = { id: Date.now(), [attrName]: lastTag };
210
+ const newSuggestionsData = [...prevSuggestions.data || [], newSuggestion];
211
+ return { ...prevSuggestions, data: newSuggestionsData };
212
+ });
213
+ }
214
+ }
215
+ setTags(newTags);
216
+ const value2 = JSON.stringify(newTags.map((tag) => ({ [attrName]: tag })));
217
+ onChange({
218
+ target: {
219
+ name,
220
+ value: value2,
221
+ type: attribute.type
222
+ }
223
+ });
224
+ };
225
+ const getSuggestions = async () => {
226
+ if (!apiUrl) return;
227
+ try {
228
+ const res = await axios.get(apiUrl);
229
+ setSuggestions(res.data);
230
+ } catch (err) {
231
+ setSuggestions({ data: [] });
232
+ }
233
+ };
234
+ const autocompleteRenderInput = (props) => {
235
+ const handleOnChange = (e, { newValue, method }) => {
236
+ if (method === "enter") {
237
+ e.preventDefault();
238
+ } else {
239
+ props.onChange(e);
240
+ }
241
+ };
242
+ const inputValue = props.value && props.value.trim() || "";
243
+ const inputLength = inputValue.length;
244
+ let s = suggestions.data || [];
245
+ if (s.length <= 0) {
246
+ getSuggestions();
247
+ }
248
+ if (inputLength > 0) {
249
+ s = s.filter((state) => {
250
+ const suggestionName = state[attrName] || "";
251
+ return suggestionName.toLowerCase().slice(0, inputLength) === inputValue;
252
+ }).map((state) => ({
253
+ id: state.id,
254
+ [attrName]: state[attrName] || ""
255
+ }));
256
+ }
257
+ return /* @__PURE__ */ jsx(
258
+ Autosuggest,
259
+ {
260
+ ref: props.ref,
261
+ suggestions: s,
262
+ shouldRenderSuggestions: (value2) => value2 && value2.trim().length > 0,
263
+ getSuggestionValue: (s2) => s2[attrName],
264
+ renderSuggestion: (s2) => /* @__PURE__ */ jsx("span", { children: s2[attrName] }),
265
+ inputProps: { ...props, onChange: handleOnChange },
266
+ onSuggestionSelected: (_, { suggestion }) => props.addTag(suggestion[attrName]),
267
+ onSuggestionsFetchRequested: () => {
268
+ }
269
+ }
270
+ );
271
+ };
272
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
273
+ Field.Root,
274
+ {
275
+ name,
276
+ id: name,
277
+ error,
278
+ hint: description && formatMessage({ id: description }),
279
+ required,
280
+ children: /* @__PURE__ */ jsxs(
281
+ Flex,
282
+ {
283
+ direction: "column",
284
+ alignItems: "stretch",
285
+ gap: 1,
286
+ style: { position: "relative" },
287
+ ref: inputEle,
288
+ children: [
289
+ label && /* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: formatMessage({ id: label, defaultMessage: label }) }),
290
+ /* @__PURE__ */ jsx(ThemeStyle, {}),
291
+ /* @__PURE__ */ jsx(Flex, { direction: "column", children: /* @__PURE__ */ jsx(
292
+ TagsInput,
293
+ {
294
+ value: tags,
295
+ onChange: handleTagsChange,
296
+ onlyUnique: true,
297
+ renderInput: autocompleteRenderInput
298
+ }
299
+ ) }),
300
+ /* @__PURE__ */ jsx(Field.Hint, {}),
301
+ /* @__PURE__ */ jsx(Field.Error, {})
302
+ ]
303
+ }
304
+ )
305
+ }
306
+ ) });
307
+ };
308
+ Tags.defaultProps = {
309
+ description: null,
310
+ disabled: false,
311
+ error: null,
312
+ labelAction: null,
313
+ required: false,
314
+ value: ""
315
+ };
316
+ Tags.propTypes = {
317
+ label: PropTypes.object.isRequired,
318
+ onChange: PropTypes.func.isRequired,
319
+ attribute: PropTypes.object.isRequired,
320
+ name: PropTypes.string.isRequired,
321
+ description: PropTypes.object,
322
+ error: PropTypes.string,
323
+ labelAction: PropTypes.object,
324
+ required: PropTypes.bool,
325
+ value: PropTypes.string
326
+ };
327
+ export {
328
+ Tags as default
329
+ };
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const react = require("react");
5
+ const PropTypes = require("prop-types");
6
+ const axios = require("axios");
7
+ const designSystem = require("@strapi/design-system");
8
+ const reactIntl = require("react-intl");
9
+ const TagsInput = require("react-tagsinput");
10
+ const Autosuggest = require("react-autosuggest");
11
+ const styledComponents = require("styled-components");
12
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
13
+ const PropTypes__default = /* @__PURE__ */ _interopDefault(PropTypes);
14
+ const axios__default = /* @__PURE__ */ _interopDefault(axios);
15
+ const TagsInput__default = /* @__PURE__ */ _interopDefault(TagsInput);
16
+ const Autosuggest__default = /* @__PURE__ */ _interopDefault(Autosuggest);
17
+ const light = styledComponents.css`
18
+ :root {
19
+ --primary: #7b79ff;
20
+ --secondary: rgb(255, 255, 255);
21
+ --text: #32324d;
22
+ --input-background: #ffffff;
23
+ --input-border: #dcdce4;
24
+ --tag-background: #f0f0ff;
25
+ --tag-text: #4945ff;
26
+ --suggestion-background: #ffffff;
27
+ --suggestion-hover: #f6f6f9;
28
+ }
29
+ `;
30
+ const dark = styledComponents.css`
31
+ :root {
32
+ --primary: #7b79ff;
33
+ --secondary: rgb(33, 33, 52);
34
+ --text: #ffffff;
35
+ --input-background: #181826;
36
+ --input-border: #4a4a6a;
37
+ --tag-background: #7b79ff;
38
+ --tag-text: #ffffff;
39
+ --suggestion-background: #181826;
40
+ --suggestion-hover: #212134;
41
+ }
42
+ `;
43
+ const styles = styledComponents.css`
44
+ .react-tagsinput {
45
+ width: 100%;
46
+ border: 1px solid var(--input-border);
47
+ border-radius: 4px;
48
+ overflow: hidden;
49
+ padding-left: 5px;
50
+ padding-top: 5px;
51
+ }
52
+
53
+ .react-tagsinput--focused {
54
+ outline: 3px solid var(--primary);
55
+ }
56
+
57
+ .react-tagsinput-tag {
58
+ background-color: var(--tag-background);
59
+ border-radius: 2px;
60
+ border: 1px solid var(--tag-background);
61
+ color: var(--tag-text);
62
+ display: inline-block;
63
+ font-family: sans-serif;
64
+ font-size: 13px;
65
+ font-weight: 400;
66
+ margin-bottom: 5px;
67
+ margin-right: 5px;
68
+ padding: 5px;
69
+ }
70
+
71
+ .react-tagsinput-remove {
72
+ cursor: pointer;
73
+ font-weight: bold;
74
+ }
75
+
76
+ .react-tagsinput-tag a::before {
77
+ content: " ×";
78
+ }
79
+
80
+ .react-tagsinput-input {
81
+ background: transparent;
82
+ border: 0;
83
+ color: var(--text);
84
+ font-family: sans-serif;
85
+ font-size: 13px;
86
+ font-weight: 400;
87
+ margin-bottom: 6px;
88
+ margin-top: 1px;
89
+ outline: none;
90
+ padding: 5px;
91
+ width: 100%;
92
+ }
93
+
94
+ .react-tagsinput > span {
95
+ display: flex;
96
+ flex-flow: wrap;
97
+ }
98
+
99
+ .react-autosuggest__container {
100
+ display: flex;
101
+ flex-direction: column;
102
+ flex: auto;
103
+ }
104
+
105
+ .react-autosuggest__suggestions-container {
106
+ position: absolute;
107
+ z-index: 200;
108
+ width: 280px;
109
+ margin: 0;
110
+ padding: 0;
111
+ list-style-type: none;
112
+ background-color: var(--suggestion-background);
113
+ }
114
+
115
+ .react-autosuggest__suggestions-container--open {
116
+ border: 1px solid var(--input-border);
117
+ }
118
+
119
+ .react-autosuggest__suggestion {
120
+ cursor: pointer;
121
+ padding: 10px 20px;
122
+ }
123
+
124
+ .react-autosuggest__suggestion > span {
125
+ font-size: 13px;
126
+ font-weight: 400;
127
+ }
128
+
129
+ .react-autosuggest__suggestion--highlighted,
130
+ .react-autosuggest__suggestion--focused {
131
+ background-color: var(--suggestion-hover);
132
+ }
133
+ `;
134
+ const getStyling = (theme) => {
135
+ let themeStyle = light;
136
+ switch (theme) {
137
+ case "dark":
138
+ themeStyle = dark;
139
+ break;
140
+ default:
141
+ themeStyle = light;
142
+ }
143
+ return styledComponents.createGlobalStyle`
144
+ ${themeStyle}
145
+ ${styles}
146
+ `;
147
+ };
148
+ const ThemeStyle = getStyling(getCurrentTheme());
149
+ const Tags = ({
150
+ attribute,
151
+ description,
152
+ error,
153
+ label,
154
+ labelAction,
155
+ name,
156
+ onChange,
157
+ required,
158
+ value
159
+ }) => {
160
+ const { formatMessage } = reactIntl.useIntl();
161
+ const apiUrl = attribute?.options?.apiUrl || "";
162
+ const attrName = apiUrl.slice(apiUrl.lastIndexOf("=") + 1) || "name";
163
+ const inputEle = react.useRef(null);
164
+ const [tags, setTags] = react.useState(() => {
165
+ try {
166
+ const values = typeof value === "string" ? JSON.parse(value) : value;
167
+ return Array.isArray(values) ? values.map((v) => v[attrName] || v.name || v) : [];
168
+ } catch (e) {
169
+ return [];
170
+ }
171
+ });
172
+ const [suggestions, setSuggestions] = react.useState([]);
173
+ react.useEffect(() => {
174
+ const suggestionsContainer = document.querySelector(
175
+ ".react-autosuggest__suggestions-container"
176
+ );
177
+ if (suggestionsContainer && inputEle.current) {
178
+ suggestionsContainer.style.top = `${inputEle.current.offsetHeight + 5}px`;
179
+ }
180
+ const handleClickOutside = (event) => {
181
+ const tagsInput = document.querySelector(".react-tagsinput");
182
+ if (tagsInput) {
183
+ tagsInput.classList.toggle(
184
+ "react-tagsinput--focused",
185
+ inputEle.current?.contains(event.target)
186
+ );
187
+ }
188
+ };
189
+ document.addEventListener("mousedown", handleClickOutside);
190
+ return () => document.removeEventListener("mousedown", handleClickOutside);
191
+ }, []);
192
+ const handleTagsChange = async (newTags) => {
193
+ const lastTag = newTags[newTags.length - 1];
194
+ const suggestionsArray = suggestions.data || [];
195
+ const existingTag = suggestionsArray.find(
196
+ (s) => s[attrName]?.toLowerCase() === lastTag?.toLowerCase()
197
+ );
198
+ if (lastTag && !existingTag) {
199
+ if (apiUrl) {
200
+ try {
201
+ const response = await axios__default.default.post(apiUrl, {
202
+ data: {
203
+ [attrName]: lastTag
204
+ }
205
+ });
206
+ setSuggestions((prevSuggestions) => {
207
+ const newSuggestionsData = [...prevSuggestions.data || [], response.data.data];
208
+ return { ...prevSuggestions, data: newSuggestionsData };
209
+ });
210
+ newTags[newTags.length - 1] = response.data.data[attrName];
211
+ } catch (error2) {
212
+ console.error("Error creating new tag:", error2);
213
+ }
214
+ } else {
215
+ setSuggestions((prevSuggestions) => {
216
+ const newSuggestion = { id: Date.now(), [attrName]: lastTag };
217
+ const newSuggestionsData = [...prevSuggestions.data || [], newSuggestion];
218
+ return { ...prevSuggestions, data: newSuggestionsData };
219
+ });
220
+ }
221
+ }
222
+ setTags(newTags);
223
+ const value2 = JSON.stringify(newTags.map((tag) => ({ [attrName]: tag })));
224
+ onChange({
225
+ target: {
226
+ name,
227
+ value: value2,
228
+ type: attribute.type
229
+ }
230
+ });
231
+ };
232
+ const getSuggestions = async () => {
233
+ if (!apiUrl) return;
234
+ try {
235
+ const res = await axios__default.default.get(apiUrl);
236
+ setSuggestions(res.data);
237
+ } catch (err) {
238
+ setSuggestions({ data: [] });
239
+ }
240
+ };
241
+ const autocompleteRenderInput = (props) => {
242
+ const handleOnChange = (e, { newValue, method }) => {
243
+ if (method === "enter") {
244
+ e.preventDefault();
245
+ } else {
246
+ props.onChange(e);
247
+ }
248
+ };
249
+ const inputValue = props.value && props.value.trim() || "";
250
+ const inputLength = inputValue.length;
251
+ let s = suggestions.data || [];
252
+ if (s.length <= 0) {
253
+ getSuggestions();
254
+ }
255
+ if (inputLength > 0) {
256
+ s = s.filter((state) => {
257
+ const suggestionName = state[attrName] || "";
258
+ return suggestionName.toLowerCase().slice(0, inputLength) === inputValue;
259
+ }).map((state) => ({
260
+ id: state.id,
261
+ [attrName]: state[attrName] || ""
262
+ }));
263
+ }
264
+ return /* @__PURE__ */ jsxRuntime.jsx(
265
+ Autosuggest__default.default,
266
+ {
267
+ ref: props.ref,
268
+ suggestions: s,
269
+ shouldRenderSuggestions: (value2) => value2 && value2.trim().length > 0,
270
+ getSuggestionValue: (s2) => s2[attrName],
271
+ renderSuggestion: (s2) => /* @__PURE__ */ jsxRuntime.jsx("span", { children: s2[attrName] }),
272
+ inputProps: { ...props, onChange: handleOnChange },
273
+ onSuggestionSelected: (_, { suggestion }) => props.addTag(suggestion[attrName]),
274
+ onSuggestionsFetchRequested: () => {
275
+ }
276
+ }
277
+ );
278
+ };
279
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsx(
280
+ designSystem.Field.Root,
281
+ {
282
+ name,
283
+ id: name,
284
+ error,
285
+ hint: description && formatMessage({ id: description }),
286
+ required,
287
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
288
+ designSystem.Flex,
289
+ {
290
+ direction: "column",
291
+ alignItems: "stretch",
292
+ gap: 1,
293
+ style: { position: "relative" },
294
+ ref: inputEle,
295
+ children: [
296
+ label && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { action: labelAction, children: formatMessage({ id: label, defaultMessage: label }) }),
297
+ /* @__PURE__ */ jsxRuntime.jsx(ThemeStyle, {}),
298
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", children: /* @__PURE__ */ jsxRuntime.jsx(
299
+ TagsInput__default.default,
300
+ {
301
+ value: tags,
302
+ onChange: handleTagsChange,
303
+ onlyUnique: true,
304
+ renderInput: autocompleteRenderInput
305
+ }
306
+ ) }),
307
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
308
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
309
+ ]
310
+ }
311
+ )
312
+ }
313
+ ) });
314
+ };
315
+ Tags.defaultProps = {
316
+ description: null,
317
+ disabled: false,
318
+ error: null,
319
+ labelAction: null,
320
+ required: false,
321
+ value: ""
322
+ };
323
+ Tags.propTypes = {
324
+ label: PropTypes__default.default.object.isRequired,
325
+ onChange: PropTypes__default.default.func.isRequired,
326
+ attribute: PropTypes__default.default.object.isRequired,
327
+ name: PropTypes__default.default.string.isRequired,
328
+ description: PropTypes__default.default.object,
329
+ error: PropTypes__default.default.string,
330
+ labelAction: PropTypes__default.default.object,
331
+ required: PropTypes__default.default.bool,
332
+ value: PropTypes__default.default.string
333
+ };
334
+ exports.default = Tags;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ const name = "@gregcm999/strapi-plugin-tagsinput";
3
+ const pluginPkg = {
4
+ name
5
+ };
6
+ const pluginId = pluginPkg.name.replace(/^(@[^/]+\/)?strapi-plugin-/i, "");
7
+ const getTranslation = (id) => `${pluginId}.${id}`;
8
+ const index = {
9
+ register(app) {
10
+ app.customFields.register({
11
+ name: "tags",
12
+ pluginId,
13
+ type: "text",
14
+ intlLabel: {
15
+ id: getTranslation("form.label"),
16
+ defaultMessage: "TagsInput"
17
+ },
18
+ intlDescription: {
19
+ id: getTranslation("form.description"),
20
+ defaultMessage: "TagsInput to add custom tags"
21
+ },
22
+ components: {
23
+ Input: async () => Promise.resolve().then(() => require(
24
+ /* webpackChunkName: "input-component" */
25
+ "../_chunks/Input-jl4WBtpd.js"
26
+ ))
27
+ },
28
+ options: {
29
+ base: [
30
+ {
31
+ sectionTitle: {
32
+ id: "form.section.apiUrl",
33
+ defaultMessage: "API Url"
34
+ },
35
+ items: [
36
+ {
37
+ intlLabel: {
38
+ id: "form.apiUrl",
39
+ defaultMessage: "Rest API URL for suggestions"
40
+ },
41
+ name: "options.apiUrl",
42
+ type: "text",
43
+ value: "",
44
+ options: []
45
+ }
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ });
51
+ }
52
+ };
53
+ module.exports = index;
@@ -0,0 +1,54 @@
1
+ const name = "@gregcm999/strapi-plugin-tagsinput";
2
+ const pluginPkg = {
3
+ name
4
+ };
5
+ const pluginId = pluginPkg.name.replace(/^(@[^/]+\/)?strapi-plugin-/i, "");
6
+ const getTranslation = (id) => `${pluginId}.${id}`;
7
+ const index = {
8
+ register(app) {
9
+ app.customFields.register({
10
+ name: "tags",
11
+ pluginId,
12
+ type: "text",
13
+ intlLabel: {
14
+ id: getTranslation("form.label"),
15
+ defaultMessage: "TagsInput"
16
+ },
17
+ intlDescription: {
18
+ id: getTranslation("form.description"),
19
+ defaultMessage: "TagsInput to add custom tags"
20
+ },
21
+ components: {
22
+ Input: async () => import(
23
+ /* webpackChunkName: "input-component" */
24
+ "../_chunks/Input-DSV3HDg_.mjs"
25
+ )
26
+ },
27
+ options: {
28
+ base: [
29
+ {
30
+ sectionTitle: {
31
+ id: "form.section.apiUrl",
32
+ defaultMessage: "API Url"
33
+ },
34
+ items: [
35
+ {
36
+ intlLabel: {
37
+ id: "form.apiUrl",
38
+ defaultMessage: "Rest API URL for suggestions"
39
+ },
40
+ name: "options.apiUrl",
41
+ type: "text",
42
+ value: "",
43
+ options: []
44
+ }
45
+ ]
46
+ }
47
+ ]
48
+ }
49
+ });
50
+ }
51
+ };
52
+ export {
53
+ index as default
54
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ function getDefaultExportFromCjs(x) {
3
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
4
+ }
5
+ var register$1 = ({ strapi }) => {
6
+ strapi.customFields.register({
7
+ name: "tags",
8
+ plugin: "tagsinput",
9
+ type: "json"
10
+ });
11
+ };
12
+ const register = register$1;
13
+ var src = {
14
+ register
15
+ };
16
+ const index = /* @__PURE__ */ getDefaultExportFromCjs(src);
17
+ module.exports = index;
@@ -0,0 +1,18 @@
1
+ function getDefaultExportFromCjs(x) {
2
+ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
3
+ }
4
+ var register$1 = ({ strapi }) => {
5
+ strapi.customFields.register({
6
+ name: "tags",
7
+ plugin: "tagsinput",
8
+ type: "json"
9
+ });
10
+ };
11
+ const register = register$1;
12
+ var src = {
13
+ register
14
+ };
15
+ const index = /* @__PURE__ */ getDefaultExportFromCjs(src);
16
+ export {
17
+ index as default
18
+ };
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@gregcm999/strapi-plugin-tagsinput",
3
+ "version": "2.0.3-beta.3",
4
+ "description": "Tagsinput plugin for your strapi project",
5
+ "strapi": {
6
+ "name": "tagsinput",
7
+ "description": "Tagsinput plugin for your strapi project",
8
+ "kind": "plugin",
9
+ "displayName": "Tagsinput"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/canopas/strapi-plugin-tagsinput.git"
14
+ },
15
+ "scripts": {
16
+ "build": "strapi-plugin build",
17
+ "verify": "strapi-plugin verify",
18
+ "watch": "strapi-plugin watch",
19
+ "watch:link": "strapi-plugin watch:link"
20
+ },
21
+ "exports": {
22
+ "./package.json": "./package.json",
23
+ "./strapi-admin": {
24
+ "source": "./admin/src/index.js",
25
+ "import": "./dist/admin/index.mjs",
26
+ "require": "./dist/admin/index.js",
27
+ "default": "./dist/admin/index.js"
28
+ },
29
+ "./strapi-server": {
30
+ "source": "./server/src/index.js",
31
+ "import": "./dist/server/index.mjs",
32
+ "require": "./dist/server/index.js",
33
+ "default": "./dist/server/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "dependencies": {
40
+ "@strapi/design-system": "^2.0.0-rc.11",
41
+ "@strapi/icons": "^2.0.0-rc.11",
42
+ "axios": "^1.7.7",
43
+ "prop-types": "^15.8.1",
44
+ "react-autosuggest": "^10.1.0",
45
+ "react-intl": "^6.7.0",
46
+ "react-tagsinput": "^3.20.3"
47
+ },
48
+ "devDependencies": {
49
+ "@strapi/sdk-plugin": "^5.2.6",
50
+ "@strapi/strapi": "^5.0.2",
51
+ "react": "^18.3.1",
52
+ "react-dom": "^18.3.1",
53
+ "react-router-dom": "^6.26.2",
54
+ "styled-components": "^6.1.13"
55
+ },
56
+ "peerDependencies": {
57
+ "@strapi/sdk-plugin": "^5.2.6",
58
+ "@strapi/strapi": "^5.0.2",
59
+ "react": "^18.3.1",
60
+ "react-dom": "^18.3.1",
61
+ "react-router-dom": "^6.26.2",
62
+ "styled-components": "^6.1.13"
63
+ },
64
+ "author": "Canopas",
65
+ "maintainers": [
66
+ "Canopas"
67
+ ],
68
+ "engines": {
69
+ "node": ">=20.0.0 <=24.x.x",
70
+ "npm": ">=6.0.0"
71
+ },
72
+ "license": "MIT",
73
+ "main": "strapi-server.js",
74
+ "keywords": [
75
+ "strapi",
76
+ "plugin",
77
+ "tagsinput"
78
+ ],
79
+ "bugs": {
80
+ "url": "https://github.com/canopas/strapi-plugin-tagsinput/issues"
81
+ },
82
+ "homepage": "https://github.com/canopas/strapi-plugin-tagsinput#readme",
83
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
84
+ }