import type { Field } from '@/types/field' import { applyIvyFormsFilter } from '../composables/IvyFormAPI' export function parentIdsMatch(a: unknown, b: unknown): boolean { if (a === undefined || a === null || b === undefined || b === null) { return false } return Number(a) === Number(b) } /** Whether the field is nested under a parent (section, name, address, etc.). */ export function hasParentField(field: Field): boolean { const parentId = field.parentId return parentId !== undefined && parentId !== null && Number(parentId) !== 0 } /** Nested layout children (e.g. section children) are not top-level canvas fields. */ export function isNestedLayoutChild(field: Field, allFields: Field[]): boolean { return applyIvyFormsFilter('ivyforms/field/is_nested_layout_child', false, field, allFields) } /** Whether parentId refers to a nested layout container (Pro registers section parents). */ export function isNestedLayoutParent( allFields: Field[], parentId: number | null | undefined, ): boolean { return applyIvyFormsFilter('ivyforms/builder/is_nested_layout_parent', false, allFields, parentId) } /** Top-level form fields plus runtime compound children (Pro registers section children). */ export function flattenFormFieldsForRuntime(fields: Field[]): Field[] { const flat: Field[] = [] for (const field of fields) { flat.push(field) const extra = applyIvyFormsFilter( 'ivyforms/form-render/runtime_compound_children', [], field, ) if (extra.length > 0) { flat.push(...extra) } } return flat } /** Fields that share the same grid row/column namespace (top-level vs nested container). */ export function getLayoutSiblingFields( allFields: Field[], parentId: number | null | undefined, ): Field[] { return applyIvyFormsFilter( 'ivyforms/builder/layout_sibling_fields', getDefaultLayoutSiblingFields(allFields, parentId), allFields, parentId, ) } function getDefaultLayoutSiblingFields( allFields: Field[], parentId: number | null | undefined, ): Field[] { if (parentId !== undefined && parentId !== null && parentId !== 0) { return allFields.filter((f) => parentIdsMatch(f.parentId, parentId)) } return allFields.filter((f) => !isNestedLayoutChild(f, allFields)) } export function remapRowIndexes(fields: Field[]): void { const rowIndexes = [...new Set(fields.map((f) => f.rowIndex ?? 0))].sort((a, b) => a - b) const rowMapping = new Map() rowIndexes.forEach((oldIdx, newIdx) => { rowMapping.set(oldIdx, newIdx) }) fields.forEach((f) => { const oldIdx = f.rowIndex ?? 0 f.rowIndex = rowMapping.get(oldIdx) ?? oldIdx }) } export function groupFieldsIntoRows(fields: Field[]): Field[][] { const rowMap = new Map() fields.forEach((field) => { const rowIdx = field.rowIndex ?? 0 if (!rowMap.has(rowIdx)) { rowMap.set(rowIdx, []) } rowMap.get(rowIdx)!.push(field) }) return Array.from(rowMap.entries()) .sort(([a], [b]) => a - b) .map(([, rowFields]) => [...rowFields].sort((a, b) => (a.columnIndex ?? 0) - (b.columnIndex ?? 0)), ) }