@burdenoff/microfe-housingvista 2026.923.1 → 2026.924.1
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":"CoordinatePicker.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/components/maps/CoordinatePicker.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;
|
|
1
|
+
{"version":3,"file":"CoordinatePicker.d.ts","sourceRoot":"","sources":["../../../../src/housingvista/components/maps/CoordinatePicker.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAmFD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,KAAK,IAAI,CAAC;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,GAAI,gCAAgC,qBAAqB,gCA8HrF,CAAC"}
|
|
@@ -4,22 +4,22 @@ import { MapContainer as r } from "../../../node_modules/react-leaflet/lib/MapCo
|
|
|
4
4
|
import { Marker as i } from "../../../node_modules/react-leaflet/lib/Marker.js";
|
|
5
5
|
import { TileLayer as a } from "../../../node_modules/react-leaflet/lib/TileLayer.js";
|
|
6
6
|
import { OSM_ATTRIBUTION as o, OSM_TILE_URL as s, createPropertyPinIcon as c, isRealCoord as l } from "./leaflet-setup.js";
|
|
7
|
-
import { useEffect as u,
|
|
8
|
-
import { Crosshair as
|
|
9
|
-
import { jsx as
|
|
7
|
+
import { useEffect as u, useRef as d, useState as f } from "react";
|
|
8
|
+
import { Crosshair as p, Trash2 as m } from "lucide-react";
|
|
9
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
10
10
|
//#region src/housingvista/components/maps/CoordinatePicker.tsx
|
|
11
|
-
var
|
|
12
|
-
function
|
|
13
|
-
let [e, t] =
|
|
11
|
+
var _ = [20.5937, 78.9629];
|
|
12
|
+
function v() {
|
|
13
|
+
let [e, t] = f(!1);
|
|
14
14
|
return u(() => t(!0), []), e;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
16
|
+
function y({ onSelect: e }) {
|
|
17
17
|
return n({ click: (t) => e({
|
|
18
18
|
latitude: Number(t.latlng.lat.toFixed(6)),
|
|
19
19
|
longitude: Number(t.latlng.lng.toFixed(6))
|
|
20
20
|
}) }), null;
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
function b({ latitude: e, longitude: n, zoom: r }) {
|
|
23
23
|
let i = t();
|
|
24
24
|
return u(() => {
|
|
25
25
|
i.setView([e, n], r);
|
|
@@ -30,38 +30,49 @@ function y({ latitude: e, longitude: n, zoom: r }) {
|
|
|
30
30
|
r
|
|
31
31
|
]), null;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
let
|
|
33
|
+
function x({ center: e }) {
|
|
34
|
+
let n = t(), r = d(e);
|
|
35
|
+
return r.current = e, u(() => {
|
|
36
|
+
let e = n.getContainer(), t = () => {
|
|
37
|
+
n.invalidateSize(), n.setView(r.current, n.getZoom(), { animate: !1 });
|
|
38
|
+
}, i = () => n.invalidateSize(), a = requestAnimationFrame(t), o = window.setTimeout(t, 250), s = typeof ResizeObserver < "u" ? new ResizeObserver(i) : null;
|
|
39
|
+
return s?.observe(e), window.addEventListener("resize", i), () => {
|
|
40
|
+
cancelAnimationFrame(a), window.clearTimeout(o), s?.disconnect(), window.removeEventListener("resize", i);
|
|
41
|
+
};
|
|
42
|
+
}, [n]), null;
|
|
43
|
+
}
|
|
44
|
+
var S = ({ value: t, onChange: n, className: u }) => {
|
|
45
|
+
let d = v(), [S, C] = f({
|
|
35
46
|
latitude: t ? String(t.latitude) : "",
|
|
36
47
|
longitude: t ? String(t.longitude) : ""
|
|
37
|
-
}),
|
|
38
|
-
n(e),
|
|
48
|
+
}), w = (e) => {
|
|
49
|
+
n(e), C(e ? {
|
|
39
50
|
latitude: String(e.latitude),
|
|
40
51
|
longitude: String(e.longitude)
|
|
41
52
|
} : {
|
|
42
53
|
latitude: "",
|
|
43
54
|
longitude: ""
|
|
44
55
|
});
|
|
45
|
-
},
|
|
46
|
-
|
|
56
|
+
}, T = (e) => w(e), E = (e) => {
|
|
57
|
+
C(e);
|
|
47
58
|
let t = Number(e.latitude), r = Number(e.longitude);
|
|
48
59
|
n(e.latitude.trim() !== "" && e.longitude.trim() !== "" && l(t, r) ? {
|
|
49
60
|
latitude: t,
|
|
50
61
|
longitude: r
|
|
51
62
|
} : null);
|
|
52
|
-
},
|
|
53
|
-
navigator.geolocation && navigator.geolocation.getCurrentPosition((e) =>
|
|
63
|
+
}, D = () => {
|
|
64
|
+
navigator.geolocation && navigator.geolocation.getCurrentPosition((e) => w({
|
|
54
65
|
latitude: Number(e.coords.latitude.toFixed(6)),
|
|
55
66
|
longitude: Number(e.coords.longitude.toFixed(6))
|
|
56
67
|
}), () => {});
|
|
57
|
-
},
|
|
58
|
-
return /* @__PURE__ */
|
|
68
|
+
}, O = t ? [t.latitude, t.longitude] : _, k = "h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]";
|
|
69
|
+
return /* @__PURE__ */ g("div", {
|
|
59
70
|
className: e("space-y-2", u),
|
|
60
71
|
children: [
|
|
61
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ h("div", {
|
|
62
73
|
className: "h-64 w-full overflow-hidden rounded-md border border-border-subtle",
|
|
63
|
-
children:
|
|
64
|
-
center:
|
|
74
|
+
children: d ? /* @__PURE__ */ g(r, {
|
|
75
|
+
center: O,
|
|
65
76
|
zoom: t ? 13 : 4,
|
|
66
77
|
scrollWheelZoom: !0,
|
|
67
78
|
style: {
|
|
@@ -70,72 +81,73 @@ var b = ({ value: t, onChange: n, className: u }) => {
|
|
|
70
81
|
},
|
|
71
82
|
className: "z-0",
|
|
72
83
|
children: [
|
|
73
|
-
/* @__PURE__ */
|
|
84
|
+
/* @__PURE__ */ h(a, {
|
|
74
85
|
url: s,
|
|
75
86
|
attribution: o
|
|
76
87
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
78
|
-
|
|
88
|
+
/* @__PURE__ */ h(x, { center: O }),
|
|
89
|
+
/* @__PURE__ */ h(y, { onSelect: T }),
|
|
90
|
+
t && /* @__PURE__ */ h(b, {
|
|
79
91
|
latitude: t.latitude,
|
|
80
92
|
longitude: t.longitude,
|
|
81
93
|
zoom: 13
|
|
82
94
|
}),
|
|
83
|
-
t && /* @__PURE__ */
|
|
95
|
+
t && /* @__PURE__ */ h(i, {
|
|
84
96
|
position: [t.latitude, t.longitude],
|
|
85
97
|
icon: c("Pin")
|
|
86
98
|
})
|
|
87
99
|
]
|
|
88
|
-
}) : /* @__PURE__ */
|
|
100
|
+
}) : /* @__PURE__ */ h("div", { className: "h-full w-full bg-bg-sunken" })
|
|
89
101
|
}),
|
|
90
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ g("div", {
|
|
91
103
|
className: "grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
92
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
93
105
|
htmlFor: "latitude",
|
|
94
106
|
className: "mb-1.5 block text-sm font-medium text-text-primary",
|
|
95
107
|
children: "Latitude"
|
|
96
|
-
}), /* @__PURE__ */
|
|
108
|
+
}), /* @__PURE__ */ h("input", {
|
|
97
109
|
id: "latitude",
|
|
98
110
|
name: "latitude",
|
|
99
111
|
inputMode: "decimal",
|
|
100
|
-
value:
|
|
101
|
-
onChange: (e) =>
|
|
112
|
+
value: S.latitude,
|
|
113
|
+
onChange: (e) => E({
|
|
102
114
|
latitude: e.target.value,
|
|
103
|
-
longitude:
|
|
115
|
+
longitude: S.longitude
|
|
104
116
|
}),
|
|
105
117
|
placeholder: "e.g., 12.9716",
|
|
106
|
-
className:
|
|
107
|
-
})] }), /* @__PURE__ */
|
|
118
|
+
className: k
|
|
119
|
+
})] }), /* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h("label", {
|
|
108
120
|
htmlFor: "longitude",
|
|
109
121
|
className: "mb-1.5 block text-sm font-medium text-text-primary",
|
|
110
122
|
children: "Longitude"
|
|
111
|
-
}), /* @__PURE__ */
|
|
123
|
+
}), /* @__PURE__ */ h("input", {
|
|
112
124
|
id: "longitude",
|
|
113
125
|
name: "longitude",
|
|
114
126
|
inputMode: "decimal",
|
|
115
|
-
value:
|
|
116
|
-
onChange: (e) =>
|
|
117
|
-
latitude:
|
|
127
|
+
value: S.longitude,
|
|
128
|
+
onChange: (e) => E({
|
|
129
|
+
latitude: S.latitude,
|
|
118
130
|
longitude: e.target.value
|
|
119
131
|
}),
|
|
120
132
|
placeholder: "e.g., 77.5946",
|
|
121
|
-
className:
|
|
133
|
+
className: k
|
|
122
134
|
})] })]
|
|
123
135
|
}),
|
|
124
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ g("div", {
|
|
125
137
|
className: "flex items-center gap-2",
|
|
126
|
-
children: [/* @__PURE__ */
|
|
138
|
+
children: [/* @__PURE__ */ g("button", {
|
|
127
139
|
type: "button",
|
|
128
|
-
onClick:
|
|
140
|
+
onClick: D,
|
|
129
141
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-primary transition-colors hover:bg-bg-sunken",
|
|
130
|
-
children: [/* @__PURE__ */
|
|
131
|
-
}), t && /* @__PURE__ */
|
|
142
|
+
children: [/* @__PURE__ */ h(p, { className: "h-4 w-4" }), "Use my location"]
|
|
143
|
+
}), t && /* @__PURE__ */ g("button", {
|
|
132
144
|
type: "button",
|
|
133
|
-
onClick: () =>
|
|
145
|
+
onClick: () => w(null),
|
|
134
146
|
className: "inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-secondary transition-colors hover:bg-bg-sunken",
|
|
135
|
-
children: [/* @__PURE__ */ m
|
|
147
|
+
children: [/* @__PURE__ */ h(m, { className: "h-4 w-4" }), "Clear"]
|
|
136
148
|
})]
|
|
137
149
|
}),
|
|
138
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ h("p", {
|
|
139
151
|
className: "text-xs text-text-secondary",
|
|
140
152
|
children: "Click the map to drop a pin, type coordinates, or use your device location. Optional — it powers map search and area insights."
|
|
141
153
|
})
|
|
@@ -143,6 +155,6 @@ var b = ({ value: t, onChange: n, className: u }) => {
|
|
|
143
155
|
});
|
|
144
156
|
};
|
|
145
157
|
//#endregion
|
|
146
|
-
export {
|
|
158
|
+
export { S as CoordinatePicker };
|
|
147
159
|
|
|
148
160
|
//# sourceMappingURL=CoordinatePicker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoordinatePicker.js","names":[],"sources":["../../../../src/housingvista/components/maps/CoordinatePicker.tsx"],"sourcesContent":["/**\n * CoordinatePicker — click-to-pin latitude/longitude capture for the listing\n * wizard (BOFF-7795 / PROP-03). A single-marker Leaflet map: clicking drops or\n * moves a pin and reports `{ latitude, longitude }`. Manual number inputs stay\n * in sync (validated with `isRealCoord`), plus a \"Use my location\"\n * (geolocation) and \"Clear\". Optional — improves map search and area insights.\n * @module housingvista\n */\n\nimport { useEffect, useState } from 'react';\nimport { MapContainer, Marker, TileLayer, useMap, useMapEvents } from 'react-leaflet';\nimport { Crosshair, Trash2 } from 'lucide-react';\nimport { createPropertyPinIcon, isRealCoord, OSM_ATTRIBUTION, OSM_TILE_URL } from './leaflet-setup';\nimport { cn } from '../../utils/helpers';\n\nexport interface PickerCoordinates {\n latitude: number;\n longitude: number;\n}\n\n// India centroid — HousingVista is India-first, so an unset pin starts here.\nconst DEFAULT_CENTER: [number, number] = [20.5937, 78.9629];\n\n/** SSR/lazy safety: only mount Leaflet (which touches the DOM) after mount. */\nfunction useIsMounted(): boolean {\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n return mounted;\n}\n\ninterface ClickCaptureProps {\n onSelect: (coords: PickerCoordinates) => void;\n}\n\n/** Reports a map click back up as a chosen coordinate. */\nfunction ClickCapture({ onSelect }: ClickCaptureProps) {\n useMapEvents({\n click: (event) =>\n onSelect({\n latitude: Number(event.latlng.lat.toFixed(6)),\n longitude: Number(event.latlng.lng.toFixed(6)),\n }),\n });\n return null;\n}\n\ninterface RecenterMapProps {\n latitude: number;\n longitude: number;\n zoom: number;\n}\n\n/**\n * react-leaflet treats `MapContainer` center/zoom as initialization-only, so\n * this re-centres the map whenever the chosen coordinates change (e.g. after\n * manual entry or \"use my location\") — otherwise a pin set off-screen is never\n * revealed (BOFF-7797 Codex P2).\n */\nfunction RecenterMap({ latitude, longitude, zoom }: RecenterMapProps) {\n const map = useMap();\n useEffect(() => {\n map.setView([latitude, longitude], zoom);\n }, [map, latitude, longitude, zoom]);\n return null;\n}\n\nexport interface CoordinatePickerProps {\n value: PickerCoordinates | null;\n onChange: (value: PickerCoordinates | null) => void;\n className?: string;\n}\n\nexport const CoordinatePicker = ({ value, onChange, className }: CoordinatePickerProps) => {\n const mounted = useIsMounted();\n const [manual, setManual] = useState<{ latitude: string; longitude: string }>({\n latitude: value ? String(value.latitude) : '',\n longitude: value ? String(value.longitude) : '',\n });\n\n const commit = (coords: PickerCoordinates | null) => {\n onChange(coords);\n setManual(\n coords\n ? { latitude: String(coords.latitude), longitude: String(coords.longitude) }\n : { latitude: '', longitude: '' }\n );\n };\n\n const handlePin = (coords: PickerCoordinates) => commit(coords);\n\n const handleManual = (next: { latitude: string; longitude: string }) => {\n setManual(next);\n const lat = Number(next.latitude);\n const lng = Number(next.longitude);\n const valid = next.latitude.trim() !== '' && next.longitude.trim() !== '' && isRealCoord(lat, lng);\n // Only ever hand a valid pair up; clear stale coordinates the moment the\n // visible inputs stop representing one, so submit can't send old values.\n onChange(valid ? { latitude: lat, longitude: lng } : null);\n };\n\n const useMyLocation = () => {\n if (!navigator.geolocation) return;\n navigator.geolocation.getCurrentPosition(\n (pos) =>\n commit({\n latitude: Number(pos.coords.latitude.toFixed(6)),\n longitude: Number(pos.coords.longitude.toFixed(6)),\n }),\n () => {\n // Permission denied / unavailable — leave the field for manual entry.\n }\n );\n };\n\n const center: [number, number] = value ? [value.latitude, value.longitude] : DEFAULT_CENTER;\n const inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\n return (\n <div className={cn('space-y-2', className)}>\n <div className=\"h-64 w-full overflow-hidden rounded-md border border-border-subtle\">\n {mounted ? (\n <MapContainer\n center={center}\n zoom={value ? 13 : 4}\n scrollWheelZoom\n style={{ height: '100%', width: '100%' }}\n className=\"z-0\"\n >\n <TileLayer url={OSM_TILE_URL} attribution={OSM_ATTRIBUTION} />\n <ClickCapture onSelect={handlePin} />\n {value && <RecenterMap latitude={value.latitude} longitude={value.longitude} zoom={13} />}\n {value && <Marker position={[value.latitude, value.longitude]} icon={createPropertyPinIcon('Pin')} />}\n </MapContainer>\n ) : (\n <div className=\"h-full w-full bg-bg-sunken\" />\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"latitude\" className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n id=\"latitude\"\n name=\"latitude\"\n inputMode=\"decimal\"\n value={manual.latitude}\n onChange={(event) => handleManual({ latitude: event.target.value, longitude: manual.longitude })}\n placeholder=\"e.g., 12.9716\"\n className={inputClass}\n />\n </div>\n <div>\n <label htmlFor=\"longitude\" className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n id=\"longitude\"\n name=\"longitude\"\n inputMode=\"decimal\"\n value={manual.longitude}\n onChange={(event) => handleManual({ latitude: manual.latitude, longitude: event.target.value })}\n placeholder=\"e.g., 77.5946\"\n className={inputClass}\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={useMyLocation}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Crosshair className=\"h-4 w-4\" />\n Use my location\n </button>\n {value && (\n <button\n type=\"button\"\n onClick={() => commit(null)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-secondary transition-colors hover:bg-bg-sunken\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n )}\n </div>\n\n <p className=\"text-xs text-text-secondary\">\n Click the map to drop a pin, type coordinates, or use your device location. Optional — it powers map search and\n area insights.\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoordinatePicker.js","names":[],"sources":["../../../../src/housingvista/components/maps/CoordinatePicker.tsx"],"sourcesContent":["/**\n * CoordinatePicker — click-to-pin latitude/longitude capture for the listing\n * wizard (BOFF-7795 / PROP-03). A single-marker Leaflet map: clicking drops or\n * moves a pin and reports `{ latitude, longitude }`. Manual number inputs stay\n * in sync (validated with `isRealCoord`), plus a \"Use my location\"\n * (geolocation) and \"Clear\". Optional — improves map search and area insights.\n * @module housingvista\n */\n\nimport { useEffect, useRef, useState } from 'react';\nimport { MapContainer, Marker, TileLayer, useMap, useMapEvents } from 'react-leaflet';\nimport { Crosshair, Trash2 } from 'lucide-react';\nimport { createPropertyPinIcon, isRealCoord, OSM_ATTRIBUTION, OSM_TILE_URL } from './leaflet-setup';\nimport { cn } from '../../utils/helpers';\n\nexport interface PickerCoordinates {\n latitude: number;\n longitude: number;\n}\n\n// India centroid — HousingVista is India-first, so an unset pin starts here.\nconst DEFAULT_CENTER: [number, number] = [20.5937, 78.9629];\n\n/** SSR/lazy safety: only mount Leaflet (which touches the DOM) after mount. */\nfunction useIsMounted(): boolean {\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n return mounted;\n}\n\ninterface ClickCaptureProps {\n onSelect: (coords: PickerCoordinates) => void;\n}\n\n/** Reports a map click back up as a chosen coordinate. */\nfunction ClickCapture({ onSelect }: ClickCaptureProps) {\n useMapEvents({\n click: (event) =>\n onSelect({\n latitude: Number(event.latlng.lat.toFixed(6)),\n longitude: Number(event.latlng.lng.toFixed(6)),\n }),\n });\n return null;\n}\n\ninterface RecenterMapProps {\n latitude: number;\n longitude: number;\n zoom: number;\n}\n\n/**\n * react-leaflet treats `MapContainer` center/zoom as initialization-only, so\n * this re-centres the map whenever the chosen coordinates change (e.g. after\n * manual entry or \"use my location\") — otherwise a pin set off-screen is never\n * revealed (BOFF-7797 Codex P2).\n */\nfunction RecenterMap({ latitude, longitude, zoom }: RecenterMapProps) {\n const map = useMap();\n useEffect(() => {\n map.setView([latitude, longitude], zoom);\n }, [map, latitude, longitude, zoom]);\n return null;\n}\n\n/**\n * Leaflet measures and anchors its container once at init. Inside the listing\n * wizard the Location step is still animating/laying out at that moment, so the\n * map caches a stale size AND pixel origin — tiles end up scattered in a narrow\n * strip instead of filling the card (BOFF-7807). `invalidateSize()` alone is not\n * enough: when the container width is unchanged Leaflet early-returns and never\n * re-anchors the pane. So on mount we re-measure AND force a re-anchor with\n * `setView`, then keep the size correct on later container resizes.\n */\nfunction AutoSize({ center }: { center: [number, number] }) {\n const map = useMap();\n const centerRef = useRef(center);\n centerRef.current = center;\n useEffect(() => {\n const container = map.getContainer();\n const relayout = () => {\n map.invalidateSize();\n map.setView(centerRef.current, map.getZoom(), { animate: false });\n };\n const onResize = () => map.invalidateSize();\n const raf = requestAnimationFrame(relayout);\n const timer = window.setTimeout(relayout, 250);\n const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(onResize) : null;\n observer?.observe(container);\n window.addEventListener('resize', onResize);\n return () => {\n cancelAnimationFrame(raf);\n window.clearTimeout(timer);\n observer?.disconnect();\n window.removeEventListener('resize', onResize);\n };\n }, [map]);\n return null;\n}\n\nexport interface CoordinatePickerProps {\n value: PickerCoordinates | null;\n onChange: (value: PickerCoordinates | null) => void;\n className?: string;\n}\n\nexport const CoordinatePicker = ({ value, onChange, className }: CoordinatePickerProps) => {\n const mounted = useIsMounted();\n const [manual, setManual] = useState<{ latitude: string; longitude: string }>({\n latitude: value ? String(value.latitude) : '',\n longitude: value ? String(value.longitude) : '',\n });\n\n const commit = (coords: PickerCoordinates | null) => {\n onChange(coords);\n setManual(\n coords\n ? { latitude: String(coords.latitude), longitude: String(coords.longitude) }\n : { latitude: '', longitude: '' }\n );\n };\n\n const handlePin = (coords: PickerCoordinates) => commit(coords);\n\n const handleManual = (next: { latitude: string; longitude: string }) => {\n setManual(next);\n const lat = Number(next.latitude);\n const lng = Number(next.longitude);\n const valid = next.latitude.trim() !== '' && next.longitude.trim() !== '' && isRealCoord(lat, lng);\n // Only ever hand a valid pair up; clear stale coordinates the moment the\n // visible inputs stop representing one, so submit can't send old values.\n onChange(valid ? { latitude: lat, longitude: lng } : null);\n };\n\n const useMyLocation = () => {\n if (!navigator.geolocation) return;\n navigator.geolocation.getCurrentPosition(\n (pos) =>\n commit({\n latitude: Number(pos.coords.latitude.toFixed(6)),\n longitude: Number(pos.coords.longitude.toFixed(6)),\n }),\n () => {\n // Permission denied / unavailable — leave the field for manual entry.\n }\n );\n };\n\n const center: [number, number] = value ? [value.latitude, value.longitude] : DEFAULT_CENTER;\n const inputClass =\n 'h-10 w-full rounded-md border border-border-subtle bg-bg-surface px-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]';\n\n return (\n <div className={cn('space-y-2', className)}>\n <div className=\"h-64 w-full overflow-hidden rounded-md border border-border-subtle\">\n {mounted ? (\n <MapContainer\n center={center}\n zoom={value ? 13 : 4}\n scrollWheelZoom\n style={{ height: '100%', width: '100%' }}\n className=\"z-0\"\n >\n <TileLayer url={OSM_TILE_URL} attribution={OSM_ATTRIBUTION} />\n <AutoSize center={center} />\n <ClickCapture onSelect={handlePin} />\n {value && <RecenterMap latitude={value.latitude} longitude={value.longitude} zoom={13} />}\n {value && <Marker position={[value.latitude, value.longitude]} icon={createPropertyPinIcon('Pin')} />}\n </MapContainer>\n ) : (\n <div className=\"h-full w-full bg-bg-sunken\" />\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div>\n <label htmlFor=\"latitude\" className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Latitude\n </label>\n <input\n id=\"latitude\"\n name=\"latitude\"\n inputMode=\"decimal\"\n value={manual.latitude}\n onChange={(event) => handleManual({ latitude: event.target.value, longitude: manual.longitude })}\n placeholder=\"e.g., 12.9716\"\n className={inputClass}\n />\n </div>\n <div>\n <label htmlFor=\"longitude\" className=\"mb-1.5 block text-sm font-medium text-text-primary\">\n Longitude\n </label>\n <input\n id=\"longitude\"\n name=\"longitude\"\n inputMode=\"decimal\"\n value={manual.longitude}\n onChange={(event) => handleManual({ latitude: manual.latitude, longitude: event.target.value })}\n placeholder=\"e.g., 77.5946\"\n className={inputClass}\n />\n </div>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={useMyLocation}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Crosshair className=\"h-4 w-4\" />\n Use my location\n </button>\n {value && (\n <button\n type=\"button\"\n onClick={() => commit(null)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-subtle px-3 py-1.5 text-sm text-text-secondary transition-colors hover:bg-bg-sunken\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n )}\n </div>\n\n <p className=\"text-xs text-text-secondary\">\n Click the map to drop a pin, type coordinates, or use your device location. Optional — it powers map search and\n area insights.\n </p>\n </div>\n );\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-housingvista",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.924.1",
|
|
4
4
|
"description": "Burdenoff HousingVista Microfrontend - Comprehensive real estate platform for property management, investment, and transactions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|