strapi-plugin-tags-custom-field 1.0.1 → 1.0.2

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/README.md CHANGED
@@ -33,7 +33,6 @@ Restart your Strapi server after installation.
33
33
  - `Enter` adds the current tag.
34
34
  - The configured separator also adds the current tag.
35
35
  - Paste supports multiple tags (newline or configured separator).
36
- - `Backspace` on empty input removes the last tag.
37
36
 
38
37
  ## Data format
39
38
 
@@ -96,10 +96,12 @@ const hasSplitCharacters = (value, separator) => [separator, "\n", "\r"].some(
96
96
  const TagsInput = React__namespace.forwardRef(
97
97
  ({
98
98
  attribute,
99
+ description,
99
100
  disabled = false,
100
101
  error,
101
102
  hint,
102
103
  intlLabel,
104
+ label,
103
105
  name,
104
106
  onChange,
105
107
  placeholder,
@@ -123,11 +125,22 @@ const TagsInput = React__namespace.forwardRef(
123
125
  );
124
126
  const allowDuplicates = parseBoolean(options.allowDuplicates, false);
125
127
  const normalizeCase = normalizeCaseValue(options.normalizeCase);
126
- const label = intlLabel?.id || intlLabel?.defaultMessage ? formatMessage({
127
- id: intlLabel?.id ?? `${name}.label`,
128
- defaultMessage: intlLabel?.defaultMessage ?? "Tags"
129
- }) : "Tags";
130
- const hintMessage = hint ?? formatMessage({
128
+ const formatIntlMessage = React__namespace.useCallback(
129
+ (message) => {
130
+ if (!message?.id && !message?.defaultMessage) {
131
+ return void 0;
132
+ }
133
+ return formatMessage({
134
+ id: message.id ?? `${name}.label`,
135
+ defaultMessage: message.defaultMessage ?? "Tags"
136
+ });
137
+ },
138
+ [formatMessage, name]
139
+ );
140
+ const labelText = typeof label === "string" ? label : formatIntlMessage(label) ?? formatIntlMessage(intlLabel);
141
+ const labelMessage = labelText && labelText.trim().length > 0 ? labelText.trim() : "Tags";
142
+ const descriptionText = typeof description === "string" ? description : formatIntlMessage(description);
143
+ const hintMessage = descriptionText && descriptionText.trim().length > 0 ? descriptionText.trim() : hint ?? formatMessage({
131
144
  id: "tags-input.hint",
132
145
  defaultMessage: "Press Enter or type the separator to add tags. Paste multiple tags at once."
133
146
  });
@@ -149,58 +162,53 @@ const TagsInput = React__namespace.forwardRef(
149
162
  if (rawTags.length === 0) {
150
163
  return false;
151
164
  }
165
+ const nextTags = [...tags];
152
166
  let didChange = false;
153
- setTags((currentTags) => {
154
- const nextTags = [...currentTags];
155
- for (const rawTag of rawTags) {
156
- if (nextTags.length >= maxTags) {
157
- setLocalError(
158
- formatMessage(
159
- {
160
- id: "tags-input.error.max-tags",
161
- defaultMessage: "You can only add up to {maxTags} tags."
162
- },
163
- { maxTags }
164
- )
165
- );
166
- break;
167
- }
168
- if (rawTag.length > maxTagLength) {
169
- setLocalError(
170
- formatMessage(
171
- {
172
- id: "tags-input.error.max-length",
173
- defaultMessage: "Each tag must be at most {maxLength} characters."
174
- },
175
- { maxLength: maxTagLength }
176
- )
177
- );
178
- continue;
179
- }
180
- const duplicateIndex = nextTags.findIndex(
181
- (tag) => tag.toLowerCase() === rawTag.toLowerCase()
167
+ let nextError;
168
+ for (const rawTag of rawTags) {
169
+ if (nextTags.length >= maxTags) {
170
+ nextError = formatMessage(
171
+ {
172
+ id: "tags-input.error.max-tags",
173
+ defaultMessage: "You can only add up to {maxTags} tags."
174
+ },
175
+ { maxTags }
182
176
  );
183
- if (duplicateIndex !== -1 && !allowDuplicates) {
184
- setLocalError(
185
- formatMessage({
186
- id: "tags-input.error.duplicate",
187
- defaultMessage: "Duplicate tags are not allowed."
188
- })
189
- );
190
- continue;
191
- }
192
- nextTags.push(rawTag);
193
- didChange = true;
177
+ break;
194
178
  }
195
- if (didChange) {
196
- setLocalError(void 0);
197
- emitChange(nextTags);
179
+ if (rawTag.length > maxTagLength) {
180
+ nextError = formatMessage(
181
+ {
182
+ id: "tags-input.error.max-length",
183
+ defaultMessage: "Each tag must be at most {maxLength} characters."
184
+ },
185
+ { maxLength: maxTagLength }
186
+ );
187
+ continue;
198
188
  }
199
- return didChange ? nextTags : currentTags;
200
- });
189
+ const duplicateIndex = nextTags.findIndex(
190
+ (tag) => tag.toLowerCase() === rawTag.toLowerCase()
191
+ );
192
+ if (duplicateIndex !== -1 && !allowDuplicates) {
193
+ nextError = formatMessage({
194
+ id: "tags-input.error.duplicate",
195
+ defaultMessage: "Duplicate tags are not allowed."
196
+ });
197
+ continue;
198
+ }
199
+ nextTags.push(rawTag);
200
+ didChange = true;
201
+ }
202
+ if (didChange) {
203
+ setTags(nextTags);
204
+ setLocalError(void 0);
205
+ emitChange(nextTags);
206
+ } else if (nextError) {
207
+ setLocalError(nextError);
208
+ }
201
209
  return didChange;
202
210
  },
203
- [allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags]
211
+ [allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags, tags]
204
212
  );
205
213
  const commitDraft = React__namespace.useCallback(() => {
206
214
  const parsedTags = parseRawTags(draft, separator, normalizeCase);
@@ -219,26 +227,11 @@ const TagsInput = React__namespace.forwardRef(
219
227
  },
220
228
  [emitChange]
221
229
  );
222
- const removeLastTag = React__namespace.useCallback(() => {
223
- setTags((currentTags) => {
224
- if (currentTags.length === 0) {
225
- return currentTags;
226
- }
227
- const nextTags = currentTags.slice(0, currentTags.length - 1);
228
- emitChange(nextTags);
229
- setLocalError(void 0);
230
- return nextTags;
231
- });
232
- }, [emitChange]);
233
230
  const onKeyDown = (event) => {
234
231
  if (event.key === "Enter" || event.key === separator) {
235
232
  event.preventDefault();
236
233
  commitDraft();
237
234
  }
238
- if (event.key === "Backspace" && draft.length === 0 && !disabled) {
239
- event.preventDefault();
240
- removeLastTag();
241
- }
242
235
  };
243
236
  const onPaste = (event) => {
244
237
  const pastedText = event.clipboardData.getData("text");
@@ -247,7 +240,10 @@ const TagsInput = React__namespace.forwardRef(
247
240
  }
248
241
  event.preventDefault();
249
242
  const parsedTags = parseRawTags(pastedText, separator, normalizeCase);
250
- addTags(parsedTags);
243
+ const added = addTags(parsedTags);
244
+ if (added) {
245
+ setDraft("");
246
+ }
251
247
  };
252
248
  return /* @__PURE__ */ jsxRuntime.jsx(
253
249
  designSystem.Field.Root,
@@ -258,7 +254,7 @@ const TagsInput = React__namespace.forwardRef(
258
254
  hint: hintMessage,
259
255
  error: shownError,
260
256
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
261
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: label }),
257
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: labelMessage }),
262
258
  tags.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
263
259
  designSystem.Box,
264
260
  {
@@ -76,10 +76,12 @@ const hasSplitCharacters = (value, separator) => [separator, "\n", "\r"].some(
76
76
  const TagsInput = React.forwardRef(
77
77
  ({
78
78
  attribute,
79
+ description,
79
80
  disabled = false,
80
81
  error,
81
82
  hint,
82
83
  intlLabel,
84
+ label,
83
85
  name,
84
86
  onChange,
85
87
  placeholder,
@@ -103,11 +105,22 @@ const TagsInput = React.forwardRef(
103
105
  );
104
106
  const allowDuplicates = parseBoolean(options.allowDuplicates, false);
105
107
  const normalizeCase = normalizeCaseValue(options.normalizeCase);
106
- const label = intlLabel?.id || intlLabel?.defaultMessage ? formatMessage({
107
- id: intlLabel?.id ?? `${name}.label`,
108
- defaultMessage: intlLabel?.defaultMessage ?? "Tags"
109
- }) : "Tags";
110
- const hintMessage = hint ?? formatMessage({
108
+ const formatIntlMessage = React.useCallback(
109
+ (message) => {
110
+ if (!message?.id && !message?.defaultMessage) {
111
+ return void 0;
112
+ }
113
+ return formatMessage({
114
+ id: message.id ?? `${name}.label`,
115
+ defaultMessage: message.defaultMessage ?? "Tags"
116
+ });
117
+ },
118
+ [formatMessage, name]
119
+ );
120
+ const labelText = typeof label === "string" ? label : formatIntlMessage(label) ?? formatIntlMessage(intlLabel);
121
+ const labelMessage = labelText && labelText.trim().length > 0 ? labelText.trim() : "Tags";
122
+ const descriptionText = typeof description === "string" ? description : formatIntlMessage(description);
123
+ const hintMessage = descriptionText && descriptionText.trim().length > 0 ? descriptionText.trim() : hint ?? formatMessage({
111
124
  id: "tags-input.hint",
112
125
  defaultMessage: "Press Enter or type the separator to add tags. Paste multiple tags at once."
113
126
  });
@@ -129,58 +142,53 @@ const TagsInput = React.forwardRef(
129
142
  if (rawTags.length === 0) {
130
143
  return false;
131
144
  }
145
+ const nextTags = [...tags];
132
146
  let didChange = false;
133
- setTags((currentTags) => {
134
- const nextTags = [...currentTags];
135
- for (const rawTag of rawTags) {
136
- if (nextTags.length >= maxTags) {
137
- setLocalError(
138
- formatMessage(
139
- {
140
- id: "tags-input.error.max-tags",
141
- defaultMessage: "You can only add up to {maxTags} tags."
142
- },
143
- { maxTags }
144
- )
145
- );
146
- break;
147
- }
148
- if (rawTag.length > maxTagLength) {
149
- setLocalError(
150
- formatMessage(
151
- {
152
- id: "tags-input.error.max-length",
153
- defaultMessage: "Each tag must be at most {maxLength} characters."
154
- },
155
- { maxLength: maxTagLength }
156
- )
157
- );
158
- continue;
159
- }
160
- const duplicateIndex = nextTags.findIndex(
161
- (tag) => tag.toLowerCase() === rawTag.toLowerCase()
147
+ let nextError;
148
+ for (const rawTag of rawTags) {
149
+ if (nextTags.length >= maxTags) {
150
+ nextError = formatMessage(
151
+ {
152
+ id: "tags-input.error.max-tags",
153
+ defaultMessage: "You can only add up to {maxTags} tags."
154
+ },
155
+ { maxTags }
162
156
  );
163
- if (duplicateIndex !== -1 && !allowDuplicates) {
164
- setLocalError(
165
- formatMessage({
166
- id: "tags-input.error.duplicate",
167
- defaultMessage: "Duplicate tags are not allowed."
168
- })
169
- );
170
- continue;
171
- }
172
- nextTags.push(rawTag);
173
- didChange = true;
157
+ break;
174
158
  }
175
- if (didChange) {
176
- setLocalError(void 0);
177
- emitChange(nextTags);
159
+ if (rawTag.length > maxTagLength) {
160
+ nextError = formatMessage(
161
+ {
162
+ id: "tags-input.error.max-length",
163
+ defaultMessage: "Each tag must be at most {maxLength} characters."
164
+ },
165
+ { maxLength: maxTagLength }
166
+ );
167
+ continue;
178
168
  }
179
- return didChange ? nextTags : currentTags;
180
- });
169
+ const duplicateIndex = nextTags.findIndex(
170
+ (tag) => tag.toLowerCase() === rawTag.toLowerCase()
171
+ );
172
+ if (duplicateIndex !== -1 && !allowDuplicates) {
173
+ nextError = formatMessage({
174
+ id: "tags-input.error.duplicate",
175
+ defaultMessage: "Duplicate tags are not allowed."
176
+ });
177
+ continue;
178
+ }
179
+ nextTags.push(rawTag);
180
+ didChange = true;
181
+ }
182
+ if (didChange) {
183
+ setTags(nextTags);
184
+ setLocalError(void 0);
185
+ emitChange(nextTags);
186
+ } else if (nextError) {
187
+ setLocalError(nextError);
188
+ }
181
189
  return didChange;
182
190
  },
183
- [allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags]
191
+ [allowDuplicates, emitChange, formatMessage, maxTagLength, maxTags, tags]
184
192
  );
185
193
  const commitDraft = React.useCallback(() => {
186
194
  const parsedTags = parseRawTags(draft, separator, normalizeCase);
@@ -199,26 +207,11 @@ const TagsInput = React.forwardRef(
199
207
  },
200
208
  [emitChange]
201
209
  );
202
- const removeLastTag = React.useCallback(() => {
203
- setTags((currentTags) => {
204
- if (currentTags.length === 0) {
205
- return currentTags;
206
- }
207
- const nextTags = currentTags.slice(0, currentTags.length - 1);
208
- emitChange(nextTags);
209
- setLocalError(void 0);
210
- return nextTags;
211
- });
212
- }, [emitChange]);
213
210
  const onKeyDown = (event) => {
214
211
  if (event.key === "Enter" || event.key === separator) {
215
212
  event.preventDefault();
216
213
  commitDraft();
217
214
  }
218
- if (event.key === "Backspace" && draft.length === 0 && !disabled) {
219
- event.preventDefault();
220
- removeLastTag();
221
- }
222
215
  };
223
216
  const onPaste = (event) => {
224
217
  const pastedText = event.clipboardData.getData("text");
@@ -227,7 +220,10 @@ const TagsInput = React.forwardRef(
227
220
  }
228
221
  event.preventDefault();
229
222
  const parsedTags = parseRawTags(pastedText, separator, normalizeCase);
230
- addTags(parsedTags);
223
+ const added = addTags(parsedTags);
224
+ if (added) {
225
+ setDraft("");
226
+ }
231
227
  };
232
228
  return /* @__PURE__ */ jsx(
233
229
  Field.Root,
@@ -238,7 +234,7 @@ const TagsInput = React.forwardRef(
238
234
  hint: hintMessage,
239
235
  error: shownError,
240
236
  children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
241
- /* @__PURE__ */ jsx(Field.Label, { children: label }),
237
+ /* @__PURE__ */ jsx(Field.Label, { children: labelMessage }),
242
238
  tags.length > 0 ? /* @__PURE__ */ jsx(
243
239
  Box,
244
240
  {
@@ -66,7 +66,7 @@ const index = {
66
66
  },
67
67
  icon: PluginIcon,
68
68
  components: {
69
- Input: async () => Promise.resolve().then(() => require("./TagsInput-BGUydL2t.js")).then((module2) => ({
69
+ Input: async () => Promise.resolve().then(() => require("./TagsInput-BfjqO_MO.js")).then((module2) => ({
70
70
  default: module2.TagsInput
71
71
  }))
72
72
  },
@@ -64,7 +64,7 @@ const index = {
64
64
  },
65
65
  icon: PluginIcon,
66
66
  components: {
67
- Input: async () => import("./TagsInput-CCehyU5Y.mjs").then((module) => ({
67
+ Input: async () => import("./TagsInput-CzXiRuEv.mjs").then((module) => ({
68
68
  default: module.TagsInput
69
69
  }))
70
70
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-tags-custom-field",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Custom field plugin for Strapi 5 to manage tags (array of strings) stored as JSON array.",
5
5
  "keywords": [
6
6
  "strapi",