@webbycrown/advanced-fields 1.0.2 → 1.0.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/dist/_chunks/en-BPwepUqK.js +84 -0
- package/dist/_chunks/en-CJmJkBE0.mjs +84 -0
- package/dist/_chunks/index-C-A6rX1K.mjs +268 -0
- package/dist/_chunks/index-CCaZAiQR.js +268 -0
- package/{admin/src/components/AdvancedInput/index.jsx → dist/_chunks/index-DTSlHlvg.mjs} +43 -92
- package/dist/_chunks/index-DXVpJkpj.mjs +282 -0
- package/dist/_chunks/index-TmoGbq0j.js +282 -0
- package/dist/_chunks/index-lJs3vGYF.js +130 -0
- package/dist/admin/index.js +867 -0
- package/dist/admin/index.mjs +868 -0
- package/package.json +5 -3
- package/admin/jsconfig.json +0 -10
- package/admin/src/components/AdvancedCheckbox/index.jsx +0 -377
- package/admin/src/components/AdvancedRadio/index.jsx +0 -344
- package/admin/src/components/Initializer.jsx +0 -18
- package/admin/src/components/PluginIcon.jsx +0 -108
- package/admin/src/index.js +0 -787
- package/admin/src/pages/App.jsx +0 -13
- package/admin/src/pluginId.js +0 -3
- package/admin/src/utils/getTranslation.js +0 -5
- /package/{admin/src → dist/admin}/translations/en.json +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webbycrown/advanced-fields",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"strapi",
|
|
6
6
|
"plugin",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"./strapi-server": "./strapi-server.js"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"
|
|
28
|
+
"dist/",
|
|
29
29
|
"strapi-server.js",
|
|
30
30
|
"strapi-server.mjs",
|
|
31
31
|
"index.js",
|
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
"LICENSE"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"build": "npm run build:admin",
|
|
36
|
+
"build": "npm run build:admin && npm run copy:translations",
|
|
37
37
|
"build:admin": "strapi-plugin build",
|
|
38
|
+
"copy:translations": "cp -r admin/src/translations dist/admin/",
|
|
39
|
+
"prepublishOnly": "npm run build",
|
|
38
40
|
"watch": "strapi-plugin watch",
|
|
39
41
|
"watch:link": "strapi-plugin watch:link",
|
|
40
42
|
"verify": "strapi-plugin verify"
|
package/admin/jsconfig.json
DELETED
|
@@ -1,377 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { useIntl } from "react-intl";
|
|
4
|
-
import { Field, Box, Flex, Typography } from "@strapi/design-system";
|
|
5
|
-
import { useState, useEffect } from "react";
|
|
6
|
-
|
|
7
|
-
const AdvancedCheckbox = ({
|
|
8
|
-
attribute = {},
|
|
9
|
-
description = { id: "", defaultMessage: "" },
|
|
10
|
-
disabled,
|
|
11
|
-
error,
|
|
12
|
-
intlLabel = { id: "", defaultMessage: "" },
|
|
13
|
-
labelAction,
|
|
14
|
-
name,
|
|
15
|
-
onChange,
|
|
16
|
-
required,
|
|
17
|
-
value,
|
|
18
|
-
}) => {
|
|
19
|
-
const { formatMessage } = useIntl();
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
checkboxType = "multiple",
|
|
23
|
-
layout = "vertical",
|
|
24
|
-
minChoices = 0,
|
|
25
|
-
maxChoices = 0,
|
|
26
|
-
defaultSelected = "",
|
|
27
|
-
checkboxOptions = "",
|
|
28
|
-
customErrorMessage = "",
|
|
29
|
-
fieldNote = "",
|
|
30
|
-
} = attribute.options || attribute;
|
|
31
|
-
|
|
32
|
-
// Also check attribute.options for fieldNote
|
|
33
|
-
const fieldNoteFromAttribute = attribute.options?.fieldNote || '';
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Initialize with default values if available
|
|
37
|
-
const getInitialValues = () => {
|
|
38
|
-
if (checkboxType === "single") {
|
|
39
|
-
// For single checkbox mode, return array with one value (like radio button)
|
|
40
|
-
if (value && Array.isArray(value) && value.length > 0) {
|
|
41
|
-
return value;
|
|
42
|
-
} else if (value && typeof value === "string" && value.trim()) {
|
|
43
|
-
return [value.trim()];
|
|
44
|
-
} else if (defaultSelected && typeof defaultSelected === "string" && defaultSelected.trim()) {
|
|
45
|
-
return [defaultSelected.trim()];
|
|
46
|
-
} else if (defaultSelected && Array.isArray(defaultSelected) && defaultSelected.length > 0) {
|
|
47
|
-
return defaultSelected;
|
|
48
|
-
}
|
|
49
|
-
return [];
|
|
50
|
-
} else {
|
|
51
|
-
// For multiple checkboxes, return array of selected values
|
|
52
|
-
if (value && Array.isArray(value)) {
|
|
53
|
-
return value;
|
|
54
|
-
} else if (value && typeof value === "string") {
|
|
55
|
-
return value.split(",").map(v => v.trim()).filter(v => v);
|
|
56
|
-
} else if (defaultSelected) {
|
|
57
|
-
return defaultSelected.split(",").map(v => v.trim()).filter(v => v);
|
|
58
|
-
}
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const [fieldValue, setFieldValue] = useState(getInitialValues);
|
|
64
|
-
const [validationError, setValidationError] = useState(null);
|
|
65
|
-
const [hasInteracted, setHasInteracted] = useState(false);
|
|
66
|
-
const [isInitialized, setIsInitialized] = useState(false);
|
|
67
|
-
|
|
68
|
-
// Parse checkbox options
|
|
69
|
-
const options = checkboxOptions
|
|
70
|
-
.split("\n")
|
|
71
|
-
.filter((opt) => opt.trim())
|
|
72
|
-
.map((opt) => {
|
|
73
|
-
const [value, label] = opt.split("|");
|
|
74
|
-
return { value: value?.trim() || "", label: label?.trim() || value?.trim() || "" };
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Initialize selected values - only run once on mount
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
const initialValues = getInitialValues();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
setFieldValue(initialValues);
|
|
83
|
-
|
|
84
|
-
// Validate the initial values
|
|
85
|
-
const validationResult = validateSelection(initialValues);
|
|
86
|
-
setValidationError(validationResult);
|
|
87
|
-
|
|
88
|
-
// Only trigger onChange if we have initial values and it's different from current value
|
|
89
|
-
// AND only if the value prop is not already set (to avoid overriding during publish)
|
|
90
|
-
// AND only if this is the first initialization
|
|
91
|
-
if (onChange && initialValues.length > 0 && (!value || (Array.isArray(value) && value.length === 0)) && !isInitialized) {
|
|
92
|
-
// Use setTimeout to ensure this happens after the component is fully mounted
|
|
93
|
-
setTimeout(() => {
|
|
94
|
-
onChange({
|
|
95
|
-
target: {
|
|
96
|
-
value: initialValues,
|
|
97
|
-
name: name,
|
|
98
|
-
id: name
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
setIsInitialized(true);
|
|
102
|
-
}, 0);
|
|
103
|
-
} else if (!isInitialized) {
|
|
104
|
-
setIsInitialized(true);
|
|
105
|
-
}
|
|
106
|
-
}, []); // Remove dependencies to only run once on mount
|
|
107
|
-
|
|
108
|
-
// Handle external value changes (like when loading existing data)
|
|
109
|
-
useEffect(() => {
|
|
110
|
-
// Only update if the value prop has changed and it's not empty
|
|
111
|
-
// This handles cases like loading existing content
|
|
112
|
-
if (value && Array.isArray(value) && value.length > 0) {
|
|
113
|
-
setFieldValue(value);
|
|
114
|
-
const validationResult = validateSelection(value);
|
|
115
|
-
setValidationError(validationResult);
|
|
116
|
-
}
|
|
117
|
-
}, [value]);
|
|
118
|
-
|
|
119
|
-
// Validation function - this should match server-side validation
|
|
120
|
-
const validateSelection = (val) => {
|
|
121
|
-
const values = Array.isArray(val) ? val : [];
|
|
122
|
-
|
|
123
|
-
// Check required validation first
|
|
124
|
-
if (required && values.length === 0) {
|
|
125
|
-
return customErrorMessage || 'This field is required';
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// If field is empty and not required, no validation error
|
|
129
|
-
if (values.length === 0) {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check min/max choices validation (only for multiple mode)
|
|
134
|
-
if (checkboxType === "multiple") {
|
|
135
|
-
if (minChoices > 0 && values.length < minChoices) {
|
|
136
|
-
return customErrorMessage || `Please select at least ${minChoices} option${minChoices > 1 ? 's' : ''}`;
|
|
137
|
-
}
|
|
138
|
-
if (maxChoices > 0 && values.length > maxChoices) {
|
|
139
|
-
return customErrorMessage || `Please select at most ${maxChoices} option${maxChoices > 1 ? 's' : ''}`;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return null;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
const handleCheckboxChange = (optionValue, isChecked) => {
|
|
147
|
-
let newValue;
|
|
148
|
-
|
|
149
|
-
if (checkboxType === "single") {
|
|
150
|
-
// For single checkbox mode, work like radio button - only one option can be selected
|
|
151
|
-
if (isChecked) {
|
|
152
|
-
// If checking an option, set it as the only selected value
|
|
153
|
-
newValue = [optionValue];
|
|
154
|
-
} else {
|
|
155
|
-
// If unchecking, clear the selection
|
|
156
|
-
newValue = [];
|
|
157
|
-
}
|
|
158
|
-
} else {
|
|
159
|
-
// For multiple checkboxes, handle array of values
|
|
160
|
-
if (isChecked) {
|
|
161
|
-
newValue = [...fieldValue, optionValue];
|
|
162
|
-
} else {
|
|
163
|
-
newValue = fieldValue.filter(val => val !== optionValue);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
setFieldValue(newValue);
|
|
168
|
-
setHasInteracted(true);
|
|
169
|
-
|
|
170
|
-
// Validate selection only after user interaction
|
|
171
|
-
const error = validateSelection(newValue);
|
|
172
|
-
setValidationError(error);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
// Always trigger onChange for user interactions
|
|
176
|
-
if (onChange) {
|
|
177
|
-
onChange({
|
|
178
|
-
target: {
|
|
179
|
-
value: newValue,
|
|
180
|
-
name: name,
|
|
181
|
-
id: name
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// Show validation error - prioritize Strapi's error, then our validation only after user interaction
|
|
188
|
-
const displayError = error || (hasInteracted && validationError);
|
|
189
|
-
const renderCheckboxes = () => {
|
|
190
|
-
|
|
191
|
-
const checkboxStyle = {
|
|
192
|
-
display: "flex",
|
|
193
|
-
alignItems: "center",
|
|
194
|
-
gap: "8px",
|
|
195
|
-
marginBottom: "8px",
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const checkboxInputStyle = {
|
|
199
|
-
width: "16px",
|
|
200
|
-
height: "16px",
|
|
201
|
-
accentColor: "#4945ff",
|
|
202
|
-
margin: "0",
|
|
203
|
-
padding: "0",
|
|
204
|
-
opacity: "1",
|
|
205
|
-
visibility: "visible",
|
|
206
|
-
display: "block",
|
|
207
|
-
position: "relative",
|
|
208
|
-
zIndex: "1",
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const checkboxLabelStyle = {
|
|
212
|
-
fontSize: "14px",
|
|
213
|
-
fontFamily: "inherit",
|
|
214
|
-
cursor: "pointer",
|
|
215
|
-
userSelect: "none",
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
// Single checkbox mode - show multiple options but only one selectable (like radio buttons)
|
|
219
|
-
if (checkboxType === "single") {
|
|
220
|
-
// If no options are defined for single mode, show a message
|
|
221
|
-
if (!options || options.length === 0) {
|
|
222
|
-
return (
|
|
223
|
-
<div style={{ padding: "8px", color: "#666", fontStyle: "italic" }}>
|
|
224
|
-
{formatMessage({
|
|
225
|
-
id: 'advanced-fields.checkbox.options.checkboxOptions.description',
|
|
226
|
-
defaultMessage: 'Define available options for multiple checkboxes (one per line: value|label)'
|
|
227
|
-
})}
|
|
228
|
-
</div>
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (layout === "horizontal") {
|
|
233
|
-
return (
|
|
234
|
-
<Flex wrap="wrap" gap={2}>
|
|
235
|
-
{options.map((option) => (
|
|
236
|
-
<div key={option.value} style={checkboxStyle}>
|
|
237
|
-
<input
|
|
238
|
-
type="checkbox"
|
|
239
|
-
id={`${name}-${option.value}`}
|
|
240
|
-
checked={fieldValue.includes(option.value)}
|
|
241
|
-
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
242
|
-
disabled={disabled}
|
|
243
|
-
style={checkboxInputStyle}
|
|
244
|
-
/>
|
|
245
|
-
<label
|
|
246
|
-
htmlFor={`${name}-${option.value}`}
|
|
247
|
-
style={checkboxLabelStyle}
|
|
248
|
-
>
|
|
249
|
-
{option.label}
|
|
250
|
-
</label>
|
|
251
|
-
</div>
|
|
252
|
-
))}
|
|
253
|
-
</Flex>
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return (
|
|
258
|
-
<div>
|
|
259
|
-
{options.map((option) => (
|
|
260
|
-
<div key={option.value} style={checkboxStyle}>
|
|
261
|
-
<input
|
|
262
|
-
type="checkbox"
|
|
263
|
-
id={`${name}-${option.value}`}
|
|
264
|
-
checked={fieldValue.includes(option.value)}
|
|
265
|
-
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
266
|
-
disabled={disabled}
|
|
267
|
-
style={checkboxInputStyle}
|
|
268
|
-
/>
|
|
269
|
-
<label
|
|
270
|
-
htmlFor={`${name}-${option.value}`}
|
|
271
|
-
style={checkboxLabelStyle}
|
|
272
|
-
>
|
|
273
|
-
{option.label}
|
|
274
|
-
</label>
|
|
275
|
-
</div>
|
|
276
|
-
))}
|
|
277
|
-
</div>
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Multiple checkbox mode
|
|
282
|
-
// If no options are defined, show a message
|
|
283
|
-
if (!options || options.length === 0) {
|
|
284
|
-
return (
|
|
285
|
-
<div style={{ padding: "8px", color: "#666", fontStyle: "italic" }}>
|
|
286
|
-
{formatMessage({
|
|
287
|
-
id: 'advanced-fields.checkbox.options.checkboxOptions.description',
|
|
288
|
-
defaultMessage: 'Define available options for multiple checkboxes (one per line: value|label)'
|
|
289
|
-
})}
|
|
290
|
-
</div>
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
if (layout === "horizontal") {
|
|
295
|
-
return (
|
|
296
|
-
<Flex wrap="wrap" gap={2}>
|
|
297
|
-
{options.map((option) => (
|
|
298
|
-
<div key={option.value} style={checkboxStyle}>
|
|
299
|
-
<input
|
|
300
|
-
type="checkbox"
|
|
301
|
-
id={`${name}-${option.value}`}
|
|
302
|
-
checked={fieldValue.includes(option.value)}
|
|
303
|
-
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
304
|
-
disabled={disabled}
|
|
305
|
-
style={checkboxInputStyle}
|
|
306
|
-
/>
|
|
307
|
-
<label
|
|
308
|
-
htmlFor={`${name}-${option.value}`}
|
|
309
|
-
style={checkboxLabelStyle}
|
|
310
|
-
>
|
|
311
|
-
{option.label}
|
|
312
|
-
</label>
|
|
313
|
-
</div>
|
|
314
|
-
))}
|
|
315
|
-
</Flex>
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return (
|
|
320
|
-
<div>
|
|
321
|
-
{options.map((option) => (
|
|
322
|
-
<div key={option.value} style={checkboxStyle}>
|
|
323
|
-
<input
|
|
324
|
-
type="checkbox"
|
|
325
|
-
id={`${name}-${option.value}`}
|
|
326
|
-
checked={fieldValue.includes(option.value)}
|
|
327
|
-
onChange={(e) => handleCheckboxChange(option.value, e.target.checked)}
|
|
328
|
-
disabled={disabled}
|
|
329
|
-
style={checkboxInputStyle}
|
|
330
|
-
/>
|
|
331
|
-
<label
|
|
332
|
-
htmlFor={`${name}-${option.value}`}
|
|
333
|
-
style={checkboxLabelStyle}
|
|
334
|
-
>
|
|
335
|
-
{option.label}
|
|
336
|
-
</label>
|
|
337
|
-
</div>
|
|
338
|
-
))}
|
|
339
|
-
</div>
|
|
340
|
-
);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
return (
|
|
344
|
-
<Box col={6}>
|
|
345
|
-
<Field.Root name={name} error={displayError}>
|
|
346
|
-
<Field.Label>
|
|
347
|
-
{intlLabel.id ? formatMessage(intlLabel) : intlLabel.defaultMessage || name}
|
|
348
|
-
{required && <span style={{ color: "#d02b20", marginLeft: "4px" }}>*</span>}
|
|
349
|
-
</Field.Label>
|
|
350
|
-
{renderCheckboxes()}
|
|
351
|
-
{displayError && (
|
|
352
|
-
<Field.Error>
|
|
353
|
-
{displayError}
|
|
354
|
-
</Field.Error>
|
|
355
|
-
)}
|
|
356
|
-
{description && (description.id || description.defaultMessage) && (
|
|
357
|
-
<Field.Hint>
|
|
358
|
-
{description.id ? formatMessage(description) : description.defaultMessage}
|
|
359
|
-
</Field.Hint>
|
|
360
|
-
)}
|
|
361
|
-
{(fieldNote || fieldNoteFromAttribute) && (
|
|
362
|
-
<span style={{
|
|
363
|
-
fontStyle: 'italic',
|
|
364
|
-
color: '#666',
|
|
365
|
-
fontSize: '12px',
|
|
366
|
-
display: 'block',
|
|
367
|
-
marginTop: '4px'
|
|
368
|
-
}}>
|
|
369
|
-
{fieldNote || fieldNoteFromAttribute}
|
|
370
|
-
</span>
|
|
371
|
-
)}
|
|
372
|
-
</Field.Root>
|
|
373
|
-
</Box>
|
|
374
|
-
);
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
export default AdvancedCheckbox;
|