@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 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
- - Only combines simple text/string fields
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 = formData[fieldPath];
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 = formData[fieldPath];
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
  ] })
@@ -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-BsQuuQxB.js"))
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
  },
@@ -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-DZSaB0Rv.mjs")
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
  },
@@ -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 = {
@@ -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.0.0",
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",