Skip to content

Commit a0159c8

Browse files
committed
feat(vue): add flexible field support to DetailField component
- Add matching flexible field resolution from FormField component - Implement getFlexibleContextPrefix() for context detection - Implement getFlexibleAttributeFormats() for pattern matching - Implement getAllFields() for parent component traversal - Update findFieldByAttribute() with prefix-based lookups Resolves #2
1 parent 25d3043 commit a0159c8

1 file changed

Lines changed: 127 additions & 3 deletions

File tree

resources/js/components/DetailField.vue

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,137 @@ export default {
112112
},
113113
114114
findFieldByAttribute(attribute) {
115-
const parent = this.$parent;
116-
if (parent && parent.fields) {
117-
return parent.fields.find(f => f.attribute === attribute);
115+
const allFields = this.getAllFields();
116+
117+
// First try exact match
118+
const exactMatch = allFields.find(f => f.attribute === attribute);
119+
if (exactMatch) {
120+
return exactMatch;
121+
}
122+
123+
// For Flexible fields: resolve attribute relative to current context
124+
const contextPrefix = this.getFlexibleContextPrefix();
125+
if (contextPrefix) {
126+
// Try to find field with same prefix (within same Flexible group)
127+
const prefixedAttribute = `${contextPrefix}${attribute}`;
128+
const prefixedMatch = allFields.find(f => f.attribute === prefixedAttribute);
129+
if (prefixedMatch) {
130+
return prefixedMatch;
131+
}
132+
133+
// Also try alternative Flexible attribute formats
134+
const alternativeFormats = this.getFlexibleAttributeFormats(contextPrefix, attribute);
135+
for (const format of alternativeFormats) {
136+
const match = allFields.find(f => f.attribute === format);
137+
if (match) {
138+
return match;
139+
}
140+
}
141+
}
142+
143+
// Fallback: find any field that ends with the attribute name (for nested contexts)
144+
const suffixMatch = allFields.find(f => {
145+
if (!f.attribute) return false;
146+
const attr = f.attribute;
147+
// Match patterns like: prefix__attribute, prefix[index][attribute]
148+
return attr.endsWith(`__${attribute}`) ||
149+
attr.endsWith(`][${attribute}]`) ||
150+
attr.endsWith(`[${attribute}]`);
151+
});
152+
153+
return suffixMatch || null;
154+
},
155+
156+
/**
157+
* Get all fields from the parent context.
158+
* Handles both standard Nova detail views and nested Flexible field contexts.
159+
*/
160+
getAllFields() {
161+
// Walk up the component tree to find fields
162+
let parent = this.$parent;
163+
let maxDepth = 10;
164+
165+
while (parent && maxDepth-- > 0) {
166+
// Check for fields array directly on parent
167+
if (parent.fields && Array.isArray(parent.fields)) {
168+
return parent.fields;
169+
}
170+
// Check for fields in parent's refs
171+
if (parent.$refs?.fields && Array.isArray(parent.$refs.fields)) {
172+
return parent.$refs.fields.map(f => f.field || f);
173+
}
174+
parent = parent.$parent;
175+
}
176+
177+
return [];
178+
},
179+
180+
/**
181+
* Detect the Flexible field context prefix from the container's own field attribute.
182+
* Flexible fields use prefixes like: flexible_key__index__ or flexible_key[index]
183+
*/
184+
getFlexibleContextPrefix() {
185+
// Check if this container has a prefixed attribute (indicating it's inside a Flexible field)
186+
const ownAttribute = this.field?.attribute || '';
187+
188+
// Pattern 1: Double underscore format (e.g., "overlay_items__0__field_name")
189+
const underscoreMatch = ownAttribute.match(/^(.+__\d+__)/);
190+
if (underscoreMatch) {
191+
return underscoreMatch[1];
192+
}
193+
194+
// Pattern 2: Bracket format (e.g., "overlay_items[0][field_name]")
195+
const bracketMatch = ownAttribute.match(/^(.+\[\d+\]\[)/);
196+
if (bracketMatch) {
197+
return bracketMatch[1];
198+
}
199+
200+
// Try to detect from parent/sibling field attributes
201+
const siblingFields = this.field?.fields || [];
202+
for (const sibling of siblingFields) {
203+
if (sibling.attribute) {
204+
const siblingUnderscoreMatch = sibling.attribute.match(/^(.+__\d+__)/);
205+
if (siblingUnderscoreMatch) {
206+
return siblingUnderscoreMatch[1];
207+
}
208+
const siblingBracketMatch = sibling.attribute.match(/^(.+\[\d+\]\[)/);
209+
if (siblingBracketMatch) {
210+
return siblingBracketMatch[1];
211+
}
212+
}
118213
}
214+
119215
return null;
120216
},
121217
218+
/**
219+
* Generate alternative attribute formats for Flexible fields.
220+
*/
221+
getFlexibleAttributeFormats(prefix, attribute) {
222+
const formats = [];
223+
224+
// Extract the base key and index from the prefix
225+
const underscoreMatch = prefix.match(/^(.+)__(\d+)__$/);
226+
const bracketMatch = prefix.match(/^(.+)\[(\d+)\]\[$/);
227+
228+
if (underscoreMatch) {
229+
const [, key, index] = underscoreMatch;
230+
// Generate bracket format alternative
231+
formats.push(`${key}[${index}][${attribute}]`);
232+
// Single underscore variant
233+
formats.push(`${key}_${index}_${attribute}`);
234+
}
235+
236+
if (bracketMatch) {
237+
const [, key, index] = bracketMatch;
238+
// Generate underscore format alternative
239+
formats.push(`${key}__${index}__${attribute}`);
240+
formats.push(`${key}_${index}_${attribute}`);
241+
}
242+
243+
return formats;
244+
},
245+
122246
isEmpty(value) {
123247
return value === null ||
124248
value === undefined ||

0 commit comments

Comments
 (0)