@tunghtml/strapi-plugin-composite-field 1.2.8 → 1.2.9

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
@@ -176,6 +176,12 @@ Result: Panel Discussion - John Doe
176
176
 
177
177
  ## Changelog
178
178
 
179
+ ### v1.2.9 (2026-10-19)
180
+
181
+ **🔧 Technical:**
182
+
183
+ - Value formatting and joining moved out of the component, regexes use `RegExp.exec`
184
+
179
185
  ### v1.2.8 (2026-10-19)
180
186
 
181
187
  **🐛 Bug Fixes:**
@@ -5,6 +5,57 @@ import get from 'lodash/get';
5
5
  import React from 'react';
6
6
  import { useIntl } from 'react-intl';
7
7
 
8
+ const ISO_DATE_REGEX = /^(\d{4})-(\d{2})-(\d{2})(T[\d:.]+Z?)?$/;
9
+ const TIME_REGEX = /^(\d{2}):(\d{2}):(\d{2})(\.\d{3})?$/;
10
+
11
+ // Format a source value for display (especially time/date)
12
+ const formatFieldValue = (val) => {
13
+ if (val === null || val === undefined) return '';
14
+
15
+ // If it's a Date object
16
+ if (val instanceof Date) {
17
+ return val.toLocaleTimeString([], {
18
+ hour: '2-digit',
19
+ minute: '2-digit',
20
+ hour12: false,
21
+ });
22
+ }
23
+
24
+ const stringVal = String(val);
25
+
26
+ // ISO date (YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD) => MM/DD/YYYY
27
+ const dateMatch = ISO_DATE_REGEX.exec(stringVal);
28
+ if (dateMatch) {
29
+ const [, year, month, day] = dateMatch;
30
+ return `${month}/${day}/${year}`;
31
+ }
32
+
33
+ // Time (HH:MM:SS.mmm or HH:MM:SS) => HH:MM
34
+ const timeMatch = TIME_REGEX.exec(stringVal);
35
+ if (timeMatch) {
36
+ return `${timeMatch[1]}:${timeMatch[2]}`;
37
+ }
38
+
39
+ return stringVal;
40
+ };
41
+
42
+ // Text of one source value: relations/objects use their name, title or label
43
+ const toDisplayText = (rawValue) => {
44
+ if (rawValue === null || rawValue === undefined) return '';
45
+ if (typeof rawValue === 'object' && !(rawValue instanceof Date)) {
46
+ return String(rawValue.name || rawValue.title || rawValue.label || '');
47
+ }
48
+ return formatFieldValue(rawValue);
49
+ };
50
+
51
+ // A space-only separator joins with single spaces, other separators get spaces around them
52
+ const joinParts = (parts, separator) => {
53
+ const cleanSeparator = separator.trim();
54
+ return cleanSeparator === ''
55
+ ? parts.join(' ').trim()
56
+ : parts.join(` ${cleanSeparator} `).trim();
57
+ };
58
+
8
59
  const CompositeInput = (props) => {
9
60
  const {
10
61
  attribute,
@@ -69,43 +120,6 @@ const CompositeInput = (props) => {
69
120
  }
70
121
  };
71
122
 
72
- // Helper function to format values (especially time/date)
73
- const formatFieldValue = React.useCallback((val) => {
74
- if (val === null || val === undefined) return '';
75
-
76
- // If it's a Date object
77
- if (val instanceof Date) {
78
- return val.toLocaleTimeString([], {
79
- hour: '2-digit',
80
- minute: '2-digit',
81
- hour12: false,
82
- });
83
- }
84
-
85
- const stringVal = String(val);
86
-
87
- // Check if value is ISO date format (YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DD)
88
- const isoDateRegex = /^(\d{4})-(\d{2})-(\d{2})(T[\d:.]+Z?)?$/;
89
- const dateMatch = stringVal.match(isoDateRegex);
90
-
91
- if (dateMatch) {
92
- // Return in MM/DD/YYYY format
93
- const [, year, month, day] = dateMatch;
94
- return `${month}/${day}/${year}`;
95
- }
96
-
97
- // Check if value is time format (HH:MM:SS.mmm or HH:MM:SS)
98
- const timeRegex = /^(\d{2}):(\d{2}):(\d{2})(\.\d{3})?$/;
99
- const timeMatch = stringVal.match(timeRegex);
100
-
101
- if (timeMatch) {
102
- // Return only HH:MM
103
- return `${timeMatch[1]}:${timeMatch[2]}`;
104
- }
105
-
106
- return stringVal;
107
- }, []);
108
-
109
123
  const handleGenerate = React.useCallback(() => {
110
124
  // Calculate parent path to resolve relative field names
111
125
  const parts = name.split('.');
@@ -113,44 +127,15 @@ const CompositeInput = (props) => {
113
127
  parts.pop(); // Remove current field name
114
128
  const parentPath = parts.join('.');
115
129
 
116
- const generatedParts = [];
117
-
118
- fields.forEach((fieldName) => {
119
- // Resolve full path for the target field
120
- const fullPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
130
+ const generatedParts = fields
131
+ .map((fieldName) => {
132
+ // Resolve full path for the target field
133
+ const fullPath = parentPath ? `${parentPath}.${fieldName}` : fieldName;
134
+ return toDisplayText(get(values, fullPath));
135
+ })
136
+ .filter(Boolean);
121
137
 
122
- // Get raw value from form values
123
- let rawValue = get(values, fullPath);
124
-
125
- // Handle different value types
126
- if (rawValue !== null && rawValue !== undefined) {
127
- let stringValue = '';
128
-
129
- // Handle relations/objects
130
- if (typeof rawValue === 'object' && !(rawValue instanceof Date)) {
131
- if (rawValue.name) stringValue = String(rawValue.name);
132
- else if (rawValue.title) stringValue = String(rawValue.title);
133
- else if (rawValue.label) stringValue = String(rawValue.label);
134
- } else {
135
- stringValue = formatFieldValue(rawValue);
136
- }
137
-
138
- if (stringValue) {
139
- generatedParts.push(stringValue);
140
- }
141
- }
142
- });
143
-
144
- // Handle separator logic - if separator is just a space, don't add extra spaces
145
- const cleanSeparator = separator.trim();
146
- let result;
147
- if (cleanSeparator === '') {
148
- // If separator is just spaces, use single space
149
- result = generatedParts.join(' ').trim();
150
- } else {
151
- // For other separators, add spaces around them
152
- result = generatedParts.join(` ${cleanSeparator} `).trim();
153
- }
138
+ const result = joinParts(generatedParts, separator);
154
139
 
155
140
  if (result !== lastGeneratedVal.current) {
156
141
  setLocalValue(result);
@@ -162,7 +147,7 @@ const CompositeInput = (props) => {
162
147
  onChange({ target: { name, value: result, type: 'text' } });
163
148
  }
164
149
  }
165
- }, [fields, separator, onChange, name, values, formatFieldValue]);
150
+ }, [fields, separator, onChange, name, values]);
166
151
 
167
152
  // Watch only the relevant field values to trigger auto-generation
168
153
  const watchedValues = React.useMemo(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tunghtml/strapi-plugin-composite-field",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
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",