| 1 |
import type { Ref } from 'vue' |
| 2 |
import { watch } from 'vue' |
| 3 |
import type { FormStyleSettings } from '@/types/form-style' |
| 4 |
|
| 5 |
/** |
| 6 |
* Represents a CSS dimension value split into numeric value and unit |
| 7 |
*/ |
| 8 |
export interface DimensionValue { |
| 9 |
value: string |
| 10 |
unit: string |
| 11 |
} |
| 12 |
|
| 13 |
/** |
| 14 |
* Represents corner radius settings with linked/unlinked state |
| 15 |
*/ |
| 16 |
export interface CornerSettings { |
| 17 |
isLinked: boolean |
| 18 |
value: string |
| 19 |
topLeft: string |
| 20 |
topRight: string |
| 21 |
bottomRight: string |
| 22 |
bottomLeft: string |
| 23 |
} |
| 24 |
|
| 25 |
/** |
| 26 |
* Form builder store interface for type safety |
| 27 |
*/ |
| 28 |
export interface FormBuilderStore { |
| 29 |
styleSettings: FormStyleSettings |
| 30 |
updateStyleSettings: (path: string, value: unknown) => void |
| 31 |
} |
| 32 |
|
| 33 |
export type Alignment = 'left' | 'center' | 'right' |
| 34 |
export type BorderStyle = 'solid' | 'dashed' | 'dotted' |
| 35 |
export type FontStyle = 'normal' | 'italic' |
| 36 |
|
| 37 |
const BORDER_STYLE_VALUES: BorderStyle[] = ['solid', 'dashed', 'dotted'] |
| 38 |
const FONT_STYLE_VALUES: FontStyle[] = ['normal', 'italic'] |
| 39 |
|
| 40 |
export const BORDER_STYLE_OPTIONS: Array<{ value: BorderStyle; label: string }> = [ |
| 41 |
{ value: 'solid', label: 'Solid' }, |
| 42 |
{ value: 'dashed', label: 'Dash' }, |
| 43 |
{ value: 'dotted', label: 'Dotted' }, |
| 44 |
] |
| 45 |
|
| 46 |
export const FONT_STYLE_OPTIONS: Array<{ value: FontStyle; label: string }> = [ |
| 47 |
{ value: 'normal', label: 'Normal' }, |
| 48 |
{ value: 'italic', label: 'Italic' }, |
| 49 |
] |
| 50 |
|
| 51 |
export function normalizeAlignment(value?: string): Alignment { |
| 52 |
if (value === 'center' || value === 'right') { |
| 53 |
return value as Alignment |
| 54 |
} |
| 55 |
return 'left' |
| 56 |
} |
| 57 |
|
| 58 |
export function resolveBorderStyle( |
| 59 |
borderStyle: unknown, |
| 60 |
fallbackValue: BorderStyle = 'solid', |
| 61 |
): BorderStyle { |
| 62 |
if (typeof borderStyle === 'string' && BORDER_STYLE_VALUES.includes(borderStyle as BorderStyle)) { |
| 63 |
return borderStyle as BorderStyle |
| 64 |
} |
| 65 |
|
| 66 |
return fallbackValue |
| 67 |
} |
| 68 |
|
| 69 |
export function resolveFontStyle( |
| 70 |
fontStyle: unknown, |
| 71 |
fallbackValue: FontStyle = 'normal', |
| 72 |
): FontStyle { |
| 73 |
if (typeof fontStyle === 'string' && FONT_STYLE_VALUES.includes(fontStyle as FontStyle)) { |
| 74 |
return fontStyle as FontStyle |
| 75 |
} |
| 76 |
|
| 77 |
return fallbackValue |
| 78 |
} |
| 79 |
|
| 80 |
export function getBorderStyleIcon(borderStyle: BorderStyle): string { |
| 81 |
if (borderStyle === 'dashed') { |
| 82 |
return 'border-dash' |
| 83 |
} |
| 84 |
if (borderStyle === 'dotted') { |
| 85 |
return 'border-dotted' |
| 86 |
} |
| 87 |
|
| 88 |
return 'border-solid' |
| 89 |
} |
| 90 |
|
| 91 |
/** |
| 92 |
* Configuration for creating a style property watcher |
| 93 |
*/ |
| 94 |
export interface StyleWatcherConfig { |
| 95 |
ref: Ref<unknown> |
| 96 |
storePath: string |
| 97 |
syncPaths?: string[] |
| 98 |
formBuilderStore: FormBuilderStore |
| 99 |
isSyncingFromStore: Ref<boolean> |
| 100 |
} |
| 101 |
|
| 102 |
/** |
| 103 |
* Parse a CSS dimension string (e.g., "12px", "1.5rem") into value and unit |
| 104 |
* @param value - CSS dimension string |
| 105 |
* @returns Object with separated value and unit |
| 106 |
*/ |
| 107 |
export function parseDimension(value: string): DimensionValue { |
| 108 |
const match = value?.match(/^([0-9.]+)(.*)$/) |
| 109 |
if (match) { |
| 110 |
return { value: match[1], unit: match[2] || 'px' } |
| 111 |
} |
| 112 |
return { value: '', unit: 'px' } |
| 113 |
} |
| 114 |
|
| 115 |
/** |
| 116 |
* Parse dimension and return numeric value with unit. |
| 117 |
* @param value - CSS dimension string |
| 118 |
* @param fallbackValue - Numeric fallback when parsed value is empty |
| 119 |
*/ |
| 120 |
export function parseDimensionToNumeric( |
| 121 |
value: string, |
| 122 |
fallbackValue: number | null = null, |
| 123 |
): { value: number | null; unit: string } { |
| 124 |
const parsedDimension = parseDimension(value) |
| 125 |
|
| 126 |
return { |
| 127 |
value: parsedDimension.value !== '' ? Number(parsedDimension.value) : fallbackValue, |
| 128 |
unit: parsedDimension.unit, |
| 129 |
} |
| 130 |
} |
| 131 |
|
| 132 |
/** |
| 133 |
* Resolve a style string value from store or fallback to the provided default. |
| 134 |
* Useful inside syncWithStore to avoid repeated `(storeVal || defaultVal) as string` casts. |
| 135 |
* @param storeValue - Value from the store (may be undefined) |
| 136 |
* @param defaultValue - Default value to use when storeValue is falsy |
| 137 |
*/ |
| 138 |
export function resolveSyncStringValue(storeValue?: string, defaultValue?: string): string { |
| 139 |
return storeValue || defaultValue || '' |
| 140 |
} |
| 141 |
|
| 142 |
/** |
| 143 |
* Parse a dimension stored in the store, falling back to the default value. |
| 144 |
* Combines resolveSyncStringValue and parseDimensionToNumeric. |
| 145 |
* @param storeValue - CSS dimension string from store (may be undefined) |
| 146 |
* @param defaultValue - CSS dimension string from defaults (may be undefined) |
| 147 |
* @param fallbackValue - Numeric fallback when the resolved string cannot be parsed |
| 148 |
*/ |
| 149 |
export function parseSyncNumericDimension( |
| 150 |
storeValue?: string, |
| 151 |
defaultValue?: string, |
| 152 |
fallbackValue: number | null = null, |
| 153 |
): { value: number | null; unit: string } { |
| 154 |
return parseDimensionToNumeric(resolveSyncStringValue(storeValue, defaultValue), fallbackValue) |
| 155 |
} |
| 156 |
|
| 157 |
/** |
| 158 |
* Parse a dimension stored in the store, falling back to the default value. |
| 159 |
* Returns a raw { value, unit } string pair (not numeric). |
| 160 |
* @param storeValue - CSS dimension string from store (may be undefined) |
| 161 |
* @param defaultValue - CSS dimension string from defaults (may be undefined) |
| 162 |
*/ |
| 163 |
export function parseSyncDimensionValue( |
| 164 |
storeValue?: string, |
| 165 |
defaultValue?: string, |
| 166 |
): DimensionValue { |
| 167 |
return parseDimension(resolveSyncStringValue(storeValue, defaultValue)) |
| 168 |
} |
| 169 |
|
| 170 |
/** |
| 171 |
* Extract the numeric corner value from a parsed DimensionValue. |
| 172 |
* Replaces the repeated `Number(parsed.value ?? fallback)` pattern at ref initialization. |
| 173 |
* Returns 0 when the parsed string value is empty. |
| 174 |
*/ |
| 175 |
export function parseCornerNumericValue(parsed: DimensionValue): number { |
| 176 |
return parsed.value !== '' ? Number(parsed.value) : 0 |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Resolve corner default value from either a plain dimension string |
| 181 |
* or a corner settings object containing a `value` field. |
| 182 |
*/ |
| 183 |
export function resolveCornerDefaultValue(cornerSetting: unknown, fallbackValue = '0px'): string { |
| 184 |
if (typeof cornerSetting === 'string' && cornerSetting !== '') { |
| 185 |
return cornerSetting |
| 186 |
} |
| 187 |
|
| 188 |
if (cornerSetting && typeof cornerSetting === 'object') { |
| 189 |
const cornerObject = cornerSetting as Record<string, unknown> |
| 190 |
const cornerValue = cornerObject.value |
| 191 |
|
| 192 |
if (typeof cornerValue === 'string' && cornerValue !== '') { |
| 193 |
return cornerValue |
| 194 |
} |
| 195 |
} |
| 196 |
|
| 197 |
return fallbackValue |
| 198 |
} |
| 199 |
|
| 200 |
/** |
| 201 |
* Resolve corner linked state from corner settings object. |
| 202 |
*/ |
| 203 |
export function resolveCornerLinkedState(cornerSetting: unknown, fallbackValue = true): boolean { |
| 204 |
if (cornerSetting && typeof cornerSetting === 'object') { |
| 205 |
const cornerObject = cornerSetting as Record<string, unknown> |
| 206 |
|
| 207 |
return cornerObject.isLinked !== false |
| 208 |
} |
| 209 |
|
| 210 |
return fallbackValue |
| 211 |
} |
| 212 |
|
| 213 |
/** |
| 214 |
* Build a CSS dimension string from value and unit |
| 215 |
* @param value - Numeric or string value |
| 216 |
* @param unit - CSS unit (px, rem, em, etc.) |
| 217 |
* @returns Formatted CSS dimension string or empty string if value is empty |
| 218 |
*/ |
| 219 |
export function buildDimensionString( |
| 220 |
value: number | string | null | undefined, |
| 221 |
unit: string, |
| 222 |
): string { |
| 223 |
if (value === '' || value === null || value === undefined) { |
| 224 |
return '' |
| 225 |
} |
| 226 |
return `${value}${unit}` |
| 227 |
} |
| 228 |
|
| 229 |
/** |
| 230 |
* Dimension input used by generators and formatters. |
| 231 |
* Accept either a CSS string (e.g. "16px"), or an object with numeric value and unit. |
| 232 |
*/ |
| 233 |
export type DimensionSpec = string | { value: number | string | null; unit: string } | undefined |
| 234 |
|
| 235 |
/** |
| 236 |
* Format a dimension spec into a CSS value string. |
| 237 |
* - If input is a string, returns it (except 'auto' returns empty string to indicate no explicit padding). |
| 238 |
* - If input is an object, returns `${value}${unit}` when value is not null/empty. |
| 239 |
* - Otherwise returns empty string. |
| 240 |
*/ |
| 241 |
export function formatDimensionToCss(input: DimensionSpec): string { |
| 242 |
if (!input) return '' |
| 243 |
|
| 244 |
if (typeof input === 'string') { |
| 245 |
return input === 'auto' ? '' : input |
| 246 |
} |
| 247 |
|
| 248 |
if (input.value === null || input.value === '' || input.value === undefined) { |
| 249 |
return '' |
| 250 |
} |
| 251 |
|
| 252 |
return `${input.value}${input.unit || 'px'}` |
| 253 |
} |
| 254 |
|
| 255 |
/** |
| 256 |
* Apply corner settings from store data to component refs |
| 257 |
* @param cornerSetting - Corner data from store (can be string or object) |
| 258 |
* @param defaultValue - Fallback dimension string |
| 259 |
* @param linkedState - Ref for isLinked boolean |
| 260 |
* @param linkedValue - Ref for linked numeric value |
| 261 |
* @param linkedUnit - Ref for linked unit |
| 262 |
* @param topLeftValue - Ref for top-left numeric value |
| 263 |
* @param topLeftUnit - Ref for top-left unit |
| 264 |
* @param topRightValue - Ref for top-right numeric value |
| 265 |
* @param topRightUnit - Ref for top-right unit |
| 266 |
* @param bottomRightValue - Ref for bottom-right numeric value |
| 267 |
* @param bottomRightUnit - Ref for bottom-right unit |
| 268 |
* @param bottomLeftValue - Ref for bottom-left numeric value |
| 269 |
* @param bottomLeftUnit - Ref for bottom-left unit |
| 270 |
*/ |
| 271 |
export function applyCornerSettings( |
| 272 |
cornerSetting: unknown, |
| 273 |
defaultValue: string, |
| 274 |
linkedState: Ref<boolean>, |
| 275 |
linkedValue: Ref<number>, |
| 276 |
linkedUnit: Ref<string>, |
| 277 |
topLeftValue: Ref<number>, |
| 278 |
topLeftUnit: Ref<string>, |
| 279 |
topRightValue: Ref<number>, |
| 280 |
topRightUnit: Ref<string>, |
| 281 |
bottomRightValue: Ref<number>, |
| 282 |
bottomRightUnit: Ref<string>, |
| 283 |
bottomLeftValue: Ref<number>, |
| 284 |
bottomLeftUnit: Ref<string>, |
| 285 |
): void { |
| 286 |
// Handle string or missing corner setting - set all to linked |
| 287 |
if (!cornerSetting || typeof cornerSetting === 'string') { |
| 288 |
const normalizedValue = parseDimension((cornerSetting as string) || defaultValue) |
| 289 |
const defaultParsed = parseDimension(defaultValue) |
| 290 |
linkedState.value = true |
| 291 |
linkedValue.value = |
| 292 |
normalizedValue.value !== '' ? Number(normalizedValue.value) : Number(defaultParsed.value) |
| 293 |
linkedUnit.value = normalizedValue.unit |
| 294 |
topLeftValue.value = linkedValue.value |
| 295 |
topLeftUnit.value = linkedUnit.value |
| 296 |
topRightValue.value = linkedValue.value |
| 297 |
topRightUnit.value = linkedUnit.value |
| 298 |
bottomRightValue.value = linkedValue.value |
| 299 |
bottomRightUnit.value = linkedUnit.value |
| 300 |
bottomLeftValue.value = linkedValue.value |
| 301 |
bottomLeftUnit.value = linkedUnit.value |
| 302 |
return |
| 303 |
} |
| 304 |
|
| 305 |
// Handle object corner setting with individual corner values |
| 306 |
const cornerObject = cornerSetting as Record<string, unknown> |
| 307 |
const isLinkedValue = cornerObject.isLinked !== false |
| 308 |
const linkedValueRaw = (cornerObject.value as string) || defaultValue |
| 309 |
const linkedValueData = parseDimension(linkedValueRaw) |
| 310 |
const defaultParsed = parseDimension(defaultValue) |
| 311 |
const topLeftValueData = parseDimension((cornerObject.topLeft as string) || linkedValueRaw) |
| 312 |
const topRightValueData = parseDimension((cornerObject.topRight as string) || linkedValueRaw) |
| 313 |
const bottomRightValueData = parseDimension( |
| 314 |
(cornerObject.bottomRight as string) || linkedValueRaw, |
| 315 |
) |
| 316 |
const bottomLeftValueData = parseDimension((cornerObject.bottomLeft as string) || linkedValueRaw) |
| 317 |
|
| 318 |
linkedState.value = isLinkedValue |
| 319 |
linkedValue.value = |
| 320 |
linkedValueData.value !== '' ? Number(linkedValueData.value) : Number(defaultParsed.value) |
| 321 |
linkedUnit.value = linkedValueData.unit |
| 322 |
|
| 323 |
const linkedFallback = linkedValue.value |
| 324 |
topLeftValue.value = |
| 325 |
topLeftValueData.value !== '' ? Number(topLeftValueData.value) : linkedFallback |
| 326 |
topLeftUnit.value = topLeftValueData.unit |
| 327 |
topRightValue.value = |
| 328 |
topRightValueData.value !== '' ? Number(topRightValueData.value) : linkedFallback |
| 329 |
topRightUnit.value = topRightValueData.unit |
| 330 |
bottomRightValue.value = |
| 331 |
bottomRightValueData.value !== '' ? Number(bottomRightValueData.value) : linkedFallback |
| 332 |
bottomRightUnit.value = bottomRightValueData.unit |
| 333 |
bottomLeftValue.value = |
| 334 |
bottomLeftValueData.value !== '' ? Number(bottomLeftValueData.value) : linkedFallback |
| 335 |
bottomLeftUnit.value = bottomLeftValueData.unit |
| 336 |
} |
| 337 |
|
| 338 |
/** |
| 339 |
* Build corner settings object from component refs for storing |
| 340 |
* @param isLinked - Whether corners are linked |
| 341 |
* @param linkedValue - Linked corner value |
| 342 |
* @param linkedUnit - Linked corner unit |
| 343 |
* @param topLeftValue - Top-left corner value |
| 344 |
* @param topLeftUnit - Top-left corner unit |
| 345 |
* @param topRightValue - Top-right corner value |
| 346 |
* @param topRightUnit - Top-right corner unit |
| 347 |
* @param bottomRightValue - Bottom-right corner value |
| 348 |
* @param bottomRightUnit - Bottom-right corner unit |
| 349 |
* @param bottomLeftValue - Bottom-left corner value |
| 350 |
* @param bottomLeftUnit - Bottom-left corner unit |
| 351 |
* @returns CornerSettings object ready for store |
| 352 |
*/ |
| 353 |
export function buildCornerSettings( |
| 354 |
isLinked: boolean, |
| 355 |
linkedValue: number, |
| 356 |
linkedUnit: string, |
| 357 |
topLeftValue: number, |
| 358 |
topLeftUnit: string, |
| 359 |
topRightValue: number, |
| 360 |
topRightUnit: string, |
| 361 |
bottomRightValue: number, |
| 362 |
bottomRightUnit: string, |
| 363 |
bottomLeftValue: number, |
| 364 |
bottomLeftUnit: string, |
| 365 |
): CornerSettings { |
| 366 |
return { |
| 367 |
isLinked, |
| 368 |
value: buildDimensionString(linkedValue, linkedUnit), |
| 369 |
topLeft: buildDimensionString(topLeftValue, topLeftUnit), |
| 370 |
topRight: buildDimensionString(topRightValue, topRightUnit), |
| 371 |
bottomRight: buildDimensionString(bottomRightValue, bottomRightUnit), |
| 372 |
bottomLeft: buildDimensionString(bottomLeftValue, bottomLeftUnit), |
| 373 |
} |
| 374 |
} |
| 375 |
|
| 376 |
/** |
| 377 |
* Toggle corner linked state and synchronize values |
| 378 |
* @param isLinked - Current linked state ref |
| 379 |
* @param linkedValue - Linked value ref |
| 380 |
* @param linkedUnit - Linked unit ref |
| 381 |
* @param topLeftValue - Top-left value ref |
| 382 |
* @param topLeftUnit - Top-left unit ref |
| 383 |
* @param topRightValue - Top-right value ref |
| 384 |
* @param topRightUnit - Top-right unit ref |
| 385 |
* @param bottomRightValue - Bottom-right value ref |
| 386 |
* @param bottomRightUnit - Bottom-right unit ref |
| 387 |
* @param bottomLeftValue - Bottom-left value ref |
| 388 |
* @param bottomLeftUnit - Bottom-left unit ref |
| 389 |
*/ |
| 390 |
export function toggleCornerLinked( |
| 391 |
isLinked: Ref<boolean>, |
| 392 |
linkedValue: Ref<number>, |
| 393 |
linkedUnit: Ref<string>, |
| 394 |
topLeftValue: Ref<number>, |
| 395 |
topLeftUnit: Ref<string>, |
| 396 |
topRightValue: Ref<number>, |
| 397 |
topRightUnit: Ref<string>, |
| 398 |
bottomRightValue: Ref<number>, |
| 399 |
bottomRightUnit: Ref<string>, |
| 400 |
bottomLeftValue: Ref<number>, |
| 401 |
bottomLeftUnit: Ref<string>, |
| 402 |
): void { |
| 403 |
if (isLinked.value) { |
| 404 |
// Unlinking: copy linked values to individual corners |
| 405 |
isLinked.value = false |
| 406 |
topLeftValue.value = linkedValue.value |
| 407 |
topRightValue.value = linkedValue.value |
| 408 |
bottomRightValue.value = linkedValue.value |
| 409 |
bottomLeftValue.value = linkedValue.value |
| 410 |
topLeftUnit.value = linkedUnit.value |
| 411 |
topRightUnit.value = linkedUnit.value |
| 412 |
bottomRightUnit.value = linkedUnit.value |
| 413 |
bottomLeftUnit.value = linkedUnit.value |
| 414 |
} else { |
| 415 |
// Linking: copy top-left to linked value |
| 416 |
isLinked.value = true |
| 417 |
linkedValue.value = topLeftValue.value ?? linkedValue.value |
| 418 |
linkedUnit.value = topLeftUnit.value ?? linkedUnit.value |
| 419 |
} |
| 420 |
} |
| 421 |
|
| 422 |
/** |
| 423 |
* Safely extract a style value from store with fallback chain |
| 424 |
* @param storeValue - Value from store (may be undefined) |
| 425 |
* @param defaultValue - Default theme value (may be undefined) |
| 426 |
* @param fallback - Final fallback value |
| 427 |
* @returns The first non-undefined value in the chain, or fallback |
| 428 |
*/ |
| 429 |
export function syncStyleValue<T>( |
| 430 |
storeValue: T | undefined, |
| 431 |
defaultValue: T | undefined, |
| 432 |
fallback: T, |
| 433 |
): T { |
| 434 |
return storeValue ?? defaultValue ?? fallback |
| 435 |
} |
| 436 |
|
| 437 |
/** |
| 438 |
* Get nested property value from object using dot notation path |
| 439 |
* @param obj - Source object |
| 440 |
* @param path - Dot-separated path (e.g., 'advancedMode.general.formBackground') |
| 441 |
* @returns Value at path or undefined |
| 442 |
*/ |
| 443 |
export function getNestedValue( |
| 444 |
obj: FormStyleSettings | Record<string, unknown>, |
| 445 |
path: string, |
| 446 |
): unknown { |
| 447 |
return path.split('.').reduce((current: unknown, key: string) => { |
| 448 |
return current && typeof current === 'object' && key in current |
| 449 |
? (current as Record<string, unknown>)[key] |
| 450 |
: undefined |
| 451 |
}, obj) |
| 452 |
} |
| 453 |
|
| 454 |
/** |
| 455 |
* Update multiple store paths with the same value |
| 456 |
* Useful for syncing Quick mode and Advanced mode simultaneously |
| 457 |
* @param formBuilderStore - The form builder store instance |
| 458 |
* @param paths - Array of dot-notation paths to update |
| 459 |
* @param value - Value to set for all paths |
| 460 |
*/ |
| 461 |
export function updateStylePaths( |
| 462 |
formBuilderStore: FormBuilderStore, |
| 463 |
paths: string[], |
| 464 |
value: unknown, |
| 465 |
): void { |
| 466 |
paths.forEach((path) => { |
| 467 |
formBuilderStore.updateStyleSettings(path, value) |
| 468 |
}) |
| 469 |
} |
| 470 |
|
| 471 |
/** |
| 472 |
* Create a watcher for a style property that automatically syncs with store |
| 473 |
* Handles the common pattern of checking sync state, comparing values, and updating store |
| 474 |
* @param config - Watcher configuration object |
| 475 |
* @returns Cleanup function to stop the watcher |
| 476 |
*/ |
| 477 |
export function createStyleWatcher(config: StyleWatcherConfig): () => void { |
| 478 |
const { ref: watchRef, storePath, syncPaths, formBuilderStore, isSyncingFromStore } = config |
| 479 |
|
| 480 |
let timeoutId: ReturnType<typeof setTimeout> | undefined |
| 481 |
|
| 482 |
const stop = watch(watchRef, (newValue) => { |
| 483 |
if (isSyncingFromStore.value) return |
| 484 |
|
| 485 |
if (timeoutId) { |
| 486 |
clearTimeout(timeoutId) |
| 487 |
} |
| 488 |
|
| 489 |
timeoutId = setTimeout(() => { |
| 490 |
const currentValue = getNestedValue(formBuilderStore.styleSettings, storePath) |
| 491 |
|
| 492 |
if (newValue !== currentValue) { |
| 493 |
// If syncPaths provided, update multiple paths; otherwise just the storePath |
| 494 |
if (syncPaths && syncPaths.length > 0) { |
| 495 |
updateStylePaths(formBuilderStore, syncPaths, newValue) |
| 496 |
} else { |
| 497 |
formBuilderStore.updateStyleSettings(storePath, newValue) |
| 498 |
} |
| 499 |
} |
| 500 |
|
| 501 |
timeoutId = undefined |
| 502 |
}, 0) |
| 503 |
}) |
| 504 |
|
| 505 |
return () => { |
| 506 |
if (timeoutId) { |
| 507 |
clearTimeout(timeoutId) |
| 508 |
timeoutId = undefined |
| 509 |
} |
| 510 |
|
| 511 |
stop() |
| 512 |
} |
| 513 |
} |
| 514 |
|
| 515 |
/** |
| 516 |
* Create watchers for dimension value+unit pairs (e.g., borderWidth) |
| 517 |
* Handles the pattern of watching both value and unit refs together |
| 518 |
* @param valueRef - Ref containing numeric value |
| 519 |
* @param unitRef - Ref containing unit string |
| 520 |
* @param storePath - Primary store path to compare against |
| 521 |
* @param syncPaths - Array of paths to update (for dual-mode sync) |
| 522 |
* @param formBuilderStore - Form builder store instance |
| 523 |
* @param isSyncingFromStore - Flag to prevent circular updates |
| 524 |
* @returns Cleanup function to stop the watcher |
| 525 |
*/ |
| 526 |
export function createDimensionWatcher( |
| 527 |
valueRef: Ref<number>, |
| 528 |
unitRef: Ref<string>, |
| 529 |
storePath: string, |
| 530 |
syncPaths: string[], |
| 531 |
formBuilderStore: FormBuilderStore, |
| 532 |
isSyncingFromStore: Ref<boolean>, |
| 533 |
): () => void { |
| 534 |
let timeoutId: ReturnType<typeof setTimeout> | undefined |
| 535 |
|
| 536 |
const stop = watch([valueRef, unitRef], () => { |
| 537 |
if (isSyncingFromStore.value) return |
| 538 |
|
| 539 |
if (timeoutId) { |
| 540 |
clearTimeout(timeoutId) |
| 541 |
} |
| 542 |
|
| 543 |
timeoutId = setTimeout(() => { |
| 544 |
const currentValue = getNestedValue(formBuilderStore.styleSettings, storePath) |
| 545 |
const newValue = buildDimensionString(valueRef.value, unitRef.value) |
| 546 |
|
| 547 |
if (newValue !== '' && newValue !== currentValue) { |
| 548 |
updateStylePaths(formBuilderStore, syncPaths, newValue) |
| 549 |
} |
| 550 |
|
| 551 |
timeoutId = undefined |
| 552 |
}, 0) |
| 553 |
}) |
| 554 |
|
| 555 |
return () => { |
| 556 |
if (timeoutId) { |
| 557 |
clearTimeout(timeoutId) |
| 558 |
timeoutId = undefined |
| 559 |
} |
| 560 |
|
| 561 |
stop() |
| 562 |
} |
| 563 |
} |
| 564 |
|