@archerjessop/utilities 4.6.13 → 4.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.js","sources":["../../src/financial/formatters.js"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"formatters.js","sources":["../../src/financial/formatters.js"],"sourcesContent":["/**\r\n * src/financial/formatters.js\r\n * \r\n * OUTPUT/DISPLAY FORMATTERS - For read-only display of financial data\r\n * \r\n * Purpose: Format calculated financial values for compact, readable display\r\n * Use cases:\r\n * - Browser extension metrics and tooltips\r\n * - Dashboard display values\r\n * - Comparison pages\r\n * - Any read-only financial data presentation\r\n * \r\n * Characteristics:\r\n * - Uses K/M notation for compact display (e.g., \"$2.5M\", \"$125K\")\r\n * - Optimized for space-constrained UI elements\r\n * - Not for user input fields (see formatting/financial-formatting.js)\r\n * \r\n * Related files:\r\n * - formatting/financial-formatting.js: Input formatters for editable fields\r\n */\r\n\r\n/**\r\n * Format currency with K/M notation for compact display\r\n * @param {number} amount - The amount to format\r\n * @param {boolean} isMonthly - If true, shows full amount with commas (for monthly payments)\r\n * @returns {string} Formatted currency string (e.g., \"$2.5M\", \"$125K\", \"$1,234\")\r\n */\r\nexport function formatCurrency(amount, isMonthly = false) {\r\n if (isNaN(amount) || !isFinite(amount)) return \"N/A\";\r\n \r\n const absAmount = Math.abs(amount);\r\n const isNegative = amount < 0;\r\n const prefix = isNegative ? \"-$\" : \"$\";\r\n \r\n if (isMonthly) {\r\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\r\n } else {\r\n if (absAmount >= 1000000) {\r\n const millions = absAmount / 1000000;\r\n const formatted = millions.toFixed(3).replace(/\\.?0+$/, \"\");\r\n return prefix + formatted + \"M\";\r\n } else if (absAmount >= 1000) {\r\n const thousands = absAmount / 1000;\r\n const formatted = thousands.toFixed(3).replace(/\\.?0+$/, \"\");\r\n return prefix + formatted + \"K\";\r\n } else {\r\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Format price value with K/M notation for compact display\r\n * Similar to formatCurrency but with fixed decimal places\r\n * @param {number} amount - The amount to format\r\n * @returns {string} Formatted price string (e.g., \"$2.5M\", \"$125K\")\r\n */\r\nexport function formatPriceValue(amount) {\r\n if (isNaN(amount) || !isFinite(amount)) return \"N/A\";\r\n \r\n const absAmount = Math.abs(amount);\r\n const isNegative = amount < 0;\r\n const prefix = isNegative ? \"-$\" : \"$\";\r\n \r\n if (absAmount >= 1000000) {\r\n return prefix + (absAmount / 1000000).toFixed(1) + \"M\";\r\n } else if (absAmount >= 1000) {\r\n return prefix + (absAmount / 1000).toFixed(0) + \"K\";\r\n } else {\r\n return prefix + absAmount.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\r\n }\r\n}\r\n\r\n/**\r\n * Format percentage for display\r\n * @param {number} percentage - The percentage value to format (e.g., 7.5 for 7.5%)\r\n * @returns {string} Formatted percentage string (e.g., \"7.5%\")\r\n */\r\nexport function formatPercentage(percentage) {\r\n if (isNaN(percentage) || !isFinite(percentage)) return \"N/A\";\r\n return percentage.toFixed(1) + \"%\";\r\n}"],"names":["formatCurrency","amount","isMonthly","isNaN","isFinite","absAmount","Math","abs","prefix","toLocaleString","maximumFractionDigits","toFixed","replace","formatPriceValue","formatPercentage","percentage"],"mappings":"AA2BO,SAASA,eAAeC,EAAQC,GAAY,GACjD,GAAIC,MAAMF,KAAYG,SAASH,GAAS,MAAO,MAE/C,MAAMI,EAAYC,KAAKC,IAAIN,GAErBO,EADaP,EAAS,EACA,KAAO,IAEnC,GAAIC,EACF,OAAOM,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,IAE3E,GAAIL,GAAa,IAAS,CAGxB,OAAOG,GAFUH,EAAY,KACFM,QAAQ,GAAGC,QAAQ,SAAU,IAC5B,GAC9B,CAAO,GAAIP,GAAa,IAAM,CAG5B,OAAOG,GAFWH,EAAY,KACFM,QAAQ,GAAGC,QAAQ,SAAU,IAC7B,GAC9B,CACE,OAAOJ,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,GAGjF,CAQO,SAASG,iBAAiBZ,GAC/B,GAAIE,MAAMF,KAAYG,SAASH,GAAS,MAAO,MAE/C,MAAMI,EAAYC,KAAKC,IAAIN,GAErBO,EADaP,EAAS,EACA,KAAO,IAEnC,OAAII,GAAa,IACRG,GAAUH,EAAY,KAASM,QAAQ,GAAK,IAC1CN,GAAa,IACfG,GAAUH,EAAY,KAAMM,QAAQ,GAAK,IAEzCH,EAASH,EAAUI,eAAe,QAAS,CAAEC,sBAAuB,GAE/E,CAOO,SAASI,iBAAiBC,GAC/B,OAAIZ,MAAMY,KAAgBX,SAASW,GAAoB,MAChDA,EAAWJ,QAAQ,GAAK,GACjC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"financial-formatting.js","sources":["../../src/formatting/financial-formatting.js"],"sourcesContent":["// Enhanced formatting for input fields\r\nexport const formatInputDisplay = (value, type) => {\r\n const num = parseFloat(value) || 0;\r\n const hasDecimals = num % 1 !== 0;\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: \"USD\",\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n case \"percent\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals && Math.abs(num) >= 0.1 ? 1 : hasDecimals ? 3 : 0,\r\n maximumFractionDigits: Math.abs(num) < 1 ? 3 : 2\r\n }).format(num) + \"%\";\r\n \r\n case \"years\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 1 : 0,\r\n maximumFractionDigits: 1\r\n }).format(num) + \" yrs.\";\r\n \r\n case \"months\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 0\r\n }).format(num) + \" mos.\";\r\n \r\n case \"number\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n default:\r\n return value;\r\n }\r\n};\r\n\r\nexport const parseNumericInput = (value) => {\r\n // Handle null, undefined, or non-string inputs\r\n if (value === null || value === undefined) {\r\n return 0;\r\n }\r\n \r\n // Remove all non-numeric characters except decimal point and negative sign\r\n const cleaned = value.toString().replace(/[^0-9.-]/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};\r\n\r\n// Add these functions to the existing file:\r\n\r\nexport const filterNumericInput = (value, allowNegative = true) => {\r\n if (!value) return \"\";\r\n \r\n let filtered = value.toString();\r\n \r\n // Remove symbols and text, keep only numbers, decimal, and negative\r\n // This regex keeps numeric chars, decimal point, and negative sign\r\n filtered = filtered.replace(/[^0-9.-]/g, \"\");\r\n \r\n // Handle negative sign - only allow at the beginning\r\n if (allowNegative) {\r\n const negativeCount = (filtered.match(/-/g) || []).length;\r\n if (negativeCount > 1) {\r\n // Remove extra negative signs, keep only the first one if input started with -\r\n const startsWithNegative = value.toString().trim().startsWith(\"-\");\r\n filtered = filtered.replace(/-/g, \"\");\r\n if (startsWithNegative) {\r\n filtered = \"-\" + filtered;\r\n }\r\n } else if (filtered.includes(\"-\") && !filtered.startsWith(\"-\")) {\r\n // Move negative to the front if it's not already there\r\n filtered = \"-\" + filtered.replace(/-/g, \"\");\r\n }\r\n } else {\r\n filtered = filtered.replace(/-/g, \"\");\r\n }\r\n \r\n // Handle decimal - only allow one decimal point\r\n const decimalCount = (filtered.match(/\\./g) || []).length;\r\n if (decimalCount > 1) {\r\n const parts = filtered.split(\".\");\r\n filtered = parts[0] + \".\" + parts.slice(1).join(\"\");\r\n }\r\n \r\n return filtered;\r\n};\r\n\r\nexport const formatLiveNumber = (value, type = \"default\", preserveTyping = false) => {\r\n if (!value && value !== 0) return \"\";\r\n \r\n const originalStr = value.toString();\r\n const num = parseFloat(originalStr);\r\n if (isNaN(num)) return \"\";\r\n \r\n const isNegative = num < 0;\r\n const absStr = isNegative ? originalStr.substring(1) : originalStr;\r\n const hasDecimal = absStr.includes(\".\");\r\n \r\n let result;\r\n \r\n if (!hasDecimal) {\r\n // No decimal point - format as integer\r\n const intStr = Math.floor(Math.abs(num)).toString();\r\n result = intStr.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else {\r\n // Has decimal point\r\n if (preserveTyping) {\r\n // LIVE TYPING: Preserve exactly what user typed, just add commas\r\n const [intPart, decPart] = absStr.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n } else {\r\n // FINAL FORMATTING: Apply type-specific decimal rules\r\n if (type === \"months\") {\r\n const rounded = Math.round(Math.abs(num));\r\n result = rounded.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else if (type === \"percent\" || type === \"years\") {\r\n let formatted = Math.abs(num).toFixed(2);\r\n formatted = formatted.replace(/\\.?0+$/, '');\r\n const [intPart, decPart] = formatted.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = decPart ? commaInt + \".\" + decPart : commaInt;\r\n } else {\r\n // Currency: always 2 decimal places\r\n const twoDecimal = Math.abs(num).toFixed(2);\r\n const [intPart, decPart] = twoDecimal.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n }\r\n }\r\n }\r\n \r\n return isNegative ? \"-\" + result : result;\r\n};\r\n\r\nexport const formatLiveInput = (value, type, preserveTyping = false) => {\r\n const filtered = filterNumericInput(value, true);\r\n let formatted = formatLiveNumber(filtered, type, preserveTyping);\r\n \r\n if (formatted === \"\" || formatted === null || formatted === undefined) formatted = \"0\";\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return \"$ \" + formatted;\r\n case \"percent\":\r\n return formatted + \" %\";\r\n case \"years\":\r\n return formatted + \" yrs.\";\r\n case \"months\":\r\n return formatted + \" mos.\";\r\n case \"number\":\r\n return formatted;\r\n default:\r\n return formatted;\r\n }\r\n};\r\n\r\nexport const calculateCursorPosition = (oldValue, newValue, oldCursor, type) => {\r\n if (oldCursor <= 0) return 0;\r\n \r\n // Handle end-of-input positioning\r\n if (oldCursor >= oldValue.length) {\r\n if (type === \"currency\") {\r\n return newValue.length;\r\n } else {\r\n const match = newValue.match(/^[0-9,.\\-\\s]+/);\r\n return match ? match[0].length : 0;\r\n }\r\n }\r\n \r\n // Get prefix length for different input types\r\n const getPrefixLength = (value, inputType) => {\r\n if (inputType === \"currency\" && value.startsWith(\"$ \")) return 2;\r\n return 0;\r\n };\r\n \r\n // Get the end of numeric content (before suffix)\r\n const getContentEnd = (value, inputType, prefixLength) => {\r\n const content = value.substring(prefixLength);\r\n \r\n if (inputType === \"percent\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"years\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"months\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n }\r\n \r\n return content.length;\r\n };\r\n \r\n const oldPrefixLength = getPrefixLength(oldValue, type);\r\n const newPrefixLength = getPrefixLength(newValue, type);\r\n \r\n const oldContentEnd = getContentEnd(oldValue, type, oldPrefixLength);\r\n const newContentEnd = getContentEnd(newValue, type, newPrefixLength);\r\n \r\n // Convert cursor position to be relative to numeric content\r\n const cursorInOldContent = Math.max(0, oldCursor - oldPrefixLength);\r\n const maxOldContentCursor = Math.min(cursorInOldContent, oldContentEnd);\r\n \r\n // Count logical position: how many significant characters before cursor\r\n const oldContent = oldValue.substring(oldPrefixLength, oldPrefixLength + oldContentEnd);\r\n let logicalPosition = 0;\r\n \r\n for (let i = 0; i < maxOldContentCursor; i++) {\r\n const char = oldContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalPosition++;\r\n }\r\n }\r\n \r\n // Find physical position for this logical position in new content\r\n const newContent = newValue.substring(newPrefixLength, newPrefixLength + newContentEnd);\r\n let physicalPosition = 0;\r\n let logicalCount = 0;\r\n \r\n // Scan through new content to find the target position\r\n for (let i = 0; i < newContent.length; i++) {\r\n const char = newContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalCount++;\r\n \r\n // If we've reached our target logical position\r\n if (logicalCount === logicalPosition) {\r\n // Look ahead to see if there's a comma or decimal after this character\r\n if (i + 1 < newContent.length && newContent[i + 1] === ',') {\r\n // Position cursor after the comma\r\n physicalPosition = i + 2;\r\n } else if (i + 1 < newContent.length && newContent[i + 1] === '.') {\r\n // Position cursor after the decimal point\r\n physicalPosition = i + 2;\r\n } else {\r\n // Position cursor after this character\r\n physicalPosition = i + 1;\r\n }\r\n break;\r\n }\r\n\r\n }\r\n }\r\n \r\n // Handle case where we need position 0 (before any characters)\r\n if (logicalPosition === 0) {\r\n physicalPosition = 0;\r\n }\r\n \r\n // Convert back to full string position\r\n const finalPosition = newPrefixLength + physicalPosition;\r\n \r\n // Ensure we don't exceed the content boundary\r\n const maxAllowedPosition = newPrefixLength + newContentEnd;\r\n return Math.min(finalPosition, maxAllowedPosition);\r\n};\r\n\r\n// Helper function to find position after N significant characters\r\nconst findPositionAfterNSignificantChars = (content, targetCount) => {\r\n let significantChars = 0;\r\n \r\n for (let i = 0; i < content.length; i++) {\r\n if (significantChars === targetCount) {\r\n return i;\r\n }\r\n \r\n if (/[0-9.-]/.test(content[i])) {\r\n significantChars++;\r\n }\r\n }\r\n \r\n return content.length;\r\n};\r\n\r\nexport const extractNumericValue = (formattedValue, type) => {\r\n if (!formattedValue) return 0;\r\n \r\n let cleaned = formattedValue.toString();\r\n \r\n switch (type) {\r\n case \"currency\":\r\n cleaned = cleaned.replace(/^\\$\\s*/, \"\").trim();\r\n break;\r\n case \"percent\":\r\n cleaned = cleaned.replace(/\\s*%$/, \"\").trim();\r\n break;\r\n case \"years\":\r\n cleaned = cleaned.replace(/\\s*yrs\\.$/, \"\").trim();\r\n break;\r\n case \"months\":\r\n cleaned = cleaned.replace(/\\s*mos\\.$/, \"\").trim();\r\n break;\r\n }\r\n \r\n cleaned = cleaned.replace(/,/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};"],"names":["formatInputDisplay","value","type","num","parseFloat","hasDecimals","Intl","NumberFormat","style","currency","minimumFractionDigits","maximumFractionDigits","format","Math","abs","parseNumericInput","cleaned","toString","replace","filterNumericInput","allowNegative","filtered","match","length","startsWithNegative","trim","startsWith","includes","parts","split","slice","join","formatLiveNumber","preserveTyping","originalStr","isNaN","isNegative","absStr","substring","result","intPart","decPart","round","formatted","toFixed","commaInt","twoDecimal","floor","formatLiveInput","calculateCursorPosition","oldValue","newValue","oldCursor","getPrefixLength","inputType","getContentEnd","prefixLength","content","oldPrefixLength","newPrefixLength","oldContentEnd","newContentEnd","cursorInOldContent","max","maxOldContentCursor","min","oldContent","logicalPosition","i","char","test","newContent","physicalPosition","logicalCount","finalPosition","maxAllowedPosition","extractNumericValue","formattedValue"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"financial-formatting.js","sources":["../../src/formatting/financial-formatting.js"],"sourcesContent":["/**\r\n * src/formatting/financial-formatting.js\r\n * \r\n * INPUT FORMATTERS - For live user input in editable fields\r\n * \r\n * Purpose: Handle real-time formatting and validation of user input in form fields\r\n * Use cases:\r\n * - FormattedInput React components\r\n * - Property dashboard input fields\r\n * - Any editable financial input that requires live formatting\r\n * \r\n * Characteristics:\r\n * - Formats with full numbers and thousand separators (e.g., \"$2,500,000\", \"$125,000\")\r\n * - Handles cursor positioning during live typing\r\n * - Preserves user typing experience with appropriate decimal handling\r\n * - Input validation and filtering\r\n * - Not for display-only values (see financial/formatters.js)\r\n * \r\n * Key functions:\r\n * - formatInputDisplay: Final formatted display of input values\r\n * - formatLiveInput: Real-time formatting as user types\r\n * - formatLiveNumber: Core number formatting with comma insertion\r\n * - calculateCursorPosition: Maintains cursor position during formatting\r\n * - filterNumericInput: Input validation and sanitization\r\n * - extractNumericValue: Parse formatted strings back to numbers\r\n * \r\n * Related files:\r\n * - financial/formatters.js: Output formatters for read-only display\r\n */\r\n\r\n// Enhanced formatting for input fields\r\nexport const formatInputDisplay = (value, type) => {\r\n const num = parseFloat(value) || 0;\r\n const hasDecimals = num % 1 !== 0;\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n style: \"currency\",\r\n currency: \"USD\",\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n case \"percent\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals && Math.abs(num) >= 0.1 ? 1 : hasDecimals ? 3 : 0,\r\n maximumFractionDigits: Math.abs(num) < 1 ? 3 : 2\r\n }).format(num) + \"%\";\r\n \r\n case \"years\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 1 : 0,\r\n maximumFractionDigits: 1\r\n }).format(num) + \" yrs.\";\r\n \r\n case \"months\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 0\r\n }).format(num) + \" mos.\";\r\n \r\n case \"number\":\r\n return new Intl.NumberFormat(\"en-US\", {\r\n minimumFractionDigits: hasDecimals ? 2 : 0,\r\n maximumFractionDigits: 2\r\n }).format(num);\r\n \r\n default:\r\n return value;\r\n }\r\n};\r\n\r\nexport const parseNumericInput = (value) => {\r\n // Handle null, undefined, or non-string inputs\r\n if (value === null || value === undefined) {\r\n return 0;\r\n }\r\n \r\n // Remove all non-numeric characters except decimal point and negative sign\r\n const cleaned = value.toString().replace(/[^0-9.-]/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};\r\n\r\n// Add these functions to the existing file:\r\n\r\nexport const filterNumericInput = (value, allowNegative = true) => {\r\n if (!value) return \"\";\r\n \r\n let filtered = value.toString();\r\n \r\n // Remove symbols and text, keep only numbers, decimal, and negative\r\n // This regex keeps numeric chars, decimal point, and negative sign\r\n filtered = filtered.replace(/[^0-9.-]/g, \"\");\r\n \r\n // Handle negative sign - only allow at the beginning\r\n if (allowNegative) {\r\n const negativeCount = (filtered.match(/-/g) || []).length;\r\n if (negativeCount > 1) {\r\n // Remove extra negative signs, keep only the first one if input started with -\r\n const startsWithNegative = value.toString().trim().startsWith(\"-\");\r\n filtered = filtered.replace(/-/g, \"\");\r\n if (startsWithNegative) {\r\n filtered = \"-\" + filtered;\r\n }\r\n } else if (filtered.includes(\"-\") && !filtered.startsWith(\"-\")) {\r\n // Move negative to the front if it's not already there\r\n filtered = \"-\" + filtered.replace(/-/g, \"\");\r\n }\r\n } else {\r\n filtered = filtered.replace(/-/g, \"\");\r\n }\r\n \r\n // Handle decimal - only allow one decimal point\r\n const decimalCount = (filtered.match(/\\./g) || []).length;\r\n if (decimalCount > 1) {\r\n const parts = filtered.split(\".\");\r\n filtered = parts[0] + \".\" + parts.slice(1).join(\"\");\r\n }\r\n \r\n return filtered;\r\n};\r\n\r\nexport const formatLiveNumber = (value, type = \"default\", preserveTyping = false) => {\r\n if (!value && value !== 0) return \"\";\r\n \r\n const originalStr = value.toString();\r\n const num = parseFloat(originalStr);\r\n if (isNaN(num)) return \"\";\r\n \r\n const isNegative = num < 0;\r\n const absStr = isNegative ? originalStr.substring(1) : originalStr;\r\n const hasDecimal = absStr.includes(\".\");\r\n \r\n let result;\r\n \r\n if (!hasDecimal) {\r\n // No decimal point - format as integer\r\n const intStr = Math.floor(Math.abs(num)).toString();\r\n result = intStr.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else {\r\n // Has decimal point\r\n if (preserveTyping) {\r\n // LIVE TYPING: Preserve exactly what user typed, just add commas\r\n const [intPart, decPart] = absStr.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n } else {\r\n // FINAL FORMATTING: Apply type-specific decimal rules\r\n if (type === \"months\") {\r\n const rounded = Math.round(Math.abs(num));\r\n result = rounded.toString().replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n } else if (type === \"percent\" || type === \"years\") {\r\n let formatted = Math.abs(num).toFixed(2);\r\n formatted = formatted.replace(/\\.?0+$/, '');\r\n const [intPart, decPart] = formatted.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = decPart ? commaInt + \".\" + decPart : commaInt;\r\n } else {\r\n // Currency: always 2 decimal places\r\n const twoDecimal = Math.abs(num).toFixed(2);\r\n const [intPart, decPart] = twoDecimal.split(\".\");\r\n const commaInt = intPart.replace(/\\B(?=(\\d{3})+(?!\\d))/g, \",\");\r\n result = commaInt + \".\" + decPart;\r\n }\r\n }\r\n }\r\n \r\n return isNegative ? \"-\" + result : result;\r\n};\r\n\r\nexport const formatLiveInput = (value, type, preserveTyping = false) => {\r\n const filtered = filterNumericInput(value, true);\r\n let formatted = formatLiveNumber(filtered, type, preserveTyping);\r\n \r\n if (formatted === \"\" || formatted === null || formatted === undefined) formatted = \"0\";\r\n \r\n switch (type) {\r\n case \"currency\":\r\n return \"$ \" + formatted;\r\n case \"percent\":\r\n return formatted + \" %\";\r\n case \"years\":\r\n return formatted + \" yrs.\";\r\n case \"months\":\r\n return formatted + \" mos.\";\r\n case \"number\":\r\n return formatted;\r\n default:\r\n return formatted;\r\n }\r\n};\r\n\r\nexport const calculateCursorPosition = (oldValue, newValue, oldCursor, type) => {\r\n if (oldCursor <= 0) return 0;\r\n \r\n // Handle end-of-input positioning\r\n if (oldCursor >= oldValue.length) {\r\n if (type === \"currency\") {\r\n return newValue.length;\r\n } else {\r\n const match = newValue.match(/^[0-9,.\\-\\s]+/);\r\n return match ? match[0].length : 0;\r\n }\r\n }\r\n \r\n // Get prefix length for different input types\r\n const getPrefixLength = (value, inputType) => {\r\n if (inputType === \"currency\" && value.startsWith(\"$ \")) return 2;\r\n return 0;\r\n };\r\n \r\n // Get the end of numeric content (before suffix)\r\n const getContentEnd = (value, inputType, prefixLength) => {\r\n const content = value.substring(prefixLength);\r\n \r\n if (inputType === \"percent\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"years\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n } else if (inputType === \"months\") {\r\n const match = content.match(/^([0-9,.\\-]+)/);\r\n return match ? match[1].length : content.length;\r\n }\r\n \r\n return content.length;\r\n };\r\n \r\n const oldPrefixLength = getPrefixLength(oldValue, type);\r\n const newPrefixLength = getPrefixLength(newValue, type);\r\n \r\n const oldContentEnd = getContentEnd(oldValue, type, oldPrefixLength);\r\n const newContentEnd = getContentEnd(newValue, type, newPrefixLength);\r\n \r\n // Convert cursor position to be relative to numeric content\r\n const cursorInOldContent = Math.max(0, oldCursor - oldPrefixLength);\r\n const maxOldContentCursor = Math.min(cursorInOldContent, oldContentEnd);\r\n \r\n // Count logical position: how many significant characters before cursor\r\n const oldContent = oldValue.substring(oldPrefixLength, oldPrefixLength + oldContentEnd);\r\n let logicalPosition = 0;\r\n \r\n for (let i = 0; i < maxOldContentCursor; i++) {\r\n const char = oldContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalPosition++;\r\n }\r\n }\r\n \r\n // Find physical position for this logical position in new content\r\n const newContent = newValue.substring(newPrefixLength, newPrefixLength + newContentEnd);\r\n let physicalPosition = 0;\r\n let logicalCount = 0;\r\n \r\n // Scan through new content to find the target position\r\n for (let i = 0; i < newContent.length; i++) {\r\n const char = newContent[i];\r\n if (/[0-9.\\-]/.test(char)) {\r\n logicalCount++;\r\n \r\n // If we've reached our target logical position\r\n if (logicalCount === logicalPosition) {\r\n // Look ahead to see if there's a comma or decimal after this character\r\n if (i + 1 < newContent.length && newContent[i + 1] === ',') {\r\n // Position cursor after the comma\r\n physicalPosition = i + 2;\r\n } else if (i + 1 < newContent.length && newContent[i + 1] === '.') {\r\n // Position cursor after the decimal point\r\n physicalPosition = i + 2;\r\n } else {\r\n // Position cursor after this character\r\n physicalPosition = i + 1;\r\n }\r\n break;\r\n }\r\n\r\n }\r\n }\r\n \r\n // Handle case where we need position 0 (before any characters)\r\n if (logicalPosition === 0) {\r\n physicalPosition = 0;\r\n }\r\n \r\n // Convert back to full string position\r\n const finalPosition = newPrefixLength + physicalPosition;\r\n \r\n // Ensure we don't exceed the content boundary\r\n const maxAllowedPosition = newPrefixLength + newContentEnd;\r\n return Math.min(finalPosition, maxAllowedPosition);\r\n};\r\n\r\n// Helper function to find position after N significant characters\r\nconst findPositionAfterNSignificantChars = (content, targetCount) => {\r\n let significantChars = 0;\r\n \r\n for (let i = 0; i < content.length; i++) {\r\n if (significantChars === targetCount) {\r\n return i;\r\n }\r\n \r\n if (/[0-9.-]/.test(content[i])) {\r\n significantChars++;\r\n }\r\n }\r\n \r\n return content.length;\r\n};\r\n\r\nexport const extractNumericValue = (formattedValue, type) => {\r\n if (!formattedValue) return 0;\r\n \r\n let cleaned = formattedValue.toString();\r\n \r\n switch (type) {\r\n case \"currency\":\r\n cleaned = cleaned.replace(/^\\$\\s*/, \"\").trim();\r\n break;\r\n case \"percent\":\r\n cleaned = cleaned.replace(/\\s*%$/, \"\").trim();\r\n break;\r\n case \"years\":\r\n cleaned = cleaned.replace(/\\s*yrs\\.$/, \"\").trim();\r\n break;\r\n case \"months\":\r\n cleaned = cleaned.replace(/\\s*mos\\.$/, \"\").trim();\r\n break;\r\n }\r\n \r\n cleaned = cleaned.replace(/,/g, \"\");\r\n return parseFloat(cleaned) || 0;\r\n};"],"names":["formatInputDisplay","value","type","num","parseFloat","hasDecimals","Intl","NumberFormat","style","currency","minimumFractionDigits","maximumFractionDigits","format","Math","abs","parseNumericInput","cleaned","toString","replace","filterNumericInput","allowNegative","filtered","match","length","startsWithNegative","trim","startsWith","includes","parts","split","slice","join","formatLiveNumber","preserveTyping","originalStr","isNaN","isNegative","absStr","substring","result","intPart","decPart","round","formatted","toFixed","commaInt","twoDecimal","floor","formatLiveInput","calculateCursorPosition","oldValue","newValue","oldCursor","getPrefixLength","inputType","getContentEnd","prefixLength","content","oldPrefixLength","newPrefixLength","oldContentEnd","newContentEnd","cursorInOldContent","max","maxOldContentCursor","min","oldContent","logicalPosition","i","char","test","newContent","physicalPosition","logicalCount","finalPosition","maxAllowedPosition","extractNumericValue","formattedValue"],"mappings":"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"}
|
package/dist/styles/base.css
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
--color-danger: #dc3545;
|
|
7
7
|
--color-warning: #ffc107;
|
|
8
8
|
--color-info: #17a2b8;
|
|
9
|
+
--color-warning-background: #ffc10725;
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
|
|
@@ -161,22 +162,4 @@
|
|
|
161
162
|
/* Grid */
|
|
162
163
|
--grid-gap: var(--spacing-xl);
|
|
163
164
|
--grid-gap-small: var(--spacing-lg);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/* Test class */
|
|
167
|
-
.test-styles {
|
|
168
|
-
background: var(--color-primary);
|
|
169
|
-
color: white;
|
|
170
|
-
padding: var(--spacing-xl) var(--spacing-xxxl);
|
|
171
|
-
border-radius: var(--border-radius);
|
|
172
|
-
font-size: var(--font-size-md);
|
|
173
|
-
font-weight: var(--font-weight-semibold);
|
|
174
|
-
border: 1px solid var(--border-color);
|
|
175
|
-
cursor: pointer;
|
|
176
|
-
display: inline-block;
|
|
177
|
-
text-decoration: none;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.test-styles:hover {
|
|
181
|
-
background: var(--color-primary-hover);
|
|
182
165
|
}
|
|
@@ -97,6 +97,10 @@
|
|
|
97
97
|
color: var(--color-dark-red) !important;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
#property-card.cash-flow-negative {
|
|
101
|
+
background-color: var(--color-warning-background) !important;
|
|
102
|
+
}
|
|
103
|
+
|
|
100
104
|
.cash-flow-negative .red-if-negative:hover {
|
|
101
105
|
color: var(--color-dark-red-hover) !important;
|
|
102
106
|
}
|