@tunghtml/strapi-plugin-composite-field 1.0.0 → 1.1.0

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
 
@@ -434,6 +434,8 @@ const CompositeInput = (props) => {
434
434
  }, [value]);
435
435
  const fieldsConfig = attribute?.options?.fields || "";
436
436
  const separator = attribute?.options?.separator || " - ";
437
+ const editable = attribute?.options?.editable !== false;
438
+ const autoGenerate = attribute?.options?.autoGenerate === true;
437
439
  let fields = [];
438
440
  if (typeof fieldsConfig === "string") {
439
441
  fields = fieldsConfig.split("\n").map((f) => f.trim()).filter(Boolean);
@@ -445,7 +447,7 @@ const CompositeInput = (props) => {
445
447
  onChange({ target: { name, value: newValue, type: "text" } });
446
448
  }
447
449
  };
448
- const handleGenerate = () => {
450
+ const handleGenerate = React__default.useCallback(() => {
449
451
  const formData = {};
450
452
  fields.forEach((fieldPath) => {
451
453
  const input = document.querySelector(
@@ -453,6 +455,22 @@ const CompositeInput = (props) => {
453
455
  );
454
456
  if (input && input.value) {
455
457
  formData[fieldPath] = input.value;
458
+ } else {
459
+ const enumSelect = document.querySelector(
460
+ `[data-strapi-field-name="${fieldPath}"] select`
461
+ );
462
+ if (enumSelect && enumSelect.value) {
463
+ formData[fieldPath] = enumSelect.value;
464
+ }
465
+ const enumButton = document.querySelector(
466
+ `[data-strapi-field-name="${fieldPath}"] button[role="combobox"]`
467
+ );
468
+ if (enumButton) {
469
+ const selectedText = enumButton.textContent?.trim();
470
+ if (selectedText && selectedText !== "Select...") {
471
+ formData[fieldPath] = selectedText;
472
+ }
473
+ }
456
474
  }
457
475
  });
458
476
  const parts = [];
@@ -468,7 +486,33 @@ const CompositeInput = (props) => {
468
486
  if (onChange) {
469
487
  onChange({ target: { name, value: result, type: "text" } });
470
488
  }
471
- };
489
+ }, [fields, separator, onChange, name]);
490
+ React__default.useEffect(() => {
491
+ if (!autoGenerate || fields.length === 0) return;
492
+ const handleFieldChange = () => {
493
+ const timeoutId = setTimeout(() => {
494
+ handleGenerate();
495
+ }, 300);
496
+ return () => clearTimeout(timeoutId);
497
+ };
498
+ const listeners = [];
499
+ fields.forEach((fieldPath) => {
500
+ const elements = document.querySelectorAll(
501
+ `input[name="${fieldPath}"], textarea[name="${fieldPath}"], select[name="${fieldPath}"], [data-strapi-field-name="${fieldPath}"] select, [data-strapi-field-name="${fieldPath}"] button`
502
+ );
503
+ elements.forEach((element) => {
504
+ element.addEventListener("change", handleFieldChange);
505
+ element.addEventListener("input", handleFieldChange);
506
+ listeners.push({ element, handler: handleFieldChange });
507
+ });
508
+ });
509
+ return () => {
510
+ listeners.forEach(({ element, handler }) => {
511
+ element.removeEventListener("change", handler);
512
+ element.removeEventListener("input", handler);
513
+ });
514
+ };
515
+ }, [autoGenerate, fields, handleGenerate]);
472
516
  return /* @__PURE__ */ jsx(
473
517
  Field.Root,
474
518
  {
@@ -486,12 +530,12 @@ const CompositeInput = (props) => {
486
530
  type: "text",
487
531
  value: localValue,
488
532
  onChange: handleChange,
489
- disabled,
490
- placeholder: "Click button to generate",
533
+ disabled: disabled || !editable,
534
+ placeholder: autoGenerate ? "Auto-generated from fields" : "Click button to generate",
491
535
  style: { paddingRight: "40px" }
492
536
  }
493
537
  ),
494
- /* @__PURE__ */ jsx(
538
+ !autoGenerate && /* @__PURE__ */ jsx(
495
539
  "div",
496
540
  {
497
541
  style: {
@@ -531,7 +575,9 @@ const CompositeInput = (props) => {
531
575
  ] }),
532
576
  fields.length > 0 && /* @__PURE__ */ jsx(Field.Hint, { children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
533
577
  "Combines: ",
534
- fields.join(", ")
578
+ fields.join(", "),
579
+ autoGenerate && " (auto-generated)",
580
+ !editable && " (read-only)"
535
581
  ] }) }),
536
582
  /* @__PURE__ */ jsx(Field.Error, {})
537
583
  ] })
