/** * Virtual Layers rows for components whose content lives in ARRAY PROPS * rather than Craft child nodes. * * FeaturesGrid, Tabs, Accordion, PricingTable, Testimonials, Gallery, * ContentSlider, NumberCounter, Menu, SocialLinks, Navbar and ContactForm all * render their items from a prop array, so Craft sees them as leaf nodes and * the Layers tree showed nothing underneath them -- the "Layers doesn't show * everything" report. These rows are display-only: they are not Craft nodes, * cannot be dragged, and selecting one selects the PARENT node (plus asks the * array editor to scroll that item into view -- see LayerFocusContext). * * ColumnLayout is deliberately absent: it uses real `` * children, which LayersPanel already nests correctly. */ export interface VirtualRow { index: number; label: string; } export interface VirtualChildSpec { /** Name of the array prop holding the items. */ prop: string; /** Per-item field used as the row label. */ label: string; /** Used as " " when the label field is missing or blank. */ fallback: string; } /** Keyed by the component's craft `displayName` -- the same string * `LayerNode` already resolves and shows as the row text. * * Verified against each component's actual item interface and * `craft.props` defaults (see task-9-report.md, Step 1): * - Tabs items are `{ label, content }`, not `{ title }` -- registry * corrected from `title` to `label`. * - ContentSlider items are `{ heading, text, ... }`, not `{ title }` -- * registry corrected from `title` to `heading`. * Every other entry (prop name and label field) matched the component as * originally drafted. * * Hero, Call to Action and CTA Section were added after a follow-up sweep * of every component under src/components/ for top-level array props (not * just the 12 files initially listed) turned up three more leaf components * with the identical pattern: all three render a `ctas?: CtaButton[]` prop * (see sections/_cta-helpers.tsx) whose items are `{ text, href, variant?, * target? }`, so `text` is the label field. Verified against each file's * `craft.displayName` and `craft.props.ctas` defaults. That sweep found no * further candidates -- the only other array fields in the tree are nested * one level down inside already-covered items (ContactFormField.options, * PricingPlan.features), not top-level component props. */ export const VIRTUAL_CHILD_PROPS: Record = { 'Features Grid': { prop: 'features', label: 'title', fallback: 'Feature' }, Tabs: { prop: 'tabs', label: 'label', fallback: 'Tab' }, Accordion: { prop: 'items', label: 'title', fallback: 'Item' }, 'Pricing Table': { prop: 'plans', label: 'name', fallback: 'Plan' }, Testimonials: { prop: 'testimonials', label: 'name', fallback: 'Testimonial' }, Gallery: { prop: 'images', label: 'alt', fallback: 'Image' }, 'Content Slider': { prop: 'slides', label: 'heading', fallback: 'Slide' }, 'Number Counter': { prop: 'counters', label: 'label', fallback: 'Counter' }, Menu: { prop: 'links', label: 'text', fallback: 'Link' }, 'Social Links': { prop: 'links', label: 'platform', fallback: 'Link' }, Navbar: { prop: 'links', label: 'text', fallback: 'Link' }, 'Contact Form': { prop: 'fields', label: 'label', fallback: 'Field' }, Hero: { prop: 'ctas', label: 'text', fallback: 'Button' }, 'Call to Action': { prop: 'ctas', label: 'text', fallback: 'Button' }, 'CTA Section': { prop: 'ctas', label: 'text', fallback: 'Button' }, }; const MAX_LABEL = 40; export function deriveVirtualRows(displayName: string, props: Record): VirtualRow[] { const spec = VIRTUAL_CHILD_PROPS[displayName]; if (!spec) return []; const items = props?.[spec.prop]; if (!Array.isArray(items)) return []; return items.map((item, index) => { const raw = item && typeof item === 'object' ? item[spec.label] : undefined; const text = typeof raw === 'string' ? raw.trim() : ''; if (!text) return { index, label: `${spec.fallback} ${index + 1}` }; const label = text.length > MAX_LABEL ? `${text.slice(0, MAX_LABEL)}…` : text; return { index, label }; }); }