PluginProbe
The Innovative Form Builder – IvyForms / 0.8.2
The Innovative Form Builder – IvyForms v0.8.2
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / composables / useFieldWidth.ts

useFieldWidth.ts in The Innovative Form Builder – IvyForms 0.8.2, at frontend/src/composables/useFieldWidth.ts

190 lines 5.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import type { Field } from '@/types/field'
2
3 /**
4 * Composable for calculating field widths in grid layout
5 *
6 * Supports:
7 * - Auto width based on fields per row (1=100%, 2=50%, 3=33%, 4=25%, 5=20%)
8 * - Custom widths from manual resize
9 * - Maximum 5 fields per row
10 */
11 export function useFieldWidth() {
12 /**
13 * Calculate width percentage based on number of fields in row
14 * Returns values with one decimal place for precision
15 * Maximum fields per row is 5 (minimum 20% each)
16 * CSS will handle the gap spacing separately
17 */
18 const getAutoWidth = (fieldsInRow: number): number => {
19 if (fieldsInRow === 1) return 100
20 if (fieldsInRow >= 5) return 20 // Max 5 fields = 20% each
21
22 // Divide 100% evenly and round to 1 decimal place
23 return Math.round((100 / fieldsInRow) * 10) / 10
24 }
25
26 /**
27 * Group fields by row index
28 */
29 const groupFieldsByRow = (fields: Field[]): Map<number, Field[]> => {
30 const rows = new Map<number, Field[]>()
31
32 fields.forEach((field) => {
33 const rowIndex = field.rowIndex ?? 0
34 if (!rows.has(rowIndex)) {
35 rows.set(rowIndex, [])
36 }
37 rows.get(rowIndex)!.push(field)
38 })
39
40 // Sort fields within each row by columnIndex
41 rows.forEach((rowFields) => {
42 rowFields.sort((a, b) => (a.columnIndex ?? 0) - (b.columnIndex ?? 0))
43 })
44
45 return rows
46 }
47
48 /**
49 * Calculate width for a field
50 * Returns custom width if set, otherwise calculates based on fields in row
51 */
52 const getFieldWidth = (field: Field, fieldsInRow: Field[]): number => {
53 // If custom width is set, use it
54 if (field.width && field.width >= 20 && field.width <= 100) {
55 return field.width
56 }
57
58 // Otherwise calculate based on number of fields in row
59 return getAutoWidth(fieldsInRow.length)
60 }
61
62 /**
63 * Get CSS width style for a field
64 * Uses calc() to account for gap between fields
65 */
66 const getFieldWidthStyle = (field: Field, fieldsInRow: Field[]): string => {
67 const width = getFieldWidth(field, fieldsInRow)
68 const numFields = fieldsInRow.length
69
70 // For single field, use full width
71 if (numFields === 1) {
72 return '100%'
73 }
74
75 // Calculate width accounting for gaps
76 // Formula: calc(width% - (total_gaps / num_fields))
77 // Gap is 12px (gap-12 class), and there are (numFields - 1) gaps total
78 const gapAdjustment = `${(12 * (numFields - 1)) / numFields}px`
79 return `calc(${width}% - ${gapAdjustment})`
80 }
81
82 /**
83 * Check if row can accept more fields (max 5 per row)
84 */
85 const canAddToRow = (fieldsInRow: Field[]): boolean => {
86 return fieldsInRow.length < 5
87 }
88
89 /**
90 * Get next available column index in row
91 */
92 const getNextColumnIndex = (fieldsInRow: Field[]): number => {
93 if (fieldsInRow.length === 0) return 0
94
95 // Find the maximum columnIndex and add 1
96 const maxColumn = Math.max(...fieldsInRow.map((f) => f.columnIndex ?? 0))
97 return maxColumn + 1
98 }
99
100 /**
101 * Distribute widths intelligently when adding a new field to a row
102 * - New field gets equal share (1/n where n is total fields after adding)
103 * - Existing fields are proportionally scaled to fit remaining space
104 *
105 * Example: Row has 2 fields at 40% and 60%, adding 3rd field:
106 * - New field gets 33.33% (1/3)
107 * - Remaining space: 100% - 33.33% = 66.67%
108 * - Existing fields scaled proportionally:
109 * - Field 1: 40/(40+60) * 66.67% = 26.67%
110 * - Field 2: 60/(40+60) * 66.67% = 40%
111 */
112 const distributeWidthsEvenly = (fields: Field[]): Field[] => {
113 if (fields.length === 0) return []
114 if (fields.length === 1) return fields.map((f) => ({ ...f, width: 100 }))
115
116 // For rows with gaps (12px each), we need to account for gap space
117 // Each field should take up width that, when combined with gaps, totals 100%
118 // Formula: width% = 100 / fieldCount
119 // The calc() in CSS will handle subtracting the gap portion
120 const width = Math.round((100 / fields.length) * 10) / 10
121
122 return fields.map((field) => ({
123 ...field,
124 width: width,
125 }))
126 }
127
128 /**
129 * Distribute widths proportionally when adding a new field to existing resized fields
130 * Maintains the relative proportions of existing fields
131 */
132 const distributeWidthsProportionally = (
133 existingFields: Field[],
134 newFieldCount: number = 1,
135 ): Field[] => {
136 if (existingFields.length === 0) return []
137
138 const totalFieldsAfter = existingFields.length + newFieldCount
139 const newFieldWidth = getAutoWidth(totalFieldsAfter)
140 const remainingSpace = 100 - newFieldWidth * newFieldCount
141
142 // Calculate total width of existing fields
143 const existingTotalWidth = existingFields.reduce((sum, f) => sum + (f.width ?? 100), 0)
144
145 // Scale existing fields proportionally to fit remaining space
146 return existingFields.map((field) => {
147 const currentWidth = field.width ?? 100
148 const proportion =
149 existingTotalWidth > 0 ? currentWidth / existingTotalWidth : 1 / existingFields.length
150 const newWidth = remainingSpace * proportion
151
152 return {
153 ...field,
154 width: Math.max(10, Math.min(100, newWidth)), // Clamp between 10% and 100%
155 }
156 })
157 }
158
159 /**
160 * Validate and normalize widths in a row
161 * Ensures total doesn't exceed 100% and all fields have valid widths
162 */
163 const normalizeRowWidths = (fields: Field[]): Field[] => {
164 const totalWidth = fields.reduce((sum, f) => sum + (f.width ?? 100), 0)
165
166 // If total exceeds 100%, distribute evenly
167 if (totalWidth > 100) {
168 return distributeWidthsEvenly(fields)
169 }
170
171 // Ensure all fields have width set
172 return fields.map((field) => ({
173 ...field,
174 width: field.width ?? getAutoWidth(fields.length),
175 }))
176 }
177
178 return {
179 getAutoWidth,
180 groupFieldsByRow,
181 getFieldWidth,
182 getFieldWidthStyle,
183 canAddToRow,
184 getNextColumnIndex,
185 distributeWidthsEvenly,
186 distributeWidthsProportionally,
187 normalizeRowWidths,
188 }
189 }
190