@mapka/maplibre-gl-sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ /** biome-ignore-all lint/correctness/noUnusedImports: <explanation> */
2
+ import { h, Fragment } from "preact";
3
+ import { useState } from "preact/hooks";
4
+ import type { MapkaTooltipOptions } from "../types/marker.js";
5
+
6
+ interface TooltipProps extends MapkaTooltipOptions {
7
+ onClose?: () => void;
8
+ }
9
+
10
+ function HeartIcon({ filled }: { filled?: boolean }) {
11
+ return (
12
+ <svg
13
+ viewBox="0 0 32 32"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ aria-hidden="true"
16
+ focusable="false"
17
+ class="mapka-tooltip-icon"
18
+ >
19
+ <path
20
+ d="M16 28c7-4.73 14-10 14-17a6.98 6.98 0 0 0-7-7c-1.8 0-3.58.68-4.95 2.05L16 8.1l-2.05-2.05a6.98 6.98 0 0 0-9.9 0A6.98 6.98 0 0 0 2 11c0 7 7 12.27 14 17z"
21
+ fill={filled ? "currentColor" : "none"}
22
+ stroke="currentColor"
23
+ stroke-width="2"
24
+ />
25
+ </svg>
26
+ );
27
+ }
28
+
29
+ function CloseIcon() {
30
+ return (
31
+ <svg
32
+ viewBox="0 0 32 32"
33
+ xmlns="http://www.w3.org/2000/svg"
34
+ aria-hidden="true"
35
+ focusable="false"
36
+ class="mapka-tooltip-icon"
37
+ >
38
+ <path d="m6 6 20 20M26 6 6 26" fill="none" stroke="currentColor" stroke-width="3" />
39
+ </svg>
40
+ );
41
+ }
42
+
43
+ function ChevronLeftIcon() {
44
+ return (
45
+ <svg
46
+ viewBox="0 0 32 32"
47
+ xmlns="http://www.w3.org/2000/svg"
48
+ aria-hidden="true"
49
+ focusable="false"
50
+ class="mapka-tooltip-icon mapka-tooltip-icon-sm"
51
+ >
52
+ <path
53
+ d="M20 28 8.7 16.7a1 1 0 0 1 0-1.4L20 4"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ stroke-width="4"
57
+ />
58
+ </svg>
59
+ );
60
+ }
61
+
62
+ function ChevronRightIcon() {
63
+ return (
64
+ <svg
65
+ viewBox="0 0 32 32"
66
+ xmlns="http://www.w3.org/2000/svg"
67
+ aria-hidden="true"
68
+ focusable="false"
69
+ class="mapka-tooltip-icon mapka-tooltip-icon-sm"
70
+ >
71
+ <path
72
+ d="m12 4 11.3 11.3a1 1 0 0 1 0 1.4L12 28"
73
+ fill="none"
74
+ stroke="currentColor"
75
+ stroke-width="4"
76
+ />
77
+ </svg>
78
+ );
79
+ }
80
+
81
+ function StarIcon() {
82
+ return (
83
+ <svg
84
+ viewBox="0 0 32 32"
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ aria-hidden="true"
87
+ focusable="false"
88
+ class="mapka-tooltip-icon mapka-tooltip-icon-star"
89
+ >
90
+ <path
91
+ d="M15.094 1.579l-4.124 8.885-9.86 1.27a1 1 0 0 0-.542 1.736l7.293 6.565-1.965 9.852a1 1 0 0 0 1.483 1.061L16 25.951l8.625 4.997a1 1 0 0 0 1.482-1.06l-1.965-9.853 7.293-6.565a1 1 0 0 0-.541-1.735l-9.86-1.271-4.127-8.885a1 1 0 0 0-1.814 0z"
92
+ fill="currentColor"
93
+ />
94
+ </svg>
95
+ );
96
+ }
97
+
98
+ function ImageCarousel({
99
+ imageUrls,
100
+ title,
101
+ onFavorite,
102
+ id,
103
+ onClose,
104
+ }: {
105
+ imageUrls: string[];
106
+ title?: string;
107
+ onFavorite?: (id: string) => void;
108
+ id?: string;
109
+ onClose?: () => void;
110
+ }) {
111
+ const [currentIndex, setCurrentIndex] = useState(0);
112
+
113
+ const handlePrev = (e: Event) => {
114
+ e.stopPropagation();
115
+ setCurrentIndex((prev) => (prev === 0 ? imageUrls.length - 1 : prev - 1));
116
+ };
117
+
118
+ const handleNext = (e: Event) => {
119
+ e.stopPropagation();
120
+ setCurrentIndex((prev) => (prev + 1) % imageUrls.length);
121
+ };
122
+
123
+ const handleFavoriteClick = (e: Event) => {
124
+ e.stopPropagation();
125
+ if (onFavorite && id) {
126
+ onFavorite(id);
127
+ }
128
+ };
129
+
130
+ const handleCloseClick = (e: Event) => {
131
+ e.stopPropagation();
132
+ onClose?.();
133
+ };
134
+
135
+ return (
136
+ <div class="mapka-tooltip-carousel">
137
+ <div
138
+ class="mapka-tooltip-carousel-track"
139
+ style={{ transform: `translateX(-${currentIndex * 100}%)` }}
140
+ >
141
+ {imageUrls.map((url, index) => (
142
+ <img
143
+ key={index}
144
+ src={url}
145
+ alt={title || `Image ${index + 1}`}
146
+ class="mapka-tooltip-carousel-image"
147
+ />
148
+ ))}
149
+ </div>
150
+
151
+ <div class="mapka-tooltip-carousel-actions">
152
+ {onFavorite && (
153
+ <button
154
+ type="button"
155
+ class="mapka-tooltip-action-btn"
156
+ onClick={handleFavoriteClick}
157
+ aria-label="Add to favorites"
158
+ >
159
+ <HeartIcon />
160
+ </button>
161
+ )}
162
+ <button
163
+ type="button"
164
+ class="mapka-tooltip-action-btn"
165
+ onClick={handleCloseClick}
166
+ aria-label="Close"
167
+ >
168
+ <CloseIcon />
169
+ </button>
170
+ </div>
171
+
172
+ {imageUrls.length > 1 && (
173
+ <Fragment>
174
+ <button
175
+ type="button"
176
+ class="mapka-tooltip-carousel-btn mapka-tooltip-carousel-prev"
177
+ onClick={handlePrev}
178
+ aria-label="Previous image"
179
+ >
180
+ <ChevronLeftIcon />
181
+ </button>
182
+ <button
183
+ type="button"
184
+ class="mapka-tooltip-carousel-btn mapka-tooltip-carousel-next"
185
+ onClick={handleNext}
186
+ aria-label="Next image"
187
+ >
188
+ <ChevronRightIcon />
189
+ </button>
190
+
191
+ <div class="mapka-tooltip-dots">
192
+ {imageUrls.map((_, index) => (
193
+ <button
194
+ key={index}
195
+ type="button"
196
+ class={`mapka-tooltip-dot ${index === currentIndex ? "mapka-tooltip-dot-active" : ""}`}
197
+ onClick={(e) => {
198
+ e.stopPropagation();
199
+ setCurrentIndex(index);
200
+ }}
201
+ aria-label={`Go to image ${index + 1}`}
202
+ />
203
+ ))}
204
+ </div>
205
+ </Fragment>
206
+ )}
207
+ </div>
208
+ );
209
+ }
210
+
211
+ export function Tooltip({
212
+ id,
213
+ title,
214
+ rating,
215
+ description,
216
+ subtitle,
217
+ price,
218
+ imageUrls,
219
+ onFavorite,
220
+ onClose,
221
+ }: TooltipProps) {
222
+ const hasImages = imageUrls && imageUrls.length > 0;
223
+
224
+ return (
225
+ <div class="mapka-tooltip">
226
+ {hasImages && (
227
+ <ImageCarousel
228
+ imageUrls={imageUrls}
229
+ title={title}
230
+ onFavorite={onFavorite}
231
+ id={id}
232
+ onClose={onClose}
233
+ />
234
+ )}
235
+
236
+ <div class="mapka-tooltip-content">
237
+ {(title || rating) && (
238
+ <div class="mapka-tooltip-header">
239
+ {title && <h3 class="mapka-tooltip-title">{title}</h3>}
240
+ {rating && (
241
+ <div class="mapka-tooltip-rating">
242
+ <StarIcon />
243
+ <span class="mapka-tooltip-rating-value">
244
+ {rating.value.toFixed(2).replace(".", ",")}
245
+ </span>
246
+ <span class="mapka-tooltip-rating-count">({rating.count})</span>
247
+ </div>
248
+ )}
249
+ </div>
250
+ )}
251
+
252
+ {description && <p class="mapka-tooltip-description">{description}</p>}
253
+ {subtitle && <p class="mapka-tooltip-subtitle">{subtitle}</p>}
254
+
255
+ {price && (
256
+ <div class="mapka-tooltip-price">
257
+ {price.original && <span class="mapka-tooltip-price-original">{price.original}</span>}
258
+ <span class="mapka-tooltip-price-current">{price.current}</span>
259
+ {price.suffix && <span class="mapka-tooltip-price-suffix">{price.suffix}</span>}
260
+ </div>
261
+ )}
262
+ </div>
263
+ </div>
264
+ );
265
+ }
@@ -0,0 +1,52 @@
1
+ // biome-ignore lint/correctness/noUnusedImports: <explanation>
2
+ import { h } from "preact";
3
+ import { Popup } from "maplibre-gl";
4
+ import { render } from "preact";
5
+ import { Tooltip } from "../components/Tooltip.js";
6
+ import type { MapkaTooltipOptions } from "../types/marker.js";
7
+
8
+ let currentPopup: maplibregl.Popup | null = null;
9
+
10
+ /**
11
+ * Shows a tooltip for a marker
12
+ */
13
+ export function showTooltip(
14
+ marker: maplibregl.Marker,
15
+ options: MapkaTooltipOptions,
16
+ map: maplibregl.Map,
17
+ ) {
18
+ hideTooltip();
19
+
20
+ const container = document.createElement("div");
21
+
22
+ render(<Tooltip {...options} onClose={hideTooltip} />, container);
23
+
24
+ const popup = new Popup({
25
+ closeButton: false,
26
+ closeOnClick: false,
27
+ maxWidth: "320px",
28
+ offset: 12,
29
+ })
30
+ .setLngLat(marker.getLngLat())
31
+ .setDOMContent(container)
32
+ .addTo(map);
33
+
34
+ currentPopup = popup;
35
+ }
36
+
37
+ /**
38
+ * Hides the current tooltip
39
+ */
40
+ export function hideTooltip() {
41
+ if (currentPopup) {
42
+ currentPopup.remove();
43
+ currentPopup = null;
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Gets the current visible popup
49
+ */
50
+ export function getCurrentTooltip(): Popup | null {
51
+ return currentPopup;
52
+ }
package/src/styles.css CHANGED
@@ -804,5 +804,225 @@ a.maplibregl-ctrl-logo.maplibregl-compact {
804
804
  position: fixed !important;
805
805
  top: 0 !important;
806
806
  width: 100% !important;
807
- z-index: 99999
807
+ }
808
+
809
+ /* Mapka Tooltip Styles */
810
+ .mapka-tooltip {
811
+ border-radius: 12px;
812
+ overflow: hidden;
813
+ background: #fff;
814
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
815
+ }
816
+
817
+ .mapka-tooltip-icon {
818
+ display: block;
819
+ fill: none;
820
+ height: 16px;
821
+ width: 16px;
822
+ stroke: currentColor;
823
+ overflow: visible;
824
+ }
825
+
826
+ .mapka-tooltip-icon-sm {
827
+ height: 12px;
828
+ width: 12px;
829
+ }
830
+
831
+ .mapka-tooltip-icon-star {
832
+ height: 12px;
833
+ width: 12px;
834
+ fill: currentColor;
835
+ stroke: none;
836
+ }
837
+
838
+ /* Carousel */
839
+ .mapka-tooltip-carousel {
840
+ position: relative;
841
+ width: 100%;
842
+ height: 200px;
843
+ overflow: hidden;
844
+ border-radius: 12px 12px 0 0;
845
+ }
846
+
847
+ .mapka-tooltip-carousel-track {
848
+ display: flex;
849
+ height: 100%;
850
+ transition: transform 0.3s ease;
851
+ }
852
+
853
+ .mapka-tooltip-carousel-image {
854
+ min-width: 100%;
855
+ height: 100%;
856
+ object-fit: cover;
857
+ flex-shrink: 0;
858
+ }
859
+
860
+ .mapka-tooltip-carousel-actions {
861
+ position: absolute;
862
+ top: 12px;
863
+ right: 12px;
864
+ display: flex;
865
+ gap: 8px;
866
+ z-index: 3;
867
+ }
868
+
869
+ .mapka-tooltip-action-btn {
870
+ background: rgba(255, 255, 255, 0.95);
871
+ border: none;
872
+ border-radius: 50%;
873
+ width: 32px;
874
+ height: 32px;
875
+ cursor: pointer;
876
+ display: flex;
877
+ align-items: center;
878
+ justify-content: center;
879
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
880
+ transition: background 0.2s ease, transform 0.2s ease;
881
+ color: #222;
882
+ }
883
+
884
+ .mapka-tooltip-action-btn:hover {
885
+ background: #fff;
886
+ transform: scale(1.05);
887
+ }
888
+
889
+ .mapka-tooltip-carousel-btn {
890
+ position: absolute;
891
+ top: 50%;
892
+ transform: translateY(-50%);
893
+ background: rgba(255, 255, 255, 0.95);
894
+ border: none;
895
+ border-radius: 50%;
896
+ width: 28px;
897
+ height: 28px;
898
+ cursor: pointer;
899
+ display: flex;
900
+ align-items: center;
901
+ justify-content: center;
902
+ z-index: 2;
903
+ transition: background 0.2s ease, transform 0.2s ease;
904
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
905
+ color: #222;
906
+ }
907
+
908
+ .mapka-tooltip-carousel-btn:hover {
909
+ background: #fff;
910
+ transform: translateY(-50%) scale(1.05);
911
+ }
912
+
913
+ .mapka-tooltip-carousel-prev {
914
+ left: 12px;
915
+ }
916
+
917
+ .mapka-tooltip-carousel-next {
918
+ right: 12px;
919
+ }
920
+
921
+ .mapka-tooltip-dots {
922
+ position: absolute;
923
+ bottom: 12px;
924
+ left: 50%;
925
+ transform: translateX(-50%);
926
+ display: flex;
927
+ gap: 6px;
928
+ z-index: 2;
929
+ }
930
+
931
+ .mapka-tooltip-dot {
932
+ width: 6px;
933
+ height: 6px;
934
+ border-radius: 50%;
935
+ background: rgba(255, 255, 255, 0.6);
936
+ border: none;
937
+ cursor: pointer;
938
+ padding: 0;
939
+ transition: background 0.2s ease, transform 0.2s ease;
940
+ }
941
+
942
+ .mapka-tooltip-dot:hover {
943
+ background: rgba(255, 255, 255, 0.8);
944
+ transform: scale(1.2);
945
+ }
946
+
947
+ .mapka-tooltip-dot-active {
948
+ background: #fff;
949
+ }
950
+
951
+ /* Content */
952
+ .mapka-tooltip-content {
953
+ padding: 12px 16px 16px;
954
+ }
955
+
956
+ .mapka-tooltip-header {
957
+ display: flex;
958
+ justify-content: space-between;
959
+ align-items: flex-start;
960
+ gap: 8px;
961
+ margin-bottom: 4px;
962
+ }
963
+
964
+ .mapka-tooltip-title {
965
+ margin: 0;
966
+ font-size: 15px;
967
+ font-weight: 600;
968
+ color: #222;
969
+ line-height: 1.3;
970
+ flex: 1;
971
+ }
972
+
973
+ .mapka-tooltip-rating {
974
+ display: flex;
975
+ align-items: center;
976
+ gap: 4px;
977
+ color: #222;
978
+ font-size: 14px;
979
+ flex-shrink: 0;
980
+ }
981
+
982
+ .mapka-tooltip-rating-value {
983
+ font-weight: 600;
984
+ }
985
+
986
+ .mapka-tooltip-rating-count {
987
+ color: #717171;
988
+ }
989
+
990
+ .mapka-tooltip-description {
991
+ margin: 0 0 4px 0;
992
+ font-size: 14px;
993
+ color: #717171;
994
+ line-height: 1.4;
995
+ overflow: hidden;
996
+ text-overflow: ellipsis;
997
+ display: -webkit-box;
998
+ -webkit-line-clamp: 2;
999
+ -webkit-box-orient: vertical;
1000
+ }
1001
+
1002
+ .mapka-tooltip-subtitle {
1003
+ margin: 0 0 8px 0;
1004
+ font-size: 14px;
1005
+ color: #717171;
1006
+ line-height: 1.4;
1007
+ }
1008
+
1009
+ .mapka-tooltip-price {
1010
+ display: flex;
1011
+ align-items: baseline;
1012
+ gap: 6px;
1013
+ font-size: 14px;
1014
+ }
1015
+
1016
+ .mapka-tooltip-price-original {
1017
+ color: #717171;
1018
+ text-decoration: line-through;
1019
+ }
1020
+
1021
+ .mapka-tooltip-price-current {
1022
+ font-weight: 600;
1023
+ color: #222;
1024
+ }
1025
+
1026
+ .mapka-tooltip-price-suffix {
1027
+ color: #717171;
808
1028
  }
@@ -1,8 +1,24 @@
1
+ export interface MapkaTooltipRating {
2
+ value: number;
3
+ count: number;
4
+ }
5
+
6
+ export interface MapkaTooltipPrice {
7
+ current: string;
8
+ original?: string;
9
+ suffix?: string;
10
+ }
11
+
1
12
  export interface MapkaTooltipOptions {
13
+ id?: string;
2
14
  trigger?: "hover" | "click";
3
15
  title?: string;
16
+ rating?: MapkaTooltipRating;
4
17
  description?: string;
18
+ subtitle?: string;
19
+ price?: MapkaTooltipPrice;
5
20
  imageUrls?: string[];
21
+ onFavorite?: (id: string) => void;
6
22
  }
7
23
 
8
24
  export interface MapkaMarkerOptions {