@@ -453,6 +453,8 @@ const CompositeInput = (props) => {
453
453
  }, [value]);
454
454
  const fieldsConfig = attribute?.options?.fields || "";
455
455
  const separator = attribute?.options?.separator || " - ";
456
+ const editable = attribute?.options?.editable !== false;
457
+ const autoGenerate = attribute?.options?.autoGenerate === true;
456
458
  let fields = [];
457
459
  if (typeof fieldsConfig === "string") {
458
460
  fields = fieldsConfig.split("\n").map((f) => f.trim()).filter(Boolean);
@@ -464,7 +466,7 @@ const CompositeInput = (props) => {
464
466
  onChange({ target: { name, value: newValue, type: "text" } });
465
467
  }
466
468
  };
467
- const handleGenerate = () => {
469
+ const handleGenerate = React__namespace.default.useCallback(() => {
468
470
  const formData = {};
469
471
  fields.forEach((fieldPath) => {
470
472
  const input = document.querySelector(
@@ -472,6 +474,22 @@ const CompositeInput = (props) => {
472
474
  );
473
475
  if (input && input.value) {
474
476
  formData[fieldPath] = input.value;
477
+ } else {
478
+ const enumSelect = document.querySelector(
479
+ `[data-strapi-field-name="${fieldPath}"] select`
480
+ );
481
+ if (enumSelect && enumSelect.value) {
482
+ formData[fieldPath] = enumSelect.value;
483
+ }
484
+ const enumButton = document.querySelector(
485
+ `[data-strapi-field-name="${fieldPath}"] button[role="combobox"]`
486
+ );
487
+ if (enumButton) {
488
+ const selectedText = enumButton.textContent?.trim();
489
+ if (selectedText && selectedText !== "Select...") {
490
+ formData[fieldPath] = selectedText;
491
+ }
492
+ }
475
493
  }
476
494
  });
477
495
  const parts = [];
@@ -487,7 +505,33 @@ const CompositeInput = (props) => {
487
505
  if (onChange) {
488
506
  onChange({ target: { name, value: result, type: "text" } });
489
507
  }
490
- };
508
+ }, [fields, separator, onChange, name]);
509
+ React__namespace.default.useEffect(() => {
510
+ if (!autoGenerate || fields.length === 0) return;
511
+ const handleFieldChange = () => {
512
+ const timeoutId = setTimeout(() => {
513
+ handleGenerate();
514
+ }, 300);
515
+ return () => clearTimeout(timeoutId);
516
+ };
517
+ const listeners = [];
518
+ fields.forEach((fieldPath) => {
519
+ const elements = document.querySelectorAll(
520
+ `input[name="${fieldPath}"], textarea[name="${fieldPath}"], select[name="${fieldPath}"], [data-strapi-field-name="${fieldPath}"] select, [data-strapi-field-name="${fieldPath}"] button`
521
+ );
522
+ elements.forEach((element) => {
523
+ element.addEventListener("change", handleFieldChange);
524
+ element.addEventListener("input", handleFieldChange);
525
+ listeners.push({ element, handler: handleFieldChange });
526
+ });
527
+ });
528
+ return () => {
529
+ listeners.forEach(({ element, handler }) => {
530
+ element.removeEventListener("change", handler);
531
+ element.removeEventListener("input", handler);
532
+ });
533
+ };
534
+ }, [autoGenerate, fields, handleGenerate]);
491
535
  return /* @__PURE__ */ jsxRuntime.jsx(
492
536
  designSystem.Field.Root,
493
537
  {
@@ -505,12 +549,12 @@ const CompositeInput = (props) => {
505
549
  type: "text",
506
550
  value: localValue,
507
551
  onChange: handleChange,
508
- disabled,
509
- placeholder: "Click button to generate",
552
+ disabled: disabled || !editable,
553
+ placeholder: autoGenerate ? "Auto-generated from fields" : "Click button to generate",
510
554
  style: { paddingRight: "40px" }
511
555
  }
512
556
  ),
513
- /* @__PURE__ */ jsxRuntime.jsx(
557
+ !autoGenerate && /* @__PURE__ */ jsxRuntime.jsx(
514
558
  "div",
515
559
  {
516
560
  style: {
@@ -550,7 +594,9 @@ const CompositeInput = (props) => {
550
594
  ] }),
551
595
  fields.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
552
596
  "Combines: ",
553
- fields.join(", ")
597
+ fields.join(", "),
598
+ autoGenerate && " (auto-generated)",
599
+ !editable && " (read-only)"
554
600
  ] }) }),
555
601
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
556
602
  ] })
@@ -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-cwqJYLtS.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-Cs0WHHpL.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.0",
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",