@amir83nasr/map 1.0.4
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/LICENSE.md +21 -0
- package/README.md +143 -0
- package/dist/colors.d.ts +26 -0
- package/dist/defaults.d.ts +12 -0
- package/dist/emitter.d.ts +7 -0
- package/dist/format.d.ts +5 -0
- package/dist/geocode.d.ts +7 -0
- package/dist/i18n.d.ts +2 -0
- package/dist/icons.d.ts +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/map-style.d.ts +3 -0
- package/dist/picker.d.ts +80 -0
- package/dist/qompick-react.cjs +76 -0
- package/dist/qompick-react.css +1 -0
- package/dist/qompick-react.js +1897 -0
- package/dist/snap.d.ts +6 -0
- package/dist/types.d.ts +115 -0
- package/fonts/IRANYekanX Regular/0-255.pbf +0 -0
- package/fonts/IRANYekanX Regular/1024-1279.pbf +3 -0
- package/fonts/IRANYekanX Regular/10240-10495.pbf +3 -0
- package/fonts/IRANYekanX Regular/10496-10751.pbf +3 -0
- package/fonts/IRANYekanX Regular/10752-11007.pbf +3 -0
- package/fonts/IRANYekanX Regular/11008-11263.pbf +3 -0
- package/fonts/IRANYekanX Regular/11264-11519.pbf +3 -0
- package/fonts/IRANYekanX Regular/11520-11775.pbf +3 -0
- package/fonts/IRANYekanX Regular/11776-12031.pbf +3 -0
- package/fonts/IRANYekanX Regular/12032-12287.pbf +3 -0
- package/fonts/IRANYekanX Regular/12288-12543.pbf +3 -0
- package/fonts/IRANYekanX Regular/12544-12799.pbf +3 -0
- package/fonts/IRANYekanX Regular/1280-1535.pbf +3 -0
- package/fonts/IRANYekanX Regular/12800-13055.pbf +3 -0
- package/fonts/IRANYekanX Regular/13056-13311.pbf +3 -0
- package/fonts/IRANYekanX Regular/13312-13567.pbf +3 -0
- package/fonts/IRANYekanX Regular/13568-13823.pbf +3 -0
- package/fonts/IRANYekanX Regular/13824-14079.pbf +3 -0
- package/fonts/IRANYekanX Regular/14080-14335.pbf +3 -0
- package/fonts/IRANYekanX Regular/14336-14591.pbf +3 -0
- package/fonts/IRANYekanX Regular/14592-14847.pbf +3 -0
- package/fonts/IRANYekanX Regular/14848-15103.pbf +3 -0
- package/fonts/IRANYekanX Regular/15104-15359.pbf +3 -0
- package/fonts/IRANYekanX Regular/1536-1791.pbf +0 -0
- package/fonts/IRANYekanX Regular/15360-15615.pbf +3 -0
- package/fonts/IRANYekanX Regular/15616-15871.pbf +3 -0
- package/fonts/IRANYekanX Regular/15872-16127.pbf +3 -0
- package/fonts/IRANYekanX Regular/16128-16383.pbf +3 -0
- package/fonts/IRANYekanX Regular/16384-16639.pbf +3 -0
- package/fonts/IRANYekanX Regular/16640-16895.pbf +3 -0
- package/fonts/IRANYekanX Regular/16896-17151.pbf +3 -0
- package/fonts/IRANYekanX Regular/17152-17407.pbf +3 -0
- package/fonts/IRANYekanX Regular/17408-17663.pbf +3 -0
- package/fonts/IRANYekanX Regular/17664-17919.pbf +3 -0
- package/fonts/IRANYekanX Regular/1792-2047.pbf +3 -0
- package/fonts/IRANYekanX Regular/17920-18175.pbf +3 -0
- package/fonts/IRANYekanX Regular/18176-18431.pbf +3 -0
- package/fonts/IRANYekanX Regular/18432-18687.pbf +3 -0
- package/fonts/IRANYekanX Regular/18688-18943.pbf +3 -0
- package/fonts/IRANYekanX Regular/18944-19199.pbf +3 -0
- package/fonts/IRANYekanX Regular/19200-19455.pbf +3 -0
- package/fonts/IRANYekanX Regular/19456-19711.pbf +3 -0
- package/fonts/IRANYekanX Regular/19712-19967.pbf +3 -0
- package/fonts/IRANYekanX Regular/19968-20223.pbf +3 -0
- package/fonts/IRANYekanX Regular/20224-20479.pbf +3 -0
- package/fonts/IRANYekanX Regular/2048-2303.pbf +3 -0
- package/fonts/IRANYekanX Regular/20480-20735.pbf +3 -0
- package/fonts/IRANYekanX Regular/20736-20991.pbf +3 -0
- package/fonts/IRANYekanX Regular/20992-21247.pbf +3 -0
- package/fonts/IRANYekanX Regular/21248-21503.pbf +3 -0
- package/fonts/IRANYekanX Regular/21504-21759.pbf +3 -0
- package/fonts/IRANYekanX Regular/21760-22015.pbf +3 -0
- package/fonts/IRANYekanX Regular/22016-22271.pbf +3 -0
- package/fonts/IRANYekanX Regular/22272-22527.pbf +3 -0
- package/fonts/IRANYekanX Regular/22528-22783.pbf +3 -0
- package/fonts/IRANYekanX Regular/22784-23039.pbf +3 -0
- package/fonts/IRANYekanX Regular/2304-2559.pbf +3 -0
- package/fonts/IRANYekanX Regular/23040-23295.pbf +3 -0
- package/fonts/IRANYekanX Regular/23296-23551.pbf +3 -0
- package/fonts/IRANYekanX Regular/23552-23807.pbf +3 -0
- package/fonts/IRANYekanX Regular/23808-24063.pbf +3 -0
- package/fonts/IRANYekanX Regular/24064-24319.pbf +3 -0
- package/fonts/IRANYekanX Regular/24320-24575.pbf +3 -0
- package/fonts/IRANYekanX Regular/24576-24831.pbf +3 -0
- package/fonts/IRANYekanX Regular/24832-25087.pbf +3 -0
- package/fonts/IRANYekanX Regular/25088-25343.pbf +3 -0
- package/fonts/IRANYekanX Regular/25344-25599.pbf +3 -0
- package/fonts/IRANYekanX Regular/256-511.pbf +3 -0
- package/fonts/IRANYekanX Regular/2560-2815.pbf +3 -0
- package/fonts/IRANYekanX Regular/25600-25855.pbf +3 -0
- package/fonts/IRANYekanX Regular/25856-26111.pbf +3 -0
- package/fonts/IRANYekanX Regular/26112-26367.pbf +3 -0
- package/fonts/IRANYekanX Regular/26368-26623.pbf +3 -0
- package/fonts/IRANYekanX Regular/26624-26879.pbf +3 -0
- package/fonts/IRANYekanX Regular/26880-27135.pbf +3 -0
- package/fonts/IRANYekanX Regular/27136-27391.pbf +3 -0
- package/fonts/IRANYekanX Regular/27392-27647.pbf +3 -0
- package/fonts/IRANYekanX Regular/27648-27903.pbf +3 -0
- package/fonts/IRANYekanX Regular/27904-28159.pbf +3 -0
- package/fonts/IRANYekanX Regular/2816-3071.pbf +3 -0
- package/fonts/IRANYekanX Regular/28160-28415.pbf +3 -0
- package/fonts/IRANYekanX Regular/28416-28671.pbf +3 -0
- package/fonts/IRANYekanX Regular/28672-28927.pbf +3 -0
- package/fonts/IRANYekanX Regular/28928-29183.pbf +3 -0
- package/fonts/IRANYekanX Regular/29184-29439.pbf +3 -0
- package/fonts/IRANYekanX Regular/29440-29695.pbf +3 -0
- package/fonts/IRANYekanX Regular/29696-29951.pbf +3 -0
- package/fonts/IRANYekanX Regular/29952-30207.pbf +3 -0
- package/fonts/IRANYekanX Regular/30208-30463.pbf +3 -0
- package/fonts/IRANYekanX Regular/30464-30719.pbf +3 -0
- package/fonts/IRANYekanX Regular/3072-3327.pbf +3 -0
- package/fonts/IRANYekanX Regular/30720-30975.pbf +3 -0
- package/fonts/IRANYekanX Regular/30976-31231.pbf +3 -0
- package/fonts/IRANYekanX Regular/31232-31487.pbf +3 -0
- package/fonts/IRANYekanX Regular/31488-31743.pbf +3 -0
- package/fonts/IRANYekanX Regular/31744-31999.pbf +3 -0
- package/fonts/IRANYekanX Regular/32000-32255.pbf +3 -0
- package/fonts/IRANYekanX Regular/32256-32511.pbf +3 -0
- package/fonts/IRANYekanX Regular/32512-32767.pbf +3 -0
- package/fonts/IRANYekanX Regular/32768-33023.pbf +3 -0
- package/fonts/IRANYekanX Regular/33024-33279.pbf +3 -0
- package/fonts/IRANYekanX Regular/3328-3583.pbf +3 -0
- package/fonts/IRANYekanX Regular/33280-33535.pbf +3 -0
- package/fonts/IRANYekanX Regular/33536-33791.pbf +3 -0
- package/fonts/IRANYekanX Regular/33792-34047.pbf +3 -0
- package/fonts/IRANYekanX Regular/34048-34303.pbf +3 -0
- package/fonts/IRANYekanX Regular/34304-34559.pbf +3 -0
- package/fonts/IRANYekanX Regular/34560-34815.pbf +3 -0
- package/fonts/IRANYekanX Regular/34816-35071.pbf +3 -0
- package/fonts/IRANYekanX Regular/35072-35327.pbf +3 -0
- package/fonts/IRANYekanX Regular/35328-35583.pbf +3 -0
- package/fonts/IRANYekanX Regular/35584-35839.pbf +3 -0
- package/fonts/IRANYekanX Regular/3584-3839.pbf +3 -0
- package/fonts/IRANYekanX Regular/35840-36095.pbf +3 -0
- package/fonts/IRANYekanX Regular/36096-36351.pbf +3 -0
- package/fonts/IRANYekanX Regular/36352-36607.pbf +3 -0
- package/fonts/IRANYekanX Regular/36608-36863.pbf +3 -0
- package/fonts/IRANYekanX Regular/36864-37119.pbf +3 -0
- package/fonts/IRANYekanX Regular/37120-37375.pbf +3 -0
- package/fonts/IRANYekanX Regular/37376-37631.pbf +3 -0
- package/fonts/IRANYekanX Regular/37632-37887.pbf +3 -0
- package/fonts/IRANYekanX Regular/37888-38143.pbf +3 -0
- package/fonts/IRANYekanX Regular/38144-38399.pbf +3 -0
- package/fonts/IRANYekanX Regular/3840-4095.pbf +3 -0
- package/fonts/IRANYekanX Regular/38400-38655.pbf +3 -0
- package/fonts/IRANYekanX Regular/38656-38911.pbf +3 -0
- package/fonts/IRANYekanX Regular/38912-39167.pbf +3 -0
- package/fonts/IRANYekanX Regular/39168-39423.pbf +3 -0
- package/fonts/IRANYekanX Regular/39424-39679.pbf +3 -0
- package/fonts/IRANYekanX Regular/39680-39935.pbf +3 -0
- package/fonts/IRANYekanX Regular/39936-40191.pbf +3 -0
- package/fonts/IRANYekanX Regular/40192-40447.pbf +3 -0
- package/fonts/IRANYekanX Regular/40448-40703.pbf +3 -0
- package/fonts/IRANYekanX Regular/40704-40959.pbf +3 -0
- package/fonts/IRANYekanX Regular/4096-4351.pbf +3 -0
- package/fonts/IRANYekanX Regular/40960-41215.pbf +3 -0
- package/fonts/IRANYekanX Regular/41216-41471.pbf +3 -0
- package/fonts/IRANYekanX Regular/41472-41727.pbf +3 -0
- package/fonts/IRANYekanX Regular/41728-41983.pbf +3 -0
- package/fonts/IRANYekanX Regular/41984-42239.pbf +3 -0
- package/fonts/IRANYekanX Regular/42240-42495.pbf +3 -0
- package/fonts/IRANYekanX Regular/42496-42751.pbf +3 -0
- package/fonts/IRANYekanX Regular/42752-43007.pbf +3 -0
- package/fonts/IRANYekanX Regular/43008-43263.pbf +3 -0
- package/fonts/IRANYekanX Regular/43264-43519.pbf +3 -0
- package/fonts/IRANYekanX Regular/4352-4607.pbf +3 -0
- package/fonts/IRANYekanX Regular/43520-43775.pbf +3 -0
- package/fonts/IRANYekanX Regular/43776-44031.pbf +3 -0
- package/fonts/IRANYekanX Regular/44032-44287.pbf +3 -0
- package/fonts/IRANYekanX Regular/44288-44543.pbf +3 -0
- package/fonts/IRANYekanX Regular/44544-44799.pbf +3 -0
- package/fonts/IRANYekanX Regular/44800-45055.pbf +3 -0
- package/fonts/IRANYekanX Regular/45056-45311.pbf +3 -0
- package/fonts/IRANYekanX Regular/45312-45567.pbf +3 -0
- package/fonts/IRANYekanX Regular/45568-45823.pbf +3 -0
- package/fonts/IRANYekanX Regular/45824-46079.pbf +3 -0
- package/fonts/IRANYekanX Regular/4608-4863.pbf +3 -0
- package/fonts/IRANYekanX Regular/46080-46335.pbf +3 -0
- package/fonts/IRANYekanX Regular/46336-46591.pbf +3 -0
- package/fonts/IRANYekanX Regular/46592-46847.pbf +3 -0
- package/fonts/IRANYekanX Regular/46848-47103.pbf +3 -0
- package/fonts/IRANYekanX Regular/47104-47359.pbf +3 -0
- package/fonts/IRANYekanX Regular/47360-47615.pbf +3 -0
- package/fonts/IRANYekanX Regular/47616-47871.pbf +3 -0
- package/fonts/IRANYekanX Regular/47872-48127.pbf +3 -0
- package/fonts/IRANYekanX Regular/48128-48383.pbf +3 -0
- package/fonts/IRANYekanX Regular/48384-48639.pbf +3 -0
- package/fonts/IRANYekanX Regular/4864-5119.pbf +3 -0
- package/fonts/IRANYekanX Regular/48640-48895.pbf +3 -0
- package/fonts/IRANYekanX Regular/48896-49151.pbf +3 -0
- package/fonts/IRANYekanX Regular/49152-49407.pbf +3 -0
- package/fonts/IRANYekanX Regular/49408-49663.pbf +3 -0
- package/fonts/IRANYekanX Regular/49664-49919.pbf +3 -0
- package/fonts/IRANYekanX Regular/49920-50175.pbf +3 -0
- package/fonts/IRANYekanX Regular/50176-50431.pbf +3 -0
- package/fonts/IRANYekanX Regular/50432-50687.pbf +3 -0
- package/fonts/IRANYekanX Regular/50688-50943.pbf +3 -0
- package/fonts/IRANYekanX Regular/50944-51199.pbf +3 -0
- package/fonts/IRANYekanX Regular/512-767.pbf +3 -0
- package/fonts/IRANYekanX Regular/5120-5375.pbf +3 -0
- package/fonts/IRANYekanX Regular/51200-51455.pbf +3 -0
- package/fonts/IRANYekanX Regular/51456-51711.pbf +3 -0
- package/fonts/IRANYekanX Regular/51712-51967.pbf +3 -0
- package/fonts/IRANYekanX Regular/51968-52223.pbf +3 -0
- package/fonts/IRANYekanX Regular/52224-52479.pbf +3 -0
- package/fonts/IRANYekanX Regular/52480-52735.pbf +3 -0
- package/fonts/IRANYekanX Regular/52736-52991.pbf +3 -0
- package/fonts/IRANYekanX Regular/52992-53247.pbf +3 -0
- package/fonts/IRANYekanX Regular/53248-53503.pbf +3 -0
- package/fonts/IRANYekanX Regular/53504-53759.pbf +3 -0
- package/fonts/IRANYekanX Regular/5376-5631.pbf +3 -0
- package/fonts/IRANYekanX Regular/53760-54015.pbf +3 -0
- package/fonts/IRANYekanX Regular/54016-54271.pbf +3 -0
- package/fonts/IRANYekanX Regular/54272-54527.pbf +3 -0
- package/fonts/IRANYekanX Regular/54528-54783.pbf +3 -0
- package/fonts/IRANYekanX Regular/54784-55039.pbf +3 -0
- package/fonts/IRANYekanX Regular/55040-55295.pbf +3 -0
- package/fonts/IRANYekanX Regular/55296-55551.pbf +3 -0
- package/fonts/IRANYekanX Regular/55552-55807.pbf +3 -0
- package/fonts/IRANYekanX Regular/55808-56063.pbf +3 -0
- package/fonts/IRANYekanX Regular/56064-56319.pbf +3 -0
- package/fonts/IRANYekanX Regular/5632-5887.pbf +3 -0
- package/fonts/IRANYekanX Regular/56320-56575.pbf +3 -0
- package/fonts/IRANYekanX Regular/56576-56831.pbf +3 -0
- package/fonts/IRANYekanX Regular/56832-57087.pbf +3 -0
- package/fonts/IRANYekanX Regular/57088-57343.pbf +3 -0
- package/fonts/IRANYekanX Regular/57344-57599.pbf +3 -0
- package/fonts/IRANYekanX Regular/57600-57855.pbf +3 -0
- package/fonts/IRANYekanX Regular/57856-58111.pbf +3 -0
- package/fonts/IRANYekanX Regular/58112-58367.pbf +3 -0
- package/fonts/IRANYekanX Regular/58368-58623.pbf +3 -0
- package/fonts/IRANYekanX Regular/58624-58879.pbf +3 -0
- package/fonts/IRANYekanX Regular/5888-6143.pbf +3 -0
- package/fonts/IRANYekanX Regular/58880-59135.pbf +3 -0
- package/fonts/IRANYekanX Regular/59136-59391.pbf +3 -0
- package/fonts/IRANYekanX Regular/59392-59647.pbf +3 -0
- package/fonts/IRANYekanX Regular/59648-59903.pbf +3 -0
- package/fonts/IRANYekanX Regular/59904-60159.pbf +3 -0
- package/fonts/IRANYekanX Regular/60160-60415.pbf +3 -0
- package/fonts/IRANYekanX Regular/60416-60671.pbf +3 -0
- package/fonts/IRANYekanX Regular/60672-60927.pbf +3 -0
- package/fonts/IRANYekanX Regular/60928-61183.pbf +3 -0
- package/fonts/IRANYekanX Regular/61184-61439.pbf +3 -0
- package/fonts/IRANYekanX Regular/6144-6399.pbf +3 -0
- package/fonts/IRANYekanX Regular/61440-61695.pbf +3 -0
- package/fonts/IRANYekanX Regular/61696-61951.pbf +3 -0
- package/fonts/IRANYekanX Regular/61952-62207.pbf +3 -0
- package/fonts/IRANYekanX Regular/62208-62463.pbf +3 -0
- package/fonts/IRANYekanX Regular/62464-62719.pbf +3 -0
- package/fonts/IRANYekanX Regular/62720-62975.pbf +3 -0
- package/fonts/IRANYekanX Regular/62976-63231.pbf +3 -0
- package/fonts/IRANYekanX Regular/63232-63487.pbf +3 -0
- package/fonts/IRANYekanX Regular/63488-63743.pbf +3 -0
- package/fonts/IRANYekanX Regular/63744-63999.pbf +3 -0
- package/fonts/IRANYekanX Regular/6400-6655.pbf +3 -0
- package/fonts/IRANYekanX Regular/64000-64255.pbf +3 -0
- package/fonts/IRANYekanX Regular/64256-64511.pbf +0 -0
- package/fonts/IRANYekanX Regular/64512-64767.pbf +3 -0
- package/fonts/IRANYekanX Regular/64768-65023.pbf +0 -0
- package/fonts/IRANYekanX Regular/65024-65279.pbf +0 -0
- package/fonts/IRANYekanX Regular/65280-65535.pbf +3 -0
- package/fonts/IRANYekanX Regular/6656-6911.pbf +3 -0
- package/fonts/IRANYekanX Regular/6912-7167.pbf +3 -0
- package/fonts/IRANYekanX Regular/7168-7423.pbf +3 -0
- package/fonts/IRANYekanX Regular/7424-7679.pbf +3 -0
- package/fonts/IRANYekanX Regular/768-1023.pbf +3 -0
- package/fonts/IRANYekanX Regular/7680-7935.pbf +3 -0
- package/fonts/IRANYekanX Regular/7936-8191.pbf +3 -0
- package/fonts/IRANYekanX Regular/8192-8447.pbf +0 -0
- package/fonts/IRANYekanX Regular/8448-8703.pbf +3 -0
- package/fonts/IRANYekanX Regular/8704-8959.pbf +0 -0
- package/fonts/IRANYekanX Regular/8960-9215.pbf +3 -0
- package/fonts/IRANYekanX Regular/9216-9471.pbf +3 -0
- package/fonts/IRANYekanX Regular/9472-9727.pbf +3 -0
- package/fonts/IRANYekanX Regular/9728-9983.pbf +3 -0
- package/fonts/IRANYekanX Regular/9984-10239.pbf +4 -0
- package/fonts/IRANYekanX-Regular.woff2 +0 -0
- package/package.json +55 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amir83Nasr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @amir83nasr/map
|
|
2
|
+
|
|
3
|
+
MapLibre location picker as a publish-ready npm library. React-only (`@amir83nasr/map`) + Vite demo. Persian RTL, mobile-first, vector tiles.
|
|
4
|
+
|
|
5
|
+
> Agent handbook: [`docs/USAGE.md`](../docs/USAGE.md) — copy-paste install, worker setup, recipes, callbacks, troubleshooting.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm add @amir83nasr/map maplibre-gl
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`maplibre-gl` v6, `react`/`react-dom` are peer dependencies and must be installed separately.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import '@amir83nasr/map/styles.css'; // includes MapLibre CSS
|
|
17
|
+
import { LocationPickerView } from '@amir83nasr/map';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start — React / Next.js
|
|
21
|
+
|
|
22
|
+
Client-only. In Next.js App Router put it in a client component (`'use client'`) and dynamic-import with `ssr: false`.
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
'use client';
|
|
26
|
+
import { LocationPickerView } from '@amir83nasr/map';
|
|
27
|
+
|
|
28
|
+
<LocationPickerView
|
|
29
|
+
markers={[{ name: 'Venue', lat: 34.63, lng: 50.87 }]}
|
|
30
|
+
onConfirm={(loc) => console.log(loc.lat, loc.lng, loc.address)}
|
|
31
|
+
/>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
41 Qom neighborhood suggestions ship as the package default — no `search.suggestions` needed. Pass your own array to replace them, or `suggestions: []` to disable.
|
|
35
|
+
|
|
36
|
+
The component calls `destroy()` in its own `useEffect` cleanup — no manual init/destroy. Give it a height (default `480px` via `style`); without one the map renders empty.
|
|
37
|
+
|
|
38
|
+
Configure MapLibre's worker before mount (bundler-specific — this package cannot auto-bundle the worker for you). Vite:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { setupQomPickWorker } from '@amir83nasr/map';
|
|
42
|
+
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
|
|
43
|
+
|
|
44
|
+
setupQomPickWorker(workerUrl);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Next.js/webpack: copy both `maplibre-gl-worker.mjs` and `maplibre-gl-shared.mjs`, then pass the public worker URL to `setupQomPickWorker()`. A missing worker now calls `onError`, writes one `console.warn`, and shows an actionable `.qp-err` over the gray map.
|
|
48
|
+
|
|
49
|
+
Public surface: one component (`LocationPickerView`), its props type (`QomPickProps`), the `setupQomPickWorker` helper (alias `setupMapWorker`), exported `QOM_SUGGESTIONS`, and exported types. The engine class stays internal. Engine options apply only on mount — remount with a `key` for new options; only callbacks stay live.
|
|
50
|
+
|
|
51
|
+
## Config reference
|
|
52
|
+
|
|
53
|
+
Props of `LocationPickerView` (`QomPickProps` = engine options minus `container`, plus `className`/`style`; callbacks are props):
|
|
54
|
+
|
|
55
|
+
| Key | Type | Default | Notes |
|
|
56
|
+
| ---------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `map.center/zoom/minZoom/maxZoom/bounds` | — | Qom `34.6416,50.8764`, z14 | — |
|
|
58
|
+
| `map.style` | URL \| StyleSpecification | Snapp-like OpenFreeMap | vector style only (never raster) |
|
|
59
|
+
| `map.glyphs` | string | jsDelivr CDN (bundled IRANYekanX PBFs) | relative paths resolve against page base; self-host by copying `fonts/` |
|
|
60
|
+
| `controls` | `{gps, confirmButton, searchTrigger, developers}` | all true except developers | `developers` opens the in-map developer docs |
|
|
61
|
+
| `search` | `{enabled, placeholder, suggestions, minLength, debounceMs, limit}` | 41 Qom neighborhoods; `3 / 350ms / 5` | Nominatim, Qom viewbox; custom array replaces, `[]` disables; `limit` also caps local suggestions |
|
|
62
|
+
| `sheet` | `{enabled}` | true | — |
|
|
63
|
+
| `behavior` | `{snapToRoad, resolveOnMove, resolveDelayMs, settleDelayMs, snapDelayMs, pickZoom, locateZoom}` | `400/250/900/15/18` | snap waits longer than settle; pinch-zoom never snaps |
|
|
64
|
+
| `markers` | `Venue[]` | `[]` (off) | generic pins, demo enables Qom data |
|
|
65
|
+
| `i18n` | `{labels}` | Persian (`fa`), always RTL | full label override |
|
|
66
|
+
| callbacks | `onLocationChange/onAddressResolved/onSearchResults/onPick/onConfirm/onLocate/onError` | — | props |
|
|
67
|
+
| `className/style` | — | height `480` | pass a height, otherwise the map is empty |
|
|
68
|
+
|
|
69
|
+
Types (`PickerLocation`, `Venue`, `SearchSuggestion`, `QomPickProps`, …) are re-exported from `@amir83nasr/map`.
|
|
70
|
+
|
|
71
|
+
## Appearance (CSS variables)
|
|
72
|
+
|
|
73
|
+
No theme prop — scope your own vars under `.qp`:
|
|
74
|
+
|
|
75
|
+
```css
|
|
76
|
+
.qp {
|
|
77
|
+
--qp-brand: #16a34a;
|
|
78
|
+
--qp-ink: #111;
|
|
79
|
+
--qp-radius: 16px;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Vars: `--qp-brand --qp-brand-dark --qp-bg --qp-card --qp-ink --qp-muted --qp-line --qp-radius --qp-shadow --qp-font`. All UI scoped under `.qp-`.
|
|
84
|
+
|
|
85
|
+
IRANYekanX ships inside the package: `styles.css` carries the `@font-face` (woff2 inlined as a data URI), so the Persian UI font works with the same single CSS import — no font setup.
|
|
86
|
+
|
|
87
|
+
## Map style
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<LocationPickerView map={{ style: 'https://demotiles.maplibre.org/style.json' }} />
|
|
91
|
+
<LocationPickerView map={{ glyphs: '/fonts/{fontstack}/{range}.pbf' }} /> // self-hosted Persian glyphs
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Default basemap is vector (OpenFreeMap + OpenMapTiles); raster styles are not supported. Default `map.glyphs` points at the IRANYekanX PBFs published with this package (jsDelivr), so Persian map labels work with zero setup; pass `map.glyphs` to self-host them.
|
|
95
|
+
|
|
96
|
+
## i18n / RTL
|
|
97
|
+
|
|
98
|
+
`i18n: { labels: { confirm: 'OK' } }` overrides any label. Persian defaults built in; layout is always RTL.
|
|
99
|
+
|
|
100
|
+
## In-map developer docs
|
|
101
|
+
|
|
102
|
+
Pass `controls={{ developers: true }}` to show a «توسعهدهندگان» button on the map. It opens Persian docs inside the map (`.qp-docs`): intro, install, React quick start including worker setup, key settings, appearance, project structure, two copyable prompts (new project / existing project), and FAQ (empty map, missing Persian map labels / glyphs, explicit MapLibre worker failure, maplibre v6 peer, Next.js).
|
|
103
|
+
|
|
104
|
+
## Project structure
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
package/ # npm package `@amir83nasr/map`
|
|
108
|
+
src/index.tsx # public: LocationPickerView + types
|
|
109
|
+
src/picker.ts # internal engine (not exported)
|
|
110
|
+
src/*.ts # helpers (geocode, snap, format, i18n, …)
|
|
111
|
+
src/styles.css # ships as dist/qompick-react.css (import as `@amir83nasr/map/styles.css`)
|
|
112
|
+
fonts/ # IRANYekanX woff2 + map glyph PBFs (published in the tarball)
|
|
113
|
+
test/helpers.test.ts # vitest
|
|
114
|
+
demo/ # Vite demo (vector map, port 5500); ships MapLibre worker files into dist
|
|
115
|
+
docs/ # ARCHITECTURE.md + CHANGELOG.md + USAGE.md (agent handbook)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Scripts
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
pnpm dev # demo
|
|
122
|
+
pnpm build # react -> demo
|
|
123
|
+
pnpm typecheck # all packages
|
|
124
|
+
pnpm lint # eslint
|
|
125
|
+
pnpm test # react vitest
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Publish
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
cd package && pnpm build && pnpm pack --dry-run # inspect tarball
|
|
132
|
+
pnpm publish --filter @amir83nasr/map --access public
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Versioning: semver, currently `1.0.4`.
|
|
136
|
+
|
|
137
|
+
## Limitations
|
|
138
|
+
|
|
139
|
+
- Nominatim reverse/search: rate-limited, needs network; no offline geocoding.
|
|
140
|
+
- Snap-to-road is pixel-space on rendered roads (zoom >= 15), not routable — use OSRM/Valhalla `nearest` for true routing.
|
|
141
|
+
- Single Persian Regular glyph weight; no bold PBFs bundled.
|
|
142
|
+
- One picker per container; module-level worker URL shared across instances.
|
|
143
|
+
- Configure `setupQomPickWorker(workerUrl)` before mount. Vite should import `maplibre-gl-worker.mjs?worker&url`; Next.js/webpack should ship both `maplibre-gl-worker.mjs` + `maplibre-gl-shared.mjs`. Missing worker triggers `onError`, one `console.warn`, and a visible `.qp-err`. Demo build already copies both files via `demo/vite.config.ts` (`maplibre-worker-assets`).
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const MAP_BG = "#e9ebee";
|
|
2
|
+
export declare const RESIDENTIAL_FILL = "#e9ebee";
|
|
3
|
+
export declare const PARK_FILL = "#a9e29b";
|
|
4
|
+
export declare const WOOD_FILL = "#9ed894";
|
|
5
|
+
export declare const GRASS_FILL = "#b5e8a6";
|
|
6
|
+
export declare const CEMETERY_FILL = "#d3ddd0";
|
|
7
|
+
export declare const SAND_FILL = "#e9dfc0";
|
|
8
|
+
export declare const WATER_FILL = "#b9d9f2";
|
|
9
|
+
export declare const WATER_FILL_FAINT = "#cfe4f7";
|
|
10
|
+
export declare const WATER_LINE = "#a9cdf0";
|
|
11
|
+
export declare const WATER_LABEL = "#4e6e94";
|
|
12
|
+
export declare const BUILDING_FILL = "#dfe3e9";
|
|
13
|
+
export declare const BUILDING_TOP = "#e9ebee";
|
|
14
|
+
export declare const ROAD_MINOR = "#ffffff";
|
|
15
|
+
export declare const ROAD_MINOR_CASING = "#d9dde2";
|
|
16
|
+
export declare const ROAD_MAJOR = "#ffffff";
|
|
17
|
+
export declare const ROAD_MAJOR_CASING = "#d5dae0";
|
|
18
|
+
export declare const PATH_COLOR = "#cdbfa8";
|
|
19
|
+
export declare const AEROWAY_AREA = "#e9ebee";
|
|
20
|
+
export declare const AEROWAY_CASING = "#c6ccd4";
|
|
21
|
+
export declare const RAIL_COLOR = "#b9bdc4";
|
|
22
|
+
export declare const BOUNDARY = "#a7aab3";
|
|
23
|
+
export declare const LABEL_ROAD = "#5b6067";
|
|
24
|
+
export declare const LABEL_PLACE = "#2f3338";
|
|
25
|
+
export declare const LABEL_HALO = "#f7f8f9";
|
|
26
|
+
export declare const POI_COLOR = "#565b62";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { I18nOption, Labels, LocationPickerOptions, SearchSuggestion } from './types.js';
|
|
2
|
+
export declare const QOM_SUGGESTIONS: SearchSuggestion[];
|
|
3
|
+
export declare const DEFAULT_CENTER: {
|
|
4
|
+
lat: number;
|
|
5
|
+
lng: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const DEFAULT_ZOOM = 14;
|
|
8
|
+
export declare function resolveLabels(i18n?: I18nOption): Labels;
|
|
9
|
+
export declare function resolveDir(): 'rtl';
|
|
10
|
+
export declare function mergeOptions(user: LocationPickerOptions): Required<Omit<LocationPickerOptions, 'container' | keyof import('./types.js').PickerCallbacks>> & {
|
|
11
|
+
container: LocationPickerOptions['container'];
|
|
12
|
+
};
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function faStr(value: string | number): string;
|
|
2
|
+
export declare function faCoord(n: number, digits?: number): string;
|
|
3
|
+
export declare function enDigits(s: string): string;
|
|
4
|
+
export declare function shortAddr(display: string | undefined): string;
|
|
5
|
+
export declare function isValidLatLng(lat: number, lng: number): boolean;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { NominatimResult } from './types.js';
|
|
2
|
+
export declare function reverseGeocode(lat: number, lng: number, opts?: {
|
|
3
|
+
signal?: AbortSignal;
|
|
4
|
+
}): Promise<string>;
|
|
5
|
+
export declare function searchLocation(query: string, limit?: number, opts?: {
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}): Promise<NominatimResult[]>;
|
package/dist/i18n.d.ts
ADDED
package/dist/icons.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ICONS: Record<'locate' | 'pin' | 'search' | 'star' | 'trophy' | 'x', string>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LocationPickerOptions } from './types.js';
|
|
2
|
+
/** Public props: engine options minus `container` (React owns the element) + layout. */
|
|
3
|
+
export type QomPickProps = Omit<LocationPickerOptions, 'container'> & {
|
|
4
|
+
className?: string;
|
|
5
|
+
style?: React.CSSProperties;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Set MapLibre's worker URL before mounting `LocationPickerView`.
|
|
9
|
+
* Worker files are bundler-specific — the package cannot ship them for you.
|
|
10
|
+
* Vite: `import url from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'`.
|
|
11
|
+
* Next.js/webpack: copy both `maplibre-gl-worker.mjs` and `maplibre-gl-shared.mjs`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setupQomPickWorker(workerUrl: string): void;
|
|
14
|
+
/** Alias matching the package name; identical to `setupQomPickWorker`. */
|
|
15
|
+
export declare const setupMapWorker: typeof setupQomPickWorker;
|
|
16
|
+
export declare function LocationPickerView({ className, style, ...opts }: QomPickProps): React.JSX.Element;
|
|
17
|
+
export type * from './types.js';
|
|
18
|
+
export { QOM_SUGGESTIONS } from './defaults.js';
|
package/dist/picker.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { EventHandler, LocationPickerOptions, PickerEvent, PickerLocation } from './types.js';
|
|
2
|
+
export declare function isMapLibreWorkerError(value: unknown): boolean;
|
|
3
|
+
export declare class LocationPicker {
|
|
4
|
+
private opts;
|
|
5
|
+
private emitter;
|
|
6
|
+
private root;
|
|
7
|
+
private map;
|
|
8
|
+
private myMarker;
|
|
9
|
+
private venueMarkers;
|
|
10
|
+
private labelEl;
|
|
11
|
+
private toastEl;
|
|
12
|
+
private overlay;
|
|
13
|
+
private resolveT;
|
|
14
|
+
private searchT;
|
|
15
|
+
private toastT;
|
|
16
|
+
private settleT;
|
|
17
|
+
private confirmT;
|
|
18
|
+
private venueT;
|
|
19
|
+
private revSeq;
|
|
20
|
+
private searchSeq;
|
|
21
|
+
private revAbort;
|
|
22
|
+
private searchAbort;
|
|
23
|
+
private destroyed;
|
|
24
|
+
private workerFailed;
|
|
25
|
+
private boundsSaved;
|
|
26
|
+
lat: number;
|
|
27
|
+
lng: number;
|
|
28
|
+
address: string;
|
|
29
|
+
resolving: boolean;
|
|
30
|
+
private onKey;
|
|
31
|
+
private isHidden;
|
|
32
|
+
constructor(options: LocationPickerOptions);
|
|
33
|
+
on(ev: PickerEvent, fn: EventHandler): () => void;
|
|
34
|
+
off(ev: PickerEvent, fn: EventHandler): void;
|
|
35
|
+
getLocation(): PickerLocation;
|
|
36
|
+
setLocation(lat: number, lng: number, opts?: {
|
|
37
|
+
moveMap?: boolean;
|
|
38
|
+
resolve?: boolean;
|
|
39
|
+
zoom?: number;
|
|
40
|
+
}): void;
|
|
41
|
+
locate(): void;
|
|
42
|
+
confirm(customAddress?: string): PickerLocation;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
private t;
|
|
45
|
+
private build;
|
|
46
|
+
private wire;
|
|
47
|
+
private initMap;
|
|
48
|
+
private showWorkerError;
|
|
49
|
+
private showMyPos;
|
|
50
|
+
private addVenues;
|
|
51
|
+
private scheduleResolve;
|
|
52
|
+
private resolveAddress;
|
|
53
|
+
private renderLabel;
|
|
54
|
+
private devDocsHtml;
|
|
55
|
+
private openDocs;
|
|
56
|
+
private closeDocs;
|
|
57
|
+
private copyPrompt;
|
|
58
|
+
private fallbackCopy;
|
|
59
|
+
private openSearch;
|
|
60
|
+
private closeSearch;
|
|
61
|
+
private pickSuggestion;
|
|
62
|
+
private pickResult;
|
|
63
|
+
private suggestionRow;
|
|
64
|
+
private resultRow;
|
|
65
|
+
private filteredSuggestions;
|
|
66
|
+
private suggestions;
|
|
67
|
+
private renderSuggest;
|
|
68
|
+
private renderDeskSuggest;
|
|
69
|
+
private onQuery;
|
|
70
|
+
private onQueryShared;
|
|
71
|
+
private runSearch;
|
|
72
|
+
private onDeskQuery;
|
|
73
|
+
private runRemoteSearch;
|
|
74
|
+
private openModal;
|
|
75
|
+
private closeModal;
|
|
76
|
+
private openConfirm;
|
|
77
|
+
private finishConfirm;
|
|
78
|
+
private toast;
|
|
79
|
+
private fail;
|
|
80
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";var le=Object.defineProperty;var ce=(s,e,t)=>e in s?le(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t;var d=(s,e,t)=>ce(s,typeof e!="symbol"?e+"":e,t);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const de=require("react/jsx-runtime"),P=require("react"),z=require("maplibre-gl"),pe={searchPlaceholder:"جستوجوی موقعیت...",searchTitle:"جستجوی موقعیت",suggestedPlaces:"مکانهای پیشنهادی",searching:"در حال جستجو...",noResults:"نتیجهای پیدا نشد؛ عبارت دیگری امتحان کنید",searchError:"خطا در جستجو؛ اتصال را بررسی کنید",confirmLocation:"تایید موقعیت",confirm:"تایید",editOnMap:"ویرایش روی نقشه",chosenAddress:"آدرس انتخابی",coordsToServer:"مختصات ارسالی به سرور",close:"بستن",clearSearch:"پاک کردن جستجو",currentLocation:"موقعیت فعلی",locating:"در حال پیدا کردن آدرس...",resolving:"در حال پیدا کردن آدرس...",registered:"موقعیت ثبت شد",gpsUnsupported:"GPS در این مرورگر پشتیبانی نمیشود",locateTimeout:"دریافت موقعیت طول کشید؛ دستی انتخاب کنید",locateUnavailable:"موقعیت در دسترس نیست؛ دستی انتخاب کنید",geoTitle:"دسترسی به موقعیت مکانی",geoText:"برای انتخاب موقعیت فعلی شما، لازم است دسترسی موقعیت مکانی را فعال کنید.",geoBlocked:"مرورگر قبلاً دسترسی را بسته است. از تنظیمات سایت مرورگر، دسترسی Location را مجاز کنید.",enableAccess:"فعال کردن دسترسی",developers:"توسعهدهندگان",backToMap:"بازگشت به نقشه",copy:"کپی",copied:"کپی شد",submitting:"در حال ثبت..."},ie=[{name:"۱۹ دی، قم",addr:"محله ۱۹ دی، شهر قم",lat:34.6558,lng:50.9055},{name:"آذر، قم",addr:"محله آذر، شهر قم",lat:34.6492,lng:50.8965},{name:"امام حسین، قم",addr:"میدان امام حسین، شهر قم",lat:34.6512,lng:50.8932},{name:"انسجام، قم",addr:"محله انسجام، شهر قم",lat:34.6058,lng:50.8752},{name:"انقلاب، قم",addr:"خیابان انقلاب، شهر قم",lat:34.6462,lng:50.8915},{name:"باجک، قم",addr:"محله باجک، شهر قم",lat:34.6601,lng:50.8823},{name:"بلوار امین، قم",addr:"محله بلوار امین، شهر قم",lat:34.6248,lng:50.8792},{name:"بنیاد، قم",addr:"محله بنیاد، شهر قم",lat:34.6475,lng:50.8985},{name:"پردیسان، قم",addr:"شهرک پردیسان، شهر قم",lat:34.6021,lng:50.8412},{name:"توحید، قم",addr:"محله توحید، شهر قم",lat:34.6685,lng:50.8748},{name:"جمکران، قم",addr:"محله جمکران، شهر قم",lat:34.5853,lng:50.9068},{name:"چهارمردان، قم",addr:"محله چهارمردان، شهر قم",lat:34.6401,lng:50.8873},{name:"حافظ، قم",addr:"خیابان حافظ، شهر قم",lat:34.6368,lng:50.8785},{name:"حرم، قم",addr:"محله حرم، شهر قم",lat:34.6448,lng:50.8791},{name:"خاکفرج، قم",addr:"محله خاکفرج، شهر قم",lat:34.6798,lng:50.9012},{name:"دروازه ری، قم",addr:"محله دروازه ری، شهر قم",lat:34.6352,lng:50.8958},{name:"دورشهر، قم",addr:"محله دورشهر، شهر قم",lat:34.6442,lng:50.8744},{name:"زاویه، قم",addr:"محله زاویه، شهر قم",lat:34.6385,lng:50.8682},{name:"زنبیلآباد، قم",addr:"محله زنبیلآباد، شهر قم",lat:34.6221,lng:50.8912},{name:"سالاریه، قم",addr:"محله سالاریه، شهر قم",lat:34.6289,lng:50.8624},{name:"سمیه، قم",addr:"خیابان سمیه، شهر قم",lat:34.6521,lng:50.8842},{name:"شهدا، قم",addr:"خیابان شهدا، شهر قم",lat:34.6425,lng:50.8831},{name:"شهرقائم، قم",addr:"شهرک قائم، شهر قم",lat:34.6158,lng:50.8505},{name:"شهرک امام حسن، قم",addr:"شهرک امام حسن، شهر قم",lat:34.6358,lng:50.8521},{name:"شهرک مهدیه، قم",addr:"شهرک مهدیه، شهر قم",lat:34.6258,lng:50.8452},{name:"شهرک ولیعصر، قم",addr:"شهرک ولیعصر، شهر قم",lat:34.6125,lng:50.8585},{name:"شیخآباد، قم",addr:"محله شیخآباد، شهر قم",lat:34.6582,lng:50.8718},{name:"صدوق، قم",addr:"خیابان صدوق، شهر قم",lat:34.6198,lng:50.8725},{name:"صفائیه، قم",addr:"محله صفائیه، شهر قم",lat:34.6327,lng:50.8713},{name:"عسگریه، قم",addr:"محله عسگریه، شهر قم",lat:34.6305,lng:50.9018},{name:"عطاران، قم",addr:"محله عطاران، شهر قم",lat:34.6548,lng:50.8895},{name:"فرهنگیان، قم",addr:"محله فرهنگیان، شهر قم",lat:34.6105,lng:50.8702},{name:"کشاورز، قم",addr:"محله کشاورز، شهر قم",lat:34.6642,lng:50.8885},{name:"کلهری، قم",addr:"منطقه کلهری، شهر قم",lat:34.6211,lng:50.9055},{name:"کیوانفر، قم",addr:"محله کیوانفر، شهر قم",lat:34.6172,lng:50.8856},{name:"مطهری، قم",addr:"خیابان مطهری، شهر قم",lat:34.6485,lng:50.8878},{name:"معلم، قم",addr:"میدان معلم، شهر قم",lat:34.6275,lng:50.8942},{name:"نوبهار، قم",addr:"محله نوبهار، شهر قم",lat:34.6623,lng:50.8952},{name:"نیروگاه، قم",addr:"محله نیروگاه، شهر قم",lat:34.6712,lng:50.8851},{name:"هفتتیر، قم",addr:"خیابان هفتتیر، شهر قم",lat:34.6442,lng:50.8995},{name:"یزدانشهر، قم",addr:"محله یزدانشهر، شهر قم",lat:34.6012,lng:50.8628}],$={lat:34.6416,lng:50.8764},ue=14;function he(s){return{...pe,...(s==null?void 0:s.labels)??{}}}function me(){return"rtl"}function ge(s){const e=s??{};return{container:e.container,map:{center:{...$},zoom:ue,minZoom:11,maxZoom:19,bounds:[[34.15,50.35],[35.05,51.45]],...e.map??{}},controls:{gps:!0,confirmButton:!0,searchTrigger:!0,developers:!1,...e.controls??{}},search:{enabled:!0,suggestions:ie,minLength:3,debounceMs:350,limit:5,...e.search??{}},sheet:{enabled:!0,...e.sheet??{}},behavior:{snapToRoad:!0,resolveOnMove:!0,resolveDelayMs:400,settleDelayMs:250,snapDelayMs:900,pickZoom:15,locateZoom:18,...e.behavior??{}},i18n:{labels:he(e.i18n)},markers:e.markers??[]}}const ye="#e9ebee",fe="#e9ebee",G="#a9e29b",be="#9ed894",ve="#b5e8a6",we="#d3ddd0",qe="#e9dfc0",xe="#b9d9f2",ke="#cfe4f7",N="#a9cdf0",V="#4e6e94",U="#dfe3e9",Se="#e9ebee",O="#ffffff",Le="#d9dde2",x="#ffffff",A="#d5dae0",Ee="#cdbfa8",Te="#e9ebee",H="#c6ccd4",F="#b9bdc4",Q="#a7aab3",Y="#5b6067",_="#2f3338",f="#f7f8f9",j="#565b62",Me=["coalesce",["get","name:fa"],["get","name:nonlatin"],["get","name"],["get","name:latin"]],Re=["0","۰","1","۱","2","۲","3","۳","4","۴","5","۵","6","۶","7","۷","8","۸","9","۹","٠","۰","١","۱","٢","۲","٣","۳","٤","۴","٥","۵","٦","۶","٧","۷","٨","۸","٩","۹"];function Ae(s,e=40){const t=["coalesce",s,""],o=["split",["to-string",t],""],r=["length",o],i=[];for(let a=0;a<e;a++){const n=["at",a,o];i.push(["case",[">",r,a],["match",n,...Re,n],""])}return i.push(["slice",["to-string",t],e]),["format",["concat",...i],{}]}const b=Ae(Me),k=["IRANYekanX Regular"],D=["IRANYekanX Regular"],q=["LineString","MultiLineString"],_e=["MultiPoint","Point"];function y(s){return["interpolate",["exponential",1.2],["zoom"],...s]}const X=["interpolate",["exponential",1.2],["zoom"],12.5,0,13,1.5,14,2.5,20,11.5],Ce=["interpolate",["exponential",1.2],["zoom"],12,.5,13,1,14,4,20,15],ze=[{id:"background",type:"background",paint:{"background-color":ye}},{id:"landuse-residential",type:"fill",source:"openmaptiles","source-layer":"landuse",filter:["match",["get","class"],["neighbourhood","residential"],!0,!1],paint:{"fill-color":fe}},{id:"landuse-cemetery",type:"fill",source:"openmaptiles","source-layer":"landuse",filter:["==",["get","class"],"cemetery"],paint:{"fill-color":we}},{id:"landcover-wood",type:"fill",source:"openmaptiles","source-layer":"landcover",filter:["==",["get","class"],"wood"],paint:{"fill-color":be}},{id:"landcover-grass",type:"fill",source:"openmaptiles","source-layer":"landcover",filter:["==",["get","class"],"grass"],paint:{"fill-color":ve}},{id:"landcover-grass-park",type:"fill",source:"openmaptiles","source-layer":"park",paint:{"fill-color":G}},{id:"park",type:"fill",source:"openmaptiles","source-layer":"park",filter:["match",["geometry-type"],["MultiPolygon","Polygon"],!0,!1],paint:{"fill-color":G}},{id:"landcover-sand",type:"fill",source:"openmaptiles","source-layer":"landcover",filter:["==",["get","class"],"sand"],paint:{"fill-color":qe}},{id:"water",type:"fill",source:"openmaptiles","source-layer":"water",filter:["all",["!=",["get","intermittent"],1],["!=",["get","brunnel"],"tunnel"]],paint:{"fill-color":xe}},{id:"water-intermittent",type:"fill",source:"openmaptiles","source-layer":"water",filter:["all",["==",["get","intermittent"],1],["!=",["get","brunnel"],"tunnel"]],paint:{"fill-color":ke,"fill-opacity":.8}},{id:"waterway-other",type:"line",source:"openmaptiles","source-layer":"waterway",filter:["all",["match",["get","class"],["canal","river","stream"],!1,!0],["==",["get","intermittent"],0]],paint:{"line-color":N,"line-width":["interpolate",["exponential",1.3],["zoom"],13,.5,20,2]}},{id:"waterway-stream-canal",type:"line",source:"openmaptiles","source-layer":"waterway",filter:["all",["match",["get","class"],["canal","stream"],!0,!1],["!=",["get","brunnel"],"tunnel"],["==",["get","intermittent"],0]],paint:{"line-color":N,"line-width":["interpolate",["exponential",1.3],["zoom"],13,.5,20,6]}},{id:"waterway-river",type:"line",source:"openmaptiles","source-layer":"waterway",filter:["all",["==",["get","class"],"river"],["!=",["get","brunnel"],"tunnel"],["!=",["get","intermittent"],1]],paint:{"line-color":N,"line-width":["interpolate",["exponential",1.2],["zoom"],10,.8,20,6]}},{id:"building",type:"fill",source:"openmaptiles","source-layer":"building",paint:{"fill-antialias":!0,"fill-color":U}},{id:"building-top",type:"fill",source:"openmaptiles","source-layer":"building",paint:{"fill-color":Se,"fill-opacity":["interpolate",["linear"],["zoom"],13,0,16,1],"fill-outline-color":U,"fill-translate":["interpolate",["linear"],["zoom"],14,["literal",[0,0]],16,["literal",[-2,-2]]]}},{id:"aeroway-area",type:"fill",source:"openmaptiles","source-layer":"aeroway",paint:{"fill-color":Te,"fill-opacity":["interpolate",["linear"],["zoom"],13,0,14,1]}},{id:"aeroway-taxiway-casing",type:"line",source:"openmaptiles","source-layer":"aeroway",filter:["==",["get","class"],"taxiway"],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":H,"line-width":["interpolate",["exponential",1.5],["zoom"],11,2,17,12]}},{id:"aeroway-taxiway",type:"line",source:"openmaptiles","source-layer":"aeroway",filter:["==",["get","class"],"taxiway"],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":O,"line-width":["interpolate",["exponential",1.5],["zoom"],11,1,17,10]}},{id:"aeroway-runway-casing",type:"line",source:"openmaptiles","source-layer":"aeroway",filter:["==",["get","class"],"runway"],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":H,"line-width":["interpolate",["exponential",1.5],["zoom"],11,5,17,55]}},{id:"aeroway-runway",type:"line",source:"openmaptiles","source-layer":"aeroway",filter:["==",["get","class"],"runway"],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":O,"line-width":["interpolate",["exponential",1.5],["zoom"],11,4,17,50]}},{id:"highway-minor-casing",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["!=",["get","brunnel"],"tunnel"],["match",["get","class"],["minor","service","track"],!0,!1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":Le,"line-opacity":["interpolate",["linear"],["zoom"],12,0,12.5,1],"line-width":Ce}},{id:"highway-link-casing",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:13,filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["primary","secondary","tertiary","trunk"],!0,!1],["==",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":A,"line-width":y([12,1,13,3,14,4,20,15])}},{id:"highway-secondary-tertiary-casing",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["secondary","tertiary"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":A,"line-width":y([8,1.5,20,17])}},{id:"highway-primary-casing",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:5,filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["primary"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":A,"line-opacity":["interpolate",["linear"],["zoom"],7,0,8,1],"line-width":y([7,0,8,.6,9,1.5,20,22])}},{id:"highway-trunk-casing",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:5,filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["trunk"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":A,"line-opacity":["interpolate",["linear"],["zoom"],5,0,6,1],"line-width":y([5,0,6,.6,7,1.5,20,22])}},{id:"highway-motorway-casing",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:4,filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["==",["get","class"],"motorway"],["!=",["get","ramp"],1]],layout:{"line-cap":"butt","line-join":"round"},paint:{"line-color":A,"line-opacity":["interpolate",["linear"],["zoom"],4,0,5,1],"line-width":y([4,0,5,.4,6,.6,7,1.5,20,22])}},{id:"highway-path",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["==",["get","class"],"path"]],paint:{"line-color":Ee,"line-dasharray":[1.5,.75],"line-width":y([15,1.2,20,4])}},{id:"highway-link",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:13,filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["primary","secondary","tertiary","trunk"],!0,!1],["==",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([12.5,0,13,1.5,14,2.5,20,11.5])}},{id:"highway-minor",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["!=",["get","brunnel"],"tunnel"],["match",["get","class"],["minor","service","track"],!0,!1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":O,"line-width":X}},{id:"highway-secondary-tertiary",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["secondary","tertiary"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,8,.5,20,13])}},{id:"highway-primary",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["primary"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([8.5,0,9,.5,20,18])}},{id:"highway-trunk",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["match",["get","class"],["trunk"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,7,.5,20,18])}},{id:"highway-motorway",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:5,filter:["all",["match",["geometry-type"],q,!0,!1],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["==",["get","class"],"motorway"],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,7,.5,20,18])}},{id:"bridge-link",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:13,filter:["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["primary","secondary","tertiary","trunk"],!0,!1],["==",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([12.5,0,13,1.5,14,2.5,20,11.5])}},{id:"bridge-minor",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["minor","service","track"],!0,!1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":O,"line-width":X}},{id:"bridge-secondary-tertiary",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["secondary","tertiary"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,8,.5,20,13])}},{id:"bridge-trunk-primary",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["==",["get","brunnel"],"bridge"],["match",["get","class"],["primary","trunk"],!0,!1],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,7,.5,20,18])}},{id:"bridge-motorway",type:"line",source:"openmaptiles","source-layer":"transportation",minzoom:5,filter:["all",["==",["get","brunnel"],"bridge"],["==",["get","class"],"motorway"],["!=",["get","ramp"],1]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":x,"line-width":y([6.5,0,7,.5,20,18])}},{id:"railway-transit",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["==",["get","class"],"transit"],["match",["get","brunnel"],["tunnel"],!1,!0]],paint:{"line-color":F,"line-width":["interpolate",["exponential",1.4],["zoom"],14,.4,20,1]}},{id:"railway-service",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["has","service"],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["==",["get","class"],"rail"]],paint:{"line-color":F,"line-width":["interpolate",["exponential",1.4],["zoom"],14,.4,20,1]}},{id:"railway",type:"line",source:"openmaptiles","source-layer":"transportation",filter:["all",["match",["geometry-type"],q,!0,!1],["!",["has","service"]],["match",["get","brunnel"],["bridge","tunnel"],!1,!0],["==",["get","class"],"rail"]],paint:{"line-color":F,"line-width":["interpolate",["exponential",1.4],["zoom"],14,.4,15,.75,20,2]}},{id:"boundary_3",type:"line",source:"openmaptiles","source-layer":"boundary",filter:["all",["==",["get","admin_level"],3],["!=",["get","maritime"],1]],paint:{"line-color":Q,"line-dasharray":[1,1],"line-width":["interpolate",["linear"],["zoom"],7,1,11,2]}},{id:"boundary_2",type:"line",source:"openmaptiles","source-layer":"boundary",filter:["all",["==",["get","admin_level"],2],["!=",["get","maritime"],1],["!=",["get","disputed"],1],["!",["has","claimed_by"]]],layout:{"line-cap":"round","line-join":"round"},paint:{"line-color":Q,"line-opacity":["interpolate",["linear"],["zoom"],0,.4,4,1],"line-width":["interpolate",["linear"],["zoom"],3,1,5,1.2,12,3]}},{id:"waterway_line_label",type:"symbol",source:"openmaptiles","source-layer":"waterway",layout:{"symbol-placement":"line","symbol-spacing":350,"text-field":b,"text-font":k,"text-size":13},paint:{"text-color":V,"text-halo-color":f,"text-halo-width":1.5}},{id:"water_name_label",type:"symbol",source:"openmaptiles","source-layer":"water_name",layout:{"text-field":b,"text-font":k,"text-max-width":8,"text-size":12},paint:{"text-color":V,"text-halo-color":f,"text-halo-width":1.5}},{id:"poi_worship_school",type:"symbol",source:"openmaptiles","source-layer":"poi",minzoom:14,filter:["match",["get","class"],["place_of_worship","school","college","library","hospital","park"],!0,!1],layout:{"icon-image":["match",["get","class"],["place_of_worship"],"place_of_worship",["school"],"school",["college"],"school",["library"],"school",["hospital"],"hospital",["park"],"park",""],"icon-size":1.1,"text-anchor":"top","text-field":b,"text-font":k,"text-max-width":8,"text-offset":[0,1],"text-size":12},paint:{"text-color":j,"text-halo-color":f,"text-halo-width":1.5}},{id:"poi_shops",type:"symbol",source:"openmaptiles","source-layer":"poi",minzoom:15.5,filter:["match",["get","class"],["shop","grocery","restaurant","cafe","fast_food","pharmacy","bank","fuel"],!0,!1],layout:{"icon-image":["match",["get","class"],["grocery"],"grocery",["restaurant"],"restaurant",["cafe"],"cafe",["fast_food"],"restaurant",["pharmacy"],"pharmacy",["bank"],"bank",["fuel"],"fuel","shop"],"icon-size":1,"text-anchor":"top","text-field":b,"text-font":k,"text-max-width":8,"text-offset":[0,1],"text-size":12},paint:{"text-color":j,"text-halo-color":f,"text-halo-width":1.5}},{id:"poi_r1",type:"symbol",source:"openmaptiles","source-layer":"poi",minzoom:16,filter:["all",["match",["geometry-type"],_e,!0,!1],[">=",["get","rank"],1],["<",["get","rank"],7]],layout:{"icon-image":["match",["get","subclass"],["florist","furniture"],["get","subclass"],["get","class"]],"icon-size":.8,"text-anchor":"top","text-field":b,"text-font":k,"text-max-width":8,"text-offset":[0,.8],"text-size":12},paint:{"text-color":j,"text-halo-color":f,"text-halo-width":1.5}},{id:"airport",type:"symbol",source:"openmaptiles","source-layer":"aerodrome_label",minzoom:10,filter:["all",["has","iata"]],layout:{"icon-image":"airport_11","icon-size":1,"text-anchor":"top","text-field":b,"text-font":k,"text-max-width":8,"text-offset":[0,.8],"text-size":12},paint:{"text-color":j,"text-halo-color":f,"text-halo-width":1.5}},{id:"highway-name-minor",type:"symbol",source:"openmaptiles","source-layer":"transportation_name",minzoom:14,filter:["all",["match",["geometry-type"],q,!0,!1],["match",["get","class"],["minor","service","track"],!0,!1]],layout:{"symbol-placement":"line","text-field":b,"text-font":k,"text-rotation-alignment":"map","text-size":["interpolate",["linear"],["zoom"],14,11,17,12]},paint:{"text-color":Y,"text-halo-color":f,"text-halo-width":1.5}},{id:"highway-name-major",type:"symbol",source:"openmaptiles","source-layer":"transportation_name",minzoom:11,filter:["match",["get","class"],["primary","secondary","tertiary","trunk","motorway"],!0,!1],layout:{"symbol-placement":"line","text-field":b,"text-font":D,"text-rotation-alignment":"map","text-size":["interpolate",["linear"],["zoom"],11,11,15,13]},paint:{"text-color":Y,"text-halo-color":f,"text-halo-width":1.5}},{id:"label_village",type:"symbol",source:"openmaptiles","source-layer":"place",minzoom:9,filter:["==",["get","class"],"village"],layout:{"text-field":b,"text-font":k,"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],9,11,12,13]},paint:{"text-color":_,"text-halo-color":f,"text-halo-width":1.5}},{id:"label_town",type:"symbol",source:"openmaptiles","source-layer":"place",minzoom:6,filter:["==",["get","class"],"town"],layout:{"text-field":b,"text-font":D,"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],7,12,11,14]},paint:{"text-color":_,"text-halo-color":f,"text-halo-width":1.5}},{id:"label_other",type:"symbol",source:"openmaptiles","source-layer":"place",minzoom:8,filter:["match",["get","class"],["city","continent","country","state","town","village"],!1,!0],layout:{"text-field":b,"text-font":k,"text-max-width":8,"text-size":["interpolate",["linear"],["zoom"],8,10,12,12]},paint:{"text-color":_,"text-halo-color":f,"text-halo-width":1.5}},{id:"label_city",type:"symbol",source:"openmaptiles","source-layer":"place",minzoom:3,filter:["all",["==",["get","class"],"city"],["!=",["get","capital"],2]],layout:{"text-field":b,"text-font":D,"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],4,11,7,13,11,16]},paint:{"text-color":_,"text-halo-color":f,"text-halo-width":1.5}},{id:"label_city_capital",type:"symbol",source:"openmaptiles","source-layer":"place",minzoom:3,filter:["all",["==",["get","class"],"city"],["==",["get","capital"],2]],layout:{"text-field":b,"text-font":D,"text-max-width":8,"text-size":["interpolate",["exponential",1.2],["zoom"],4,12,7,14,11,18]},paint:{"text-color":_,"text-halo-color":f,"text-halo-width":1.5}}],$e="https://tiles.openfreemap.org/planet",Pe={version:8,name:"snapp-like",glyphs:"https://cdn.jsdelivr.net/npm/@amir83nasr/map@1/fonts/{fontstack}/{range}.pbf",sprite:"https://tiles.openfreemap.org/sprites/ofm_f384/ofm",sources:{openmaptiles:{type:"vector",url:$e}},layers:ze};class Oe{constructor(){d(this,"map",new Map)}on(e,t){let o=this.map.get(e);return o||this.map.set(e,o=new Set),o.add(t),()=>this.off(e,t)}off(e,t){var o;(o=this.map.get(e))==null||o.delete(t)}emit(e,t){for(const o of this.map.get(e)??[])try{o(t)}catch{}}}function C(s){return`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${s}</svg>`}const v={locate:C('<line x1="2" x2="5" y1="12" y2="12"/><line x1="19" x2="22" y1="12" y2="12"/><line x1="12" x2="12" y1="2" y2="5"/><line x1="12" x2="12" y1="19" y2="22"/><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="3"/>'),pin:C('<path d="M20 10c0 6-8 12-8 12s-8-6-8-12a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/>'),search:C('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>'),star:C('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" fill="currentColor" stroke="none"/>'),x:C('<path d="M18 6 6 18"/><path d="m6 6 12 12"/>')},K="۰۱۲۳۴۵۶۷۸۹";function S(s){return String(s).replace(/\d/g,e=>K[Number(e)]).replace(/[٠-٩]/g,e=>K["٠١٢٣٤٥٦٧٨٩".indexOf(e)])}function J(s,e=5){return S(Number(s).toFixed(e))}function M(s){return String(s).replace(/[۰-۹]/g,e=>String("۰۱۲۳۴۵۶۷۸۹".indexOf(e))).replace(/[٠-٩]/g,e=>String("٠١٢٣٤٥٦٧٨٩".indexOf(e)))}function ee(s){if(!s)return"";const e=String(s).split(",").map(o=>o.trim()).filter(Boolean),t=o=>!!(/^(ایران|Iran)$/i.test(o)||/^استان\b/.test(o)||/^شهرستان\b/.test(o)||/^بخش\b/.test(o)||/^[\d\s\-–—]+$/.test(M(o)));return e.filter(o=>!t(o)).slice(0,4).reverse().join(", ")}function je(s,e){return Number.isFinite(s)&&Number.isFinite(e)&&s>=-90&&s<=90&&e>=-180&&e<=180}const De="50.35,35.05,51.45,34.15",W=new Map,Z=new Map;function se(s,e){s.size>e&&s.delete(s.keys().next().value)}async function Ie(s,e,t){const o=`${s.toFixed(4)},${e.toFixed(4)}`,r=W.get(o);if(r)return r;const i=await fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${s}&lon=${e}&accept-language=fa`,{headers:{Accept:"application/json"},signal:t==null?void 0:t.signal});if(!i.ok)throw new Error(`http ${i.status}`);const a=await i.json();if(!a.display_name)throw new Error("empty result");return W.set(o,a.display_name),se(W,200),a.display_name}async function Ne(s,e=5,t){const o=`${M(s).trim().toLowerCase()}|${e}`,r=Z.get(o);if(r)return r;const i=await fetch(`https://nominatim.openstreetmap.org/search?format=jsonv2&accept-language=fa&limit=${e}&viewbox=${De}&bounded=1&q=${encodeURIComponent(M(s))}`,{headers:{Accept:"application/json"},signal:t==null?void 0:t.signal});if(!i.ok)throw new Error(`http ${i.status}`);const a=await i.json();return Z.set(o,a),se(Z,50),a}const Fe=["highway-minor","highway-link","highway-secondary-tertiary","highway-primary","highway-trunk","highway-motorway","bridge-minor","bridge-link","bridge-secondary-tertiary","bridge-trunk-primary","bridge-motorway"],T=64,We=15,te=2;function Ze(s,e,t,o,r,i){const a=r-t,n=i-o,l=a*a+n*n,g=l===0?0:((s-t)*a+(e-o)*n)/l,m=Math.max(0,Math.min(1,g)),p=t+m*a,u=o+m*n;return{x:p,y:u,d2:(s-p)*(s-p)+(e-u)*(e-u)}}function Be(s,e,t){if(s.getZoom()<We)return null;const o=s.project([t,e]);let r;try{const l=[[o.x-T,o.y-T],[o.x+T,o.y+T]];r=s.queryRenderedFeatures(l,{layers:Fe})}catch{return null}let i=null,a=T*T;for(const l of r){const g=l.geometry,m=g.type==="LineString"?[g.coordinates]:g.type==="MultiLineString"?g.coordinates:[];for(const p of m){let u=null;for(const L of p){const c=s.project(L);if(u){const h=Ze(o.x,o.y,u.x,u.y,c.x,c.y);h.d2<a&&(a=h.d2,i={x:h.x,y:h.y})}u={x:c.x,y:c.y}}}}if(!i||a<te*te)return null;const n=s.unproject([i.x,i.y]);return{lat:n.lat,lng:n.lng}}const oe=3,Ge="Vite: import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url'; سپس setupQomPickWorker(workerUrl) را قبل از mount صدا بزنید. Next.js/webpack: هر دو فایل worker و shared را کپی کنید.",Ve="qompick: MapLibre worker failed to load. Call setupQomPickWorker(workerUrl) from @amir83nasr/map before mounting LocationPickerView. Vite: use maplibre-gl-worker.mjs?worker&url. Next.js/webpack: copy maplibre-gl-worker.mjs and maplibre-gl-shared.mjs.";let B=!1;function Ue(s){const e=globalThis.Worker;if(typeof e!="function")return()=>{};class t extends e{constructor(r,i){super(r,i),B=!1,this.addEventListener("error",()=>{B=!0,s()})}}return globalThis.Worker=t,()=>{globalThis.Worker=e}}function He(s){const e=s,t=[e==null?void 0:e.message,e==null?void 0:e.filename,e==null?void 0:e.url,s].filter(o=>typeof o=="string").join(" ").toLowerCase();return/maplibre-gl-(worker|shared)(?:-dev)?\.mjs/.test(t)||/failed to fetch worker script/.test(t)||/worker/.test(t)&&/failed to load|cannot load|dynamically imported module/.test(t)}const I="من میخواهم با پکیج قمپیک (@amir83nasr/map) یک نقشه انتخاب موقعیت (LocationPickerView) از صفر در پروژه React/Next.js بسازم. این پکیج کاملاً متنباز و رایگان است و روی MapLibre GL ساخته شده و فقط نسخه React دارد. لطفاً قدمبهقدم و به ترتیب راهنماییام کن: ۱) نصب — دستور نصب @amir83nasr/map و maplibre-gl نسخه ۶ (که peer dependency است و باید جدا نصب شود)؛ ۲) راهاندازی پایه — ایمپورت @amir83nasr/map/styles.css (شامل CSS مپلایبر، فقط یک import CSS) و رندر کامپوننت LocationPickerView با ارتفاع مشخص (مثلاً 480px، چون بدون ارتفاع نقشه خالی دیده میشود) و center و zoom اولیه؛ ۳) جستجو — استفاده از پیشنهادهای پیشفرض محلههای قم، یا جایگزینی آنها با search.suggestions شامل name و addr و lat و lng؛۴) مارکرها — نمایش مارکر مکانها با markers؛ ۵) دریافت نتیجه — گرفتن مختصات و آدرس تاییدشده کاربر با onConfirm؛ ۶) پاکسازی — کامپوننت LocationPickerView خودش destroy را در cleanup صدا میزند. کد کامل و قابل اجرا بده شامل همه importها و cssها، بگو هر بخش چه میکند، و در پایان خروجی مورد انتظار را توصیف کن: نقشه فارسی راستچین (RTL) و موبایلفرست با پین وسط، دکمه GPS، جستجو و دکمه تایید موقعیت. اگر چیزی از پروژه من لازم داری (نسخه React/Next.js، نسخه پکیجها) اول بپرس.",ae="پروژه React/Next.js من از قبل وجود دارد و میخواهم نقشه انتخاب موقعیت قمپیک (@amir83nasr/map) را به آن اضافه کنم بدون اینکه چیز دیگری خراب شود. این پکیج کاملاً متنباز و رایگان است و روی MapLibre GL ساخته شده و فقط نسخه React دارد. لطفاً قدمبهقدم و به ترتیب راهنماییام کن: ۱) نصب — دستور نصب @amir83nasr/map و maplibre-gl نسخه ۶ (که peer dependency است و باید جدا نصب شود)، با توجه به اینکه بقیه dependencyهای پروژه نباید بههم بخورد؛ ۲) ایمپورت css — اضافه کردن @amir83nasr/map/styles.css (شامل CSS مپلایبر؛ فقط یک import CSS) طوری که با استایلهای فعلی پروژه تداخل نکند (همه کلاسها و متغیرهای قمپیک با qp- شروع میشوند)؛ ۳) کانتینر — رندر LocationPickerView با ارتفاع مشخص (مثلاً 480px، چون بدون ارتفاع نقشه خالی دیده میشود) در جای مناسب صفحه فعلی؛ ۴) اتصال — دادن center و zoom، استفاده یا جایگزینی پیشنهادهای پیشفرض search، نمایش markers، و گرفتن مختصات و آدرس تاییدشده با onConfirm؛۵) پاکسازی — کامپوننت LocationPickerView خودش destroy را در cleanup صدا میزند. کد کامل و قابل اجرا بده شامل همه importها، بگو هر بخش چه میکند، و در پایان خروجی مورد انتظار را توصیف کن: نقشه فارسی راستچین (RTL) و موبایلفرست با پین وسط، دکمه GPS، جستجو و دکمه تایید موقعیت. اگر چیزی از پروژه من لازم داری (نسخه React/Next.js، نسخه پکیجها، ساختار فایلها) اول بپرس.",re={zero:I,existing:ae};function w(s){const e=document.createElement("template");return e.innerHTML=s.trim(),e.content.firstElementChild}class Qe{constructor(e){d(this,"opts");d(this,"emitter",new Oe);d(this,"root");d(this,"map");d(this,"myMarker",null);d(this,"venueMarkers",[]);d(this,"labelEl");d(this,"toastEl");d(this,"overlay");d(this,"resolveT",0);d(this,"searchT",0);d(this,"toastT",0);d(this,"settleT",0);d(this,"confirmT",0);d(this,"venueT",[]);d(this,"revSeq",0);d(this,"searchSeq",0);d(this,"revAbort",null);d(this,"searchAbort",null);d(this,"destroyed",!1);d(this,"workerFailed",!1);d(this,"boundsSaved",null);d(this,"lat",$.lat);d(this,"lng",$.lng);d(this,"address","");d(this,"resolving",!1);d(this,"onKey",e=>{e.key==="Escape"&&(this.overlay.hidden?this.isHidden(".qp-docs")?this.isHidden(".qp-confirm")?this.isHidden(".qp-geo")||this.closeModal("qp-geo"):this.closeModal("qp-confirm"):this.closeDocs():this.closeSearch())});var o,r;const t=typeof e.container=="string"?document.querySelector(e.container):e.container;if(!t)throw new Error("qompick: container not found");this.opts=ge(e);for(const[i,a]of[["onLocationChange","locationChange"],["onAddressResolved","addressResolved"],["onSearchResults","searchResults"],["onPick","pick"],["onConfirm","confirm"],["onLocate","locate"],["onError","error"]]){const n=e[i];typeof n=="function"&&this.emitter.on(a,n)}this.build(t),this.initMap(),this.opts.markers.length&&this.addVenues(this.opts.markers),this.setLocation(((o=this.opts.map.center)==null?void 0:o.lat)??$.lat,((r=this.opts.map.center)==null?void 0:r.lng)??$.lng,{resolve:!0}),this.emitter.emit("ready",this.getLocation())}isHidden(e){var t;return((t=this.root.querySelector(e))==null?void 0:t.hidden)??!0}on(e,t){return this.emitter.on(e,t)}off(e,t){this.emitter.off(e,t)}getLocation(){return{lat:this.lat,lng:this.lng,address:this.address}}setLocation(e,t,o={}){if(!je(e,t)){this.fail(new Error("invalid coordinates"));return}this.lat=e,this.lng=t;const r=this.getLocation();if(this.emitter.emit("locationChange",r),o.moveMap&&this.map){const i=o.zoom??Math.max(this.map.getZoom(),this.opts.behavior.pickZoom??15);this.map.flyTo({center:[t,e],zoom:i,duration:450})}o.resolve!==!1&&this.opts.behavior.resolveOnMove!==!1?this.scheduleResolve(e,t):this.renderLabel()}locate(){if(!navigator.geolocation){this.toast(this.t("gpsUnsupported")),this.fail(new Error("geolocate: unsupported"));return}const e=this.root.querySelector(".qp-gps");if(!window.isSecureContext){this.openModal("qp-geo"),this.fail(new Error("geolocate: insecure-context"));return}e==null||e.classList.add("is-locating"),navigator.geolocation.getCurrentPosition(t=>{if(e==null||e.classList.remove("is-locating"),!this.boundsSaved){const o=this.map.getMaxBounds();o&&(this.boundsSaved={sw:o.getSouthWest().toArray(),ne:o.getNorthEast().toArray()})}this.map.setMaxBounds(null),this.showMyPos(t.coords.latitude,t.coords.longitude),this.setLocation(t.coords.latitude,t.coords.longitude,{moveMap:!0,zoom:this.opts.behavior.locateZoom??18}),this.boundsSaved&&(this.map.setMaxBounds([this.boundsSaved.sw,this.boundsSaved.ne]),this.boundsSaved=null),this.emitter.emit("locate",this.getLocation())},t=>{e==null||e.classList.remove("is-locating"),(t==null?void 0:t.code)===t.PERMISSION_DENIED?this.openModal("qp-geo"):(this.toast((t==null?void 0:t.code)===t.TIMEOUT?this.t("locateTimeout"):this.t("locateUnavailable")),this.scheduleResolve(this.lat,this.lng,100)),this.fail(new Error(`geolocate: ${t==null?void 0:t.code}`))},{timeout:1e4})}confirm(e){const t={lat:this.lat,lng:this.lng,address:e??this.address};return this.emitter.emit("confirm",t),t}destroy(){var e,t,o,r;this.destroyed=!0,document.removeEventListener("keydown",this.onKey),window.clearTimeout(this.resolveT),window.clearTimeout(this.searchT),window.clearTimeout(this.toastT),window.clearTimeout(this.settleT),window.clearTimeout(this.confirmT),this.venueT.forEach(i=>window.clearTimeout(i)),(e=this.revAbort)==null||e.abort(),(t=this.searchAbort)==null||t.abort(),this.venueMarkers.forEach(i=>i.remove()),(o=this.myMarker)==null||o.remove(),(r=this.map)==null||r.remove(),this.root.remove()}t(e){return this.opts.i18n.labels[e]}build(e){const t=this.opts,o=me(),r=(t.search.placeholder??this.t("searchPlaceholder")).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");this.root=w(`<div class="qp qp-app" dir="${o}"></div>`);const i='<div class="qp-pin" aria-hidden="true"><div class="qp-pin-body"><div class="qp-pin-head"><div class="qp-pin-hole"></div></div><div class="qp-pin-stem"></div><div class="qp-pin-foot"></div></div><div class="qp-pin-shadow"></div></div>';this.root.innerHTML=`
|
|
2
|
+
<div class="qp-layout">
|
|
3
|
+
<main class="qp-map-wrap"><div class="qp-map"></div>${i}
|
|
4
|
+
${t.controls.gps?`<button class="qp-gps" type="button" aria-label="${this.t("currentLocation")}">${v.locate}</button>`:""}
|
|
5
|
+
${t.controls.developers?`<button class="qp-dev" type="button">${this.t("developers")}</button>`:""}
|
|
6
|
+
</main>
|
|
7
|
+
${t.sheet.enabled?`<section class="qp-sheet"><div class="qp-grab"></div>
|
|
8
|
+
${t.controls.searchTrigger&&t.search.enabled?`<div class="qp-search"><button class="qp-search-trigger" type="button"><span class="qp-sic">${v.search}</span><span class="qp-search-label"></span></button></div>`:""}
|
|
9
|
+
<div class="qp-desk">
|
|
10
|
+
<div class="qp-search-bar"><div class="qp-field"><input class="qp-desk-input" type="search" placeholder="${r}" autocomplete="off" aria-label="${this.t("searchTitle")}"/><button class="qp-clear qp-desk-clear" hidden aria-label="${this.t("clearSearch")}">${v.x}</button><span class="qp-sic">${v.search}</span></div></div>
|
|
11
|
+
<div class="qp-desk-body"><div class="qp-desk-home"><h3 class="qp-sec-t">${this.t("suggestedPlaces")}</h3><div class="qp-suggest-desk"></div></div><div class="qp-results-desk"></div></div>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="qp-sheet-row">${t.controls.confirmButton?`<button class="qp-cta">${this.t("confirmLocation")}</button>`:""}</div>
|
|
14
|
+
</section>`:""}
|
|
15
|
+
</div>
|
|
16
|
+
${t.search.enabled?`<div class="qp-overlay" hidden><div class="qp-ov-head"><h2>${this.t("searchTitle")}</h2><button class="qp-ov-close" type="button" aria-label="${this.t("close")}">${v.x}</button></div>
|
|
17
|
+
<div class="qp-search-bar"><div class="qp-field"><input type="search" placeholder="${r}" autocomplete="off"/><button class="qp-clear" hidden aria-label="${this.t("clearSearch")}">${v.x}</button><span class="qp-sic">${v.search}</span></div></div>
|
|
18
|
+
<div class="qp-ov-body"><div class="qp-home"><h3 class="qp-sec-t">${this.t("suggestedPlaces")}</h3><div class="qp-suggest"></div></div><div class="qp-results"></div></div>
|
|
19
|
+
</div>`:""}
|
|
20
|
+
<div class="qp-modal qp-geo" hidden><div class="qp-modal-card"><div class="qp-modal-head"><h2>${this.t("geoTitle")}</h2><button class="qp-x" type="button" aria-label="${this.t("close")}">${v.x}</button></div><p>${this.t("geoText")}</p><p class="qp-modal-sub">${this.t("geoBlocked")}</p><div class="qp-modal-row"><button class="qp-cta qp-geo-retry" type="button">${this.t("enableAccess")}</button></div></div></div>
|
|
21
|
+
<div class="qp-modal qp-confirm" hidden><div class="qp-modal-card"><div class="qp-modal-head"><h2>${this.t("confirmLocation")}</h2><button class="qp-x" type="button" aria-label="${this.t("close")}">${v.x}</button></div><label>${this.t("chosenAddress")}</label><textarea class="qp-addr" rows="3"></textarea><div>${this.t("coordsToServer")}</div><div class="qp-coords" dir="ltr"></div><div class="qp-modal-row"><button class="qp-cta qp-final" type="button">${this.t("confirm")}</button><button class="qp-ghost qp-edit" type="button">${this.t("editOnMap")}</button></div></div></div>
|
|
22
|
+
${t.controls.developers?this.devDocsHtml():""}
|
|
23
|
+
<div class="qp-toast" role="status"></div>`,e.appendChild(this.root),this.labelEl=this.root.querySelector(".qp-search-label")??w("<span></span>"),this.toastEl=this.root.querySelector(".qp-toast"),this.overlay=this.root.querySelector(".qp-overlay")??w("<div></div>"),this.wire(),requestAnimationFrame(()=>this.root.classList.add("is-ready"))}wire(){var o,r,i,a,n,l,g,m,p,u,L;(o=this.root.querySelector(".qp-gps"))==null||o.addEventListener("click",()=>this.locate()),(r=this.root.querySelector(".qp-search-trigger"))==null||r.addEventListener("click",()=>this.openSearch()),(i=this.root.querySelector(".qp-ov-close"))==null||i.addEventListener("click",()=>this.closeSearch()),(a=this.root.querySelector(".qp-cta"))==null||a.addEventListener("click",()=>this.openConfirm()),(n=this.root.querySelector(".qp-final"))==null||n.addEventListener("click",()=>this.finishConfirm()),(l=this.root.querySelector(".qp-edit"))==null||l.addEventListener("click",()=>this.closeModal("qp-confirm")),(g=this.root.querySelector(".qp-geo-retry"))==null||g.addEventListener("click",()=>{this.closeModal("qp-geo"),this.locate()}),this.root.querySelectorAll(".qp-modal").forEach(c=>c.addEventListener("click",h=>{h.target===c&&(c.hidden=!0)})),this.root.querySelectorAll(".qp-modal .qp-x").forEach(c=>c.addEventListener("click",()=>{var h;(h=c.closest(".qp-modal"))==null||h.setAttribute("hidden","")})),(m=this.root.querySelector(".qp-docs .qp-x"))==null||m.addEventListener("click",()=>this.closeDocs()),document.addEventListener("keydown",this.onKey),(p=this.root.querySelector(".qp-dev"))==null||p.addEventListener("click",()=>this.openDocs()),(u=this.root.querySelector(".qp-back"))==null||u.addEventListener("click",()=>this.closeDocs()),(L=this.root.querySelector(".qp-docs"))==null||L.addEventListener("click",c=>{c.target===this.root.querySelector(".qp-docs")&&this.closeDocs()}),this.root.querySelectorAll(".qp-copy").forEach(c=>c.addEventListener("click",h=>{this.copyPrompt(h.currentTarget)}));const e=this.root.querySelector(".qp-overlay .qp-field input");if(e){const c=this.root.querySelector(".qp-overlay .qp-clear"),h=()=>{c&&(c.hidden=e.value.length===0)};e.addEventListener("input",()=>{h(),this.onQuery(e.value)}),e.addEventListener("keydown",R=>{R.key==="Enter"&&this.runSearch(e.value.trim())}),c==null||c.addEventListener("click",()=>{e.value="",h(),this.renderSuggest(""),e.focus()}),this.renderSuggest("")}const t=this.root.querySelector(".qp-desk-input");if(t){const c=this.root.querySelector(".qp-desk-clear"),h=()=>{c&&(c.hidden=t.value.length===0)};t.addEventListener("input",()=>{h(),this.onDeskQuery(t.value)}),t.addEventListener("keydown",R=>{R.key==="Enter"&&this.runRemoteSearch(t.value.trim(),".qp-results-desk",".qp-desk-home")}),c==null||c.addEventListener("click",()=>{t.value="",h(),this.renderDeskSuggest(""),t.focus()}),this.renderDeskSuggest("")}}initMap(){const e=this.opts,t=this.root.querySelector(".qp-map"),o=typeof e.map.style=="object"&&e.map.style!==null?{...e.map.style}:{...Pe},r=e.map.glyphs?{...o,glyphs:e.map.glyphs}:o;if(typeof r.glyphs=="string"&&!/^(https?:|data:|blob:|\/)/.test(r.glyphs)){const p=document.baseURI,u=p.endsWith("/")?p:p.slice(0,p.lastIndexOf("/")+1);r.glyphs=u+r.glyphs}const i=e.map.bounds,a=Ue(()=>this.showWorkerError());try{this.map=new z.Map({container:t,style:r,center:[e.map.center.lng,e.map.center.lat],zoom:e.map.zoom,minZoom:e.map.minZoom,maxZoom:e.map.maxZoom,attributionControl:!1,renderWorldCopies:!1,fadeDuration:100,crossSourceCollisions:!1,scrollZoom:{around:"center"},dragRotate:!1,touchPitch:!1,clickTolerance:5,maxBounds:[[i[0][1],i[0][0]],[i[1][1],i[1][0]]]})}finally{a()}B&&this.showWorkerError(),this.map.on("error",p=>{if(He(p.error)){this.showWorkerError();return}console.error(p.error),this.fail(p.error)}),requestAnimationFrame(()=>this.map.resize());const n=this.root.querySelector(".qp-map-wrap");let l=null,g=this.map.getZoom(),m=this.map.getCenter();this.map.on("movestart",()=>{n.classList.add("is-moving"),window.clearTimeout(this.settleT),g=this.map.getZoom(),m=this.map.getCenter()}),this.map.on("move",()=>{const p=this.map.getCenter();this.lat=p.lat,this.lng=p.lng}),this.map.on("moveend",p=>{n.classList.remove("is-moving");const u=this.map.getCenter();if(window.clearTimeout(this.settleT),l&&Math.abs(u.lat-l.lat)<1e-7&&Math.abs(u.lng-l.lng)<1e-7){l=null;return}if(!p.originalEvent){this.setLocation(u.lat,u.lng);return}const L=Math.hypot(u.lat-m.lat,u.lng-m.lng)*111320,c=this.map.getZoom(),h=Math.abs(c-g)>1&&L<10,R=h?0:this.opts.behavior.snapDelayMs??this.opts.behavior.settleDelayMs;this.settleT=window.setTimeout(()=>{if(!this.destroyed){if(this.opts.behavior.snapToRoad&&!h)try{const E=Be(this.map,u.lat,u.lng);if(E){l=E,this.map.easeTo({center:[E.lng,E.lat],duration:400}),this.setLocation(E.lat,E.lng);return}}catch{}this.setLocation(u.lat,u.lng)}},R)})}showWorkerError(){var a;if(this.destroyed||this.workerFailed)return;this.workerFailed=!0;const e=new Error(Ve);console.warn(e.message),this.fail(e);const t=w('<div class="qp-err qp-map-err" role="alert"></div>'),o=w("<strong></strong>");o.textContent="ورکر MapLibre بارگذاری نشد؛ نقشه قابل استفاده نیست.";const r=w("<span></span>");r.textContent="راهحل: ";const i=w('<code dir="ltr"></code>');i.textContent=Ge,r.appendChild(i),t.append(o,r),(a=this.root.querySelector(".qp-map-wrap"))==null||a.appendChild(t)}showMyPos(e,t){if(this.myMarker){this.myMarker.setLngLat([t,e]);return}const o=w('<div class="qp-my-wrap"><div class="qp-my-dot"></div></div>');this.myMarker=new z.Marker({element:o}).setLngLat([t,e]).addTo(this.map)}addVenues(e){for(const t of e){const o=document.createElement("button");o.type="button",o.className="qp-venue",o.setAttribute("aria-label",t.name),o.innerHTML=v.star,o.addEventListener("click",()=>{this.setLocation(t.lat,t.lng,{moveMap:!0,zoom:this.opts.behavior.pickZoom}),this.emitter.emit("pick",this.getLocation())});const r=w('<div class="qp-venue-pop"><span class="qp-venue-dot"></span><span></span></div>');r.lastElementChild.textContent=t.name;const i=new z.Popup({offset:26,anchor:"top",closeButton:!1,className:"qp-venue-pop-wrap"}).setDOMContent(r),a=new z.Marker({element:o}).setLngLat([t.lng,t.lat]).setPopup(i).addTo(this.map);let n=0;i.on("open",()=>{window.clearTimeout(n),n=window.setTimeout(()=>i.remove(),4e3),this.venueT.push(n)}),i.on("close",()=>window.clearTimeout(n)),this.venueMarkers.push(a)}}scheduleResolve(e,t,o){window.clearTimeout(this.resolveT);const r=o??this.opts.behavior.resolveDelayMs??600;this.resolveT=window.setTimeout(()=>void this.resolveAddress(e,t),r)}async resolveAddress(e,t){var r;(r=this.revAbort)==null||r.abort(),this.revAbort=new AbortController;const o=++this.revSeq;this.resolving=!0,this.labelEl.textContent=this.t("resolving");try{const i=await Ie(e,t,{signal:this.revAbort.signal});if(o!==this.revSeq||this.destroyed)return;this.address=ee(i),this.emitter.emit("addressResolved",this.getLocation())}catch(i){if(i instanceof DOMException&&i.name==="AbortError"||o!==this.revSeq)return;this.address="",this.fail(i)}finally{if(o!==this.revSeq)return;this.resolving=!1,this.renderLabel()}}renderLabel(){this.labelEl.textContent=this.address?S(this.address):this.t("searchTitle")}devDocsHtml(){return`<div class="qp-docs" hidden><div class="qp-docs-card">
|
|
24
|
+
<div class="qp-modal-head"><h2>${this.t("developers")}</h2><button class="qp-x" type="button" aria-label="${this.t("close")}">${v.x}</button></div>
|
|
25
|
+
<section><h3>معرفی</h3><p>قمپیک (@amir83nasr/map) نقشه انتخاب موقعیت روی MapLibre برای React است؛ کاملاً متنباز و رایگان. فقط نسخه React دارد (کلاس vanilla در exports نیست؛ engine داخلی است). نقشه پیشفرض همیشه VECTOR است (نه raster)، با جستجوی آدرس، پین وسط، مارکر مکانها، رابط فارسی راستچین (RTL) و طراحی موبایلفرست.</p></section>
|
|
26
|
+
<section><h3>نصب</h3><pre dir="ltr">pnpm add @amir83nasr/map maplibre-gl</pre>
|
|
27
|
+
<p class="qp-note">نکته: maplibre-gl نسخه ۶ و react/react-dom peer dependency هستند و باید جدا نصب شوند.</p>
|
|
28
|
+
<pre dir="ltr">npm i @amir83nasr/map maplibre-gl
|
|
29
|
+
yarn add @amir83nasr/map maplibre-gl</pre></section>
|
|
30
|
+
<section><h3>شروع سریع (React)</h3><pre dir="ltr">import '@amir83nasr/map/styles.css';
|
|
31
|
+
import { LocationPickerView } from '@amir83nasr/map';
|
|
32
|
+
|
|
33
|
+
<LocationPickerView
|
|
34
|
+
onConfirm={(loc) => console.log(loc)}
|
|
35
|
+
/>;</pre>
|
|
36
|
+
<p class="qp-note">نکته: CSS مپلایبر داخل <code dir="ltr">@amir83nasr/map/styles.css</code> بستهبندی شده — فقط همین یک import CSS کافی است.</p>
|
|
37
|
+
<p class="qp-note">نکته: فونت فارسی IRANYekanX داخل خود پکیج است (با همین import CSS لود میشود) و لیبلهای فارسی نقشه هم از PBFهای داخل پکیج میآیند — بدون تنظیم اضافه.</p>
|
|
38
|
+
<p class="qp-note">نکته: کامپوننت خودش init و destroy را در lifecycle (useEffect) انجام میدهد — بدون init/destroy دستی. تنظیمات engine فقط موقع mount اعمال میشوند؛ برای تنظیمات جدید با <code dir="ltr">key</code> ریمانت کنید (فقط callbackها زنده میمانند).</p>
|
|
39
|
+
<p class="qp-note">ورکر MapLibre را قبل از mount تنظیم کنید: در Vite فایل <code dir="ltr">maplibre-gl-worker.mjs?worker&url</code> را import و به <code dir="ltr">setupQomPickWorker()</code> بدهید؛ در Next.js/webpack هر دو فایل worker و shared را کپی کنید. خطای ورکر در <code dir="ltr">onError</code>، کنسول و <code dir="ltr">.qp-err</code> نمایش داده میشود.</p>
|
|
40
|
+
<p class="qp-note">نکته Next.js App Router: کامپوننت client است؛ با <code dir="ltr">dynamic(..., { ssr: false })</code> لود کنید.</p></section>
|
|
41
|
+
<section><h3>تنظیمات مهم</h3><ul class="qp-list">
|
|
42
|
+
<li><code dir="ltr">map</code> — مرکز، زوم و محدوده نقشه (<code dir="ltr">center / zoom / minZoom / maxZoom / bounds</code>) و استایل سفارشی برداری (<code dir="ltr">style</code>).</li>
|
|
43
|
+
<li><code dir="ltr">search</code> — جستجو با پیشنهادهای پیشفرض ۴۱ محله قم: <code dir="ltr">enabled / placeholder / suggestions / minLength / debounceMs / limit</code>. آرایه سفارشی جایگزین پیشفرض میشود؛ <code dir="ltr">suggestions: []</code> آن را خالی میکند. <code dir="ltr">limit</code> پیشنهادهای محلی را هم محدود میکند.</li>
|
|
44
|
+
<li><code dir="ltr">markers</code> — مارکر مکانها با <code dir="ltr">name / lat / lng</code>.</li>
|
|
45
|
+
<li><code dir="ltr">controls</code> — دکمهها: <code dir="ltr">gps / confirmButton / searchTrigger / developers</code>.</li>
|
|
46
|
+
<li><code dir="ltr">behavior</code> — رفتار: <code dir="ltr">snapToRoad / resolveOnMove / resolveDelayMs / settleDelayMs / snapDelayMs / pickZoom / locateZoom</code>.</li>
|
|
47
|
+
<li><code dir="ltr">i18n.labels</code> — متنها؛ پیشفرض فارسی راستچین.</li>
|
|
48
|
+
<li>callbackها: <code dir="ltr">onLocationChange / onAddressResolved / onSearchResults / onPick / onConfirm / onLocate / onError</code>.</li></ul>
|
|
49
|
+
<pre dir="ltr"><LocationPickerView
|
|
50
|
+
map={{ center: { lat: 34.64, lng: 50.87 }, zoom: 15 }}
|
|
51
|
+
search={{ minLength: 3, limit: 5 }}
|
|
52
|
+
controls={{ gps: true, developers: true }}
|
|
53
|
+
onConfirm={(loc) => console.log(loc)}
|
|
54
|
+
/>;</pre></section>
|
|
55
|
+
<section><h3>ساختار پروژه</h3><ul class="qp-list">
|
|
56
|
+
<li><code dir="ltr">package/src/index.tsx</code> — API عمومی: کامپوننت <code dir="ltr">LocationPickerView</code>، هلپر <code dir="ltr">setupQomPickWorker</code> + تایپها.</li>
|
|
57
|
+
<li><code dir="ltr">package/src/picker.ts</code> — engine داخلی (در exports عمومی نیست).</li>
|
|
58
|
+
<li><code dir="ltr">package/src/*.ts</code> — helperها: geocode، snap، format، i18n، map-style، …</li>
|
|
59
|
+
<li><code dir="ltr">package/src/styles.css</code> — استایلها؛ خروجی build <code dir="ltr">dist/qompick-react.css</code> است (ایمپورت: <code dir="ltr">@amir83nasr/map/styles.css</code>).</li>
|
|
60
|
+
<li><code dir="ltr">package/fonts/</code> — فونت IRANYekanX (woff2) و PBFهای glyph نقشه؛ داخل tarball منتشر میشود.</li>
|
|
61
|
+
<li><code dir="ltr">demo/</code> — دموی Vite؛ <code dir="ltr">docs/</code> — ARCHITECTURE و CHANGELOG.</li></ul></section>
|
|
62
|
+
<section><h3>سفارشیسازی ظاهر</h3><p>همه کلاسها و متغیرها با <code dir="ltr">qp-</code> شروع میشوند و با استایل پروژه تداخل نمیکنند. prop جداگانه theme نیست؛ رنگ و فونت را با متغیرهای CSS عوض کنید:</p>
|
|
63
|
+
<pre dir="ltr">.qp { --qp-brand: #16a34a; --qp-radius: 16px; }</pre></section>
|
|
64
|
+
<section><h3>پرامپت شروع از صفر</h3><div class="qp-prompt">${I}</div>
|
|
65
|
+
<div class="qp-modal-row"><button class="qp-ghost qp-copy" type="button" data-prompt="zero">${this.t("copy")}</button></div></section>
|
|
66
|
+
<section><h3>پرامپت افزودن به پروژه موجود</h3><div class="qp-prompt">${ae}</div>
|
|
67
|
+
<div class="qp-modal-row"><button class="qp-ghost qp-copy" type="button" data-prompt="existing">${this.t("copy")}</button></div></section>
|
|
68
|
+
<section><h3>سوالات پرتکرار</h3><ul class="qp-list">
|
|
69
|
+
<li>نقشه خالی است؟ به کامپوننت ارتفاع بدهید (مثلاً <code dir="ltr">style={{ height: 480 }}</code>)؛ بدون ارتفاع نقشه دیده نمیشود.</li>
|
|
70
|
+
<li>لیبلهای فارسی نقشه نمایش داده نمیشود؟ glyphهای پیشفرض از CDN پکیج میآیند؛ برای self-host کردن، <code dir="ltr">map.glyphs</code> را به مسیر فونتهای پکیج (مثلاً <code dir="ltr">/fonts/{fontstack}/{range}.pbf</code> بعد از کپی <code dir="ltr">node_modules/@amir83nasr/map/fonts</code>) تنظیم کنید.</li>
|
|
71
|
+
<li>پیام خطای ورکر روی نقشه میبینید؟ در Vite، <code dir="ltr">maplibre-gl-worker.mjs?worker&url</code> را import و قبل از mount به <code dir="ltr">setupQomPickWorker()</code> بدهید؛ در Next.js/webpack هر دو فایل <code dir="ltr">maplibre-gl-worker.mjs</code> و <code dir="ltr">maplibre-gl-shared.mjs</code> را کپی کنید. پکیج همین خطا را یکبار به <code dir="ltr">onError</code> و کنسول هم میفرستد.</li>
|
|
72
|
+
<li>کدام نسخه maplibre؟ نسخه ۶؛ چون peer dependency است باید جدا نصب شود.</li>
|
|
73
|
+
<li>در Next.js App Router؟ کامپوننت client است؛ با <code dir="ltr">dynamic(..., { ssr: false })</code> لود کنید.</li>
|
|
74
|
+
<li>رایگان است؟ بله، قمپیک کاملاً متنباز و رایگان است.</li></ul></section>
|
|
75
|
+
<div class="qp-modal-row"><button class="qp-cta qp-back" type="button">${this.t("backToMap")}</button></div>
|
|
76
|
+
</div></div>`}openDocs(){var e;(e=this.root.querySelector(".qp-docs"))==null||e.removeAttribute("hidden")}closeDocs(){const e=this.root.querySelector(".qp-docs");e&&(e.hidden=!0)}copyPrompt(e){var r;const t=()=>{const i=e.textContent??"";e.textContent=this.t("copied"),window.setTimeout(()=>{e.textContent=i},1500)};if((r=navigator.clipboard)!=null&&r.writeText){const i=e.dataset.prompt??"zero",a=re[i]??I;navigator.clipboard.writeText(a).then(t,()=>this.fallbackCopy(a,t));return}const o=e.dataset.prompt??"zero";this.fallbackCopy(re[o]??I,t)}fallbackCopy(e,t){const o=document.createElement("textarea");o.value=e,o.style.position="fixed",o.style.opacity="0",document.body.appendChild(o),o.select();try{document.execCommand("copy")}catch{}o.remove(),t()}openSearch(){this.overlay.hidden=!1;const e=this.root.querySelector(".qp-overlay .qp-field input");e&&(e.value="",this.renderSuggest(""),window.setTimeout(()=>e.focus(),50))}closeSearch(){this.overlay.hidden=!0;const e=this.root.querySelector(".qp-overlay .qp-field input");e&&e.blur()}pickSuggestion(e){this.setLocation(e.lat,e.lng,{moveMap:!0,zoom:this.opts.behavior.pickZoom}),this.emitter.emit("pick",this.getLocation()),this.closeSearch()}pickResult(e,t){this.searchSeq++,this.setLocation(parseFloat(e),parseFloat(t),{moveMap:!0,zoom:this.opts.behavior.pickZoom});const o=this.getLocation();this.emitter.emit("pick",o),this.closeSearch()}suggestionRow(e){const t=w(`<button class="qp-row" type="button"><span class="qp-ric">${v.pin}</span><span class="qp-t"><b></b><small></small></span></button>`);return t.querySelector("b").textContent=S(e.name),t.querySelector("small").textContent=S(e.addr),t.addEventListener("click",()=>this.pickSuggestion(e)),t}resultRow(e,t,o){const r=w(`<button class="qp-card" type="button"><span class="qp-ric">${v.pin}</span><span><b></b><small></small></span></button>`);return r.querySelector("b").textContent=S(String(e).split(",")[0]),r.querySelector("small").textContent=S(ee(e)),r.addEventListener("click",()=>this.pickResult(t,o)),r}filteredSuggestions(e){const t=M(e).trim(),o=this.opts.search.limit??5;return this.suggestions().filter(r=>!t||M(r.name).includes(t)||M(r.addr).includes(t)).sort((r,i)=>r.name.localeCompare(i.name,"fa")).slice(0,Math.max(1,o))}suggestions(){return this.opts.search.suggestions??[]}renderSuggest(e){const t=this.root.querySelector(".qp-suggest"),o=this.root.querySelector(".qp-home"),r=this.root.querySelector(".qp-results");if(t){t.innerHTML="";for(const i of this.filteredSuggestions(e))t.appendChild(this.suggestionRow(i));o&&(o.hidden=!1),r&&(r.innerHTML="")}}renderDeskSuggest(e){const t=this.root.querySelector(".qp-suggest-desk"),o=this.root.querySelector(".qp-desk-home"),r=this.root.querySelector(".qp-results-desk");if(t){t.innerHTML="";for(const i of this.filteredSuggestions(e))t.appendChild(this.suggestionRow(i));o&&(o.hidden=!1),r&&(r.innerHTML="")}}onQuery(e){this.onQueryShared(e,t=>this.renderSuggest(t),t=>void this.runSearch(t))}onQueryShared(e,t,o){this.searchSeq++;const r=this.searchSeq;window.clearTimeout(this.searchT);const i=e.trim(),a=this.opts.search.minLength??oe;if(i.length<1){t("");return}if(i.length<a){t(i);return}this.searchT=window.setTimeout(()=>{r===this.searchSeq&&o(i)},this.opts.search.debounceMs??350)}async runSearch(e){await this.runRemoteSearch(e,".qp-results",".qp-home")}onDeskQuery(e){this.onQueryShared(e,t=>this.renderDeskSuggest(t),t=>void this.runRemoteSearch(t,".qp-results-desk",".qp-desk-home"))}async runRemoteSearch(e,t,o){var g;const r=e.trim(),i=this.root.querySelector(t),a=this.root.querySelector(o);if(!i)return;if(!r){t.includes("desk")?this.renderDeskSuggest(""):this.renderSuggest("");return}const n=this.opts.search.minLength??oe;if(r.length<n){t.includes("desk")?this.renderDeskSuggest(r):this.renderSuggest(r);return}const l=++this.searchSeq;(g=this.searchAbort)==null||g.abort(),this.searchAbort=new AbortController,a&&(a.hidden=!0),i.innerHTML=`<div class="qp-err">${this.t("searching")}</div>`;try{const m=await Ne(r,this.opts.search.limit??5,{signal:this.searchAbort.signal});if(l!==this.searchSeq)return;if(this.emitter.emit("searchResults",m),!m.length){i.innerHTML=`<div class="qp-err">${this.t("noResults")}</div>`;return}i.innerHTML="";for(const p of m)i.appendChild(this.resultRow(p.display_name,p.lat,p.lon))}catch(m){if(m instanceof DOMException&&m.name==="AbortError"||l!==this.searchSeq)return;i.innerHTML=`<div class="qp-err">${this.t("searchError")}</div>`,this.fail(m)}}openModal(e){var t;(t=this.root.querySelector(`.${e}`))==null||t.removeAttribute("hidden")}closeModal(e){const t=this.root.querySelector(`.${e}`);t&&(t.hidden=!0)}openConfirm(){!this.address&&!this.resolving&&this.scheduleResolve(this.lat,this.lng,100),this.root.querySelector(".qp-addr").value=this.address?S(this.address):"",this.root.querySelector(".qp-coords").textContent=`${Number(this.lat).toFixed(6)}, ${Number(this.lng).toFixed(6)}`,this.openModal("qp-confirm")}finishConfirm(){const e=this.root.querySelector(".qp-final"),t=this.root.querySelector(".qp-addr").value;e.disabled=!0;const o=e.textContent??"";e.textContent=this.t("submitting"),window.clearTimeout(this.confirmT),this.confirmT=window.setTimeout(()=>{if(this.destroyed)return;e.disabled=!1,e.textContent=o;const r=this.confirm(t);this.closeModal("qp-confirm"),this.toast(`${this.t("registered")} (${J(Number(r.lat))}, ${J(Number(r.lng))})`)},800)}toast(e){this.toastEl.textContent=e,this.toastEl.classList.add("show"),window.clearTimeout(this.toastT),this.toastT=window.setTimeout(()=>this.toastEl.classList.remove("show"),2600)}fail(e){const t=e instanceof Error?e:new Error(String(e));this.emitter.emit("error",t)}}function ne(s){z.setWorkerUrl(s)}const Ye=ne;function Xe({className:s,style:e,...t}){const o=P.useRef(null),r=P.useRef(t);return P.useEffect(()=>{r.current=t}),P.useEffect(()=>{if(!o.current)return;const i=new Qe({...r.current,container:o.current,onLocationChange:a=>{var n,l;return(l=(n=r.current).onLocationChange)==null?void 0:l.call(n,a)},onAddressResolved:a=>{var n,l;return(l=(n=r.current).onAddressResolved)==null?void 0:l.call(n,a)},onSearchResults:a=>{var n,l;return(l=(n=r.current).onSearchResults)==null?void 0:l.call(n,a)},onPick:a=>{var n,l;return(l=(n=r.current).onPick)==null?void 0:l.call(n,a)},onConfirm:a=>{var n,l;return(l=(n=r.current).onConfirm)==null?void 0:l.call(n,a)},onLocate:a=>{var n,l;return(l=(n=r.current).onLocate)==null?void 0:l.call(n,a)},onError:a=>{var n,l;return(l=(n=r.current).onError)==null?void 0:l.call(n,a)}});return()=>i.destroy()},[]),de.jsx("div",{ref:o,className:s,style:{height:480,...e}})}exports.LocationPickerView=Xe;exports.QOM_SUGGESTIONS=ie;exports.setupMapWorker=Ye;exports.setupQomPickWorker=ne;
|