@datum-cloud/datum-ui 0.2.0-alpha.3 → 0.2.0-alpha.5
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/README.md +66 -32
- package/dist/alert/index.mjs +3 -0
- package/dist/alert-BC2Mccfo.mjs +95 -0
- package/dist/autocomplete/index.mjs +7 -0
- package/dist/autocomplete-DZtI97HP.mjs +295 -0
- package/dist/avatar-stack/index.mjs +5 -0
- package/dist/avatar-stack-JCfBlPB9.mjs +80 -0
- package/dist/badge/index.mjs +3 -0
- package/dist/badge-bFgeYceE.mjs +185 -0
- package/dist/breadcrumb/index.mjs +4 -0
- package/dist/breadcrumb-BGYJgom_.mjs +71 -0
- package/dist/button/index.mjs +4 -0
- package/dist/button-AzpnV-WB.mjs +49 -0
- package/dist/button-C1wRfGtT.mjs +230 -0
- package/dist/button-group/index.mjs +5 -0
- package/dist/button-group-C1IB2K5s.mjs +40 -0
- package/dist/calendar/index.mjs +5 -0
- package/dist/calendar-DlIHeWb0.mjs +113 -0
- package/dist/card/index.mjs +4 -0
- package/dist/card-3Kd0VdNf.mjs +63 -0
- package/dist/chart/index.mjs +4 -0
- package/dist/chart-BZqUKpkh.mjs +143 -0
- package/dist/checkbox/index.mjs +4 -0
- package/dist/checkbox-LG1OKTpG.mjs +34 -0
- package/dist/col-lrLMZaTJ.mjs +184 -0
- package/dist/collapsible/index.mjs +3 -0
- package/dist/collapsible-Bt9UYfv3.mjs +9 -0
- package/dist/command/index.mjs +5 -0
- package/dist/command-s0Yv3abE.mjs +86 -0
- package/dist/components/features/date-picker/index.d.ts +3 -0
- package/dist/components/features/date-picker/index.d.ts.map +1 -0
- package/dist/components/features/dropzone/index.d.ts +1 -0
- package/dist/components/features/dropzone/index.d.ts.map +1 -1
- package/dist/components/themes/index.d.ts +1 -1
- package/dist/components/themes/index.d.ts.map +1 -1
- package/dist/components/themes/types.d.ts +0 -2
- package/dist/components/themes/types.d.ts.map +1 -1
- package/dist/date-picker/index.mjs +9 -0
- package/dist/dialog/index.mjs +5 -0
- package/dist/dialog-DXBaT9gA.mjs +86 -0
- package/dist/dialog-bnMMf9GD.mjs +73 -0
- package/dist/dropdown/index.mjs +3 -0
- package/dist/dropdown-DtSa_lqc.mjs +112 -0
- package/dist/dropzone/index.mjs +5 -0
- package/dist/dropzone-BkOnwrS4.mjs +221 -0
- package/dist/empty-content/index.mjs +3 -0
- package/dist/empty-content-BM9rzI13.mjs +196 -0
- package/dist/exports/map.d.ts +3 -0
- package/dist/exports/map.d.ts.map +1 -0
- package/dist/fonts/AllianceNo1-Medium.ttf +0 -0
- package/dist/fonts/AllianceNo1-Regular.ttf +0 -0
- package/dist/fonts/AllianceNo1-SemiBold.ttf +0 -0
- package/dist/form/index.mjs +146 -0
- package/dist/grid/index.mjs +3 -0
- package/dist/hooks/index.mjs +2 -3
- package/dist/hover-card/index.mjs +4 -0
- package/dist/hover-card-CUPfFUqE.mjs +33 -0
- package/dist/icon-wrapper-9ticVbRL.mjs +14 -0
- package/dist/icons/index.mjs +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +65 -9
- package/dist/input/index.mjs +5 -0
- package/dist/input-DuyjEKEW.mjs +17 -0
- package/dist/input-fzXBheCN.mjs +17 -0
- package/dist/input-group/index.mjs +7 -0
- package/dist/input-group-CPaFSTEV.mjs +80 -0
- package/dist/input-number/index.mjs +6 -0
- package/dist/input-number-9o62JHRl.mjs +106 -0
- package/dist/input-with-addons/index.mjs +3 -0
- package/dist/input-with-addons-BQn7KCTU.mjs +30 -0
- package/dist/label/index.mjs +4 -0
- package/dist/label-_ste_Re3.mjs +44 -0
- package/dist/link-button-TIF2Zdrk.mjs +36 -0
- package/dist/loader-overlay/index.mjs +3 -0
- package/dist/loader-overlay-DUaQSZQP.mjs +17 -0
- package/dist/map/index.mjs +13 -0
- package/dist/map-WL6jhkSM.mjs +1094 -0
- package/dist/more-actions/index.mjs +5 -0
- package/dist/more-actions-Ch1f6Mh3.mjs +54 -0
- package/dist/nprogress/index.mjs +32 -0
- package/dist/page-title/index.mjs +3 -0
- package/dist/page-title-BJuo81rT.mjs +26 -0
- package/dist/popover/index.mjs +4 -0
- package/dist/popover-SQlKSz6L.mjs +36 -0
- package/dist/radio-group/index.mjs +4 -0
- package/dist/radio-group-Oshv0b-U.mjs +49 -0
- package/dist/select/index.mjs +4 -0
- package/dist/select-DVlEzD2W.mjs +166 -0
- package/dist/separator/index.mjs +4 -0
- package/dist/separator-T2ppyD-8.mjs +18 -0
- package/dist/sheet/index.mjs +5 -0
- package/dist/sheet-BKiCwtNO.mjs +45 -0
- package/dist/sheet-CtnP6gTD.mjs +77 -0
- package/dist/sidebar/index.mjs +11 -0
- package/dist/sidebar-DfqezV8t.mjs +945 -0
- package/dist/skeleton/index.mjs +4 -0
- package/dist/skeleton-vzbxA-DQ.mjs +13 -0
- package/dist/spinner/index.mjs +4 -0
- package/dist/spinner-BE7k2bAD.mjs +16 -0
- package/dist/{icon-wrapper-BgPkifId.mjs → spinner.icon-Bg8zgGh0.mjs} +1 -12
- package/dist/stepper/index.mjs +5 -0
- package/dist/stepper-SWB-u_nM.mjs +323 -0
- package/dist/{style.css → styles.css} +317 -575
- package/dist/styles.mjs +1 -0
- package/dist/switch/index.mjs +4 -0
- package/dist/switch-Calk7Gyw.mjs +32 -0
- package/dist/table/index.mjs +4 -0
- package/dist/table-CsXBcQLI.mjs +68 -0
- package/dist/tabs/index.mjs +3 -0
- package/dist/tabs-D8n-dqnw.mjs +52 -0
- package/dist/tag-input/index.mjs +5 -0
- package/dist/tag-input-Di7SDNbK.mjs +284 -0
- package/dist/task-queue/index.mjs +7 -0
- package/dist/task-queue-dropdown-DW72ikDH.mjs +1356 -0
- package/dist/textarea/index.mjs +5 -0
- package/dist/textarea-CxE3YbC7.mjs +17 -0
- package/dist/textarea-QYRcDEpK.mjs +15 -0
- package/dist/theme/index.mjs +3 -0
- package/dist/{theme.provider-DpFLwtHe.mjs → theme.provider-CzCxEFFh.mjs} +63 -1
- package/dist/to-api-format-C2xjQUcI.mjs +1506 -0
- package/dist/toast/index.mjs +3 -0
- package/dist/tooltip/index.mjs +4 -0
- package/dist/tooltip-Dd3ActSS.mjs +74 -0
- package/dist/typography/index.mjs +3 -0
- package/dist/typography-UA7ZZvgJ.mjs +200 -0
- package/dist/use-copy-to-clipboard-ki-WoTml.mjs +31 -0
- package/dist/use-stepper-BaToCYMs.mjs +2017 -0
- package/dist/{use-copy-to-clipboard-BfrpD6G8.mjs → use-toast-mdn_CqRY.mjs} +34 -27
- package/dist/utils/index.mjs +0 -1
- package/dist/utils-Bfgoe-Gm.mjs +20 -0
- package/dist/visually-hidden/index.mjs +3 -0
- package/dist/visuallyhidden-aaTUk4Yo.mjs +7 -0
- package/package.json +223 -24
- package/dist/components/index.mjs +0 -8
- package/dist/datum.provider-D6VMjSV0.mjs +0 -37
- package/dist/providers/datum.provider.d.ts +0 -20
- package/dist/providers/datum.provider.d.ts.map +0 -1
- package/dist/providers/index.d.ts +0 -3
- package/dist/providers/index.d.ts.map +0 -1
- package/dist/providers/index.mjs +0 -4
- package/dist/theme-script-DHyLk25i.mjs +0 -11128
- /package/dist/{close.icon-chkXPAUC.mjs → close.icon-CMNMoXM_.mjs} +0 -0
- /package/dist/{map-leaflet-imports-OKaoesjZ.mjs → map-leaflet-imports-C4JYls8q.mjs} +0 -0
- /package/dist/{use-debounce-BYB-jPeX.mjs → use-debounce-B6wPrZV8.mjs} +0 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import { t as cn } from "./utils-Bfgoe-Gm.mjs";
|
|
2
|
+
import { t as ButtonGroup } from "./button-group-C1IB2K5s.mjs";
|
|
3
|
+
import { t as Button } from "./button-AzpnV-WB.mjs";
|
|
4
|
+
import { i as CommandGroup, o as CommandItem, r as CommandEmpty, s as CommandList, t as Command } from "./command-s0Yv3abE.mjs";
|
|
5
|
+
import { i as InputGroupInput, n as InputGroupAddon, t as InputGroup } from "./input-group-CPaFSTEV.mjs";
|
|
6
|
+
import { t as Spinner } from "./spinner-BE7k2bAD.mjs";
|
|
7
|
+
import { CheckIcon, ChevronRightIcon, CircleIcon, LayersIcon, LoaderCircleIcon, MapPinIcon, MaximizeIcon, MinimizeIcon, MinusIcon, NavigationIcon, PenLineIcon, PentagonIcon, PlusIcon, SearchIcon, SquareIcon, Trash2Icon, Undo2Icon, WaypointsIcon } from "lucide-react";
|
|
8
|
+
import * as React$1 from "react";
|
|
9
|
+
import React, { Suspense, createContext, lazy, useContext, useEffect, useRef, useState } from "react";
|
|
10
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
12
|
+
import { renderToString } from "react-dom/server.browser";
|
|
13
|
+
|
|
14
|
+
//#region ../shadcn/hooks/use-theme.ts
|
|
15
|
+
/**
|
|
16
|
+
* Lightweight theme detection hook for shadcn components.
|
|
17
|
+
* Detects dark mode via the `dark` class on <html> (Tailwind convention)
|
|
18
|
+
* or falls back to prefers-color-scheme media query.
|
|
19
|
+
*/
|
|
20
|
+
function useTheme() {
|
|
21
|
+
const [resolvedTheme, setResolvedTheme] = React$1.useState("light");
|
|
22
|
+
React$1.useEffect(() => {
|
|
23
|
+
const detect = () => {
|
|
24
|
+
if (document.documentElement.classList.contains("dark")) return "dark";
|
|
25
|
+
if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
|
|
26
|
+
return "light";
|
|
27
|
+
};
|
|
28
|
+
setResolvedTheme(detect());
|
|
29
|
+
const observer = new MutationObserver(() => setResolvedTheme(detect()));
|
|
30
|
+
observer.observe(document.documentElement, {
|
|
31
|
+
attributes: true,
|
|
32
|
+
attributeFilter: ["class"]
|
|
33
|
+
});
|
|
34
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
35
|
+
const onChange = () => setResolvedTheme(detect());
|
|
36
|
+
mql.addEventListener("change", onChange);
|
|
37
|
+
return () => {
|
|
38
|
+
observer.disconnect();
|
|
39
|
+
mql.removeEventListener("change", onChange);
|
|
40
|
+
};
|
|
41
|
+
}, []);
|
|
42
|
+
return { resolvedTheme };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region ../shadcn/ui/dropdown-menu.tsx
|
|
47
|
+
/**
|
|
48
|
+
* Vanilla shadcn/ui DropdownMenu Component
|
|
49
|
+
* Pure shadcn dropdown without Datum customizations
|
|
50
|
+
* For Datum-specific features (destructive MenuItem), import from @/modules/datum-ui
|
|
51
|
+
*/
|
|
52
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
53
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
54
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
55
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
56
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
57
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
58
|
+
const DropdownMenuSubTrigger = React$1.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.SubTrigger, {
|
|
59
|
+
ref,
|
|
60
|
+
className: cn("focus:bg-accent data-[state=open]:bg-accent flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className),
|
|
61
|
+
...props,
|
|
62
|
+
children: [children, /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ml-auto" })]
|
|
63
|
+
}));
|
|
64
|
+
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
65
|
+
const DropdownMenuSubContent = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.SubContent, {
|
|
66
|
+
ref,
|
|
67
|
+
className: cn("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg", className),
|
|
68
|
+
...props
|
|
69
|
+
}));
|
|
70
|
+
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
71
|
+
const DropdownMenuContent = React$1.forwardRef(({ className, sideOffset = 4, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.Content, {
|
|
72
|
+
ref,
|
|
73
|
+
sideOffset,
|
|
74
|
+
className: cn("bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md", className),
|
|
75
|
+
...props
|
|
76
|
+
}) }));
|
|
77
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
78
|
+
const DropdownMenuItem = React$1.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Item, {
|
|
79
|
+
ref,
|
|
80
|
+
className: cn("focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", inset && "pl-8", className),
|
|
81
|
+
...props
|
|
82
|
+
}));
|
|
83
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
84
|
+
const DropdownMenuCheckboxItem = React$1.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.CheckboxItem, {
|
|
85
|
+
ref,
|
|
86
|
+
className: cn("focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
|
|
87
|
+
checked,
|
|
88
|
+
...props,
|
|
89
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
90
|
+
className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
|
|
91
|
+
children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "h-4 w-4" }) })
|
|
92
|
+
}), children]
|
|
93
|
+
}));
|
|
94
|
+
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
95
|
+
const DropdownMenuRadioItem = React$1.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DropdownMenuPrimitive.RadioItem, {
|
|
96
|
+
ref,
|
|
97
|
+
className: cn("focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center rounded-sm py-1.5 pr-2 pl-8 text-sm transition-colors outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
|
|
98
|
+
...props,
|
|
99
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
100
|
+
className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
|
|
101
|
+
children: /* @__PURE__ */ jsx(DropdownMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CircleIcon, { className: "h-2 w-2 fill-current" }) })
|
|
102
|
+
}), children]
|
|
103
|
+
}));
|
|
104
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
105
|
+
const DropdownMenuLabel = React$1.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Label, {
|
|
106
|
+
ref,
|
|
107
|
+
className: cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className),
|
|
108
|
+
...props
|
|
109
|
+
}));
|
|
110
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
111
|
+
const DropdownMenuSeparator = React$1.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DropdownMenuPrimitive.Separator, {
|
|
112
|
+
ref,
|
|
113
|
+
className: cn("bg-muted -mx-1 my-1 h-px", className),
|
|
114
|
+
...props
|
|
115
|
+
}));
|
|
116
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
117
|
+
const DropdownMenuShortcut = ({ className, ...props }) => {
|
|
118
|
+
return /* @__PURE__ */ jsx("span", {
|
|
119
|
+
className: cn("ml-auto text-xs tracking-widest opacity-60", className),
|
|
120
|
+
...props
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region ../shadcn/ui/place-autocomplete.tsx
|
|
127
|
+
function formatAddress(properties) {
|
|
128
|
+
const parts = [];
|
|
129
|
+
if (properties.name) parts.push(properties.name);
|
|
130
|
+
if (properties.housenumber && properties.street) parts.push(`${properties.housenumber} ${properties.street}`);
|
|
131
|
+
else if (properties.street) parts.push(properties.street);
|
|
132
|
+
if (properties.city) parts.push(properties.city);
|
|
133
|
+
else if (properties.locality) parts.push(properties.locality);
|
|
134
|
+
if (properties.state && properties.state !== properties.city) parts.push(properties.state);
|
|
135
|
+
if (properties.country) parts.push(properties.country);
|
|
136
|
+
return [...new Set(parts)].join(", ");
|
|
137
|
+
}
|
|
138
|
+
function buildSearchUrl({ query, bbox, lang, lat, limit, locationBiasScale, lon, zoom }) {
|
|
139
|
+
const url = new URL("https://photon.komoot.io/api");
|
|
140
|
+
url.searchParams.set("q", query);
|
|
141
|
+
if (lang) url.searchParams.set("lang", lang);
|
|
142
|
+
if (limit) url.searchParams.set("limit", String(limit));
|
|
143
|
+
if (bbox) url.searchParams.set("bbox", bbox.join(","));
|
|
144
|
+
if (lat !== void 0 && lon !== void 0) {
|
|
145
|
+
url.searchParams.set("lat", String(lat));
|
|
146
|
+
url.searchParams.set("lon", String(lon));
|
|
147
|
+
}
|
|
148
|
+
if (zoom !== void 0) url.searchParams.set("zoom", String(zoom));
|
|
149
|
+
if (locationBiasScale !== void 0) url.searchParams.set("location_bias_scale", String(locationBiasScale));
|
|
150
|
+
return String(url);
|
|
151
|
+
}
|
|
152
|
+
function useDebounce(value, delay = 300) {
|
|
153
|
+
const [debouncedValue, setDebouncedValue] = React$1.useState(value);
|
|
154
|
+
React$1.useEffect(() => {
|
|
155
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
156
|
+
return () => clearTimeout(timer);
|
|
157
|
+
}, [value, delay]);
|
|
158
|
+
return debouncedValue;
|
|
159
|
+
}
|
|
160
|
+
function usePlaceSearch({ debounceMs, query, ...props }) {
|
|
161
|
+
const [results, setResults] = React$1.useState([]);
|
|
162
|
+
const [isLoading, setIsLoading] = React$1.useState(false);
|
|
163
|
+
const [error, setError] = React$1.useState(null);
|
|
164
|
+
const [hasSearched, setHasSearched] = React$1.useState(false);
|
|
165
|
+
const debouncedQuery = useDebounce(query, debounceMs);
|
|
166
|
+
React$1.useEffect(() => {
|
|
167
|
+
if (!debouncedQuery.trim()) {
|
|
168
|
+
setResults([]);
|
|
169
|
+
setIsLoading(false);
|
|
170
|
+
setHasSearched(false);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const abortController = new AbortController();
|
|
174
|
+
async function fetchResults() {
|
|
175
|
+
setIsLoading(true);
|
|
176
|
+
setError(null);
|
|
177
|
+
setHasSearched(true);
|
|
178
|
+
try {
|
|
179
|
+
const url = buildSearchUrl({
|
|
180
|
+
query: debouncedQuery,
|
|
181
|
+
...props
|
|
182
|
+
});
|
|
183
|
+
const response = await fetch(url, { signal: abortController.signal });
|
|
184
|
+
if (!response.ok) throw new Error(`Photon API error: ${response.status} ${response.statusText}`);
|
|
185
|
+
const data = await response.json();
|
|
186
|
+
const addressOsmIds = /* @__PURE__ */ new Set();
|
|
187
|
+
setResults(data.features.filter((feature) => {
|
|
188
|
+
const id = feature.properties.osm_id;
|
|
189
|
+
if (addressOsmIds.has(id)) return false;
|
|
190
|
+
addressOsmIds.add(id);
|
|
191
|
+
return true;
|
|
192
|
+
}));
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err instanceof Error && err.name !== "AbortError") {
|
|
195
|
+
setError(err);
|
|
196
|
+
setResults([]);
|
|
197
|
+
}
|
|
198
|
+
} finally {
|
|
199
|
+
setIsLoading(false);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
fetchResults();
|
|
203
|
+
return () => abortController.abort();
|
|
204
|
+
}, [
|
|
205
|
+
debouncedQuery,
|
|
206
|
+
props.lang,
|
|
207
|
+
props.limit,
|
|
208
|
+
props.bbox,
|
|
209
|
+
props.lat,
|
|
210
|
+
props.lon,
|
|
211
|
+
props.zoom,
|
|
212
|
+
props.locationBiasScale
|
|
213
|
+
]);
|
|
214
|
+
return {
|
|
215
|
+
results,
|
|
216
|
+
isLoading,
|
|
217
|
+
error,
|
|
218
|
+
hasSearched
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function PlaceAutocomplete({ debounceMs = 300, lang, limit = 5, bbox, lat, lon, zoom, locationBiasScale, className, value: controlledValue, defaultValue = "", onChange: controlledOnChange, onPlaceSelect, onResultsChange, ...props }) {
|
|
222
|
+
const [internalValue, setInternalValue] = React$1.useState(defaultValue);
|
|
223
|
+
const [searchQuery, setSearchQuery] = React$1.useState("");
|
|
224
|
+
const isControlled = controlledValue !== void 0;
|
|
225
|
+
const displayValue = isControlled ? controlledValue : internalValue;
|
|
226
|
+
const { results, isLoading, error, hasSearched } = usePlaceSearch({
|
|
227
|
+
query: searchQuery,
|
|
228
|
+
debounceMs,
|
|
229
|
+
lang,
|
|
230
|
+
limit,
|
|
231
|
+
bbox,
|
|
232
|
+
lat,
|
|
233
|
+
lon,
|
|
234
|
+
zoom,
|
|
235
|
+
locationBiasScale
|
|
236
|
+
});
|
|
237
|
+
React$1.useEffect(() => {
|
|
238
|
+
onResultsChange?.(results);
|
|
239
|
+
}, [results, onResultsChange]);
|
|
240
|
+
const hasNoResults = hasSearched && !isLoading && !error && results.length === 0;
|
|
241
|
+
const showCommandList = error || hasNoResults || results.length > 0;
|
|
242
|
+
return /* @__PURE__ */ jsx(Command, {
|
|
243
|
+
className: cn("h-fit overflow-visible", className),
|
|
244
|
+
shouldFilter: false,
|
|
245
|
+
loop: true,
|
|
246
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
247
|
+
className: "relative",
|
|
248
|
+
children: [/* @__PURE__ */ jsxs(InputGroup, {
|
|
249
|
+
className: cn("border-input! bg-popover! ring-0!", showCommandList && "rounded-b-none"),
|
|
250
|
+
children: [
|
|
251
|
+
/* @__PURE__ */ jsx(InputGroupAddon, { children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
|
|
252
|
+
/* @__PURE__ */ jsx(InputGroupInput, {
|
|
253
|
+
placeholder: "Search",
|
|
254
|
+
value: displayValue,
|
|
255
|
+
onChange: (event) => {
|
|
256
|
+
const newValue = event.target.value;
|
|
257
|
+
if (!isControlled) setInternalValue(newValue);
|
|
258
|
+
setSearchQuery(newValue);
|
|
259
|
+
controlledOnChange?.(newValue);
|
|
260
|
+
},
|
|
261
|
+
...props
|
|
262
|
+
}),
|
|
263
|
+
isLoading && /* @__PURE__ */ jsx(InputGroupAddon, {
|
|
264
|
+
align: "inline-end",
|
|
265
|
+
children: /* @__PURE__ */ jsx(Spinner, {})
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
}), showCommandList && /* @__PURE__ */ jsxs(CommandList, {
|
|
269
|
+
"data-state": showCommandList ? "open" : "closed",
|
|
270
|
+
className: cn("bg-popover border-input absolute top-full right-0 left-0 rounded-b-md border border-t-0 shadow-md", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95", "data-[state=open]:slide-in-from-top-2 data-[state=closed]:slide-out-to-top-2"),
|
|
271
|
+
children: [
|
|
272
|
+
error && /* @__PURE__ */ jsxs(CommandEmpty, { children: ["Error: ", error.message] }),
|
|
273
|
+
hasNoResults && /* @__PURE__ */ jsxs(CommandEmpty, { children: [
|
|
274
|
+
"Can't find ",
|
|
275
|
+
displayValue,
|
|
276
|
+
"."
|
|
277
|
+
] }),
|
|
278
|
+
results.length > 0 && /* @__PURE__ */ jsx(CommandGroup, { children: results.map((feature) => {
|
|
279
|
+
const formattedAddress = formatAddress(feature.properties);
|
|
280
|
+
return /* @__PURE__ */ jsxs(CommandItem, {
|
|
281
|
+
value: String(feature.properties.osm_id),
|
|
282
|
+
onSelect: () => {
|
|
283
|
+
const formattedAddress = formatAddress(feature.properties);
|
|
284
|
+
if (!isControlled) setInternalValue(formattedAddress);
|
|
285
|
+
setSearchQuery("");
|
|
286
|
+
controlledOnChange?.(formattedAddress);
|
|
287
|
+
onPlaceSelect?.(feature);
|
|
288
|
+
},
|
|
289
|
+
children: [/* @__PURE__ */ jsx(MapPinIcon, {}), /* @__PURE__ */ jsxs("div", {
|
|
290
|
+
className: "flex flex-col items-start text-start",
|
|
291
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
292
|
+
className: "font-medium",
|
|
293
|
+
children: feature.properties.name || feature.properties.street || "Unknown"
|
|
294
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
295
|
+
className: "text-muted-foreground text-xs",
|
|
296
|
+
children: formattedAddress
|
|
297
|
+
})]
|
|
298
|
+
})]
|
|
299
|
+
}, feature.properties.osm_id);
|
|
300
|
+
}) })
|
|
301
|
+
]
|
|
302
|
+
})]
|
|
303
|
+
})
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region ../shadcn/ui/map.tsx
|
|
309
|
+
let _useMap;
|
|
310
|
+
let _useMapEvents;
|
|
311
|
+
if (typeof window !== "undefined") import("./map-leaflet-imports-C4JYls8q.mjs").then((mod) => {
|
|
312
|
+
_useMap = mod.useMap;
|
|
313
|
+
_useMapEvents = mod.useMapEvents;
|
|
314
|
+
});
|
|
315
|
+
function createLazyComponent(factory) {
|
|
316
|
+
const LazyComponent = lazy(factory);
|
|
317
|
+
return (props) => {
|
|
318
|
+
const [isMounted, setIsMounted] = useState(false);
|
|
319
|
+
useEffect(() => {
|
|
320
|
+
setIsMounted(true);
|
|
321
|
+
}, []);
|
|
322
|
+
if (!isMounted) return null;
|
|
323
|
+
return /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(LazyComponent, { ...props }) });
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const LeafletMapContainer = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.MapContainer })));
|
|
327
|
+
const LeafletTileLayer = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.TileLayer })));
|
|
328
|
+
const LeafletMarker = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Marker })));
|
|
329
|
+
const LeafletPopup = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Popup })));
|
|
330
|
+
const LeafletTooltip = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Tooltip })));
|
|
331
|
+
const LeafletCircle = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Circle })));
|
|
332
|
+
const LeafletCircleMarker = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.CircleMarker })));
|
|
333
|
+
const LeafletPolyline = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Polyline })));
|
|
334
|
+
const LeafletPolygon = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Polygon })));
|
|
335
|
+
const LeafletRectangle = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.Rectangle })));
|
|
336
|
+
const LeafletLayerGroup = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.LayerGroup })));
|
|
337
|
+
const LeafletFeatureGroup = createLazyComponent(() => import("react-leaflet").then((mod) => ({ default: mod.FeatureGroup })));
|
|
338
|
+
const LeafletMarkerClusterGroup = createLazyComponent(async () => import("react-leaflet-markercluster").then((mod) => ({ default: mod.default })));
|
|
339
|
+
function Map({ zoom = 15, maxZoom = 18, className, ...props }) {
|
|
340
|
+
return /* @__PURE__ */ jsx(LeafletMapContainer, {
|
|
341
|
+
zoom,
|
|
342
|
+
maxZoom,
|
|
343
|
+
attributionControl: false,
|
|
344
|
+
zoomControl: false,
|
|
345
|
+
className: cn("z-50 size-full min-h-96 flex-1 rounded-md", className),
|
|
346
|
+
...props
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const MapLayersContext = createContext(null);
|
|
350
|
+
function useMapLayersContext() {
|
|
351
|
+
return useContext(MapLayersContext);
|
|
352
|
+
}
|
|
353
|
+
function MapTileLayer({ name = "Default", url, attribution, darkUrl, darkAttribution, ...props }) {
|
|
354
|
+
const map = _useMap();
|
|
355
|
+
if (map.attributionControl) map.attributionControl.setPrefix("");
|
|
356
|
+
const context = useContext(MapLayersContext);
|
|
357
|
+
const DEFAULT_URL = "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png";
|
|
358
|
+
const DEFAULT_DARK_URL = "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png";
|
|
359
|
+
const { resolvedTheme } = useTheme();
|
|
360
|
+
const resolvedUrl = resolvedTheme === "dark" ? darkUrl ?? url ?? DEFAULT_DARK_URL : url ?? DEFAULT_URL;
|
|
361
|
+
const resolvedAttribution = resolvedTheme === "dark" && darkAttribution ? darkAttribution : attribution ?? "© <a href=\"http://www.openstreetmap.org/copyright\">OpenStreetMap</a>, © <a href=\"https://carto.com/attributions\">CARTO</a>";
|
|
362
|
+
useEffect(() => {
|
|
363
|
+
if (context) context.registerTileLayer({
|
|
364
|
+
name,
|
|
365
|
+
url: resolvedUrl,
|
|
366
|
+
attribution: resolvedAttribution
|
|
367
|
+
});
|
|
368
|
+
}, [
|
|
369
|
+
context,
|
|
370
|
+
name,
|
|
371
|
+
url,
|
|
372
|
+
attribution
|
|
373
|
+
]);
|
|
374
|
+
if (context && context.selectedTileLayer !== name) return null;
|
|
375
|
+
return /* @__PURE__ */ jsx(LeafletTileLayer, {
|
|
376
|
+
url: resolvedUrl,
|
|
377
|
+
attribution: resolvedAttribution,
|
|
378
|
+
...props
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
function MapLayerGroup({ name, disabled, ...props }) {
|
|
382
|
+
const context = useMapLayersContext();
|
|
383
|
+
useEffect(() => {
|
|
384
|
+
if (context) context.registerLayerGroup({
|
|
385
|
+
name,
|
|
386
|
+
disabled
|
|
387
|
+
});
|
|
388
|
+
}, [
|
|
389
|
+
context,
|
|
390
|
+
name,
|
|
391
|
+
disabled
|
|
392
|
+
]);
|
|
393
|
+
if (context && !context.activeLayerGroups.includes(name)) return null;
|
|
394
|
+
return /* @__PURE__ */ jsx(LeafletLayerGroup, { ...props });
|
|
395
|
+
}
|
|
396
|
+
function MapFeatureGroup({ name, disabled, ...props }) {
|
|
397
|
+
const context = useMapLayersContext();
|
|
398
|
+
useEffect(() => {
|
|
399
|
+
if (context) context.registerLayerGroup({
|
|
400
|
+
name,
|
|
401
|
+
disabled
|
|
402
|
+
});
|
|
403
|
+
}, [
|
|
404
|
+
context,
|
|
405
|
+
name,
|
|
406
|
+
disabled
|
|
407
|
+
]);
|
|
408
|
+
if (context && !context.activeLayerGroups.includes(name)) return null;
|
|
409
|
+
return /* @__PURE__ */ jsx(LeafletFeatureGroup, { ...props });
|
|
410
|
+
}
|
|
411
|
+
function MapLayers({ defaultTileLayer, defaultLayerGroups = [], ...props }) {
|
|
412
|
+
const [tileLayers, setTileLayers] = useState([]);
|
|
413
|
+
const [selectedTileLayer, setSelectedTileLayer] = useState(defaultTileLayer || "");
|
|
414
|
+
const [layerGroups, setLayerGroups] = useState([]);
|
|
415
|
+
const [activeLayerGroups, setActiveLayerGroups] = useState(defaultLayerGroups);
|
|
416
|
+
function registerTileLayer(tileLayer) {
|
|
417
|
+
setTileLayers((prevTileLayers) => {
|
|
418
|
+
if (prevTileLayers.some((layer) => layer.name === tileLayer.name)) return prevTileLayers;
|
|
419
|
+
return [...prevTileLayers, tileLayer];
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
function registerLayerGroup(layerGroup) {
|
|
423
|
+
setLayerGroups((prevLayerGroups) => {
|
|
424
|
+
if (prevLayerGroups.some((group) => group.name === layerGroup.name)) return prevLayerGroups;
|
|
425
|
+
return [...prevLayerGroups, layerGroup];
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
if (defaultTileLayer && tileLayers.length > 0 && !tileLayers.some((tileLayer) => tileLayer.name === defaultTileLayer)) throw new Error(`Invalid defaultTileLayer "${defaultTileLayer}" provided to MapLayers. It must match a MapTileLayer's name prop.`);
|
|
430
|
+
if (tileLayers.length > 0 && !selectedTileLayer) setSelectedTileLayer(defaultTileLayer && tileLayers.some((layer) => layer.name === defaultTileLayer) ? defaultTileLayer : tileLayers[0].name);
|
|
431
|
+
if (defaultLayerGroups.length > 0 && layerGroups.length > 0 && defaultLayerGroups.some((name) => !layerGroups.some((group) => group.name === name))) throw new Error(`Invalid defaultLayerGroups value provided to MapLayers. All names must match a MapLayerGroup's name prop.`);
|
|
432
|
+
}, [
|
|
433
|
+
tileLayers,
|
|
434
|
+
defaultTileLayer,
|
|
435
|
+
selectedTileLayer,
|
|
436
|
+
layerGroups,
|
|
437
|
+
defaultLayerGroups
|
|
438
|
+
]);
|
|
439
|
+
return /* @__PURE__ */ jsx(MapLayersContext.Provider, {
|
|
440
|
+
value: {
|
|
441
|
+
registerTileLayer,
|
|
442
|
+
tileLayers,
|
|
443
|
+
selectedTileLayer,
|
|
444
|
+
setSelectedTileLayer,
|
|
445
|
+
registerLayerGroup,
|
|
446
|
+
layerGroups,
|
|
447
|
+
activeLayerGroups,
|
|
448
|
+
setActiveLayerGroups
|
|
449
|
+
},
|
|
450
|
+
...props
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function MapLayersControl({ tileLayersLabel = "Map Type", layerGroupsLabel = "Layers", position = "top-1 right-1", className, ...props }) {
|
|
454
|
+
const layersContext = useMapLayersContext();
|
|
455
|
+
if (!layersContext) throw new Error("MapLayersControl must be used within MapLayers");
|
|
456
|
+
const { tileLayers, selectedTileLayer, setSelectedTileLayer, layerGroups, activeLayerGroups, setActiveLayerGroups } = layersContext;
|
|
457
|
+
if (tileLayers.length === 0 && layerGroups.length === 0) return null;
|
|
458
|
+
function handleLayerGroupToggle(name, checked) {
|
|
459
|
+
setActiveLayerGroups(checked ? [...activeLayerGroups, name] : activeLayerGroups.filter((groupName) => groupName !== name));
|
|
460
|
+
}
|
|
461
|
+
const showTileLayersDropdown = tileLayers.length > 1;
|
|
462
|
+
const showLayerGroupsDropdown = layerGroups.length > 0;
|
|
463
|
+
if (!showTileLayersDropdown && !showLayerGroupsDropdown) return null;
|
|
464
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
|
|
465
|
+
asChild: true,
|
|
466
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
467
|
+
type: "button",
|
|
468
|
+
variant: "secondary",
|
|
469
|
+
size: "sm",
|
|
470
|
+
"aria-label": "Select layers",
|
|
471
|
+
title: "Select layers",
|
|
472
|
+
className: cn("absolute z-1000 border", position, className),
|
|
473
|
+
...props,
|
|
474
|
+
children: /* @__PURE__ */ jsx(LayersIcon, {})
|
|
475
|
+
})
|
|
476
|
+
}), /* @__PURE__ */ jsxs(DropdownMenuContent, {
|
|
477
|
+
align: "end",
|
|
478
|
+
className: "z-1000",
|
|
479
|
+
children: [
|
|
480
|
+
showTileLayersDropdown && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuLabel, { children: tileLayersLabel }), /* @__PURE__ */ jsx(DropdownMenuRadioGroup, {
|
|
481
|
+
value: selectedTileLayer,
|
|
482
|
+
onValueChange: setSelectedTileLayer,
|
|
483
|
+
children: tileLayers.map((tileLayer) => /* @__PURE__ */ jsx(DropdownMenuRadioItem, {
|
|
484
|
+
value: tileLayer.name,
|
|
485
|
+
children: tileLayer.name
|
|
486
|
+
}, tileLayer.name))
|
|
487
|
+
})] }),
|
|
488
|
+
showTileLayersDropdown && showLayerGroupsDropdown && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
489
|
+
showLayerGroupsDropdown && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DropdownMenuLabel, { children: layerGroupsLabel }), layerGroups.map((layerGroup) => /* @__PURE__ */ jsx(DropdownMenuCheckboxItem, {
|
|
490
|
+
checked: activeLayerGroups.includes(layerGroup.name),
|
|
491
|
+
disabled: layerGroup.disabled,
|
|
492
|
+
onCheckedChange: (checked) => handleLayerGroupToggle(layerGroup.name, checked),
|
|
493
|
+
children: layerGroup.name
|
|
494
|
+
}, layerGroup.name))] })
|
|
495
|
+
]
|
|
496
|
+
})] });
|
|
497
|
+
}
|
|
498
|
+
function MapMarker({ icon = /* @__PURE__ */ jsx(MapPinIcon, { className: "size-6" }), iconAnchor = [12, 12], bgPos, popupAnchor, tooltipAnchor, ...props }) {
|
|
499
|
+
const { L } = useLeaflet();
|
|
500
|
+
if (!L) return null;
|
|
501
|
+
return /* @__PURE__ */ jsx(LeafletMarker, {
|
|
502
|
+
icon: L.divIcon({
|
|
503
|
+
html: renderToString(icon),
|
|
504
|
+
iconAnchor,
|
|
505
|
+
...bgPos ? { bgPos } : {},
|
|
506
|
+
...popupAnchor ? { popupAnchor } : {},
|
|
507
|
+
...tooltipAnchor ? { tooltipAnchor } : {}
|
|
508
|
+
}),
|
|
509
|
+
riseOnHover: true,
|
|
510
|
+
...props
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function MapMarkerClusterGroup({ polygonOptions = { className: "fill-foreground stroke-foreground stroke-2" }, spiderLegPolylineOptions = { className: "fill-foreground stroke-foreground stroke-2" }, icon, ...props }) {
|
|
514
|
+
const { L } = useLeaflet();
|
|
515
|
+
if (!L) return null;
|
|
516
|
+
return /* @__PURE__ */ jsx(LeafletMarkerClusterGroup, {
|
|
517
|
+
polygonOptions,
|
|
518
|
+
spiderLegPolylineOptions,
|
|
519
|
+
iconCreateFunction: icon ? (cluster) => {
|
|
520
|
+
const iconNode = icon(cluster.getChildCount());
|
|
521
|
+
return L.divIcon({ html: renderToString(iconNode) });
|
|
522
|
+
} : void 0,
|
|
523
|
+
...props
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
function MapCircle({ className, ...props }) {
|
|
527
|
+
return /* @__PURE__ */ jsx(LeafletCircle, {
|
|
528
|
+
className: cn("fill-foreground stroke-foreground stroke-2", className),
|
|
529
|
+
...props
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
function MapCircleMarker({ className, ...props }) {
|
|
533
|
+
return /* @__PURE__ */ jsx(LeafletCircleMarker, {
|
|
534
|
+
className: cn("fill-foreground stroke-foreground stroke-2", className),
|
|
535
|
+
...props
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
function MapPolyline({ className, ...props }) {
|
|
539
|
+
return /* @__PURE__ */ jsx(LeafletPolyline, {
|
|
540
|
+
className: cn("fill-foreground stroke-foreground stroke-2", className),
|
|
541
|
+
...props
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
function MapPolygon({ className, ...props }) {
|
|
545
|
+
return /* @__PURE__ */ jsx(LeafletPolygon, {
|
|
546
|
+
className: cn("fill-foreground stroke-foreground stroke-2", className),
|
|
547
|
+
...props
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
function MapRectangle({ className, ...props }) {
|
|
551
|
+
return /* @__PURE__ */ jsx(LeafletRectangle, {
|
|
552
|
+
className: cn("fill-foreground stroke-foreground stroke-2", className),
|
|
553
|
+
...props
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function MapPopup({ className, ...props }) {
|
|
557
|
+
return /* @__PURE__ */ jsx(LeafletPopup, {
|
|
558
|
+
className: cn("bg-popover text-popover-foreground animate-in fade-out-0 fade-in-0 zoom-out-95 zoom-in-95 slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 font-sans shadow-md outline-hidden", className),
|
|
559
|
+
...props
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
function MapTooltip({ className, children, side = "top", sideOffset = 15, ...props }) {
|
|
563
|
+
const ARROW_POSITION_CLASSES = {
|
|
564
|
+
top: "bottom-0.5 left-1/2 -translate-x-1/2 translate-y-1/2",
|
|
565
|
+
bottom: "top-0.5 left-1/2 -translate-x-1/2 -translate-y-1/2",
|
|
566
|
+
left: "right-0.5 top-1/2 translate-x-1/2 -translate-y-1/2",
|
|
567
|
+
right: "left-0.5 top-1/2 -translate-x-1/2 -translate-y-1/2"
|
|
568
|
+
};
|
|
569
|
+
const DEFAULT_OFFSET = {
|
|
570
|
+
top: [0, -sideOffset],
|
|
571
|
+
bottom: [0, sideOffset],
|
|
572
|
+
left: [-sideOffset, 0],
|
|
573
|
+
right: [sideOffset, 0]
|
|
574
|
+
};
|
|
575
|
+
return /* @__PURE__ */ jsxs(LeafletTooltip, {
|
|
576
|
+
className: cn("animate-in fade-in-0 zoom-in-95 fade-out-0 zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 w-fit text-xs text-balance transition-opacity", className),
|
|
577
|
+
"data-side": side,
|
|
578
|
+
direction: side,
|
|
579
|
+
offset: DEFAULT_OFFSET[side],
|
|
580
|
+
opacity: 1,
|
|
581
|
+
...props,
|
|
582
|
+
children: [children, /* @__PURE__ */ jsx("div", { className: cn("bg-foreground fill-foreground absolute z-50 size-2.5 rotate-45 rounded-[2px]", ARROW_POSITION_CLASSES[side]) })]
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
function MapZoomControl({ position = "top-1 left-1", className, ...props }) {
|
|
586
|
+
const map = _useMap();
|
|
587
|
+
const [zoomLevel, setZoomLevel] = useState(map.getZoom());
|
|
588
|
+
_useMapEvents({ zoomend: () => {
|
|
589
|
+
setZoomLevel(map.getZoom());
|
|
590
|
+
} });
|
|
591
|
+
return /* @__PURE__ */ jsx(MapControlContainer, {
|
|
592
|
+
className: cn(position, className),
|
|
593
|
+
children: /* @__PURE__ */ jsxs(ButtonGroup, {
|
|
594
|
+
orientation: "vertical",
|
|
595
|
+
"aria-label": "Zoom controls",
|
|
596
|
+
...props,
|
|
597
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
598
|
+
type: "button",
|
|
599
|
+
size: "icon",
|
|
600
|
+
variant: "outline",
|
|
601
|
+
"aria-label": "Zoom in",
|
|
602
|
+
title: "Zoom in",
|
|
603
|
+
className: "border",
|
|
604
|
+
disabled: zoomLevel >= map.getMaxZoom(),
|
|
605
|
+
onClick: () => map.zoomIn(),
|
|
606
|
+
children: /* @__PURE__ */ jsx(PlusIcon, {})
|
|
607
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
608
|
+
type: "button",
|
|
609
|
+
size: "icon",
|
|
610
|
+
variant: "outline",
|
|
611
|
+
"aria-label": "Zoom out",
|
|
612
|
+
title: "Zoom out",
|
|
613
|
+
className: "border",
|
|
614
|
+
disabled: zoomLevel <= map.getMinZoom(),
|
|
615
|
+
onClick: () => map.zoomOut(),
|
|
616
|
+
children: /* @__PURE__ */ jsx(MinusIcon, {})
|
|
617
|
+
})]
|
|
618
|
+
})
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function MapFullscreenControl({ position = "top-1 right-1", className, ...props }) {
|
|
622
|
+
const map = _useMap();
|
|
623
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
624
|
+
const { L } = useLeaflet();
|
|
625
|
+
useEffect(() => {
|
|
626
|
+
if (!L) return;
|
|
627
|
+
const fullscreenControl = new L.Control.FullScreen();
|
|
628
|
+
fullscreenControl.addTo(map);
|
|
629
|
+
const container = fullscreenControl.getContainer();
|
|
630
|
+
if (container) container.style.display = "none";
|
|
631
|
+
const handleEnter = () => setIsFullscreen(true);
|
|
632
|
+
const handleExit = () => setIsFullscreen(false);
|
|
633
|
+
map.on("enterFullscreen", handleEnter);
|
|
634
|
+
map.on("exitFullscreen", handleExit);
|
|
635
|
+
return () => {
|
|
636
|
+
fullscreenControl.remove();
|
|
637
|
+
map.off("enterFullscreen", handleEnter);
|
|
638
|
+
map.off("exitFullscreen", handleExit);
|
|
639
|
+
};
|
|
640
|
+
}, [L, map]);
|
|
641
|
+
return /* @__PURE__ */ jsx(MapControlContainer, {
|
|
642
|
+
className: cn(position, className),
|
|
643
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
644
|
+
type: "button",
|
|
645
|
+
size: "icon",
|
|
646
|
+
variant: "outline",
|
|
647
|
+
onClick: () => map.toggleFullscreen(),
|
|
648
|
+
"aria-label": isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
649
|
+
title: isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
650
|
+
className: "border",
|
|
651
|
+
...props,
|
|
652
|
+
children: isFullscreen ? /* @__PURE__ */ jsx(MinimizeIcon, {}) : /* @__PURE__ */ jsx(MaximizeIcon, {})
|
|
653
|
+
})
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
function MapLocatePulseIcon() {
|
|
657
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
658
|
+
className: "absolute -top-1 -right-1 flex size-3 rounded-full",
|
|
659
|
+
children: [/* @__PURE__ */ jsx("div", { className: "bg-primary absolute inline-flex size-full animate-ping rounded-full opacity-75" }), /* @__PURE__ */ jsx("div", { className: "bg-primary relative inline-flex size-3 rounded-full" })]
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function MapLocateControl({ watch = false, onLocationFound, onLocationError, position = "right-1 bottom-1", className, ...props }) {
|
|
663
|
+
const map = _useMap();
|
|
664
|
+
const [isLocating, setIsLocating] = useDebounceLoadingState(200);
|
|
665
|
+
const [location, setLocation] = useState(null);
|
|
666
|
+
function startLocating() {
|
|
667
|
+
setIsLocating(true);
|
|
668
|
+
map.locate({
|
|
669
|
+
setView: true,
|
|
670
|
+
maxZoom: map.getMaxZoom(),
|
|
671
|
+
watch
|
|
672
|
+
});
|
|
673
|
+
map.on("locationfound", (location) => {
|
|
674
|
+
setLocation(location.latlng);
|
|
675
|
+
setIsLocating(false);
|
|
676
|
+
onLocationFound?.(location);
|
|
677
|
+
});
|
|
678
|
+
map.on("locationerror", (error) => {
|
|
679
|
+
setLocation(null);
|
|
680
|
+
setIsLocating(false);
|
|
681
|
+
onLocationError?.(error);
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
function stopLocating() {
|
|
685
|
+
map.stopLocate();
|
|
686
|
+
map.off("locationfound");
|
|
687
|
+
map.off("locationerror");
|
|
688
|
+
setLocation(null);
|
|
689
|
+
setIsLocating(false);
|
|
690
|
+
}
|
|
691
|
+
useEffect(() => () => stopLocating(), []);
|
|
692
|
+
return /* @__PURE__ */ jsxs(MapControlContainer, {
|
|
693
|
+
className: cn(position, className),
|
|
694
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
695
|
+
type: "button",
|
|
696
|
+
size: "sm",
|
|
697
|
+
variant: location ? "default" : "secondary",
|
|
698
|
+
onClick: location ? stopLocating : startLocating,
|
|
699
|
+
disabled: isLocating,
|
|
700
|
+
title: isLocating ? "Locating..." : location ? "Stop tracking" : "Track location",
|
|
701
|
+
"aria-label": isLocating ? "Locating..." : location ? "Stop location tracking" : "Start location tracking",
|
|
702
|
+
className: "border",
|
|
703
|
+
...props,
|
|
704
|
+
children: isLocating ? /* @__PURE__ */ jsx(LoaderCircleIcon, { className: "animate-spin" }) : /* @__PURE__ */ jsx(NavigationIcon, {})
|
|
705
|
+
}), location && /* @__PURE__ */ jsx(MapMarker, {
|
|
706
|
+
position: location,
|
|
707
|
+
icon: /* @__PURE__ */ jsx(MapLocatePulseIcon, {})
|
|
708
|
+
})]
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function MapSearchControl({ position = "top-1 left-1", className, ...props }) {
|
|
712
|
+
return /* @__PURE__ */ jsx(MapControlContainer, {
|
|
713
|
+
className: cn("z-1001 w-60", position, className),
|
|
714
|
+
children: /* @__PURE__ */ jsx(PlaceAutocomplete, { ...props })
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
const MapDrawContext = createContext(null);
|
|
718
|
+
function useMapDrawContext() {
|
|
719
|
+
return useContext(MapDrawContext);
|
|
720
|
+
}
|
|
721
|
+
function MapDrawControl({ onLayersChange, position = "bottom-1 left-1", className, ...props }) {
|
|
722
|
+
const { L, LeafletDraw } = useLeaflet();
|
|
723
|
+
const map = _useMap();
|
|
724
|
+
const featureGroupRef = useRef(null);
|
|
725
|
+
const editControlRef = useRef(null);
|
|
726
|
+
const deleteControlRef = useRef(null);
|
|
727
|
+
const [activeMode, setActiveMode] = useState(null);
|
|
728
|
+
const [layersCount, setLayersCount] = useState(0);
|
|
729
|
+
function updateLayersCount() {
|
|
730
|
+
if (featureGroupRef.current) setLayersCount(featureGroupRef.current.getLayers().length);
|
|
731
|
+
}
|
|
732
|
+
function handleDrawCreated(event) {
|
|
733
|
+
if (!featureGroupRef.current) return;
|
|
734
|
+
const { layer } = event;
|
|
735
|
+
featureGroupRef.current.addLayer(layer);
|
|
736
|
+
onLayersChange?.(featureGroupRef.current);
|
|
737
|
+
updateLayersCount();
|
|
738
|
+
setActiveMode(null);
|
|
739
|
+
}
|
|
740
|
+
function handleDrawEditedOrDeleted() {
|
|
741
|
+
if (!featureGroupRef.current) return;
|
|
742
|
+
onLayersChange?.(featureGroupRef.current);
|
|
743
|
+
updateLayersCount();
|
|
744
|
+
setActiveMode(null);
|
|
745
|
+
}
|
|
746
|
+
useEffect(() => {
|
|
747
|
+
if (!L || !LeafletDraw || !map) return;
|
|
748
|
+
map.on(L.Draw.Event.CREATED, handleDrawCreated);
|
|
749
|
+
map.on(L.Draw.Event.EDITED, handleDrawEditedOrDeleted);
|
|
750
|
+
map.on(L.Draw.Event.DELETED, handleDrawEditedOrDeleted);
|
|
751
|
+
return () => {
|
|
752
|
+
map.off(L.Draw.Event.CREATED, handleDrawCreated);
|
|
753
|
+
map.off(L.Draw.Event.EDITED, handleDrawEditedOrDeleted);
|
|
754
|
+
map.off(L.Draw.Event.DELETED, handleDrawEditedOrDeleted);
|
|
755
|
+
};
|
|
756
|
+
}, [
|
|
757
|
+
L,
|
|
758
|
+
LeafletDraw,
|
|
759
|
+
map,
|
|
760
|
+
onLayersChange
|
|
761
|
+
]);
|
|
762
|
+
return /* @__PURE__ */ jsxs(MapDrawContext.Provider, {
|
|
763
|
+
value: {
|
|
764
|
+
featureGroup: featureGroupRef.current,
|
|
765
|
+
activeMode,
|
|
766
|
+
setActiveMode,
|
|
767
|
+
editControlRef,
|
|
768
|
+
deleteControlRef,
|
|
769
|
+
layersCount
|
|
770
|
+
},
|
|
771
|
+
children: [/* @__PURE__ */ jsx(LeafletFeatureGroup, { ref: featureGroupRef }), /* @__PURE__ */ jsx(MapControlContainer, {
|
|
772
|
+
className: cn(position, className),
|
|
773
|
+
children: /* @__PURE__ */ jsx(ButtonGroup, {
|
|
774
|
+
orientation: "vertical",
|
|
775
|
+
...props
|
|
776
|
+
})
|
|
777
|
+
})]
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
function MapDrawShapeButton({ drawMode, createDrawTool, className, ...props }) {
|
|
781
|
+
const drawContext = useMapDrawContext();
|
|
782
|
+
if (!drawContext) throw new Error("MapDrawShapeButton must be used within MapDrawControl");
|
|
783
|
+
const { L } = useLeaflet();
|
|
784
|
+
const map = _useMap();
|
|
785
|
+
const controlRef = useRef(null);
|
|
786
|
+
const { activeMode, setActiveMode } = drawContext;
|
|
787
|
+
const isActive = activeMode === drawMode;
|
|
788
|
+
useEffect(() => {
|
|
789
|
+
if (!L || !isActive) {
|
|
790
|
+
controlRef.current?.disable();
|
|
791
|
+
controlRef.current = null;
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const control = createDrawTool(L, map);
|
|
795
|
+
control.enable();
|
|
796
|
+
controlRef.current = control;
|
|
797
|
+
return () => {
|
|
798
|
+
control.disable();
|
|
799
|
+
controlRef.current = null;
|
|
800
|
+
};
|
|
801
|
+
}, [
|
|
802
|
+
L,
|
|
803
|
+
map,
|
|
804
|
+
isActive,
|
|
805
|
+
createDrawTool
|
|
806
|
+
]);
|
|
807
|
+
function handleClick() {
|
|
808
|
+
setActiveMode(isActive ? null : drawMode);
|
|
809
|
+
}
|
|
810
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
811
|
+
type: "button",
|
|
812
|
+
size: "sm",
|
|
813
|
+
"aria-label": `Draw ${drawMode}`,
|
|
814
|
+
title: `Draw ${drawMode}`,
|
|
815
|
+
className: cn("border", className),
|
|
816
|
+
variant: isActive ? "default" : "secondary",
|
|
817
|
+
disabled: activeMode === "edit" || activeMode === "delete",
|
|
818
|
+
onClick: handleClick,
|
|
819
|
+
...props
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function MapDrawMarker({ ...props }) {
|
|
823
|
+
return /* @__PURE__ */ jsx(MapDrawShapeButton, {
|
|
824
|
+
drawMode: "marker",
|
|
825
|
+
createDrawTool: (L, map) => new L.Draw.Marker(map, {
|
|
826
|
+
icon: L.divIcon({
|
|
827
|
+
className: "",
|
|
828
|
+
iconAnchor: [12, 12],
|
|
829
|
+
html: renderToString(/* @__PURE__ */ jsx(MapPinIcon, { className: "size-6" }))
|
|
830
|
+
}),
|
|
831
|
+
...props
|
|
832
|
+
}),
|
|
833
|
+
children: /* @__PURE__ */ jsx(MapPinIcon, {})
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
function MapDrawPolyline({ showLength = false, drawError = { color: "var(--color-destructive)" }, shapeOptions = {
|
|
837
|
+
color: "var(--color-primary)",
|
|
838
|
+
opacity: 1,
|
|
839
|
+
weight: 2
|
|
840
|
+
}, ...props }) {
|
|
841
|
+
const mapDrawHandleIcon = useMapDrawHandleIcon();
|
|
842
|
+
return /* @__PURE__ */ jsx(MapDrawShapeButton, {
|
|
843
|
+
drawMode: "polyline",
|
|
844
|
+
createDrawTool: (L, map) => new L.Draw.Polyline(map, {
|
|
845
|
+
...mapDrawHandleIcon ? {
|
|
846
|
+
icon: mapDrawHandleIcon,
|
|
847
|
+
touchIcon: mapDrawHandleIcon
|
|
848
|
+
} : {},
|
|
849
|
+
showLength,
|
|
850
|
+
drawError,
|
|
851
|
+
shapeOptions,
|
|
852
|
+
...props
|
|
853
|
+
}),
|
|
854
|
+
children: /* @__PURE__ */ jsx(WaypointsIcon, {})
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
function MapDrawCircle({ showRadius = false, shapeOptions = {
|
|
858
|
+
color: "var(--color-primary)",
|
|
859
|
+
opacity: 1,
|
|
860
|
+
weight: 2
|
|
861
|
+
}, ...props }) {
|
|
862
|
+
return /* @__PURE__ */ jsx(MapDrawShapeButton, {
|
|
863
|
+
drawMode: "circle",
|
|
864
|
+
createDrawTool: (L, map) => new L.Draw.Circle(map, {
|
|
865
|
+
showRadius,
|
|
866
|
+
shapeOptions,
|
|
867
|
+
...props
|
|
868
|
+
}),
|
|
869
|
+
children: /* @__PURE__ */ jsx(CircleIcon, {})
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
function MapDrawRectangle({ showArea = false, shapeOptions = {
|
|
873
|
+
color: "var(--color-primary)",
|
|
874
|
+
opacity: 1,
|
|
875
|
+
weight: 2
|
|
876
|
+
}, ...props }) {
|
|
877
|
+
return /* @__PURE__ */ jsx(MapDrawShapeButton, {
|
|
878
|
+
drawMode: "rectangle",
|
|
879
|
+
createDrawTool: (L, map) => new L.Draw.Rectangle(map, {
|
|
880
|
+
showArea,
|
|
881
|
+
shapeOptions,
|
|
882
|
+
...props
|
|
883
|
+
}),
|
|
884
|
+
children: /* @__PURE__ */ jsx(SquareIcon, {})
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function MapDrawPolygon({ drawError = { color: "var(--color-destructive)" }, shapeOptions = {
|
|
888
|
+
color: "var(--color-primary)",
|
|
889
|
+
opacity: 1,
|
|
890
|
+
weight: 2
|
|
891
|
+
}, ...props }) {
|
|
892
|
+
const mapDrawHandleIcon = useMapDrawHandleIcon();
|
|
893
|
+
return /* @__PURE__ */ jsx(MapDrawShapeButton, {
|
|
894
|
+
drawMode: "polygon",
|
|
895
|
+
createDrawTool: (L, map) => new L.Draw.Polygon(map, {
|
|
896
|
+
...mapDrawHandleIcon ? {
|
|
897
|
+
icon: mapDrawHandleIcon,
|
|
898
|
+
touchIcon: mapDrawHandleIcon
|
|
899
|
+
} : {},
|
|
900
|
+
drawError,
|
|
901
|
+
shapeOptions,
|
|
902
|
+
...props
|
|
903
|
+
}),
|
|
904
|
+
children: /* @__PURE__ */ jsx(PentagonIcon, {})
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
function MapDrawActionButton({ drawAction, createDrawTool, controlRef, className, ...props }) {
|
|
908
|
+
const drawContext = useMapDrawContext();
|
|
909
|
+
if (!drawContext) throw new Error("MapDrawActionButton must be used within MapDrawControl");
|
|
910
|
+
const { L } = useLeaflet();
|
|
911
|
+
const map = _useMap();
|
|
912
|
+
const { featureGroup, activeMode, setActiveMode, layersCount } = drawContext;
|
|
913
|
+
const isActive = activeMode === drawAction;
|
|
914
|
+
const hasFeatures = layersCount > 0;
|
|
915
|
+
useEffect(() => {
|
|
916
|
+
if (!L || !featureGroup || !isActive) {
|
|
917
|
+
controlRef.current?.disable?.();
|
|
918
|
+
controlRef.current = null;
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
const control = createDrawTool(L, map, featureGroup);
|
|
922
|
+
control.enable?.();
|
|
923
|
+
controlRef.current = control;
|
|
924
|
+
return () => {
|
|
925
|
+
control.disable?.();
|
|
926
|
+
controlRef.current = null;
|
|
927
|
+
};
|
|
928
|
+
}, [
|
|
929
|
+
L,
|
|
930
|
+
map,
|
|
931
|
+
isActive,
|
|
932
|
+
featureGroup,
|
|
933
|
+
createDrawTool
|
|
934
|
+
]);
|
|
935
|
+
function handleClick() {
|
|
936
|
+
controlRef.current?.save();
|
|
937
|
+
setActiveMode(isActive ? null : drawAction);
|
|
938
|
+
}
|
|
939
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
940
|
+
type: "button",
|
|
941
|
+
size: "sm",
|
|
942
|
+
"aria-label": `${drawAction === "edit" ? "Edit" : "Remove"} shapes`,
|
|
943
|
+
title: `${drawAction === "edit" ? "Edit" : "Remove"} shapes`,
|
|
944
|
+
variant: isActive ? "default" : "secondary",
|
|
945
|
+
disabled: !hasFeatures,
|
|
946
|
+
onClick: handleClick,
|
|
947
|
+
className: cn("border", className),
|
|
948
|
+
...props
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function MapDrawEdit({ selectedPathOptions = {
|
|
952
|
+
color: "var(--color-primary)",
|
|
953
|
+
fillColor: "var(--color-primary)",
|
|
954
|
+
weight: 2
|
|
955
|
+
}, ...props }) {
|
|
956
|
+
const { L } = useLeaflet();
|
|
957
|
+
const mapDrawHandleIcon = useMapDrawHandleIcon();
|
|
958
|
+
const drawContext = useMapDrawContext();
|
|
959
|
+
if (!drawContext) throw new Error("MapDrawEdit must be used within MapDrawControl");
|
|
960
|
+
useEffect(() => {
|
|
961
|
+
if (!L || !mapDrawHandleIcon) return;
|
|
962
|
+
L.Edit.PolyVerticesEdit.mergeOptions({
|
|
963
|
+
icon: mapDrawHandleIcon,
|
|
964
|
+
touchIcon: mapDrawHandleIcon,
|
|
965
|
+
drawError: { color: "var(--color-destructive)" }
|
|
966
|
+
});
|
|
967
|
+
L.Edit.SimpleShape.mergeOptions({
|
|
968
|
+
moveIcon: mapDrawHandleIcon,
|
|
969
|
+
resizeIcon: mapDrawHandleIcon,
|
|
970
|
+
touchMoveIcon: mapDrawHandleIcon,
|
|
971
|
+
touchResizeIcon: mapDrawHandleIcon
|
|
972
|
+
});
|
|
973
|
+
L.drawLocal.edit.handlers.edit.tooltip = {
|
|
974
|
+
text: "Drag handles or markers to edit.",
|
|
975
|
+
subtext: ""
|
|
976
|
+
};
|
|
977
|
+
L.drawLocal.edit.handlers.remove.tooltip = { text: "Click on a shape to remove." };
|
|
978
|
+
}, [mapDrawHandleIcon]);
|
|
979
|
+
return /* @__PURE__ */ jsx(MapDrawActionButton, {
|
|
980
|
+
drawAction: "edit",
|
|
981
|
+
controlRef: drawContext.editControlRef,
|
|
982
|
+
createDrawTool: (L, map, featureGroup) => new L.EditToolbar.Edit(map, {
|
|
983
|
+
featureGroup,
|
|
984
|
+
selectedPathOptions,
|
|
985
|
+
...props
|
|
986
|
+
}),
|
|
987
|
+
children: /* @__PURE__ */ jsx(PenLineIcon, {})
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function MapDrawDelete() {
|
|
991
|
+
const drawContext = useMapDrawContext();
|
|
992
|
+
if (!drawContext) throw new Error("MapDrawDelete must be used within MapDrawControl");
|
|
993
|
+
return /* @__PURE__ */ jsx(MapDrawActionButton, {
|
|
994
|
+
drawAction: "delete",
|
|
995
|
+
controlRef: drawContext.deleteControlRef,
|
|
996
|
+
createDrawTool: (L, map, featureGroup) => new L.EditToolbar.Delete(map, { featureGroup }),
|
|
997
|
+
children: /* @__PURE__ */ jsx(Trash2Icon, {})
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
function MapDrawUndo({ className, ...props }) {
|
|
1001
|
+
const drawContext = useMapDrawContext();
|
|
1002
|
+
if (!drawContext) throw new Error("MapDrawUndo must be used within MapDrawControl");
|
|
1003
|
+
const { activeMode, setActiveMode, editControlRef, deleteControlRef, layersCount } = drawContext;
|
|
1004
|
+
const isInEditMode = activeMode === "edit";
|
|
1005
|
+
const isInDeleteMode = activeMode === "delete";
|
|
1006
|
+
const isActive = (isInEditMode || isInDeleteMode) && layersCount > 0;
|
|
1007
|
+
function handleUndo() {
|
|
1008
|
+
if (isInEditMode) editControlRef.current?.revertLayers();
|
|
1009
|
+
else if (isInDeleteMode) deleteControlRef.current?.revertLayers();
|
|
1010
|
+
setActiveMode(null);
|
|
1011
|
+
}
|
|
1012
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
1013
|
+
type: "button",
|
|
1014
|
+
size: "sm",
|
|
1015
|
+
variant: "secondary",
|
|
1016
|
+
"aria-label": `Undo ${activeMode}`,
|
|
1017
|
+
title: `Undo ${activeMode}`,
|
|
1018
|
+
onClick: handleUndo,
|
|
1019
|
+
disabled: !isActive,
|
|
1020
|
+
className: cn("border", className),
|
|
1021
|
+
...props,
|
|
1022
|
+
children: /* @__PURE__ */ jsx(Undo2Icon, {})
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
function MapControlContainer({ className, ...props }) {
|
|
1026
|
+
const { L } = useLeaflet();
|
|
1027
|
+
const containerRef = useRef(null);
|
|
1028
|
+
useEffect(() => {
|
|
1029
|
+
if (!L) return;
|
|
1030
|
+
const element = containerRef.current;
|
|
1031
|
+
if (!element) return;
|
|
1032
|
+
L.DomEvent.disableClickPropagation(element);
|
|
1033
|
+
L.DomEvent.disableScrollPropagation(element);
|
|
1034
|
+
}, [L]);
|
|
1035
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1036
|
+
ref: containerRef,
|
|
1037
|
+
className: cn("absolute z-1000 size-fit cursor-default", className),
|
|
1038
|
+
...props
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
function useMapDrawHandleIcon() {
|
|
1042
|
+
const { L } = useLeaflet();
|
|
1043
|
+
if (!L) return null;
|
|
1044
|
+
return L.divIcon({
|
|
1045
|
+
iconAnchor: [8, 8],
|
|
1046
|
+
html: renderToString(/* @__PURE__ */ jsx(CircleIcon, { className: "fill-primary stroke-primary size-4 transition-transform hover:scale-110" }))
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
function useLeaflet() {
|
|
1050
|
+
const [L, setL] = useState(null);
|
|
1051
|
+
const [LeafletDraw, setLeafletDraw] = useState(null);
|
|
1052
|
+
useEffect(() => {
|
|
1053
|
+
async function loadLeaflet() {
|
|
1054
|
+
const leaflet = await import("leaflet");
|
|
1055
|
+
const leafletFullscreen = await import("leaflet.fullscreen");
|
|
1056
|
+
const leafletDraw = await import("leaflet-draw");
|
|
1057
|
+
const L_object = leaflet.default;
|
|
1058
|
+
if (L_object.Control && !L_object.Control.FullScreen) L_object.Control.FullScreen = leafletFullscreen.default || leafletFullscreen;
|
|
1059
|
+
setLeafletDraw(leafletDraw);
|
|
1060
|
+
setL(L_object);
|
|
1061
|
+
}
|
|
1062
|
+
if (L && LeafletDraw) return;
|
|
1063
|
+
if (typeof window === "undefined") return;
|
|
1064
|
+
loadLeaflet();
|
|
1065
|
+
}, [L, LeafletDraw]);
|
|
1066
|
+
return {
|
|
1067
|
+
L,
|
|
1068
|
+
LeafletDraw
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function useDebounceLoadingState(delay = 200) {
|
|
1072
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
1073
|
+
const [showLoading, setShowLoading] = useState(false);
|
|
1074
|
+
const timeoutRef = useRef(null);
|
|
1075
|
+
useEffect(() => {
|
|
1076
|
+
if (isLoading) timeoutRef.current = setTimeout(() => {
|
|
1077
|
+
setShowLoading(true);
|
|
1078
|
+
}, delay);
|
|
1079
|
+
else {
|
|
1080
|
+
if (timeoutRef.current) {
|
|
1081
|
+
clearTimeout(timeoutRef.current);
|
|
1082
|
+
timeoutRef.current = null;
|
|
1083
|
+
}
|
|
1084
|
+
setShowLoading(false);
|
|
1085
|
+
}
|
|
1086
|
+
return () => {
|
|
1087
|
+
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
|
1088
|
+
};
|
|
1089
|
+
}, [isLoading, delay]);
|
|
1090
|
+
return [showLoading, setIsLoading];
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
//#endregion
|
|
1094
|
+
export { useLeaflet as A, MapPolyline as C, MapTileLayer as D, MapSearchControl as E, MapTooltip as O, MapPolygon as S, MapRectangle as T, MapLayers as _, MapDrawCircle as a, MapMarker as b, MapDrawEdit as c, MapDrawPolyline as d, MapDrawRectangle as f, MapLayerGroup as g, MapFullscreenControl as h, MapControlContainer as i, PlaceAutocomplete as j, MapZoomControl as k, MapDrawMarker as l, MapFeatureGroup as m, MapCircle as n, MapDrawControl as o, MapDrawUndo as p, MapCircleMarker as r, MapDrawDelete as s, Map as t, MapDrawPolygon as u, MapLayersControl as v, MapPopup as w, MapMarkerClusterGroup as x, MapLocateControl as y };
|