@archerjessop/utilities 7.3.0 → 7.5.0
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.
- package/dist/browser/data/extractors.js.map +1 -0
- package/dist/browser/financial/tooltip-calculations.js +2 -0
- package/dist/browser/financial/tooltip-calculations.js.map +1 -0
- package/dist/browser/financial/tooltip-content-generators.js +2 -0
- package/dist/browser/financial/tooltip-content-generators.js.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/ui/click-handlers.js +2 -0
- package/dist/browser/ui/click-handlers.js.map +1 -0
- package/dist/browser/ui/tooltip-config.js +2 -0
- package/dist/browser/ui/tooltip-config.js.map +1 -0
- package/dist/browser/ui/tooltip-manager.js +2 -0
- package/dist/browser/ui/tooltip-manager.js.map +1 -0
- package/dist/browser/widget/createNavigationGuard.js +2 -0
- package/dist/browser/widget/createNavigationGuard.js.map +1 -0
- package/dist/browser/widget/createPanel.js +2 -0
- package/dist/browser/widget/createPanel.js.map +1 -0
- package/dist/export/export-logic.js +2 -0
- package/dist/export/export-logic.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +8 -2
- package/dist/data/extractors.js.map +0 -1
- /package/dist/{data → browser/data}/extractors.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractors.js","sources":["../../../src/browser/data/extractors.js"],"sourcesContent":["export function extractPhoneNumber() {\r\n const phoneElement = document.querySelector(\".phone-number\") ||\r\n document.querySelector(\"a[href^='tel:']\") ||\r\n document.querySelector(\".number\") ||\r\n document.querySelector(\"[class*='phone']\");\r\n\r\n if (phoneElement) {\r\n if (phoneElement.textContent && phoneElement.textContent.trim() !== \"Call\") {\r\n return phoneElement.textContent.trim();\r\n } else if (phoneElement.href) {\r\n // Extract from tel: link\r\n const telMatch = phoneElement.href.match(/tel:(.+)/);\r\n if (telMatch) {\r\n return telMatch[1];\r\n }\r\n }\r\n }\r\n\r\n // Fallback to text search with multiple patterns\r\n const pageText = document.body ? document.body.textContent || \"\" : \"\";\r\n const phoneMatch = pageText.match(/(\\+?1?\\s*\\(?[0-9]{3}\\)?[\\s.-]*[0-9]{3}[\\s.-]*[0-9]{4})/);\r\n if (phoneMatch) {\r\n return phoneMatch[1].trim();\r\n }\r\n\r\n return \"Not found\";\r\n}\r\n\r\nexport function extractBedrooms() {\r\n try {\r\n // Look for bedroom information in various places\r\n const bodyText = document.body?.textContent || \"\";\r\n\r\n // Common patterns for bedroom information\r\n const bedroomPatterns = [\r\n /(\\d+)\\s*bed/i,\r\n /(\\d+)\\s*bedroom/i,\r\n /beds?\\s*:\\s*(\\d+)/i,\r\n /bedrooms?\\s*:\\s*(\\d+)/i,\r\n /(\\d+)\\s*BR/i,\r\n /(\\d+)br/i\r\n ];\r\n\r\n for (const pattern of bedroomPatterns) {\r\n const match = bodyText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) { // Sanity check\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n\r\n // Look in property details section specifically\r\n const propertyDetails = document.querySelector(\".property-details\") ||\r\n document.querySelector(\"#PropertyDetails\") ||\r\n document.querySelector(\".details\");\r\n\r\n if (propertyDetails) {\r\n const detailsText = propertyDetails.textContent || \"\";\r\n for (const pattern of bedroomPatterns) {\r\n const match = detailsText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) {\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Default fallback\r\n return 10; // Default assumption for assisted living\r\n } catch (error) {\r\n return 10; // Default fallback\r\n }\r\n}\r\n"],"names":["extractPhoneNumber","phoneElement","document","querySelector","textContent","trim","href","telMatch","match","phoneMatch","body","extractBedrooms","bodyText","bedroomPatterns","pattern","bedrooms","parseInt","propertyDetails","detailsText","error"],"mappings":"AAAO,SAASA,qBACd,MAAMC,EAAeC,SAASC,cAAc,kBACxBD,SAASC,cAAc,oBACvBD,SAASC,cAAc,YACvBD,SAASC,cAAc,oBAE3C,GAAIF,EAAc,CAChB,GAAIA,EAAaG,aAAmD,SAApCH,EAAaG,YAAYC,OACvD,OAAOJ,EAAaG,YAAYC,OAC3B,GAAIJ,EAAaK,KAAM,CAE5B,MAAMC,EAAWN,EAAaK,KAAKE,MAAM,YACzC,GAAID,EACF,OAAOA,EAAS,EAEpB,CACF,CAGA,MACME,GADWP,SAASQ,MAAOR,SAASQ,KAAKN,aAAoB,IACvCI,MAAM,0DAClC,OAAIC,EACKA,EAAW,GAAGJ,OAGhB,WACT,CAEO,SAASM,kBACd,IAEE,MAAMC,EAAWV,SAASQ,MAAMN,aAAe,GAGzCS,EAAkB,CACtB,eACA,mBACA,qBACA,yBACA,cACA,YAGF,IAAK,MAAMC,KAAWD,EAAiB,CACrC,MAAML,EAAQI,EAASJ,MAAMM,GAC7B,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CAGA,MAAME,EAAkBf,SAASC,cAAc,sBACxBD,SAASC,cAAc,qBACvBD,SAASC,cAAc,YAE9C,GAAIc,EAAiB,CACnB,MAAMC,EAAcD,EAAgBb,aAAe,GACnD,IAAK,MAAMU,KAAWD,EAAiB,CACrC,MAAML,EAAQU,EAAYV,MAAMM,GAChC,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CACF,CAGA,OAAO,EACT,CAAE,MAAOI,GACP,OAAO,EACT,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{calculateCashFlowYield as a,calculatePMT as t}from"../../financial/calculations.js";import{formatCurrency as n,formatPercentage as e}from"../../financial/formatters.js";import{FINANCIAL_CONSTANTS as l}from"../../config/financial.js";function calculateDownPaymentTooltip(a,n,o,r,c,i="dscr_residential"){try{const r=l.INTEREST_RATE_TIERS[i||"dscr_residential"],c=a*(o/100),s=100-o,p=Math.min(s,70),u=Math.max(0,s-70),h=a*(u/100),m=12*t(a*(p/100),r.rate,r.amortization),F=h>0?12*t(h,l.SELLER_FI_INTEREST_RATE,l.SELLER_FI_AMORTIZATION):0;return`COCR at ${o}% down (${u>0?`${p}% DSCR + ${u}% Seller FI`:`${p}% DSCR only`}): ${e((n-m-F)/c*100)}`}catch(a){return`Down payment at ${o}%`}}function calculateCashFlowTooltip(t,e){try{const l=n(12*e);return`Cash Flow Yield: ${a(e,t).toFixed(1)}% (${l}/yr)`}catch(a){return`Monthly cash flow: ${n(e,!0)} (${n(12*e)}/yr)`}}function parseFinancialData(a,t){const n=a.match(/[\d,]+/),e=t.match(/[\d,.]+/);if(n&&e){const a=parseFloat(n[0].replace(/,/g,""));let l=parseFloat(e[0].replace(/,/g,""));return t.includes("K")?l*=1e3:t.includes("M")&&(l*=1e6),{price:a,noi:l}}return null}function parseCashFlowData(a,t){const n=a.match(/[\d,]+/),e=t.match(/-?[\d,]+/);if(n&&e){const a=parseFloat(n[0].replace(/,/g,""));let l=parseFloat(e[0].replace(/,/g,""));return t.includes("-")&&(l=-Math.abs(l)),{price:a,monthlyCashFlow:l}}return null}export{calculateCashFlowTooltip,calculateDownPaymentTooltip,parseCashFlowData,parseFinancialData};
|
|
2
|
+
//# sourceMappingURL=tooltip-calculations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-calculations.js","sources":["../../../src/browser/financial/tooltip-calculations.js"],"sourcesContent":["import { calculatePMT, calculateCashFlowYield } from '../../financial/calculations.js';\r\nimport { formatPercentage, formatCurrency } from '../../financial/formatters.js';\r\nimport { FINANCIAL_CONSTANTS } from '../../config/financial.js';\r\n\r\nexport function calculateDownPaymentTooltip(price, noi, downPercent, dscrPercent, sellerFiPercent, interestRateType = \"dscr_residential\") {\r\n try {\r\n const tier = FINANCIAL_CONSTANTS.INTEREST_RATE_TIERS[interestRateType || \"dscr_residential\"];\r\n const downDecimal = downPercent / 100;\r\n const cashInvested = price * downDecimal;\r\n\r\n const remainingPercent = 100 - downPercent;\r\n const dscrLoanPercent = Math.min(remainingPercent, 70);\r\n const actualSellerFiPercent = Math.max(0, remainingPercent - 70);\r\n\r\n const dscrLoanAmount = price * (dscrLoanPercent / 100);\r\n const sellerFiAmount = price * (actualSellerFiPercent / 100);\r\n\r\n const dscrPayment = calculatePMT(dscrLoanAmount, tier.rate, tier.amortization) * 12;\r\n const sellerFiPayment = sellerFiAmount > 0 ?\r\n calculatePMT(sellerFiAmount, FINANCIAL_CONSTANTS.SELLER_FI_INTEREST_RATE, FINANCIAL_CONSTANTS.SELLER_FI_AMORTIZATION) * 12 : 0;\r\n\r\n const annualCashFlow = noi - dscrPayment - sellerFiPayment;\r\n const cocr = (annualCashFlow / cashInvested) * 100;\r\n\r\n let financingType = actualSellerFiPercent > 0 ?\r\n `${dscrLoanPercent}% DSCR + ${actualSellerFiPercent}% Seller FI` :\r\n `${dscrLoanPercent}% DSCR only`;\r\n\r\n return `COCR at ${downPercent}% down (${financingType}): ${formatPercentage(cocr)}`;\r\n } catch (error) {\r\n return `Down payment at ${downPercent}%`;\r\n }\r\n}\r\n\r\nexport function calculateCashFlowTooltip(price, monthlyCashFlow) {\r\n try {\r\n const annualCashFlow = formatCurrency(monthlyCashFlow * 12);\r\n const cashFlowYield = calculateCashFlowYield(monthlyCashFlow, price);\r\n return `Cash Flow Yield: ${cashFlowYield.toFixed(1)}% (${annualCashFlow}/yr)`;\r\n } catch (error) {\r\n return `Monthly cash flow: ${formatCurrency(monthlyCashFlow, true)} (${formatCurrency(monthlyCashFlow * 12)}/yr)`;\r\n }\r\n}\r\n\r\nexport function parseFinancialData(priceText, noiText) {\r\n const priceMatch = priceText.match(/[\\d,]+/);\r\n const noiMatch = noiText.match(/[\\d,.]+/);\r\n\r\n if (priceMatch && noiMatch) {\r\n const price = parseFloat(priceMatch[0].replace(/,/g, \"\"));\r\n let noi = parseFloat(noiMatch[0].replace(/,/g, \"\"));\r\n\r\n if (noiText.includes(\"K\")) {\r\n noi *= 1000;\r\n } else if (noiText.includes(\"M\")) {\r\n noi *= 1000000;\r\n }\r\n\r\n return { price, noi };\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport function parseCashFlowData(priceText, cashFlowText) {\r\n const priceMatch = priceText.match(/[\\d,]+/);\r\n const cashFlowMatch = cashFlowText.match(/-?[\\d,]+/);\r\n\r\n if (priceMatch && cashFlowMatch) {\r\n const price = parseFloat(priceMatch[0].replace(/,/g, \"\"));\r\n let monthlyCashFlow = parseFloat(cashFlowMatch[0].replace(/,/g, \"\"));\r\n\r\n if (cashFlowText.includes(\"-\")) {\r\n monthlyCashFlow = -Math.abs(monthlyCashFlow);\r\n }\r\n\r\n return { price, monthlyCashFlow };\r\n }\r\n\r\n return null;\r\n}\r\n"],"names":["calculateDownPaymentTooltip","price","noi","downPercent","dscrPercent","sellerFiPercent","interestRateType","tier","FINANCIAL_CONSTANTS","INTEREST_RATE_TIERS","cashInvested","remainingPercent","dscrLoanPercent","Math","min","actualSellerFiPercent","max","sellerFiAmount","dscrPayment","calculatePMT","rate","amortization","sellerFiPayment","SELLER_FI_INTEREST_RATE","SELLER_FI_AMORTIZATION","formatPercentage","error","calculateCashFlowTooltip","monthlyCashFlow","annualCashFlow","formatCurrency","calculateCashFlowYield","toFixed","parseFinancialData","priceText","noiText","priceMatch","match","noiMatch","parseFloat","replace","includes","parseCashFlowData","cashFlowText","cashFlowMatch","abs"],"mappings":"gPAIO,SAASA,4BAA4BC,EAAOC,EAAKC,EAAaC,EAAaC,EAAiBC,EAAmB,oBACpH,IACE,MAAMC,EAAOC,EAAoBC,oBAAoBH,GAAoB,oBAEnEI,EAAeT,GADDE,EAAc,KAG5BQ,EAAmB,IAAMR,EACzBS,EAAkBC,KAAKC,IAAIH,EAAkB,IAC7CI,EAAwBF,KAAKG,IAAI,EAAGL,EAAmB,IAGvDM,EAAiBhB,GAASc,EAAwB,KAElDG,EAA2E,GAA7DC,EAHGlB,GAASW,EAAkB,KAGDL,EAAKa,KAAMb,EAAKc,cAC3DC,EAAkBL,EAAiB,EACiF,GAAxHE,EAAaF,EAAgBT,EAAoBe,wBAAyBf,EAAoBgB,wBAA+B,EAS/H,MAAO,WAAWrB,YAJEY,EAAwB,EAC1C,GAAGH,aAA2BG,eAC9B,GAAGH,oBAEsDa,GAPpCvB,EAAMgB,EAAcI,GACZZ,EAAgB,MAOjD,CAAE,MAAOgB,GACP,MAAO,mBAAmBvB,IAC5B,CACF,CAEO,SAASwB,yBAAyB1B,EAAO2B,GAC9C,IACE,MAAMC,EAAiBC,EAAiC,GAAlBF,GAEtC,MAAO,oBADeG,EAAuBH,EAAiB3B,GACrB+B,QAAQ,QAAQH,OAC3D,CAAE,MAAOH,GACP,MAAO,sBAAsBI,EAAeF,GAAiB,OAAUE,EAAiC,GAAlBF,QACxF,CACF,CAEO,SAASK,mBAAmBC,EAAWC,GAC5C,MAAMC,EAAaF,EAAUG,MAAM,UAC7BC,EAAWH,EAAQE,MAAM,WAE/B,GAAID,GAAcE,EAAU,CAC1B,MAAMrC,EAAQsC,WAAWH,EAAW,GAAGI,QAAQ,KAAM,KACrD,IAAItC,EAAMqC,WAAWD,EAAS,GAAGE,QAAQ,KAAM,KAQ/C,OANIL,EAAQM,SAAS,KACnBvC,GAAO,IACEiC,EAAQM,SAAS,OAC1BvC,GAAO,KAGF,CAAED,QAAOC,MAClB,CAEA,OAAO,IACT,CAEO,SAASwC,kBAAkBR,EAAWS,GAC3C,MAAMP,EAAaF,EAAUG,MAAM,UAC7BO,EAAgBD,EAAaN,MAAM,YAEzC,GAAID,GAAcQ,EAAe,CAC/B,MAAM3C,EAAQsC,WAAWH,EAAW,GAAGI,QAAQ,KAAM,KACrD,IAAIZ,EAAkBW,WAAWK,EAAc,GAAGJ,QAAQ,KAAM,KAMhE,OAJIG,EAAaF,SAAS,OACxBb,GAAmBf,KAAKgC,IAAIjB,IAGvB,CAAE3B,QAAO2B,kBAClB,CAEA,OAAO,IACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{calculateDownPaymentTooltip as e}from"./tooltip-calculations.js";function generatePriceTooltipHTML(e){return`\n <strong>Click the price to decrease by 10%</strong><br>\n Current discount: ${e}%\n <hr>\n <em>Click the label to reset</em>\n `}function generateCashFlowTooltipHTML(e,t){const n=12*t;return`\n <strong>Cash Flow Yield:</strong> ${(n/e*100).toFixed(1)}%<br>\n <strong>Annual Cash Flow:</strong> $${n.toLocaleString()}\n `}function generateCapRateTooltipHTML(e){return e?"\n <strong>Click the cap rate to increase by 1%</strong>\n <hr>\n <em>Click the label to reset</em>\n ":null}function generateDownPaymentTooltipHTML(t,n,o,r,l,a="dscr_residential"){return`\n <strong>Click the down payment to decrease by 10%</strong><br>\n ${e(t,n,o,r,l,a)}\n <hr>\n <em>Click the label to reset</em>\n `}export{generateCapRateTooltipHTML,generateCashFlowTooltipHTML,generateDownPaymentTooltipHTML,generatePriceTooltipHTML};
|
|
2
|
+
//# sourceMappingURL=tooltip-content-generators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-content-generators.js","sources":["../../../src/browser/financial/tooltip-content-generators.js"],"sourcesContent":["import { calculateDownPaymentTooltip, calculateCashFlowTooltip } from './tooltip-calculations.js';\r\n\r\nexport function generatePriceTooltipHTML(priceDiscount) {\r\n return `\r\n <strong>Click the price to decrease by 10%</strong><br>\r\n Current discount: ${priceDiscount}%\r\n <hr>\r\n <em>Click the label to reset</em>\r\n `;\r\n}\r\n\r\nexport function generateCashFlowTooltipHTML(price, monthlyCashFlow) {\r\n const annualCashFlow = monthlyCashFlow * 12;\r\n const cashFlowYield = ((annualCashFlow / price) * 100).toFixed(1);\r\n\r\n return `\r\n <strong>Cash Flow Yield:</strong> ${cashFlowYield}%<br>\r\n <strong>Annual Cash Flow:</strong> $${annualCashFlow.toLocaleString()}\r\n `;\r\n}\r\n\r\nexport function generateCapRateTooltipHTML(isUsingEstimatedCapRate) {\r\n if (!isUsingEstimatedCapRate) return null;\r\n\r\n return `\r\n <strong>Click the cap rate to increase by 1%</strong>\r\n <hr>\r\n <em>Click the label to reset</em>\r\n `;\r\n}\r\n\r\nexport function generateDownPaymentTooltipHTML(price, noi, downPercent, dscrPercent, sellerFiPercent, interestRateType = \"dscr_residential\") {\r\n // Use existing calculation function\r\n const cocrText = calculateDownPaymentTooltip(price, noi, downPercent, dscrPercent, sellerFiPercent, interestRateType);\r\n\r\n return `\r\n <strong>Click the down payment to decrease by 10%</strong><br>\r\n ${cocrText}\r\n <hr>\r\n <em>Click the label to reset</em>\r\n `;\r\n}\r\n"],"names":["generatePriceTooltipHTML","priceDiscount","generateCashFlowTooltipHTML","price","monthlyCashFlow","annualCashFlow","toFixed","toLocaleString","generateCapRateTooltipHTML","isUsingEstimatedCapRate","generateDownPaymentTooltipHTML","noi","downPercent","dscrPercent","sellerFiPercent","interestRateType","calculateDownPaymentTooltip"],"mappings":"wEAEO,SAASA,yBAAyBC,GACvC,MAAO,wFAEeA,yDAIxB,CAEO,SAASC,4BAA4BC,EAAOC,GACjD,MAAMC,EAAmC,GAAlBD,EAGvB,MAAO,4CAFiBC,EAAiBF,EAAS,KAAKG,QAAQ,oDAIvBD,EAAeE,sBAEzD,CAEO,SAASC,2BAA2BC,GACzC,OAAKA,EAEE,mHAF8B,IAOvC,CAEO,SAASC,+BAA+BP,EAAOQ,EAAKC,EAAaC,EAAaC,EAAiBC,EAAmB,oBAIvH,MAAO,6EAFUC,EAA4Bb,EAAOQ,EAAKC,EAAaC,EAAaC,EAAiBC,yDAQtG"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export{extractBedrooms,extractPhoneNumber}from"./data/extractors.js";export{calculateCashFlowTooltip,calculateDownPaymentTooltip,parseCashFlowData,parseFinancialData}from"./financial/tooltip-calculations.js";export{generateCapRateTooltipHTML,generateCashFlowTooltipHTML,generateDownPaymentTooltipHTML,generatePriceTooltipHTML}from"./financial/tooltip-content-generators.js";export{setupCapRateClickHandler,setupDiscountButtonHandler,setupDownPaymentClickHandler,setupPriceClickHandler}from"./ui/click-handlers.js";export{CLICKABLE_TOOLTIPS,TOOLTIP_ENABLED_METRICS}from"./ui/tooltip-config.js";export{attachTooltip,hasTooltip,isTooltipVisible,removeAllTooltips,removeTooltip,updateTooltipContent}from"./ui/tooltip-manager.js";export{createNavigationGuard}from"./widget/createNavigationGuard.js";export{createPanel}from"./widget/createPanel.js";
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{updateTooltipContent as t,attachTooltip as e,removeTooltip as n}from"./tooltip-manager.js";import{generateCapRateTooltipHTML as r,generateDownPaymentTooltipHTML as c,generatePriceTooltipHTML as a}from"../financial/tooltip-content-generators.js";import{FINANCIAL_CONSTANTS as i}from"../../config/financial.js";function updateDiscountButtonText(t){const e=document.getElementById("ln-discount-btn");e&&(e.textContent=t.currentPriceDiscount>0?"Reset to Asking":"85% of Asking")}function setupDiscountButtonHandler(t,e){if(!t)return;if("true"===t.dataset.handlerAttached)return;t.dataset.handlerAttached="true";const{state:n,updateState:r}=e;t.addEventListener("click",function(t){t.preventDefault(),t.stopPropagation(),n.currentPriceDiscount>0?r({currentPriceDiscount:0}):r({currentPriceDiscount:15});const c=document.getElementById("prop-price");c&&(c.textContent=e.getCurrentPrice()),e.updatePriceLabel(),e.recalculateFinancials(),updateDiscountButtonText(n)})}function setupPriceClickHandler(n,r,c){if(!n||!r)return;if("true"===n.dataset.handlerAttached)return;n.dataset.handlerAttached="true";const{state:i,updateState:o}=c,s=n.closest(".metric");if(n.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation();let r=10*Math.floor(i.currentPriceDiscount/10)+10;r>50&&(r=0),o({currentPriceDiscount:r});const u=c.getCurrentPrice();if(n.textContent=u,c.updatePriceLabel(),c.recalculateFinancials(),updateDiscountButtonText(i),s){const e=a(i.currentPriceDiscount);t(s,e)}}),r.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation(),o({currentPriceDiscount:0});const r=i.originalPrice;if(n.textContent=r,c.updatePriceLabel(),c.recalculateFinancials(),updateDiscountButtonText(i),s){const e=a(i.currentPriceDiscount);t(s,e)}}),s){const t=a(i.currentPriceDiscount);e(s,t),r.classList.add("has-tooltip")}n.style.cursor="pointer",r.style.cursor="pointer"}function setupCapRateClickHandler(n,c,a){if(!n||!c)return;const{state:o,updateState:s}=a;if(!o.isUsingEstimatedCapRate)return;if("true"===n.dataset.handlerAttached)return;n.dataset.handlerAttached="true";const u=n.closest(".metric");if(n.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation();let c=o.currentEstimatedCapRate+1;if(c>20&&(c=5),s({currentEstimatedCapRate:c}),n.textContent=`${c}%*`,a.recalculateFinancials(),u){const e=r(o.isUsingEstimatedCapRate);e&&t(u,e)}}),c.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation();const c=o.originalEstimatedCapRate||i.DEFAULT_CAP_RATE;if(s({currentEstimatedCapRate:c}),n.textContent=`${c}%*`,a.recalculateFinancials(),u){const e=r(o.isUsingEstimatedCapRate);e&&t(u,e)}}),u){const t=r(o.isUsingEstimatedCapRate);t&&(e(u,t),c.classList.add("has-tooltip"))}n.style.cursor="pointer",c.style.cursor="pointer"}function setupDownPaymentClickHandler(e,r,a){if(!e||!r)return;if("true"===e.dataset.handlerAttached)return;e.dataset.handlerAttached="true";const{state:o,updateState:s}=a,u=e.closest(".metric");e.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation();let r=o.currentDownPaymentPercent-10,i=o.currentDSCRPercent-10,l=o.currentSellerFiPercent+10;r<0&&(r=60,i=70,l=40),s({currentDownPaymentPercent:r,currentDSCRPercent:i,currentSellerFiPercent:l}),a.updatePercentageLabels(),a.recalculateFinancials(),setTimeout(()=>{const e=document.getElementById("prop-price"),r=document.getElementById("prop-noi");if(e&&r&&u){const a=e.textContent.match(/[\d,]+/),i=r.textContent.match(/[\d,.]+/);if(a&&i){const e=parseFloat(a[0].replace(/,/g,""));let s=parseFloat(i[0].replace(/,/g,""));r.textContent.includes("K")&&(s*=1e3),r.textContent.includes("M")&&(s*=1e6),n(u),setTimeout(()=>{const n=c(e,s,o.currentDownPaymentPercent,o.currentDSCRPercent,o.currentSellerFiPercent,o.currentInterestRateType);t(u,n)},50)}}},100)}),r.addEventListener("click",function(e){e.preventDefault(),e.stopPropagation(),s({currentDownPaymentPercent:100*i.SELLER_FI_DOWN_PAYMENT,currentDSCRPercent:100*i.DEFAULT_DSCR_PERCENTAGE,currentSellerFiPercent:100*i.SELLER_FI_CARRY}),a.updatePercentageLabels(),a.recalculateFinancials(),setTimeout(()=>{const e=document.getElementById("prop-price"),r=document.getElementById("prop-noi");if(e&&r&&u){const a=e.textContent.match(/[\d,]+/),i=r.textContent.match(/[\d,.]+/);if(a&&i){const e=parseFloat(a[0].replace(/,/g,""));let s=parseFloat(i[0].replace(/,/g,""));r.textContent.includes("K")&&(s*=1e3),r.textContent.includes("M")&&(s*=1e6),n(u),setTimeout(()=>{const n=c(e,s,o.currentDownPaymentPercent,o.currentDSCRPercent,o.currentSellerFiPercent,o.currentInterestRateType);t(u,n)},50)}}},100)}),u&&r&&r.classList.add("has-tooltip"),e.style.cursor="pointer",r.style.cursor="pointer"}export{setupCapRateClickHandler,setupDiscountButtonHandler,setupDownPaymentClickHandler,setupPriceClickHandler};
|
|
2
|
+
//# sourceMappingURL=click-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"click-handlers.js","sources":["../../../src/browser/ui/click-handlers.js"],"sourcesContent":["import { attachTooltip, removeTooltip, updateTooltipContent } from './tooltip-manager.js';\r\nimport { generatePriceTooltipHTML, generateCapRateTooltipHTML, generateDownPaymentTooltipHTML } from '../financial/tooltip-content-generators.js';\r\nimport { FINANCIAL_CONSTANTS } from '../../config/financial.js';\r\n\r\n// State is injected via the `callbacks` object (callbacks.state / callbacks.updateState)\r\n// so this shared module has no dependency on any per-platform global-state singleton.\r\n\r\nfunction updateDiscountButtonText(state) {\r\n const btn = document.getElementById(\"ln-discount-btn\");\r\n if (!btn) return;\r\n btn.textContent = state.currentPriceDiscount > 0 ? \"Reset to Asking\" : \"85% of Asking\";\r\n}\r\n\r\nexport function setupDiscountButtonHandler(buttonElement, callbacks) {\r\n if (!buttonElement) return;\r\n\r\n if (buttonElement.dataset.handlerAttached === 'true') return;\r\n buttonElement.dataset.handlerAttached = 'true';\r\n\r\n const { state, updateState } = callbacks;\r\n\r\n buttonElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n if (state.currentPriceDiscount > 0) {\r\n updateState({ currentPriceDiscount: 0 });\r\n } else {\r\n updateState({ currentPriceDiscount: 15 });\r\n }\r\n\r\n const priceElement = document.getElementById(\"prop-price\");\r\n if (priceElement) {\r\n priceElement.textContent = callbacks.getCurrentPrice();\r\n }\r\n callbacks.updatePriceLabel();\r\n callbacks.recalculateFinancials();\r\n updateDiscountButtonText(state);\r\n });\r\n}\r\n\r\nexport function setupPriceClickHandler(priceElement, priceLabelElement, callbacks) {\r\n if (!priceElement || !priceLabelElement) return;\r\n\r\n // Prevent duplicate attachment\r\n if (priceElement.dataset.handlerAttached === 'true') return;\r\n priceElement.dataset.handlerAttached = 'true';\r\n\r\n const { state, updateState } = callbacks;\r\n const metric = priceElement.closest('.metric');\r\n\r\n priceElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n let newDiscount = Math.floor(state.currentPriceDiscount / 10) * 10 + 10;\r\n if (newDiscount > 50) {\r\n newDiscount = 0;\r\n }\r\n\r\n updateState({ currentPriceDiscount: newDiscount });\r\n\r\n const newPrice = callbacks.getCurrentPrice();\r\n priceElement.textContent = newPrice;\r\n callbacks.updatePriceLabel();\r\n callbacks.recalculateFinancials();\r\n updateDiscountButtonText(state);\r\n\r\n if (metric) {\r\n const tooltipContent = generatePriceTooltipHTML(state.currentPriceDiscount);\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n });\r\n\r\n priceLabelElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n updateState({ currentPriceDiscount: 0 });\r\n\r\n const resetPrice = state.originalPrice;\r\n priceElement.textContent = resetPrice;\r\n callbacks.updatePriceLabel();\r\n callbacks.recalculateFinancials();\r\n updateDiscountButtonText(state);\r\n\r\n if (metric) {\r\n const tooltipContent = generatePriceTooltipHTML(state.currentPriceDiscount);\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n });\r\n\r\n if (metric) {\r\n const tooltipContent = generatePriceTooltipHTML(state.currentPriceDiscount);\r\n attachTooltip(metric, tooltipContent);\r\n priceLabelElement.classList.add('has-tooltip');\r\n }\r\n\r\n priceElement.style.cursor = \"pointer\";\r\n priceLabelElement.style.cursor = \"pointer\";\r\n}\r\n\r\nexport function setupCapRateClickHandler(capElement, capLabelElement, callbacks) {\r\n if (!capElement || !capLabelElement) return;\r\n\r\n const { state, updateState } = callbacks;\r\n if (!state.isUsingEstimatedCapRate) return;\r\n\r\n // Prevent duplicate attachment\r\n if (capElement.dataset.handlerAttached === 'true') return;\r\n capElement.dataset.handlerAttached = 'true';\r\n\r\n const metric = capElement.closest('.metric');\r\n\r\n capElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n let newCapRate = state.currentEstimatedCapRate + 1;\r\n if (newCapRate > 20) {\r\n newCapRate = 5;\r\n }\r\n\r\n updateState({ currentEstimatedCapRate: newCapRate });\r\n\r\n capElement.textContent = `${newCapRate}%*`;\r\n callbacks.recalculateFinancials();\r\n\r\n if (metric) {\r\n const tooltipContent = generateCapRateTooltipHTML(state.isUsingEstimatedCapRate);\r\n if (tooltipContent) {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n });\r\n\r\n capLabelElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n const originalCapRate = state.originalEstimatedCapRate || FINANCIAL_CONSTANTS.DEFAULT_CAP_RATE;\r\n updateState({ currentEstimatedCapRate: originalCapRate });\r\n\r\n capElement.textContent = `${originalCapRate}%*`;\r\n callbacks.recalculateFinancials();\r\n\r\n if (metric) {\r\n const tooltipContent = generateCapRateTooltipHTML(state.isUsingEstimatedCapRate);\r\n if (tooltipContent) {\r\n updateTooltipContent(metric, tooltipContent);\r\n }\r\n }\r\n });\r\n\r\n if (metric) {\r\n const tooltipContent = generateCapRateTooltipHTML(state.isUsingEstimatedCapRate);\r\n if (tooltipContent) {\r\n attachTooltip(metric, tooltipContent);\r\n capLabelElement.classList.add('has-tooltip');\r\n }\r\n }\r\n\r\n capElement.style.cursor = \"pointer\";\r\n capLabelElement.style.cursor = \"pointer\";\r\n}\r\n\r\nexport function setupDownPaymentClickHandler(downElement, downLabelElement, callbacks) {\r\n if (!downElement || !downLabelElement) return;\r\n\r\n // Prevent duplicate attachment\r\n if (downElement.dataset.handlerAttached === 'true') return;\r\n downElement.dataset.handlerAttached = 'true';\r\n\r\n const { state, updateState } = callbacks;\r\n const metric = downElement.closest('.metric');\r\n\r\n downElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n let newDownPercent = state.currentDownPaymentPercent - 10;\r\n let newDSCRPercent = state.currentDSCRPercent - 10;\r\n let newSellerFiPercent = state.currentSellerFiPercent + 10;\r\n\r\n if (newDownPercent < 0) {\r\n newDownPercent = 60;\r\n newDSCRPercent = 70;\r\n newSellerFiPercent = 40;\r\n }\r\n\r\n updateState({\r\n currentDownPaymentPercent: newDownPercent,\r\n currentDSCRPercent: newDSCRPercent,\r\n currentSellerFiPercent: newSellerFiPercent\r\n });\r\n\r\n callbacks.updatePercentageLabels();\r\n callbacks.recalculateFinancials();\r\n\r\n setTimeout(() => {\r\n const priceElement = document.getElementById(\"prop-price\");\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n\r\n if (priceElement && noiElement && metric) {\r\n const priceMatch = priceElement.textContent.match(/[\\d,]+/);\r\n const noiMatch = noiElement.textContent.match(/[\\d,.]+/);\r\n\r\n if (priceMatch && noiMatch) {\r\n const price = parseFloat(priceMatch[0].replace(/,/g, \"\"));\r\n let noi = parseFloat(noiMatch[0].replace(/,/g, \"\"));\r\n\r\n if (noiElement.textContent.includes(\"K\")) noi *= 1000;\r\n if (noiElement.textContent.includes(\"M\")) noi *= 1000000;\r\n\r\n removeTooltip(metric);\r\n setTimeout(() => {\r\n const tooltipContent = generateDownPaymentTooltipHTML(\r\n price,\r\n noi,\r\n state.currentDownPaymentPercent,\r\n state.currentDSCRPercent,\r\n state.currentSellerFiPercent,\r\n state.currentInterestRateType\r\n );\r\n updateTooltipContent(metric, tooltipContent);\r\n }, 50);\r\n }\r\n }\r\n }, 100);\r\n });\r\n\r\n downLabelElement.addEventListener(\"click\", function(e) {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n\r\n updateState({\r\n currentDownPaymentPercent: FINANCIAL_CONSTANTS.SELLER_FI_DOWN_PAYMENT * 100,\r\n currentDSCRPercent: FINANCIAL_CONSTANTS.DEFAULT_DSCR_PERCENTAGE * 100,\r\n currentSellerFiPercent: FINANCIAL_CONSTANTS.SELLER_FI_CARRY * 100\r\n });\r\n\r\n callbacks.updatePercentageLabels();\r\n callbacks.recalculateFinancials();\r\n\r\n setTimeout(() => {\r\n const priceElement = document.getElementById(\"prop-price\");\r\n const noiElement = document.getElementById(\"prop-noi\");\r\n\r\n if (priceElement && noiElement && metric) {\r\n const priceMatch = priceElement.textContent.match(/[\\d,]+/);\r\n const noiMatch = noiElement.textContent.match(/[\\d,.]+/);\r\n\r\n if (priceMatch && noiMatch) {\r\n const price = parseFloat(priceMatch[0].replace(/,/g, \"\"));\r\n let noi = parseFloat(noiMatch[0].replace(/,/g, \"\"));\r\n\r\n if (noiElement.textContent.includes(\"K\")) noi *= 1000;\r\n if (noiElement.textContent.includes(\"M\")) noi *= 1000000;\r\n\r\n removeTooltip(metric);\r\n setTimeout(() => {\r\n const tooltipContent = generateDownPaymentTooltipHTML(\r\n price,\r\n noi,\r\n state.currentDownPaymentPercent,\r\n state.currentDSCRPercent,\r\n state.currentSellerFiPercent,\r\n state.currentInterestRateType\r\n );\r\n updateTooltipContent(metric, tooltipContent);\r\n }, 50);\r\n }\r\n }\r\n }, 100);\r\n });\r\n\r\n if (metric && downLabelElement) {\r\n downLabelElement.classList.add('has-tooltip');\r\n }\r\n\r\n downElement.style.cursor = \"pointer\";\r\n downLabelElement.style.cursor = \"pointer\";\r\n}\r\n"],"names":["updateDiscountButtonText","state","btn","document","getElementById","textContent","currentPriceDiscount","setupDiscountButtonHandler","buttonElement","callbacks","dataset","handlerAttached","updateState","addEventListener","e","preventDefault","stopPropagation","priceElement","getCurrentPrice","updatePriceLabel","recalculateFinancials","setupPriceClickHandler","priceLabelElement","metric","closest","newDiscount","Math","floor","newPrice","tooltipContent","generatePriceTooltipHTML","updateTooltipContent","resetPrice","originalPrice","attachTooltip","classList","add","style","cursor","setupCapRateClickHandler","capElement","capLabelElement","isUsingEstimatedCapRate","newCapRate","currentEstimatedCapRate","generateCapRateTooltipHTML","originalCapRate","originalEstimatedCapRate","FINANCIAL_CONSTANTS","DEFAULT_CAP_RATE","setupDownPaymentClickHandler","downElement","downLabelElement","newDownPercent","currentDownPaymentPercent","newDSCRPercent","currentDSCRPercent","newSellerFiPercent","currentSellerFiPercent","updatePercentageLabels","setTimeout","noiElement","priceMatch","match","noiMatch","price","parseFloat","replace","noi","includes","removeTooltip","generateDownPaymentTooltipHTML","currentInterestRateType","SELLER_FI_DOWN_PAYMENT","DEFAULT_DSCR_PERCENTAGE","SELLER_FI_CARRY"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const a={"prop-down":{dynamic:!0,calculate:"calculateDownPaymentTooltip"},"prop-cashflow":{dynamic:!0,calculate:"calculateCashFlowTooltip"},"prop-cap":{dynamic:!0,conditional:!0,calculate:"calculateCapRateTooltip"}},c={"prop-price":"Click to adjust asking price discount","prop-cap":"Click to adjust cap rate","prop-down":"Click to decrease down payment by 10%"};export{c as CLICKABLE_TOOLTIPS,a as TOOLTIP_ENABLED_METRICS};
|
|
2
|
+
//# sourceMappingURL=tooltip-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-config.js","sources":["../../../src/browser/ui/tooltip-config.js"],"sourcesContent":["// Metrics that should have tooltips\r\nexport const TOOLTIP_ENABLED_METRICS = {\r\n \"prop-down\": {\r\n dynamic: true, // Content updates based on calculations\r\n calculate: \"calculateDownPaymentTooltip\"\r\n },\r\n \"prop-cashflow\": {\r\n dynamic: true,\r\n calculate: \"calculateCashFlowTooltip\"\r\n },\r\n \"prop-cap\": {\r\n dynamic: true,\r\n conditional: true, // Only shows when price is discounted\r\n calculate: \"calculateCapRateTooltip\"\r\n }\r\n // Add more as needed\r\n};\r\n\r\n// Static tooltips for clickable elements\r\nexport const CLICKABLE_TOOLTIPS = {\r\n \"prop-price\": \"Click to adjust asking price discount\",\r\n \"prop-cap\": \"Click to adjust cap rate\",\r\n \"prop-down\": \"Click to decrease down payment by 10%\"\r\n};\r\n"],"names":["TOOLTIP_ENABLED_METRICS","dynamic","calculate","conditional","CLICKABLE_TOOLTIPS"],"mappings":"AACY,MAACA,EAA0B,CACrC,YAAa,CACXC,SAAS,EACTC,UAAW,+BAEb,gBAAiB,CACfD,SAAS,EACTC,UAAW,4BAEb,WAAY,CACVD,SAAS,EACTE,aAAa,EACbD,UAAW,4BAMFE,EAAqB,CAChC,aAAc,wCACd,WAAY,2BACZ,YAAa"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{computePosition as t,offset as o,flip as e,shift as i,arrow as l}from"@floating-ui/dom";const n=new Map;function attachTooltip(s,p,r={}){if(!s||!p)return;const a=`tooltip-${Math.random().toString(36).substr(2,9)}`,d=document.createElement("div");d.className="floating-tooltip",d.id=a,d.innerHTML=p,d.setAttribute("role","tooltip");const c=document.createElement("div");c.className="floating-tooltip-arrow",d.appendChild(c),document.body.appendChild(d);const m=r.placement||"top";async function updatePosition(){const{x:n,y:p,placement:r,middlewareData:a}=await t(s,d,{placement:m,middleware:[o(8),e(),i({padding:5}),l({element:c})]});if(Object.assign(d.style,{left:`${n}px`,top:`${p}px`}),a.arrow){const{x:t,y:o}=a.arrow,e={top:"bottom",right:"left",bottom:"top",left:"right"}[r.split("-")[0]];Object.assign(c.style,{left:null!=t?`${t}px`:"",top:null!=o?`${o}px`:"",right:"",bottom:"",[e]:"-4px"})}}function showTooltip(){d.style.display="block",updatePosition()}function hideTooltip(){d.style.display=""}return s.addEventListener("mouseenter",showTooltip),s.addEventListener("mouseleave",hideTooltip),s.addEventListener("focus",showTooltip),s.addEventListener("blur",hideTooltip),n.set(s,{tooltipElement:d,arrowElement:c,showTooltip:showTooltip,hideTooltip:hideTooltip,updatePosition:updatePosition,cleanup:()=>{s.removeEventListener("mouseenter",showTooltip),s.removeEventListener("mouseleave",hideTooltip),s.removeEventListener("focus",showTooltip),s.removeEventListener("blur",hideTooltip),d.remove(),n.delete(s)}}),a}function updateTooltipContent(t,o){const e=n.get(t);if(!e)return;const i="block"===e.tooltipElement.style.display;Array.from(e.tooltipElement.childNodes).forEach(t=>{t!==e.arrowElement&&t.remove()});const l=document.createElement("div");for(l.innerHTML=o;l.firstChild;)e.tooltipElement.insertBefore(l.firstChild,e.arrowElement);i&&(e.tooltipElement.style.display="block",e.updatePosition())}function removeTooltip(t){const o=n.get(t);o&&o.cleanup()}function hasTooltip(t){return n.has(t)}function removeAllTooltips(){n.forEach(t=>t.cleanup()),n.clear()}function isTooltipVisible(t){const o=n.get(t);return!!o&&"block"===o.tooltipElement.style.display}export{attachTooltip,hasTooltip,isTooltipVisible,removeAllTooltips,removeTooltip,updateTooltipContent};
|
|
2
|
+
//# sourceMappingURL=tooltip-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tooltip-manager.js","sources":["../../../src/browser/ui/tooltip-manager.js"],"sourcesContent":["import { computePosition, flip, shift, offset, arrow } from \"@floating-ui/dom\";\r\n\r\nconst tooltips = new Map();\r\n\r\n/**\r\n * Creates and attaches a Floating UI tooltip to an element\r\n * @param {HTMLElement} element - The element to attach tooltip to\r\n * @param {string} content - The tooltip content\r\n * @param {Object} options - Configuration options\r\n */\r\nexport function attachTooltip(element, content, options = {}) {\r\n if (!element || !content) return;\r\n\r\n const tooltipId = `tooltip-${Math.random().toString(36).substr(2, 9)}`;\r\n\r\n const tooltip = document.createElement(\"div\");\r\n tooltip.className = \"floating-tooltip\";\r\n tooltip.id = tooltipId;\r\n tooltip.innerHTML = content;\r\n tooltip.setAttribute(\"role\", \"tooltip\");\r\n\r\n const arrowElement = document.createElement(\"div\");\r\n arrowElement.className = \"floating-tooltip-arrow\";\r\n tooltip.appendChild(arrowElement);\r\n\r\n document.body.appendChild(tooltip);\r\n\r\n const placement = options.placement || \"top\";\r\n\r\n async function updatePosition() {\r\n const { x, y, placement: finalPlacement, middlewareData } = await computePosition(element, tooltip, {\r\n placement,\r\n middleware: [\r\n offset(8),\r\n flip(),\r\n shift({ padding: 5 }),\r\n arrow({ element: arrowElement })\r\n ]\r\n });\r\n\r\n Object.assign(tooltip.style, {\r\n left: `${x}px`,\r\n top: `${y}px`\r\n });\r\n\r\n if (middlewareData.arrow) {\r\n const { x: arrowX, y: arrowY } = middlewareData.arrow;\r\n const staticSide = {\r\n top: \"bottom\",\r\n right: \"left\",\r\n bottom: \"top\",\r\n left: \"right\"\r\n }[finalPlacement.split(\"-\")[0]];\r\n\r\n Object.assign(arrowElement.style, {\r\n left: arrowX != null ? `${arrowX}px` : \"\",\r\n top: arrowY != null ? `${arrowY}px` : \"\",\r\n right: \"\",\r\n bottom: \"\",\r\n [staticSide]: \"-4px\"\r\n });\r\n }\r\n }\r\n\r\n function showTooltip() {\r\n tooltip.style.display = \"block\";\r\n updatePosition();\r\n }\r\n\r\n function hideTooltip() {\r\n tooltip.style.display = \"\";\r\n }\r\n\r\n element.addEventListener(\"mouseenter\", showTooltip);\r\n element.addEventListener(\"mouseleave\", hideTooltip);\r\n element.addEventListener(\"focus\", showTooltip);\r\n element.addEventListener(\"blur\", hideTooltip);\r\n\r\n tooltips.set(element, {\r\n tooltipElement: tooltip,\r\n arrowElement,\r\n showTooltip,\r\n hideTooltip,\r\n updatePosition,\r\n cleanup: () => {\r\n element.removeEventListener(\"mouseenter\", showTooltip);\r\n element.removeEventListener(\"mouseleave\", hideTooltip);\r\n element.removeEventListener(\"focus\", showTooltip);\r\n element.removeEventListener(\"blur\", hideTooltip);\r\n tooltip.remove();\r\n tooltips.delete(element);\r\n }\r\n });\r\n\r\n return tooltipId;\r\n}\r\n\r\n/**\r\n * Updates the content of an existing tooltip\r\n * @param {HTMLElement} element - The element with the tooltip\r\n * @param {string} newContent - The new tooltip content\r\n */\r\nexport function updateTooltipContent(element, newContent) {\r\n const tooltipData = tooltips.get(element);\r\n if (!tooltipData) return;\r\n\r\n const wasVisible = tooltipData.tooltipElement.style.display === 'block';\r\n\r\n // Clear existing content (except arrow)\r\n Array.from(tooltipData.tooltipElement.childNodes).forEach(node => {\r\n if (node !== tooltipData.arrowElement) {\r\n node.remove();\r\n }\r\n });\r\n\r\n // Insert new HTML content before arrow\r\n const temp = document.createElement('div');\r\n temp.innerHTML = newContent;\r\n\r\n while (temp.firstChild) {\r\n tooltipData.tooltipElement.insertBefore(temp.firstChild, tooltipData.arrowElement);\r\n }\r\n\r\n // Restore visibility and update position\r\n if (wasVisible) {\r\n tooltipData.tooltipElement.style.display = 'block';\r\n tooltipData.updatePosition();\r\n }\r\n}\r\n\r\n/**\r\n * Removes a tooltip from an element\r\n * @param {HTMLElement} element - The element to remove tooltip from\r\n */\r\nexport function removeTooltip(element) {\r\n const tooltipData = tooltips.get(element);\r\n if (tooltipData) {\r\n tooltipData.cleanup();\r\n }\r\n}\r\n\r\n/**\r\n * Checks if an element has a tooltip attached\r\n * @param {HTMLElement} element - The element to check\r\n * @returns {boolean}\r\n */\r\nexport function hasTooltip(element) {\r\n return tooltips.has(element);\r\n}\r\n\r\n/**\r\n * Removes all tooltips\r\n */\r\nexport function removeAllTooltips() {\r\n tooltips.forEach((data) => data.cleanup());\r\n tooltips.clear();\r\n}\r\n\r\n/**\r\n * Check if tooltip is currently visible\r\n */\r\nexport function isTooltipVisible(element) {\r\n const tooltipData = tooltips.get(element);\r\n if (!tooltipData) return false;\r\n return tooltipData.tooltipElement.style.display === 'block';\r\n}\r\n"],"names":["tooltips","Map","attachTooltip","element","content","options","tooltipId","Math","random","toString","substr","tooltip","document","createElement","className","id","innerHTML","setAttribute","arrowElement","appendChild","body","placement","async","updatePosition","x","y","finalPlacement","middlewareData","computePosition","middleware","offset","flip","shift","padding","arrow","Object","assign","style","left","top","arrowX","arrowY","staticSide","right","bottom","split","showTooltip","display","hideTooltip","addEventListener","set","tooltipElement","cleanup","removeEventListener","remove","delete","updateTooltipContent","newContent","tooltipData","get","wasVisible","Array","from","childNodes","forEach","node","temp","firstChild","insertBefore","removeTooltip","hasTooltip","has","removeAllTooltips","data","clear","isTooltipVisible"],"mappings":"+FAEA,MAAMA,EAAW,IAAIC,IAQd,SAASC,cAAcC,EAASC,EAASC,EAAU,CAAA,GACxD,IAAKF,IAAYC,EAAS,OAE1B,MAAME,EAAY,WAAWC,KAAKC,SAASC,SAAS,IAAIC,OAAO,EAAG,KAE5DC,EAAUC,SAASC,cAAc,OACvCF,EAAQG,UAAY,mBACpBH,EAAQI,GAAKT,EACbK,EAAQK,UAAYZ,EACpBO,EAAQM,aAAa,OAAQ,WAE7B,MAAMC,EAAeN,SAASC,cAAc,OAC5CK,EAAaJ,UAAY,yBACzBH,EAAQQ,YAAYD,GAEpBN,SAASQ,KAAKD,YAAYR,GAE1B,MAAMU,EAAYhB,EAAQgB,WAAa,MAEvCC,eAAeC,iBACb,MAAMC,EAAEA,EAACC,EAAEA,EAAGJ,UAAWK,EAAcC,eAAEA,SAAyBC,EAAgBzB,EAASQ,EAAS,CAClGU,YACAQ,WAAY,CACVC,EAAO,GACPC,IACAC,EAAM,CAAEC,QAAS,IACjBC,EAAM,CAAE/B,QAASe,OASrB,GALAiB,OAAOC,OAAOzB,EAAQ0B,MAAO,CAC3BC,KAAM,GAAGd,MACTe,IAAK,GAAGd,QAGNE,EAAeO,MAAO,CACxB,MAAQV,EAAGgB,EAAQf,EAAGgB,GAAWd,EAAeO,MAC1CQ,EAAa,CACjBH,IAAK,SACLI,MAAO,OACPC,OAAQ,MACRN,KAAM,SACNZ,EAAemB,MAAM,KAAK,IAE5BV,OAAOC,OAAOlB,EAAamB,MAAO,CAChCC,KAAgB,MAAVE,EAAiB,GAAGA,MAAa,GACvCD,IAAe,MAAVE,EAAiB,GAAGA,MAAa,GACtCE,MAAO,GACPC,OAAQ,GACRF,CAACA,GAAa,QAElB,CACF,CAEA,SAASI,cACPnC,EAAQ0B,MAAMU,QAAU,QACxBxB,gBACF,CAEA,SAASyB,cACPrC,EAAQ0B,MAAMU,QAAU,EAC1B,CAuBA,OArBA5C,EAAQ8C,iBAAiB,aAAcH,aACvC3C,EAAQ8C,iBAAiB,aAAcD,aACvC7C,EAAQ8C,iBAAiB,QAASH,aAClC3C,EAAQ8C,iBAAiB,OAAQD,aAEjChD,EAASkD,IAAI/C,EAAS,CACpBgD,eAAgBxC,EAChBO,eACA4B,wBACAE,wBACAzB,8BACA6B,QAAS,KACPjD,EAAQkD,oBAAoB,aAAcP,aAC1C3C,EAAQkD,oBAAoB,aAAcL,aAC1C7C,EAAQkD,oBAAoB,QAASP,aACrC3C,EAAQkD,oBAAoB,OAAQL,aACpCrC,EAAQ2C,SACRtD,EAASuD,OAAOpD,MAIbG,CACT,CAOO,SAASkD,qBAAqBrD,EAASsD,GAC5C,MAAMC,EAAc1D,EAAS2D,IAAIxD,GACjC,IAAKuD,EAAa,OAElB,MAAME,EAA0D,UAA7CF,EAAYP,eAAed,MAAMU,QAGpDc,MAAMC,KAAKJ,EAAYP,eAAeY,YAAYC,QAAQC,IACpDA,IAASP,EAAYxC,cACvB+C,EAAKX,WAKT,MAAMY,EAAOtD,SAASC,cAAc,OAGpC,IAFAqD,EAAKlD,UAAYyC,EAEVS,EAAKC,YACVT,EAAYP,eAAeiB,aAAaF,EAAKC,WAAYT,EAAYxC,cAInE0C,IACFF,EAAYP,eAAed,MAAMU,QAAU,QAC3CW,EAAYnC,iBAEhB,CAMO,SAAS8C,cAAclE,GAC5B,MAAMuD,EAAc1D,EAAS2D,IAAIxD,GAC7BuD,GACFA,EAAYN,SAEhB,CAOO,SAASkB,WAAWnE,GACzB,OAAOH,EAASuE,IAAIpE,EACtB,CAKO,SAASqE,oBACdxE,EAASgE,QAASS,GAASA,EAAKrB,WAChCpD,EAAS0E,OACX,CAKO,SAASC,iBAAiBxE,GAC/B,MAAMuD,EAAc1D,EAAS2D,IAAIxD,GACjC,QAAKuD,GAC+C,UAA7CA,EAAYP,eAAed,MAAMU,OAC1C"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function createNavigationGuard(e){if("function"!=typeof e)throw new TypeError("createNavigationGuard requires a getCurrentId function");let r,t=!1;return{capture:()=>(r=e(),t=!0,r),isStale:()=>!!t&&e()!==r}}export{createNavigationGuard};
|
|
2
|
+
//# sourceMappingURL=createNavigationGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createNavigationGuard.js","sources":["../../../src/browser/widget/createNavigationGuard.js"],"sourcesContent":["/**\r\n * Navigation guard for in-flight async work on SPA platforms.\r\n *\r\n * On a single-page-app target (e.g. Zillow) the user can navigate from listing A\r\n * to listing B before a slow request (equity, STR revenue) resolves. Without a\r\n * guard, A's late response gets cached onto B and the panel silently shows the\r\n * wrong numbers. Capture an identity at request start; drop the response if the\r\n * current identity no longer matches.\r\n *\r\n * Multi-page-app targets (e.g. LoopNet, full reload) pass a getCurrentId whose\r\n * value is stable for the page's life — isStale() is then always false, so the\r\n * shared async services behave exactly as before with no per-platform branching.\r\n *\r\n * @param {() => (string|number|null|undefined)} getCurrentId - returns the\r\n * current listing identity (e.g. the zpid parsed from the URL)\r\n * @returns {{ capture: () => (string|number|null|undefined), isStale: () => boolean }}\r\n */\r\nexport function createNavigationGuard(getCurrentId) {\r\n if (typeof getCurrentId !== \"function\") {\r\n throw new TypeError(\"createNavigationGuard requires a getCurrentId function\");\r\n }\r\n\r\n let capturedId;\r\n let captured = false;\r\n\r\n return {\r\n /**\r\n * Snapshot the current identity. Call this at the start of an async request.\r\n * @returns the captured identity\r\n */\r\n capture() {\r\n capturedId = getCurrentId();\r\n captured = true;\r\n return capturedId;\r\n },\r\n\r\n /**\r\n * True if the current identity differs from the captured one (the user\r\n * navigated away). Returns false if capture() was never called.\r\n * @returns {boolean}\r\n */\r\n isStale() {\r\n if (!captured) return false;\r\n return getCurrentId() !== capturedId;\r\n }\r\n };\r\n}\r\n"],"names":["createNavigationGuard","getCurrentId","TypeError","capturedId","captured","capture","isStale"],"mappings":"AAiBO,SAASA,sBAAsBC,GACpC,GAA4B,mBAAjBA,EACT,MAAM,IAAIC,UAAU,0DAGtB,IAAIC,EACAC,GAAW,EAEf,MAAO,CAKLC,QAAO,KACLF,EAAaF,IACbG,GAAW,EACJD,GAQTG,QAAO,MACAF,GACEH,MAAmBE,EAGhC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const n=[{value:"multifamily",label:"Multifamily"},{value:"str",label:"STR"},{value:"assisted",label:"Assisted/Co Living"},{value:"business",label:"Business"},{value:"mixed_use",label:"Mixed Use"},{value:"rv_park",label:"RV Park"}];function createPanel(n){const{cssUrls:e=[],defaultPropertyType:s="multifamily",callbacks:a={}}=n||{};console.log("🎨 createPanel() called");const t=document.getElementById("ln-footer");t&&(console.log("🗑️ Removing existing footer"),t.remove()),console.log("📦 Loading CSS files...");const l=e.map(n=>{const e=document.createElement("link");return e.rel="stylesheet",e.href=n,e});let i=0;const c=l.length,onLoad=()=>{i++,console.log(`📄 CSS file loaded (${i}/${c})`),i===c&&(console.log("✨ All CSS loaded, creating footer elements"),createPanelElements(s,a))};l.forEach(n=>{n.onload=onLoad}),console.log("⏰ Setting 100ms fallback timeout"),setTimeout(()=>{console.log("⚠️ Fallback timeout reached, creating footer elements anyway"),createPanelElements(s,a)},100),l.forEach(n=>{document.head.appendChild(n)})}function createPanelElements(e,s){if(document.getElementById("ln-footer"))return;const{updateState:a}=s,t=document.createElement("div");t.id="ln-footer",t.className="ext-footer",t.innerHTML=`\n <div class="footer-container">\n <div class="footer-content">\n <div class="metrics-grid">\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Address</span>\n <span id="prop-name" class="metric-value clickable prop-name">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Lead Status</span>\n <span id="prop-lead-status" class="metric-value prop-lead-status">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Price</span>\n <span id="prop-price" class="metric-value triangle prop-price">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Cash Flow</span>\n <span id="prop-cashflow" class="metric-value weight-semibold prop-cashflow">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Cap Rate</span>\n <span id="prop-cap" class="metric-value prop-cap">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">NOI</span>\n <span id="prop-noi" class="metric-value prop-noi">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">COCR (15%)</span>\n <span id="prop-cocr-15" class="metric-value prop-cocr-15">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">COCR (30%)</span>\n <span id="prop-cocr-30" class="metric-value prop-cocr-30">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric prop-dom-metric">\n <span class="metric-label">DOM</span>\n <span id="prop-dom" class="metric-value triangle prop-dom">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Equity</span>\n <span id="prop-equity" class="metric-value prop-equity">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Seller FI (40%)</span>\n <span id="prop-seller-fi" class="metric-value prop-seller-fi">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Down (60%)</span>\n <span id="prop-down" class="metric-value triangle prop-down">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">DSCR (70%)</span>\n <span id="prop-dscr" class="metric-value prop-dscr">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">SF Payment</span>\n <span id="prop-sf" class="metric-value prop-sf">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Contact</span>\n <span id="prop-contact" class="metric-value prop-contact">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Phone</span>\n <span id="prop-phone" class="metric-value prop-contact">Loading...</span>\n </div>\n </div>\n\n <div class="metric-column">\n <div class="metric">\n <span class="metric-label">Net to Buyer</span>\n <span id="prop-net" class="metric-value prop-net">Loading...</span>\n </div>\n <div class="metric">\n <span class="metric-label">Assignment</span>\n <span id="prop-assignment" class="metric-value prop-assignment">Loading...</span>\n </div>\n </div>\n </div>\n </div>\n <div class="footer-controls">\n <div class="footer-controls-col">\n <div class="units-input-row">\n <input type="number" id="ln-units-input" class="units-input" min="1" max="999" value="4">\n <span class="units-inline-label">units</span>\n </div>\n <button class="btn-discount" id="ln-discount-btn">85% of Asking</button>\n </div>\n <div class="footer-controls-col">\n <select class="dropdown" id="ln-property-type">\n ${function(e){return n.map(({value:n,label:s})=>`<option value="${n}"${n===e?" selected":""}>${s}</option>`).join("\n ")}(e)}\n </select>\n <select class="dropdown" id="ln-interest-rate-type">\n <option value="dscr_residential" selected>DSCR Res (8%)</option>\n <option value="dscr_commercial">DSCR Com (10%)</option>\n <option value="commercial">Commercial (10%)</option>\n <option value="mixed_use">Mixed Use (10%)</option>\n <option value="rv_park">RV Park (11%)</option>\n </select>\n <button class="btn-primary" id="ln-export-btn" title="Open dashboard with property data">\n <svg class="icon" viewBox="0 0 24 24">\n <path d="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z" />\n </svg>\n Dashboard\n </button>\n </div>\n </div>\n </div>\n `;try{document.body?document.body.appendChild(t):document.documentElement&&document.documentElement.appendChild(t)}catch(n){}const l=document.getElementById("ln-export-btn");l&&l.addEventListener("click",()=>{s.onExportClick?.()});const i=document.getElementById("ln-property-type");i&&i.addEventListener("change",()=>{s.onPropertyTypeChange?.(i.value)});const c=document.getElementById("ln-interest-rate-type");c&&c.addEventListener("change",()=>{a({currentInterestRateType:c.value}),s.onInterestRateTypeChange?.(c.value)});const o=document.getElementById("ln-units-input");return o&&o.addEventListener("change",()=>{const n=parseInt(o.value)||4;a({numberOfUnits:n});const e=document.getElementById("ln-interest-rate-type");e&&(n>11&&"dscr_commercial"!==e.value?(e.value="dscr_commercial",a({currentInterestRateType:"dscr_commercial"}),s.onInterestRateTypeChange?.("dscr_commercial")):n<=11&&"dscr_commercial"===e.value&&(e.value="dscr_residential",a({currentInterestRateType:"dscr_residential"}),s.onInterestRateTypeChange?.("dscr_residential")))}),t}export{createPanel};
|
|
2
|
+
//# sourceMappingURL=createPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createPanel.js","sources":["../../../src/browser/widget/createPanel.js"],"sourcesContent":["// Shared analysis-panel (footer) builder for the property analyzers.\r\n//\r\n// Extracted verbatim from each analyzer's dom-utils.js createFooter/createFooterElements.\r\n// Per-platform values are injected so this module has no Chrome or global-state dependency:\r\n// - cssUrls: stylesheet hrefs (callers resolve chrome.runtime.getURL themselves)\r\n// - defaultPropertyType: which property-type <option> is pre-selected (loopnet \"multifamily\", zillow \"str\")\r\n// - callbacks.state / callbacks.updateState: the per-platform state singleton\r\n// - callbacks.onExportClick / onPropertyTypeChange / onInterestRateTypeChange: optional notifications\r\n// (today these are CustomEvent dispatches; callers that need them pass a handler, others omit)\r\n\r\nconst PROPERTY_TYPE_OPTIONS = [\r\n { value: \"multifamily\", label: \"Multifamily\" },\r\n { value: \"str\", label: \"STR\" },\r\n { value: \"assisted\", label: \"Assisted/Co Living\" },\r\n { value: \"business\", label: \"Business\" },\r\n { value: \"mixed_use\", label: \"Mixed Use\" },\r\n { value: \"rv_park\", label: \"RV Park\" }\r\n];\r\n\r\nfunction renderPropertyTypeOptions(defaultPropertyType) {\r\n return PROPERTY_TYPE_OPTIONS.map(({ value, label }) => {\r\n const selected = value === defaultPropertyType ? \" selected\" : \"\";\r\n return `<option value=\"${value}\"${selected}>${label}</option>`;\r\n }).join(\"\\n \");\r\n}\r\n\r\nexport function createPanel(config) {\r\n const { cssUrls = [], defaultPropertyType = \"multifamily\", callbacks = {} } = config || {};\r\n\r\n console.log(\"🎨 createPanel() called\");\r\n\r\n const existing = document.getElementById(\"ln-footer\");\r\n if (existing) {\r\n console.log(\"🗑️ Removing existing footer\");\r\n existing.remove();\r\n }\r\n\r\n console.log(\"📦 Loading CSS files...\");\r\n\r\n const links = cssUrls.map((href) => {\r\n const link = document.createElement(\"link\");\r\n link.rel = \"stylesheet\";\r\n link.href = href;\r\n return link;\r\n });\r\n\r\n // Wait for all stylesheets to load before creating footer\r\n let loadedCount = 0;\r\n const total = links.length;\r\n const onLoad = () => {\r\n loadedCount++;\r\n console.log(`📄 CSS file loaded (${loadedCount}/${total})`);\r\n if (loadedCount === total) {\r\n console.log(\"✨ All CSS loaded, creating footer elements\");\r\n createPanelElements(defaultPropertyType, callbacks);\r\n }\r\n };\r\n\r\n links.forEach((link) => { link.onload = onLoad; });\r\n\r\n console.log(\"⏰ Setting 100ms fallback timeout\");\r\n // Fallback - create footer after timeout even if shared CSS doesn't load\r\n setTimeout(() => {\r\n console.log(\"⚠️ Fallback timeout reached, creating footer elements anyway\");\r\n createPanelElements(defaultPropertyType, callbacks);\r\n }, 100);\r\n\r\n links.forEach((link) => { document.head.appendChild(link); });\r\n}\r\n\r\nfunction createPanelElements(defaultPropertyType, callbacks) {\r\n // Prevent duplicate creation\r\n if (document.getElementById(\"ln-footer\")) return;\r\n\r\n const { updateState } = callbacks;\r\n\r\n const footer = document.createElement(\"div\");\r\n footer.id = \"ln-footer\";\r\n footer.className = \"ext-footer\";\r\n\r\n footer.innerHTML = `\r\n <div class=\"footer-container\">\r\n <div class=\"footer-content\">\r\n <div class=\"metrics-grid\">\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Address</span>\r\n <span id=\"prop-name\" class=\"metric-value clickable prop-name\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Lead Status</span>\r\n <span id=\"prop-lead-status\" class=\"metric-value prop-lead-status\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Price</span>\r\n <span id=\"prop-price\" class=\"metric-value triangle prop-price\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Cash Flow</span>\r\n <span id=\"prop-cashflow\" class=\"metric-value weight-semibold prop-cashflow\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Cap Rate</span>\r\n <span id=\"prop-cap\" class=\"metric-value prop-cap\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">NOI</span>\r\n <span id=\"prop-noi\" class=\"metric-value prop-noi\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">COCR (15%)</span>\r\n <span id=\"prop-cocr-15\" class=\"metric-value prop-cocr-15\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">COCR (30%)</span>\r\n <span id=\"prop-cocr-30\" class=\"metric-value prop-cocr-30\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric prop-dom-metric\">\r\n <span class=\"metric-label\">DOM</span>\r\n <span id=\"prop-dom\" class=\"metric-value triangle prop-dom\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Equity</span>\r\n <span id=\"prop-equity\" class=\"metric-value prop-equity\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Seller FI (40%)</span>\r\n <span id=\"prop-seller-fi\" class=\"metric-value prop-seller-fi\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Down (60%)</span>\r\n <span id=\"prop-down\" class=\"metric-value triangle prop-down\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">DSCR (70%)</span>\r\n <span id=\"prop-dscr\" class=\"metric-value prop-dscr\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">SF Payment</span>\r\n <span id=\"prop-sf\" class=\"metric-value prop-sf\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Contact</span>\r\n <span id=\"prop-contact\" class=\"metric-value prop-contact\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Phone</span>\r\n <span id=\"prop-phone\" class=\"metric-value prop-contact\">Loading...</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"metric-column\">\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Net to Buyer</span>\r\n <span id=\"prop-net\" class=\"metric-value prop-net\">Loading...</span>\r\n </div>\r\n <div class=\"metric\">\r\n <span class=\"metric-label\">Assignment</span>\r\n <span id=\"prop-assignment\" class=\"metric-value prop-assignment\">Loading...</span>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"footer-controls\">\r\n <div class=\"footer-controls-col\">\r\n <div class=\"units-input-row\">\r\n <input type=\"number\" id=\"ln-units-input\" class=\"units-input\" min=\"1\" max=\"999\" value=\"4\">\r\n <span class=\"units-inline-label\">units</span>\r\n </div>\r\n <button class=\"btn-discount\" id=\"ln-discount-btn\">85% of Asking</button>\r\n </div>\r\n <div class=\"footer-controls-col\">\r\n <select class=\"dropdown\" id=\"ln-property-type\">\r\n ${renderPropertyTypeOptions(defaultPropertyType)}\r\n </select>\r\n <select class=\"dropdown\" id=\"ln-interest-rate-type\">\r\n <option value=\"dscr_residential\" selected>DSCR Res (8%)</option>\r\n <option value=\"dscr_commercial\">DSCR Com (10%)</option>\r\n <option value=\"commercial\">Commercial (10%)</option>\r\n <option value=\"mixed_use\">Mixed Use (10%)</option>\r\n <option value=\"rv_park\">RV Park (11%)</option>\r\n </select>\r\n <button class=\"btn-primary\" id=\"ln-export-btn\" title=\"Open dashboard with property data\">\r\n <svg class=\"icon\" viewBox=\"0 0 24 24\">\r\n <path d=\"M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z\" />\r\n </svg>\r\n Dashboard\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n `;\r\n\r\n try {\r\n if (document.body) {\r\n document.body.appendChild(footer);\r\n } else if (document.documentElement) {\r\n document.documentElement.appendChild(footer);\r\n }\r\n } catch (error) {\r\n // Silent fail\r\n }\r\n\r\n const exportBtn = document.getElementById(\"ln-export-btn\");\r\n if (exportBtn) {\r\n exportBtn.addEventListener(\"click\", () => {\r\n callbacks.onExportClick?.();\r\n });\r\n }\r\n\r\n const propertyTypeDropdown = document.getElementById(\"ln-property-type\");\r\n if (propertyTypeDropdown) {\r\n propertyTypeDropdown.addEventListener(\"change\", () => {\r\n callbacks.onPropertyTypeChange?.(propertyTypeDropdown.value);\r\n });\r\n }\r\n\r\n const interestRateTypeDropdown = document.getElementById(\"ln-interest-rate-type\");\r\n if (interestRateTypeDropdown) {\r\n interestRateTypeDropdown.addEventListener(\"change\", () => {\r\n updateState({ currentInterestRateType: interestRateTypeDropdown.value });\r\n callbacks.onInterestRateTypeChange?.(interestRateTypeDropdown.value);\r\n });\r\n }\r\n\r\n const unitsInput = document.getElementById(\"ln-units-input\");\r\n if (unitsInput) {\r\n unitsInput.addEventListener(\"change\", () => {\r\n const value = parseInt(unitsInput.value) || 4;\r\n updateState({ numberOfUnits: value });\r\n\r\n const irDropdown = document.getElementById(\"ln-interest-rate-type\");\r\n if (irDropdown) {\r\n if (value > 11 && irDropdown.value !== \"dscr_commercial\") {\r\n irDropdown.value = \"dscr_commercial\";\r\n updateState({ currentInterestRateType: \"dscr_commercial\" });\r\n callbacks.onInterestRateTypeChange?.(\"dscr_commercial\");\r\n } else if (value <= 11 && irDropdown.value === \"dscr_commercial\") {\r\n irDropdown.value = \"dscr_residential\";\r\n updateState({ currentInterestRateType: \"dscr_residential\" });\r\n callbacks.onInterestRateTypeChange?.(\"dscr_residential\");\r\n }\r\n }\r\n });\r\n }\r\n\r\n return footer;\r\n}\r\n"],"names":["PROPERTY_TYPE_OPTIONS","value","label","createPanel","config","cssUrls","defaultPropertyType","callbacks","console","log","existing","document","getElementById","remove","links","map","href","link","createElement","rel","loadedCount","total","length","onLoad","createPanelElements","forEach","onload","setTimeout","head","appendChild","updateState","footer","id","className","innerHTML","join","renderPropertyTypeOptions","body","documentElement","error","exportBtn","addEventListener","onExportClick","propertyTypeDropdown","onPropertyTypeChange","interestRateTypeDropdown","currentInterestRateType","onInterestRateTypeChange","unitsInput","parseInt","numberOfUnits","irDropdown"],"mappings":"AAUA,MAAMA,EAAwB,CAC5B,CAAEC,MAAO,cAAeC,MAAO,eAC/B,CAAED,MAAO,MAAOC,MAAO,OACvB,CAAED,MAAO,WAAYC,MAAO,sBAC5B,CAAED,MAAO,WAAYC,MAAO,YAC5B,CAAED,MAAO,YAAaC,MAAO,aAC7B,CAAED,MAAO,UAAWC,MAAO,YAUtB,SAASC,YAAYC,GAC1B,MAAMC,QAAEA,EAAU,GAAEC,oBAAEA,EAAsB,cAAaC,UAAEA,EAAY,CAAA,GAAOH,GAAU,GAExFI,QAAQC,IAAI,2BAEZ,MAAMC,EAAWC,SAASC,eAAe,aACrCF,IACFF,QAAQC,IAAI,gCACZC,EAASG,UAGXL,QAAQC,IAAI,2BAEZ,MAAMK,EAAQT,EAAQU,IAAKC,IACzB,MAAMC,EAAON,SAASO,cAAc,QAGpC,OAFAD,EAAKE,IAAM,aACXF,EAAKD,KAAOA,EACLC,IAIT,IAAIG,EAAc,EAClB,MAAMC,EAAQP,EAAMQ,OACdC,OAAS,KACbH,IACAZ,QAAQC,IAAI,uBAAuBW,KAAeC,MAC9CD,IAAgBC,IAClBb,QAAQC,IAAI,8CACZe,oBAAoBlB,EAAqBC,KAI7CO,EAAMW,QAASR,IAAWA,EAAKS,OAASH,SAExCf,QAAQC,IAAI,oCAEZkB,WAAW,KACTnB,QAAQC,IAAI,gEACZe,oBAAoBlB,EAAqBC,IACxC,KAEHO,EAAMW,QAASR,IAAWN,SAASiB,KAAKC,YAAYZ,IACtD,CAEA,SAASO,oBAAoBlB,EAAqBC,GAEhD,GAAII,SAASC,eAAe,aAAc,OAE1C,MAAMkB,YAAEA,GAAgBvB,EAElBwB,EAASpB,SAASO,cAAc,OACtCa,EAAOC,GAAK,YACZD,EAAOE,UAAY,aAEnBF,EAAOG,UAAY,qwJA7DrB,SAAmC5B,GACjC,OAAON,EAAsBe,IAAI,EAAGd,QAAOC,WAElC,kBAAkBD,KADRA,IAAUK,EAAsB,YAAc,MACjBJ,cAC7CiC,KAAK,iBACV,CA0KcC,CAA0B9B,2zBAoBtC,IACMK,SAAS0B,KACX1B,SAAS0B,KAAKR,YAAYE,GACjBpB,SAAS2B,iBAClB3B,SAAS2B,gBAAgBT,YAAYE,EAEzC,CAAE,MAAOQ,GAET,CAEA,MAAMC,EAAY7B,SAASC,eAAe,iBACtC4B,GACFA,EAAUC,iBAAiB,QAAS,KAClClC,EAAUmC,oBAId,MAAMC,EAAuBhC,SAASC,eAAe,oBACjD+B,GACFA,EAAqBF,iBAAiB,SAAU,KAC9ClC,EAAUqC,uBAAuBD,EAAqB1C,SAI1D,MAAM4C,EAA2BlC,SAASC,eAAe,yBACrDiC,GACFA,EAAyBJ,iBAAiB,SAAU,KAClDX,EAAY,CAAEgB,wBAAyBD,EAAyB5C,QAChEM,EAAUwC,2BAA2BF,EAAyB5C,SAIlE,MAAM+C,EAAarC,SAASC,eAAe,kBAqB3C,OApBIoC,GACFA,EAAWP,iBAAiB,SAAU,KACpC,MAAMxC,EAAQgD,SAASD,EAAW/C,QAAU,EAC5C6B,EAAY,CAAEoB,cAAejD,IAE7B,MAAMkD,EAAaxC,SAASC,eAAe,yBACvCuC,IACElD,EAAQ,IAA2B,oBAArBkD,EAAWlD,OAC3BkD,EAAWlD,MAAQ,kBACnB6B,EAAY,CAAEgB,wBAAyB,oBACvCvC,EAAUwC,2BAA2B,oBAC5B9C,GAAS,IAA2B,oBAArBkD,EAAWlD,QACnCkD,EAAWlD,MAAQ,mBACnB6B,EAAY,CAAEgB,wBAAyB,qBACvCvC,EAAUwC,2BAA2B,wBAMtChB,CACT"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function mapPropertyType(e){return{assisted:"assisted",business:"business",mixed_use:"mixed_use",multifamily:"mfr",rv_park:"rv_park",str:"str"}[e]||"mfr"}function createExportObjectCore(e,t={}){const{cachedEquity:n=null,currentDownPaymentPercent:r,currentInterestRateType:a="dscr_residential",currentPriceDiscount:o=0,currentPropertyType:c="str",equitySource:i="scraped",isUsingEstimatedCapRate:s=!1,numberOfUnits:u=4,priceWasDefaulted:p=!1,windowLocation:d=""}=t;if(p)return null;const l={};if(e.name&&"Property Details"!==e.name&&"Not found"!==e.name&&(l.address=e.name),e.capRate&&"Loading..."!==e.capRate&&"Not found"!==e.capRate){const t=e.capRate.match(/[\d.]+/);if(t){const n=parseFloat(t[0]);e.capRate.includes("%")||n>1?l.capRate=Math.round(n/100*1e6)/1e6:l.capRate=Math.round(1e6*n)/1e6}}if(l.capRateSource=s?"estimated":"scraped",e.contact&&"Not found"!==e.contact&&(l.contact=e.contact),e.listingDate&&"Not found"!==e.listingDate&&(l.dateListed=e.listingDate),e.price&&"Loading..."!==e.price&&"Not found"!==e.price){const t=e.price.match(/[\d,]+/);if(t){const e=parseFloat(t[0].replace(/,/g,""));if(o>0){const t=e/(1-o/100);l.price=Math.round(t)}else l.price=e}}if(void 0!==r&&(l.downPaymentPercent=Math.round(r/100*1e6)/1e6),n&&"Loading..."!==n){const e=n.match(/[\d.]+/);e&&(l.equityPercent=Math.round(parseFloat(e[0])/100*1e6)/1e6)}l.equitySource=i,l.numberOfUnits=u,e.phone&&"Not found"!==e.phone&&(l.phone=e.phone),l.priceDiscountPercent=o>0?Math.round(o/100*1e6)/1e6:0,l.interestRateType=a,l.propertyType=mapPropertyType(c),l.url=d,console.log("exportData",l);const f={};return Object.keys(l).sort().forEach(e=>{f[e]=l[e]}),f}function calculateOriginalPrice(e,t){if(t>0){return e/(1-t/100)}return e}function convertCapRateToDecimal(e){if(!e||"Loading..."===e||"Not found"===e)return null;const t=e.match(/[\d.]+/);if(t){const n=parseFloat(t[0]);return e.includes("%")||n>1?Math.round(n/100*1e6)/1e6:Math.round(1e6*n)/1e6}return null}function formatDownPaymentPercent(e){return Math.round(e/100*1e6)/1e6}export{calculateOriginalPrice,convertCapRateToDecimal,createExportObjectCore,formatDownPaymentPercent,mapPropertyType};
|
|
2
|
+
//# sourceMappingURL=export-logic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export-logic.js","sources":["../../src/export/export-logic.js"],"sourcesContent":["// Map the on-screen property type to the dashboard's DB enum. Only \"multifamily\" -> \"mfr\"\r\n// actually differs; the rest pass through. Mirrors property-dashboard/validation/property.js\r\n// mapPropertyType (same table, same unknown -> \"mfr\" default) so the export URL carries the\r\n// real enum instead of relying on the server to convert it.\r\nexport function mapPropertyType(type) {\r\n const typeMap = {\r\n assisted: \"assisted\",\r\n business: \"business\",\r\n mixed_use: \"mixed_use\",\r\n multifamily: \"mfr\",\r\n rv_park: \"rv_park\",\r\n str: \"str\",\r\n };\r\n return typeMap[type] || \"mfr\";\r\n}\r\n\r\n// Pure business logic for data export - no DOM, no Chrome APIs.\r\n// Returns null to REFUSE export when the price was defaulted (no real price found):\r\n// a fabricated price would flow into NOI and silently land garbage in the dashboard.\r\nexport function createExportObjectCore(data, options = {}) {\r\n const {\r\n cachedEquity = null,\r\n currentDownPaymentPercent,\r\n currentInterestRateType = \"dscr_residential\",\r\n currentPriceDiscount = 0,\r\n currentPropertyType = \"str\",\r\n equitySource = \"scraped\",\r\n isUsingEstimatedCapRate = false,\r\n numberOfUnits = 4,\r\n priceWasDefaulted = false,\r\n windowLocation = \"\",\r\n } = options;\r\n\r\n if (priceWasDefaulted) return null;\r\n\r\n const exportData = {};\r\n\r\n // 1. Address\r\n if (data.name && data.name !== \"Property Details\" && data.name !== \"Not found\") {\r\n exportData.address = data.name;\r\n }\r\n\r\n // 2. Cap Rate - convert to decimal\r\n if (data.capRate && data.capRate !== \"Loading...\" && data.capRate !== \"Not found\") {\r\n const capMatch = data.capRate.match(/[\\d.]+/);\r\n if (capMatch) {\r\n const numericValue = parseFloat(capMatch[0]);\r\n\r\n // If the original string contains %, it's a percentage that needs conversion\r\n // If it's already a small decimal (< 1), it's likely already in decimal format\r\n if (data.capRate.includes(\"%\") || numericValue > 1) {\r\n // Percentage format - convert to decimal\r\n exportData.capRate = Math.round((numericValue / 100) * 1000000) / 1000000;\r\n } else {\r\n // Already in decimal format - use as-is\r\n exportData.capRate = Math.round(numericValue * 1000000) / 1000000;\r\n }\r\n }\r\n }\r\n\r\n // 3. Cap Rate Source\r\n exportData.capRateSource = isUsingEstimatedCapRate ? \"estimated\" : \"scraped\";\r\n\r\n // 4. Contact name\r\n if (data.contact && data.contact !== \"Not found\") {\r\n exportData.contact = data.contact;\r\n }\r\n\r\n // 5. Date Listed\r\n if (data.listingDate && data.listingDate !== \"Not found\") {\r\n exportData.dateListed = data.listingDate;\r\n }\r\n\r\n // 6. Price - calculate original price if discount applied\r\n if (data.price && data.price !== \"Loading...\" && data.price !== \"Not found\") {\r\n const priceMatch = data.price.match(/[\\d,]+/);\r\n if (priceMatch) {\r\n const displayedPrice = parseFloat(priceMatch[0].replace(/,/g, \"\"));\r\n\r\n if (currentPriceDiscount > 0) {\r\n const discountDecimal = currentPriceDiscount / 100;\r\n const originalPrice = displayedPrice / (1 - discountDecimal);\r\n exportData.price = Math.round(originalPrice);\r\n } else {\r\n exportData.price = displayedPrice;\r\n }\r\n }\r\n }\r\n\r\n // 7. Down Payment Percent (user-controlled value)\r\n if (currentDownPaymentPercent !== undefined) {\r\n exportData.downPaymentPercent = Math.round((currentDownPaymentPercent / 100) * 1000000) / 1000000;\r\n }\r\n\r\n // 8. Equity Percent\r\n if (cachedEquity && cachedEquity !== \"Loading...\") {\r\n const equityMatch = cachedEquity.match(/[\\d.]+/);\r\n if (equityMatch) {\r\n exportData.equityPercent = Math.round((parseFloat(equityMatch[0]) / 100) * 1000000) / 1000000;\r\n }\r\n }\r\n\r\n // 9. Equity Source\r\n exportData.equitySource = equitySource;\r\n\r\n // 10. Number of Units\r\n exportData.numberOfUnits = numberOfUnits;\r\n\r\n // 11. Phone number\r\n if (data.phone && data.phone !== \"Not found\") {\r\n exportData.phone = data.phone;\r\n }\r\n\r\n // 11. Price Discount Percent\r\n if (currentPriceDiscount > 0) {\r\n exportData.priceDiscountPercent = Math.round((currentPriceDiscount / 100) * 1000000) / 1000000;\r\n } else {\r\n exportData.priceDiscountPercent = 0;\r\n }\r\n\r\n // 12. Interest Rate Type\r\n exportData.interestRateType = currentInterestRateType;\r\n\r\n // 13. Property Type - mapped to the DB enum (multifamily -> mfr; rest pass through)\r\n exportData.propertyType = mapPropertyType(currentPropertyType);\r\n\r\n // 13. URL\r\n exportData.url = windowLocation;\r\n\r\n console.log(\"exportData\", exportData);\r\n\r\n // Alphabetize keys\r\n const alphabetized = {};\r\n Object.keys(exportData).sort().forEach(key => {\r\n alphabetized[key] = exportData[key];\r\n });\r\n\r\n return alphabetized;\r\n}\r\n\r\n// Pure calculation functions\r\nexport function calculateOriginalPrice(displayedPrice, discountPercent) {\r\n if (discountPercent > 0) {\r\n const discountDecimal = discountPercent / 100;\r\n return displayedPrice / (1 - discountDecimal);\r\n }\r\n return displayedPrice;\r\n}\r\n\r\nexport function convertCapRateToDecimal(capRateString) {\r\n if (!capRateString || capRateString === \"Loading...\" || capRateString === \"Not found\") {\r\n return null;\r\n }\r\n\r\n const capMatch = capRateString.match(/[\\d.]+/);\r\n if (capMatch) {\r\n const numericValue = parseFloat(capMatch[0]);\r\n\r\n if (capRateString.includes(\"%\") || numericValue > 1) {\r\n return Math.round((numericValue / 100) * 1000000) / 1000000;\r\n } else {\r\n return Math.round(numericValue * 1000000) / 1000000;\r\n }\r\n }\r\n\r\n return null;\r\n}\r\n\r\nexport function formatDownPaymentPercent(percentage) {\r\n return Math.round((percentage / 100) * 1000000) / 1000000;\r\n}\r\n"],"names":["mapPropertyType","type","assisted","business","mixed_use","multifamily","rv_park","str","createExportObjectCore","data","options","cachedEquity","currentDownPaymentPercent","currentInterestRateType","currentPriceDiscount","currentPropertyType","equitySource","isUsingEstimatedCapRate","numberOfUnits","priceWasDefaulted","windowLocation","exportData","name","address","capRate","capMatch","match","numericValue","parseFloat","includes","Math","round","capRateSource","contact","listingDate","dateListed","price","priceMatch","displayedPrice","replace","originalPrice","undefined","downPaymentPercent","equityMatch","equityPercent","phone","priceDiscountPercent","interestRateType","propertyType","url","console","log","alphabetized","Object","keys","sort","forEach","key","calculateOriginalPrice","discountPercent","convertCapRateToDecimal","capRateString","formatDownPaymentPercent","percentage"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,safePercentage}from"./financial/calculations.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{
|
|
1
|
+
export{calculateAppreciatedValue,calculateAssignmentFee,calculateBalloonBalance,calculateCOCR30,calculateCOCRAtPercent,calculateCashFlow,calculateCashFlowYield,calculateCashOutAfterRefi,calculateDiscountFromPrice,calculateNOIByType,calculateNetToBuyer,calculatePMT,calculatePriceForCOCR,calculatePriceFromDiscount,calculateSTRNOI,safePercentage}from"./financial/calculations.js";export{formatCurrency,formatPercentage,formatPriceValue}from"./financial/formatters.js";export{calculateOriginalPrice,convertCapRateToDecimal,createExportObjectCore,formatDownPaymentPercent,mapPropertyType}from"./export/export-logic.js";export{calculateDOM,formatDate}from"./date/utilities.js";export{calculateCursorPosition,extractNumericValue,filterNumericInput,formatInputDisplay,formatLiveInput,formatLiveNumber,parseNumericInput}from"./formatting/financial-formatting.js";export{normalizeWhitespace}from"./formatting/text.js";export{CALCULATION_TOLERANCE,DEFAULT_CAP_RATE,DEFAULT_DOWN_PAYMENT,DEFAULT_DSCR_PERCENTAGE,DEFAULT_EQUITY_ESTIMATE,DEFAULT_INTEREST_RATE_TYPE,FINANCIAL_CONSTANTS,INTEREST_RATE_TIERS,MAX_ITERATIONS,SELLER_FI_AMORTIZATION,SELLER_FI_CARRY,SELLER_FI_DOWN_PAYMENT,SELLER_FI_INTEREST_RATE,determineInterestRateType}from"./config/financial.js";export{ASSISTED_LIVING,MULTIFAMILY,PROPERTY_TYPES,PROPERTY_TYPE_CONSTANTS,STR}from"./config/property-types.js";export{ASSIGNMENT_FEE_PERCENTAGE,BUSINESS_CONSTANTS,BUYER_AGENT_COMMISSION,CLOSING_COSTS_PERCENTAGE,CONSERVATIVE_COCR15_PRICE_MULTIPLIER,HARD_MONEY_RATE,MAX_COCR15_PRICE_MULTIPLIER,MINIMUM_COCR15_PRICE,NET_TO_BUYER_PERCENTAGE,REHAB_RATE,SELLER_AGENT_COMMISSION}from"./config/business.js";export{lookupLOI}from"./services/loi-lookup.js";export{LOI_LOOKUP_CONFIG,LOI_SENT_STATUS,MATCH_TYPES}from"./config/loi-lookup.js";export{getEnvVar,isBrowserEnvironment,isNodeEnvironment}from"./environment/utilities.js";const e="./dist/styles/base.css";export{e as STYLES_PATH};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * @archerjessop/utilities\r\n * Shared utilities for ArcherJessop property analysis tools\r\n */\r\n\r\n// Financial calculations\r\nexport { \r\n calculateAppreciatedValue,\r\n calculateAssignmentFee,\r\n calculateBalloonBalance,\r\n calculateCashFlow,\r\n calculateCashFlowYield,\r\n calculateCashOutAfterRefi,\r\n calculateCOCR30, \r\n calculateCOCRAtPercent,\r\n calculateDiscountFromPrice,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT,\r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSTRNOI,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n//
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.js"],"sourcesContent":["/**\r\n * @archerjessop/utilities\r\n * Shared utilities for ArcherJessop property analysis tools\r\n */\r\n\r\n// Financial calculations\r\nexport { \r\n calculateAppreciatedValue,\r\n calculateAssignmentFee,\r\n calculateBalloonBalance,\r\n calculateCashFlow,\r\n calculateCashFlowYield,\r\n calculateCashOutAfterRefi,\r\n calculateCOCR30, \r\n calculateCOCRAtPercent,\r\n calculateDiscountFromPrice,\r\n calculateNetToBuyer,\r\n calculateNOIByType,\r\n calculatePMT,\r\n calculatePriceForCOCR,\r\n calculatePriceFromDiscount,\r\n calculateSTRNOI,\r\n safePercentage,\r\n} from \"./financial/calculations.js\";\r\n\r\n// Financial formatters\r\nexport { formatCurrency, formatPriceValue, formatPercentage } from \"./financial/formatters.js\";\r\n\r\n// Export logic (pure export-object creation)\r\nexport {\r\n calculateOriginalPrice,\r\n convertCapRateToDecimal,\r\n createExportObjectCore,\r\n formatDownPaymentPercent,\r\n mapPropertyType,\r\n} from \"./export/export-logic.js\";\r\n\r\n// Date utilities\r\nexport { calculateDOM, formatDate } from \"./date/utilities.js\";\r\n\r\n// Formatting utilities\r\nexport { \r\n calculateCursorPosition,\r\n extractNumericValue,\r\n filterNumericInput,\r\n formatInputDisplay,\r\n formatLiveInput,\r\n formatLiveNumber,\r\n parseNumericInput\r\n} from \"./formatting/financial-formatting.js\";\r\n\r\n// Text formatting utilities\r\nexport { normalizeWhitespace } from \"./formatting/text.js\";\r\n\r\n// Configuration constants\r\nexport * from \"./config/financial.js\";\r\nexport * from \"./config/property-types.js\";\r\nexport * from \"./config/business.js\";\r\n\r\nexport const STYLES_PATH = \"./dist/styles/base.css\";\r\n\r\n// LOI Lookup service and config\r\nexport { lookupLOI } from \"./services/loi-lookup.js\";\r\nexport { LOI_LOOKUP_CONFIG, MATCH_TYPES, LOI_SENT_STATUS } from \"./config/loi-lookup.js\";\r\n\r\n// Environment utilities\r\nexport { \r\n getEnvVar, \r\n isNodeEnvironment, \r\n isBrowserEnvironment \r\n} from \"./environment/utilities.js\";"],"names":["STYLES_PATH"],"mappings":"y0DA2DY,MAACA,EAAc"}
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@archerjessop/utilities",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.0",
|
|
4
4
|
"description": "Shared utilities for ArcherJessop property analysis tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js",
|
|
9
|
+
"./browser": "./dist/browser/index.js",
|
|
9
10
|
"./dist/styles/*.css": "./dist/styles/*.css"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
|
-
"sideEffects":
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"./dist/browser/**"
|
|
18
|
+
],
|
|
16
19
|
"engines": {
|
|
17
20
|
"node": ">=16"
|
|
18
21
|
},
|
|
@@ -36,6 +39,9 @@
|
|
|
36
39
|
},
|
|
37
40
|
"author": "ArcherJessop",
|
|
38
41
|
"license": "MIT",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@floating-ui/dom": "^1.7.4"
|
|
44
|
+
},
|
|
39
45
|
"devDependencies": {
|
|
40
46
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
41
47
|
"@rollup/plugin-terser": "^0.4.4",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extractors.js","sources":["../../src/data/extractors.js"],"sourcesContent":["export function extractPhoneNumber() {\r\n const phoneElement = document.querySelector(\".phone-number\") ||\r\n document.querySelector(\"a[href^='tel:']\") ||\r\n document.querySelector(\".number\") ||\r\n document.querySelector(\"[class*='phone']\");\r\n \r\n if (phoneElement) {\r\n if (phoneElement.textContent && phoneElement.textContent.trim() !== \"Call\") {\r\n return phoneElement.textContent.trim();\r\n } else if (phoneElement.href) {\r\n // Extract from tel: link\r\n const telMatch = phoneElement.href.match(/tel:(.+)/);\r\n if (telMatch) {\r\n return telMatch[1];\r\n }\r\n }\r\n }\r\n \r\n // Fallback to text search with multiple patterns\r\n const pageText = document.body ? document.body.textContent || \"\" : \"\";\r\n const phoneMatch = pageText.match(/(\\+?1?\\s*\\(?[0-9]{3}\\)?[\\s.-]*[0-9]{3}[\\s.-]*[0-9]{4})/);\r\n if (phoneMatch) {\r\n return phoneMatch[1].trim();\r\n }\r\n \r\n return \"Not found\";\r\n}\r\n\r\nexport function extractBedrooms() {\r\n try {\r\n // Look for bedroom information in various places\r\n const bodyText = document.body?.textContent || \"\";\r\n \r\n // Common patterns for bedroom information\r\n const bedroomPatterns = [\r\n /(\\d+)\\s*bed/i,\r\n /(\\d+)\\s*bedroom/i,\r\n /beds?\\s*:\\s*(\\d+)/i,\r\n /bedrooms?\\s*:\\s*(\\d+)/i,\r\n /(\\d+)\\s*BR/i,\r\n /(\\d+)br/i\r\n ];\r\n \r\n for (const pattern of bedroomPatterns) {\r\n const match = bodyText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) { // Sanity check\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n \r\n // Look in property details section specifically\r\n const propertyDetails = document.querySelector(\".property-details\") || \r\n document.querySelector(\"#PropertyDetails\") ||\r\n document.querySelector(\".details\");\r\n \r\n if (propertyDetails) {\r\n const detailsText = propertyDetails.textContent || \"\";\r\n for (const pattern of bedroomPatterns) {\r\n const match = detailsText.match(pattern);\r\n if (match) {\r\n const bedrooms = parseInt(match[1]);\r\n if (bedrooms > 0 && bedrooms < 100) {\r\n return bedrooms;\r\n }\r\n }\r\n }\r\n }\r\n \r\n // Default fallback\r\n return 10; // Default assumption for assisted living\r\n } catch (error) {\r\n return 10; // Default fallback\r\n }\r\n}\r\n\r\n"],"names":["extractPhoneNumber","phoneElement","document","querySelector","textContent","trim","href","telMatch","match","phoneMatch","body","extractBedrooms","bodyText","bedroomPatterns","pattern","bedrooms","parseInt","propertyDetails","detailsText","error"],"mappings":"AAAO,SAASA,qBACd,MAAMC,EAAeC,SAASC,cAAc,kBACxBD,SAASC,cAAc,oBACvBD,SAASC,cAAc,YACvBD,SAASC,cAAc,oBAE3C,GAAIF,EAAc,CAChB,GAAIA,EAAaG,aAAmD,SAApCH,EAAaG,YAAYC,OACvD,OAAOJ,EAAaG,YAAYC,OAC3B,GAAIJ,EAAaK,KAAM,CAE5B,MAAMC,EAAWN,EAAaK,KAAKE,MAAM,YACzC,GAAID,EACF,OAAOA,EAAS,EAEpB,CACF,CAGA,MACME,GADWP,SAASQ,MAAOR,SAASQ,KAAKN,aAAoB,IACvCI,MAAM,0DAClC,OAAIC,EACKA,EAAW,GAAGJ,OAGhB,WACT,CAEO,SAASM,kBACd,IAEE,MAAMC,EAAWV,SAASQ,MAAMN,aAAe,GAGzCS,EAAkB,CACtB,eACA,mBACA,qBACA,yBACA,cACA,YAGF,IAAK,MAAMC,KAAWD,EAAiB,CACrC,MAAML,EAAQI,EAASJ,MAAMM,GAC7B,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CAGA,MAAME,EAAkBf,SAASC,cAAc,sBACxBD,SAASC,cAAc,qBACvBD,SAASC,cAAc,YAE9C,GAAIc,EAAiB,CACnB,MAAMC,EAAcD,EAAgBb,aAAe,GACnD,IAAK,MAAMU,KAAWD,EAAiB,CACrC,MAAML,EAAQU,EAAYV,MAAMM,GAChC,GAAIN,EAAO,CACT,MAAMO,EAAWC,SAASR,EAAM,IAChC,GAAIO,EAAW,GAAKA,EAAW,IAC7B,OAAOA,CAEX,CACF,CACF,CAGA,OAAO,EACT,CAAE,MAAOI,GACP,OAAO,EACT,CACF"}
|
|
File without changes
|