|
| 1 | +/** |
| 2 | + * Type inference utilities for form values |
| 3 | + * |
| 4 | + * When users type values in text inputs, they enter strings. This utility |
| 5 | + * infers the intended type and converts appropriately: |
| 6 | + * - "123" -> 123 (number) |
| 7 | + * - "true" / "false" -> boolean |
| 8 | + * - "null" -> null |
| 9 | + * - Template expressions like "{{path}}" stay as strings (resolved at runtime) |
| 10 | + * - Everything else stays as string |
| 11 | + */ |
| 12 | + |
| 13 | +/** |
| 14 | + * Infer and convert a string value to its intended type |
| 15 | + * |
| 16 | + * @param value The string value from a text input |
| 17 | + * @returns The value converted to its inferred type |
| 18 | + * |
| 19 | + * @example |
| 20 | + * inferType("123") // => 123 (number) |
| 21 | + * inferType("12.5") // => 12.5 (number) |
| 22 | + * inferType("true") // => true (boolean) |
| 23 | + * inferType("false") // => false (boolean) |
| 24 | + * inferType("null") // => null |
| 25 | + * inferType("hello") // => "hello" (string) |
| 26 | + * inferType("{{x}}") // => "{{x}}" (string - template, resolved at runtime) |
| 27 | + * inferType("") // => "" (empty string) |
| 28 | + */ |
| 29 | +export function inferType(value: string): unknown { |
| 30 | + // Handle empty string |
| 31 | + if (value === '') { |
| 32 | + return '' |
| 33 | + } |
| 34 | + |
| 35 | + // Don't convert template expressions - they're resolved at runtime |
| 36 | + if (value.includes('{{') && value.includes('}}')) { |
| 37 | + return value |
| 38 | + } |
| 39 | + |
| 40 | + // Handle boolean literals (case-insensitive) |
| 41 | + const lower = value.toLowerCase() |
| 42 | + if (lower === 'true') { |
| 43 | + return true |
| 44 | + } |
| 45 | + if (lower === 'false') { |
| 46 | + return false |
| 47 | + } |
| 48 | + |
| 49 | + // Handle null literal |
| 50 | + if (lower === 'null') { |
| 51 | + return null |
| 52 | + } |
| 53 | + |
| 54 | + // Try to parse as number |
| 55 | + const trimmed = value.trim() |
| 56 | + if (trimmed !== '') { |
| 57 | + const num = Number(trimmed) |
| 58 | + // Only convert if it's a valid number AND the string representation matches |
| 59 | + // This prevents "123abc" from being parsed as 123 |
| 60 | + if (!isNaN(num) && (trimmed === String(num) || trimmed === num.toString())) { |
| 61 | + return num |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + // Default: return as string |
| 66 | + return value |
| 67 | +} |
| 68 | + |
| 69 | +/** |
| 70 | + * Convert a typed value back to string for display in a text input |
| 71 | + * |
| 72 | + * @param value The typed value |
| 73 | + * @returns String representation for display |
| 74 | + */ |
| 75 | +export function toDisplayString(value: unknown): string { |
| 76 | + if (value === null) { |
| 77 | + return 'null' |
| 78 | + } |
| 79 | + if (value === undefined) { |
| 80 | + return '' |
| 81 | + } |
| 82 | + return String(value) |
| 83 | +} |
0 commit comments