@tunghtml/strapi-plugin-composite-field 1.0.0 → 1.1.1
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 +38 -2
- package/dist/_chunks/{CompositeInput-DZSaB0Rv.mjs → CompositeInput-B-IQTi47.mjs} +21 -17
- package/dist/_chunks/{CompositeInput-BsQuuQxB.js → CompositeInput-B2zE74Yh.js} +21 -17
- package/dist/admin/index.js +35 -2
- package/dist/admin/index.mjs +35 -2
- package/dist/server/index.js +0 -4
- package/dist/server/index.mjs +0 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,8 +8,10 @@ A Strapi v5 plugin that creates composite display fields by combining multiple t
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- 🔗 **Multiple Fields**: Combine any text/string fields
|
|
11
|
+
- 🔗 **Multiple Fields**: Combine any text/string/enum fields
|
|
12
12
|
- 🔄 **Generate Button**: Click icon to generate composite value
|
|
13
|
+
- ⚡ **Auto-Generate**: Automatically update when source fields change
|
|
14
|
+
- 🔒 **Editable Control**: Make field read-only or editable
|
|
13
15
|
- 📝 **Easy Config**: Textarea input (one field per line)
|
|
14
16
|
- ⚙️ **Custom Separator**: Configure separator (default: " - ")
|
|
15
17
|
- ✨ **Auto Spacing**: Automatically adds spaces around separator
|
|
@@ -115,17 +117,51 @@ Result: Tung Le
|
|
|
115
117
|
|
|
116
118
|
## Configuration Options
|
|
117
119
|
|
|
120
|
+
### Base Settings
|
|
121
|
+
|
|
118
122
|
| Option | Type | Default | Description |
|
|
119
123
|
| ----------- | -------- | ------- | ------------------------------------- |
|
|
120
124
|
| `fields` | textarea | - | Field names to combine (one per line) |
|
|
121
125
|
| `separator` | text | `" - "` | Separator between fields |
|
|
122
126
|
|
|
127
|
+
### Advanced Settings
|
|
128
|
+
|
|
129
|
+
| Option | Type | Default | Description |
|
|
130
|
+
| -------------- | -------- | ------- | ------------------------------------------------ |
|
|
131
|
+
| `editable` | checkbox | `true` | Allow manual editing of the composite field |
|
|
132
|
+
| `autoGenerate` | checkbox | `false` | Automatically generate when source fields change |
|
|
133
|
+
|
|
134
|
+
## Advanced Usage
|
|
135
|
+
|
|
136
|
+
### Auto-Generate Mode
|
|
137
|
+
|
|
138
|
+
Enable **Auto-generate** in Advanced settings to automatically update the composite field when any source field changes. This is useful for fields that should always reflect the current values.
|
|
139
|
+
|
|
140
|
+
### Read-Only Mode
|
|
141
|
+
|
|
142
|
+
Disable **Editable** in Advanced settings to make the composite field read-only. Users can only generate values using the button or auto-generate, but cannot manually edit the field.
|
|
143
|
+
|
|
144
|
+
### Enum Field Support
|
|
145
|
+
|
|
146
|
+
The plugin now supports enumeration fields! Simply include the enum field name in your fields list, and the plugin will automatically extract the selected value.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
Fields:
|
|
152
|
+
meetingType (enum)
|
|
153
|
+
name
|
|
154
|
+
|
|
155
|
+
Result: Panel Discussion - John Doe
|
|
156
|
+
```
|
|
157
|
+
|
|
123
158
|
## Notes
|
|
124
159
|
|
|
125
|
-
-
|
|
160
|
+
- Supports text, string, and enumeration fields
|
|
126
161
|
- Automatically adds spaces around separator for clean output
|
|
127
162
|
- Empty fields are skipped automatically
|
|
128
163
|
- Relation fields are not supported (use simple fields only)
|
|
164
|
+
- Auto-generate uses a 300ms debounce to avoid excessive updates
|
|
129
165
|
|
|
130
166
|
## Requirements
|
|
131
167
|
|
|
@@ -3,6 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
import React__default from "react";
|
|
4
4
|
import { Field, Flex, Button, Typography } from "@strapi/design-system";
|
|
5
5
|
import { Play } from "@strapi/icons";
|
|
6
|
+
import { useCMEditViewDataManager } from "@strapi/strapi/admin";
|
|
6
7
|
var __assign = function() {
|
|
7
8
|
__assign = Object.assign || function __assign2(t) {
|
|
8
9
|
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
@@ -427,6 +428,7 @@ const CompositeInput = (props) => {
|
|
|
427
428
|
label,
|
|
428
429
|
intlLabel
|
|
429
430
|
} = props;
|
|
431
|
+
const { modifiedData } = useCMEditViewDataManager();
|
|
430
432
|
const { formatMessage } = useIntl();
|
|
431
433
|
const [localValue, setLocalValue] = React__default.useState(value || "");
|
|
432
434
|
React__default.useEffect(() => {
|
|
@@ -434,6 +436,8 @@ const CompositeInput = (props) => {
|
|
|
434
436
|
}, [value]);
|
|
435
437
|
const fieldsConfig = attribute?.options?.fields || "";
|
|
436
438
|
const separator = attribute?.options?.separator || " - ";
|
|
439
|
+
const editable = attribute?.options?.editable !== false;
|
|
440
|
+
const autoGenerate = attribute?.options?.autoGenerate === true;
|
|
437
441
|
let fields = [];
|
|
438
442
|
if (typeof fieldsConfig === "string") {
|
|
439
443
|
fields = fieldsConfig.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
@@ -445,20 +449,11 @@ const CompositeInput = (props) => {
|
|
|
445
449
|
onChange({ target: { name, value: newValue, type: "text" } });
|
|
446
450
|
}
|
|
447
451
|
};
|
|
448
|
-
const handleGenerate = () => {
|
|
449
|
-
const formData = {};
|
|
450
|
-
fields.forEach((fieldPath) => {
|
|
451
|
-
const input = document.querySelector(
|
|
452
|
-
`input[name="${fieldPath}"], textarea[name="${fieldPath}"], select[name="${fieldPath}"]`
|
|
453
|
-
);
|
|
454
|
-
if (input && input.value) {
|
|
455
|
-
formData[fieldPath] = input.value;
|
|
456
|
-
}
|
|
457
|
-
});
|
|
452
|
+
const handleGenerate = React__default.useCallback(() => {
|
|
458
453
|
const parts = [];
|
|
459
454
|
fields.forEach((fieldPath) => {
|
|
460
|
-
const fieldValue =
|
|
461
|
-
if (fieldValue) {
|
|
455
|
+
const fieldValue = modifiedData?.[fieldPath];
|
|
456
|
+
if (fieldValue !== null && fieldValue !== void 0 && fieldValue !== "") {
|
|
462
457
|
parts.push(fieldValue);
|
|
463
458
|
}
|
|
464
459
|
});
|
|
@@ -468,7 +463,14 @@ const CompositeInput = (props) => {
|
|
|
468
463
|
if (onChange) {
|
|
469
464
|
onChange({ target: { name, value: result, type: "text" } });
|
|
470
465
|
}
|
|
471
|
-
};
|
|
466
|
+
}, [fields, separator, onChange, name, modifiedData]);
|
|
467
|
+
React__default.useEffect(() => {
|
|
468
|
+
if (!autoGenerate || fields.length === 0 || !modifiedData) return;
|
|
469
|
+
const timeoutId = setTimeout(() => {
|
|
470
|
+
handleGenerate();
|
|
471
|
+
}, 300);
|
|
472
|
+
return () => clearTimeout(timeoutId);
|
|
473
|
+
}, [autoGenerate, fields, modifiedData, handleGenerate]);
|
|
472
474
|
return /* @__PURE__ */ jsx(
|
|
473
475
|
Field.Root,
|
|
474
476
|
{
|
|
@@ -486,12 +488,12 @@ const CompositeInput = (props) => {
|
|
|
486
488
|
type: "text",
|
|
487
489
|
value: localValue,
|
|
488
490
|
onChange: handleChange,
|
|
489
|
-
disabled,
|
|
490
|
-
placeholder: "Click button to generate",
|
|
491
|
+
disabled: disabled || !editable,
|
|
492
|
+
placeholder: autoGenerate ? "Auto-generated from fields" : "Click button to generate",
|
|
491
493
|
style: { paddingRight: "40px" }
|
|
492
494
|
}
|
|
493
495
|
),
|
|
494
|
-
/* @__PURE__ */ jsx(
|
|
496
|
+
!autoGenerate && /* @__PURE__ */ jsx(
|
|
495
497
|
"div",
|
|
496
498
|
{
|
|
497
499
|
style: {
|
|
@@ -531,7 +533,9 @@ const CompositeInput = (props) => {
|
|
|
531
533
|
] }),
|
|
532
534
|
fields.length > 0 && /* @__PURE__ */ jsx(Field.Hint, { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
533
535
|
"Combines: ",
|
|
534
|
-
fields.join(", ")
|
|
536
|
+
fields.join(", "),
|
|
537
|
+
autoGenerate && " (auto-generated)",
|
|
538
|
+
!editable && " (read-only)"
|
|
535
539
|
] }) }),
|
|
536
540
|
/* @__PURE__ */ jsx(Field.Error, {})
|
|
537
541
|
] })
|
|
@@ -4,6 +4,7 @@ const jsxRuntime = require("react/jsx-runtime");
|
|
|
4
4
|
const React = require("react");
|
|
5
5
|
const designSystem = require("@strapi/design-system");
|
|
6
6
|
const icons = require("@strapi/icons");
|
|
7
|
+
const admin = require("@strapi/strapi/admin");
|
|
7
8
|
function _interopNamespace(e) {
|
|
8
9
|
if (e && e.__esModule) return e;
|
|
9
10
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -446,6 +447,7 @@ const CompositeInput = (props) => {
|
|
|
446
447
|
label,
|
|
447
448
|
intlLabel
|
|
448
449
|
} = props;
|
|
450
|
+
const { modifiedData } = admin.useCMEditViewDataManager();
|
|
449
451
|
const { formatMessage } = useIntl();
|
|
450
452
|
const [localValue, setLocalValue] = React__namespace.default.useState(value || "");
|
|
451
453
|
React__namespace.default.useEffect(() => {
|
|
@@ -453,6 +455,8 @@ const CompositeInput = (props) => {
|
|
|
453
455
|
}, [value]);
|
|
454
456
|
const fieldsConfig = attribute?.options?.fields || "";
|
|
455
457
|
const separator = attribute?.options?.separator || " - ";
|
|
458
|
+
const editable = attribute?.options?.editable !== false;
|
|
459
|
+
const autoGenerate = attribute?.options?.autoGenerate === true;
|
|
456
460
|
let fields = [];
|
|
457
461
|
if (typeof fieldsConfig === "string") {
|
|
458
462
|
fields = fieldsConfig.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
@@ -464,20 +468,11 @@ const CompositeInput = (props) => {
|
|
|
464
468
|
onChange({ target: { name, value: newValue, type: "text" } });
|
|
465
469
|
}
|
|
466
470
|
};
|
|
467
|
-
const handleGenerate = () => {
|
|
468
|
-
const formData = {};
|
|
469
|
-
fields.forEach((fieldPath) => {
|
|
470
|
-
const input = document.querySelector(
|
|
471
|
-
`input[name="${fieldPath}"], textarea[name="${fieldPath}"], select[name="${fieldPath}"]`
|
|
472
|
-
);
|
|
473
|
-
if (input && input.value) {
|
|
474
|
-
formData[fieldPath] = input.value;
|
|
475
|
-
}
|
|
476
|
-
});
|
|
471
|
+
const handleGenerate = React__namespace.default.useCallback(() => {
|
|
477
472
|
const parts = [];
|
|
478
473
|
fields.forEach((fieldPath) => {
|
|
479
|
-
const fieldValue =
|
|
480
|
-
if (fieldValue) {
|
|
474
|
+
const fieldValue = modifiedData?.[fieldPath];
|
|
475
|
+
if (fieldValue !== null && fieldValue !== void 0 && fieldValue !== "") {
|
|
481
476
|
parts.push(fieldValue);
|
|
482
477
|
}
|
|
483
478
|
});
|
|
@@ -487,7 +482,14 @@ const CompositeInput = (props) => {
|
|
|
487
482
|
if (onChange) {
|
|
488
483
|
onChange({ target: { name, value: result, type: "text" } });
|
|
489
484
|
}
|
|
490
|
-
};
|
|
485
|
+
}, [fields, separator, onChange, name, modifiedData]);
|
|
486
|
+
React__namespace.default.useEffect(() => {
|
|
487
|
+
if (!autoGenerate || fields.length === 0 || !modifiedData) return;
|
|
488
|
+
const timeoutId = setTimeout(() => {
|
|
489
|
+
handleGenerate();
|
|
490
|
+
}, 300);
|
|
491
|
+
return () => clearTimeout(timeoutId);
|
|
492
|
+
}, [autoGenerate, fields, modifiedData, handleGenerate]);
|
|
491
493
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
492
494
|
designSystem.Field.Root,
|
|
493
495
|
{
|
|
@@ -505,12 +507,12 @@ const CompositeInput = (props) => {
|
|
|
505
507
|
type: "text",
|
|
506
508
|
value: localValue,
|
|
507
509
|
onChange: handleChange,
|
|
508
|
-
disabled,
|
|
509
|
-
placeholder: "Click button to generate",
|
|
510
|
+
disabled: disabled || !editable,
|
|
511
|
+
placeholder: autoGenerate ? "Auto-generated from fields" : "Click button to generate",
|
|
510
512
|
style: { paddingRight: "40px" }
|
|
511
513
|
}
|
|
512
514
|
),
|
|
513
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
515
|
+
!autoGenerate && /* @__PURE__ */ jsxRuntime.jsx(
|
|
514
516
|
"div",
|
|
515
517
|
{
|
|
516
518
|
style: {
|
|
@@ -550,7 +552,9 @@ const CompositeInput = (props) => {
|
|
|
550
552
|
] }),
|
|
551
553
|
fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
552
554
|
"Combines: ",
|
|
553
|
-
fields.join(", ")
|
|
555
|
+
fields.join(", "),
|
|
556
|
+
autoGenerate && " (auto-generated)",
|
|
557
|
+
!editable && " (read-only)"
|
|
554
558
|
] }) }),
|
|
555
559
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
556
560
|
] })
|
package/dist/admin/index.js
CHANGED
|
@@ -37,7 +37,7 @@ const index = {
|
|
|
37
37
|
defaultMessage: "Combine multiple fields into one display value"
|
|
38
38
|
},
|
|
39
39
|
components: {
|
|
40
|
-
Input: async () => Promise.resolve().then(() => require("../_chunks/CompositeInput-
|
|
40
|
+
Input: async () => Promise.resolve().then(() => require("../_chunks/CompositeInput-B2zE74Yh.js"))
|
|
41
41
|
},
|
|
42
42
|
options: {
|
|
43
43
|
base: [
|
|
@@ -82,7 +82,40 @@ const index = {
|
|
|
82
82
|
]
|
|
83
83
|
}
|
|
84
84
|
],
|
|
85
|
-
advanced: [
|
|
85
|
+
advanced: [
|
|
86
|
+
{
|
|
87
|
+
sectionTitle: {
|
|
88
|
+
id: "composite-field.advanced.title",
|
|
89
|
+
defaultMessage: "Settings"
|
|
90
|
+
},
|
|
91
|
+
items: [
|
|
92
|
+
{
|
|
93
|
+
name: "options.editable",
|
|
94
|
+
type: "checkbox",
|
|
95
|
+
intlLabel: {
|
|
96
|
+
id: "composite-field.editable.label",
|
|
97
|
+
defaultMessage: "Editable"
|
|
98
|
+
},
|
|
99
|
+
description: {
|
|
100
|
+
id: "composite-field.editable.description",
|
|
101
|
+
defaultMessage: "Allow manual editing of the composite field value"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "options.autoGenerate",
|
|
106
|
+
type: "checkbox",
|
|
107
|
+
intlLabel: {
|
|
108
|
+
id: "composite-field.autoGenerate.label",
|
|
109
|
+
defaultMessage: "Auto-generate"
|
|
110
|
+
},
|
|
111
|
+
description: {
|
|
112
|
+
id: "composite-field.autoGenerate.description",
|
|
113
|
+
defaultMessage: "Automatically generate value when source fields change"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
]
|
|
86
119
|
}
|
|
87
120
|
});
|
|
88
121
|
},
|
package/dist/admin/index.mjs
CHANGED
|
@@ -34,7 +34,7 @@ const index = {
|
|
|
34
34
|
defaultMessage: "Combine multiple fields into one display value"
|
|
35
35
|
},
|
|
36
36
|
components: {
|
|
37
|
-
Input: async () => import("../_chunks/CompositeInput-
|
|
37
|
+
Input: async () => import("../_chunks/CompositeInput-B-IQTi47.mjs")
|
|
38
38
|
},
|
|
39
39
|
options: {
|
|
40
40
|
base: [
|
|
@@ -79,7 +79,40 @@ const index = {
|
|
|
79
79
|
]
|
|
80
80
|
}
|
|
81
81
|
],
|
|
82
|
-
advanced: [
|
|
82
|
+
advanced: [
|
|
83
|
+
{
|
|
84
|
+
sectionTitle: {
|
|
85
|
+
id: "composite-field.advanced.title",
|
|
86
|
+
defaultMessage: "Settings"
|
|
87
|
+
},
|
|
88
|
+
items: [
|
|
89
|
+
{
|
|
90
|
+
name: "options.editable",
|
|
91
|
+
type: "checkbox",
|
|
92
|
+
intlLabel: {
|
|
93
|
+
id: "composite-field.editable.label",
|
|
94
|
+
defaultMessage: "Editable"
|
|
95
|
+
},
|
|
96
|
+
description: {
|
|
97
|
+
id: "composite-field.editable.description",
|
|
98
|
+
defaultMessage: "Allow manual editing of the composite field value"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "options.autoGenerate",
|
|
103
|
+
type: "checkbox",
|
|
104
|
+
intlLabel: {
|
|
105
|
+
id: "composite-field.autoGenerate.label",
|
|
106
|
+
defaultMessage: "Auto-generate"
|
|
107
|
+
},
|
|
108
|
+
description: {
|
|
109
|
+
id: "composite-field.autoGenerate.description",
|
|
110
|
+
defaultMessage: "Automatically generate value when source fields change"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
]
|
|
83
116
|
}
|
|
84
117
|
});
|
|
85
118
|
},
|
package/dist/server/index.js
CHANGED
|
@@ -3,15 +3,11 @@ function getDefaultExportFromCjs(x) {
|
|
|
3
3
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
4
4
|
}
|
|
5
5
|
var register$1 = ({ strapi }) => {
|
|
6
|
-
console.log("🔌 Composite Field Plugin: Registering custom field...");
|
|
7
6
|
strapi.customFields.register({
|
|
8
7
|
name: "composite",
|
|
9
8
|
plugin: "composite-field",
|
|
10
9
|
type: "string"
|
|
11
10
|
});
|
|
12
|
-
console.log(
|
|
13
|
-
"✅ Composite Field Plugin: Custom field registered successfully"
|
|
14
|
-
);
|
|
15
11
|
};
|
|
16
12
|
const register = register$1;
|
|
17
13
|
var src = {
|
package/dist/server/index.mjs
CHANGED
|
@@ -2,15 +2,11 @@ function getDefaultExportFromCjs(x) {
|
|
|
2
2
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
3
3
|
}
|
|
4
4
|
var register$1 = ({ strapi }) => {
|
|
5
|
-
console.log("🔌 Composite Field Plugin: Registering custom field...");
|
|
6
5
|
strapi.customFields.register({
|
|
7
6
|
name: "composite",
|
|
8
7
|
plugin: "composite-field",
|
|
9
8
|
type: "string"
|
|
10
9
|
});
|
|
11
|
-
console.log(
|
|
12
|
-
"✅ Composite Field Plugin: Custom field registered successfully"
|
|
13
|
-
);
|
|
14
10
|
};
|
|
15
11
|
const register = register$1;
|
|
16
12
|
var src = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tunghtml/strapi-plugin-composite-field",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A Strapi v5 plugin to create composite display fields by combining multiple text fields",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "A Strapi v5 plugin to create composite display fields by combining multiple text, string, and enum fields with auto-generate support",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
7
7
|
"strapi-plugin",
|