@mapka/maplibre-gl-sdk 0.9.0 → 0.11.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,177 @@
1
+
2
+ /* Mapka Tooltip Styles */
3
+ .mapka-tooltip {
4
+ border-radius: 12px;
5
+ overflow: hidden;
6
+ background: #fff;
7
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
8
+ }
9
+
10
+ .mapka-popup-icon {
11
+ display: block;
12
+ fill: none;
13
+ height: 16px;
14
+ width: 16px;
15
+ stroke: currentColor;
16
+ overflow: visible;
17
+ }
18
+
19
+ .mapka-popup-icon-sm {
20
+ height: 12px;
21
+ width: 12px;
22
+ }
23
+
24
+ .mapka-popup-icon-star {
25
+ height: 12px;
26
+ width: 12px;
27
+ fill: currentColor;
28
+ stroke: none;
29
+ }
30
+
31
+ /* Carousel */
32
+ .mapka-popup-carousel {
33
+ position: relative;
34
+ width: 100%;
35
+ height: 200px;
36
+ overflow: hidden;
37
+ border-radius: 12px 12px 0 0;
38
+ }
39
+
40
+ .mapka-popup-carousel-track {
41
+ display: flex;
42
+ height: 100%;
43
+ transition: transform 0.3s ease;
44
+ }
45
+
46
+ .mapka-popup-carousel-image {
47
+ min-width: 100%;
48
+ height: 100%;
49
+ object-fit: cover;
50
+ flex-shrink: 0;
51
+ }
52
+
53
+ .mapka-popup-carousel-actions {
54
+ position: absolute;
55
+ top: 12px;
56
+ right: 12px;
57
+ display: flex;
58
+ gap: 8px;
59
+ z-index: 3;
60
+ }
61
+
62
+ .mapka-popup-action-btn {
63
+ background: rgba(255, 255, 255, 0.95);
64
+ border: none;
65
+ border-radius: 50%;
66
+ width: 32px;
67
+ height: 32px;
68
+ cursor: pointer;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
73
+ transition: background 0.2s ease, transform 0.2s ease;
74
+ color: #222;
75
+ }
76
+
77
+ .mapka-popup-action-btn:hover {
78
+ background: #fff;
79
+ transform: scale(1.05);
80
+ }
81
+
82
+ .mapka-popup-carousel-btn {
83
+ position: absolute;
84
+ top: 50%;
85
+ transform: translateY(-50%);
86
+ background: rgba(255, 255, 255, 0.95);
87
+ border: none;
88
+ border-radius: 50%;
89
+ width: 28px;
90
+ height: 28px;
91
+ cursor: pointer;
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: center;
95
+ z-index: 2;
96
+ transition: background 0.2s ease, transform 0.2s ease;
97
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.18);
98
+ color: #222;
99
+ }
100
+
101
+ .mapka-popup-carousel-btn:hover {
102
+ background: #fff;
103
+ transform: translateY(-50%) scale(1.05);
104
+ }
105
+
106
+ .mapka-popup-carousel-prev {
107
+ left: 12px;
108
+ }
109
+
110
+ .mapka-popup-carousel-next {
111
+ right: 12px;
112
+ }
113
+
114
+ .mapka-popup-dots {
115
+ position: absolute;
116
+ bottom: 12px;
117
+ left: 50%;
118
+ transform: translateX(-50%);
119
+ display: flex;
120
+ gap: 6px;
121
+ z-index: 2;
122
+ }
123
+
124
+ .mapka-popup-dot {
125
+ width: 6px;
126
+ height: 6px;
127
+ border-radius: 50%;
128
+ background: rgba(255, 255, 255, 0.6);
129
+ border: none;
130
+ cursor: pointer;
131
+ padding: 0;
132
+ transition: background 0.2s ease, transform 0.2s ease;
133
+ }
134
+
135
+ .mapka-popup-dot:hover {
136
+ background: rgba(255, 255, 255, 0.8);
137
+ transform: scale(1.2);
138
+ }
139
+
140
+ .mapka-popup-dot-active {
141
+ background: #fff;
142
+ }
143
+
144
+ /* Content */
145
+ .mapka-popup-content {
146
+ padding: 12px 16px 16px;
147
+ }
148
+
149
+ .mapka-popup-header {
150
+ display: flex;
151
+ justify-content: space-between;
152
+ align-items: flex-start;
153
+ gap: 8px;
154
+ margin-bottom: 4px;
155
+ }
156
+
157
+ .mapka-popup-title {
158
+ margin: 0;
159
+ font-size: 15px;
160
+ font-weight: 600;
161
+ color: #222;
162
+ line-height: 1.3;
163
+ flex: 1;
164
+ }
165
+
166
+
167
+ .mapka-popup-description {
168
+ margin: 0 0 4px 0;
169
+ font-size: 14px;
170
+ color: #717171;
171
+ line-height: 1.4;
172
+ overflow: hidden;
173
+ text-overflow: ellipsis;
174
+ display: -webkit-box;
175
+ -webkit-line-clamp: 2;
176
+ -webkit-box-orient: vertical;
177
+ }
@@ -1,10 +1,11 @@
1
1
  /** biome-ignore-all lint/correctness/noUnusedImports: <explanation> */
2
2
  import { h, Fragment } from "preact";
3
3
  import { useState } from "preact/hooks";
4
- import type { MapkaTooltipOptions } from "../types/marker.js";
4
+ import type { MapkaPopupContent } from "../types/marker.js";
5
5
 
6
- interface TooltipProps extends MapkaTooltipOptions {
6
+ interface PopupProps extends MapkaPopupContent {
7
7
  onClose?: () => void;
8
+ closeButton?: boolean;
8
9
  }
9
10
 
10
11
  function HeartIcon({ filled }: { filled?: boolean }) {
@@ -14,7 +15,7 @@ function HeartIcon({ filled }: { filled?: boolean }) {
14
15
  xmlns="http://www.w3.org/2000/svg"
15
16
  aria-hidden="true"
16
17
  focusable="false"
17
- class="mapka-tooltip-icon"
18
+ class="mapka-popup-icon"
18
19
  >
19
20
  <path
20
21
  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"
@@ -33,7 +34,7 @@ function CloseIcon() {
33
34
  xmlns="http://www.w3.org/2000/svg"
34
35
  aria-hidden="true"
35
36
  focusable="false"
36
- class="mapka-tooltip-icon"
37
+ class="mapka-popup-icon"
37
38
  >
38
39
  <path d="m6 6 20 20M26 6 6 26" fill="none" stroke="currentColor" stroke-width="3" />
39
40
  </svg>
@@ -47,7 +48,7 @@ function ChevronLeftIcon() {
47
48
  xmlns="http://www.w3.org/2000/svg"
48
49
  aria-hidden="true"
49
50
  focusable="false"
50
- class="mapka-tooltip-icon mapka-tooltip-icon-sm"
51
+ class="mapka-popup-icon mapka-popup-icon-sm"
51
52
  >
52
53
  <path
53
54
  d="M20 28 8.7 16.7a1 1 0 0 1 0-1.4L20 4"
@@ -66,7 +67,7 @@ function ChevronRightIcon() {
66
67
  xmlns="http://www.w3.org/2000/svg"
67
68
  aria-hidden="true"
68
69
  focusable="false"
69
- class="mapka-tooltip-icon mapka-tooltip-icon-sm"
70
+ class="mapka-popup-icon mapka-popup-icon-sm"
70
71
  >
71
72
  <path
72
73
  d="m12 4 11.3 11.3a1 1 0 0 1 0 1.4L12 28"
@@ -78,32 +79,17 @@ function ChevronRightIcon() {
78
79
  );
79
80
  }
80
81
 
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
82
  function ImageCarousel({
99
83
  imageUrls,
100
84
  title,
101
85
  onFavorite,
102
86
  id,
87
+ closeButton,
103
88
  onClose,
104
89
  }: {
105
90
  imageUrls: string[];
106
91
  title?: string;
92
+ closeButton?: boolean;
107
93
  onFavorite?: (id: string) => void;
108
94
  id?: string;
109
95
  onClose?: () => void;
@@ -133,9 +119,9 @@ function ImageCarousel({
133
119
  };
134
120
 
135
121
  return (
136
- <div class="mapka-tooltip-carousel">
122
+ <div class="mapka-popup-carousel">
137
123
  <div
138
- class="mapka-tooltip-carousel-track"
124
+ class="mapka-popup-carousel-track"
139
125
  style={{ transform: `translateX(-${currentIndex * 100}%)` }}
140
126
  >
141
127
  {imageUrls.map((url, index) => (
@@ -143,37 +129,39 @@ function ImageCarousel({
143
129
  key={index}
144
130
  src={url}
145
131
  alt={title || `Image ${index + 1}`}
146
- class="mapka-tooltip-carousel-image"
132
+ class="mapka-popup-carousel-image"
147
133
  />
148
134
  ))}
149
135
  </div>
150
136
 
151
- <div class="mapka-tooltip-carousel-actions">
137
+ <div class="mapka-popup-carousel-actions">
152
138
  {onFavorite && (
153
139
  <button
154
140
  type="button"
155
- class="mapka-tooltip-action-btn"
141
+ class="mapka-popup-action-btn"
156
142
  onClick={handleFavoriteClick}
157
143
  aria-label="Add to favorites"
158
144
  >
159
145
  <HeartIcon />
160
146
  </button>
161
147
  )}
162
- <button
163
- type="button"
164
- class="mapka-tooltip-action-btn"
165
- onClick={handleCloseClick}
166
- aria-label="Close"
167
- >
168
- <CloseIcon />
169
- </button>
148
+ {closeButton && (
149
+ <button
150
+ type="button"
151
+ class="mapka-popup-action-btn"
152
+ onClick={handleCloseClick}
153
+ aria-label="Close"
154
+ >
155
+ <CloseIcon />
156
+ </button>
157
+ )}
170
158
  </div>
171
159
 
172
160
  {imageUrls.length > 1 && (
173
161
  <Fragment>
174
162
  <button
175
163
  type="button"
176
- class="mapka-tooltip-carousel-btn mapka-tooltip-carousel-prev"
164
+ class="mapka-popup-carousel-btn mapka-popup-carousel-prev"
177
165
  onClick={handlePrev}
178
166
  aria-label="Previous image"
179
167
  >
@@ -181,19 +169,19 @@ function ImageCarousel({
181
169
  </button>
182
170
  <button
183
171
  type="button"
184
- class="mapka-tooltip-carousel-btn mapka-tooltip-carousel-next"
172
+ class="mapka-popup-carousel-btn mapka-popup-carousel-next"
185
173
  onClick={handleNext}
186
174
  aria-label="Next image"
187
175
  >
188
176
  <ChevronRightIcon />
189
177
  </button>
190
178
 
191
- <div class="mapka-tooltip-dots">
179
+ <div class="mapka-popup-dots">
192
180
  {imageUrls.map((_, index) => (
193
181
  <button
194
182
  key={index}
195
183
  type="button"
196
- class={`mapka-tooltip-dot ${index === currentIndex ? "mapka-tooltip-dot-active" : ""}`}
184
+ class={`mapka-popup-dot ${index === currentIndex ? "mapka-popup-dot-active" : ""}`}
197
185
  onClick={(e) => {
198
186
  e.stopPropagation();
199
187
  setCurrentIndex(index);
@@ -208,17 +196,14 @@ function ImageCarousel({
208
196
  );
209
197
  }
210
198
 
211
- export function Tooltip({
212
- id,
199
+ export function PopupContent({
213
200
  title,
214
- rating,
215
201
  description,
216
- subtitle,
217
- price,
202
+ closeButton,
218
203
  imageUrls,
219
204
  onFavorite,
220
205
  onClose,
221
- }: TooltipProps) {
206
+ }: PopupProps) {
222
207
  const hasImages = imageUrls && imageUrls.length > 0;
223
208
 
224
209
  return (
@@ -227,38 +212,15 @@ export function Tooltip({
227
212
  <ImageCarousel
228
213
  imageUrls={imageUrls}
229
214
  title={title}
215
+ closeButton={closeButton}
230
216
  onFavorite={onFavorite}
231
- id={id}
232
217
  onClose={onClose}
233
218
  />
234
219
  )}
235
220
 
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
- )}
221
+ <div class="mapka-popup-content">
222
+ {title && <h3 class="mapka-popup-title">{title}</h3>}
223
+ {description && <p class="mapka-popup-description">{description}</p>}
262
224
  </div>
263
225
  </div>
264
226
  );
package/src/map.ts CHANGED
@@ -1,21 +1,40 @@
1
1
  import * as maplibregl from "maplibre-gl";
2
2
  import { loadLayersIcons } from "./modules/icons.js";
3
- import { addMarkers, addMarkersStyleDiff } from "./modules/markers.js";
3
+ import {
4
+ closeOnMapClickPopups,
5
+ closePopupsById,
6
+ getPopupId,
7
+ openPopup,
8
+ updatePopup,
9
+ } from "./modules/popup.js";
10
+ import {
11
+ addMarkers,
12
+ addStyleDiffMarkers,
13
+ addStyleMarkers,
14
+ clearMarkers,
15
+ updateMarkers,
16
+ } from "./modules/markers.js";
4
17
  import type {
18
+ Marker,
19
+ Popup,
5
20
  RequestTransformFunction,
6
21
  MapStyleImageMissingEvent,
7
22
  MapOptions,
23
+ TransformStyleFunction,
8
24
  StyleSwapOptions,
9
25
  StyleOptions,
10
26
  StyleSpecification,
11
27
  } from "maplibre-gl";
28
+ import type { MapkaMarkerOptions, MapkaPopupOptions } from "./types/marker.js";
12
29
 
13
30
  export interface MapkaMapOptions extends MapOptions {
31
+ maxPopups?: number;
32
+
14
33
  apiKey: string;
15
34
  env?: "dev" | "prod" | "local";
16
35
  }
17
36
 
18
- const noopTransformRequest: maplibregl.RequestTransformFunction = (url) => {
37
+ const noopTransformRequest: RequestTransformFunction = (url) => {
19
38
  return {
20
39
  url,
21
40
  };
@@ -35,26 +54,49 @@ const createTransformRequest =
35
54
  return transformRequest ? transformRequest(url) : noopTransformRequest(url);
36
55
  };
37
56
 
38
- const noopTransformStyle: maplibregl.TransformStyleFunction = (_, next) => {
57
+ const noopTransformStyle: TransformStyleFunction = (_, next) => {
39
58
  return next;
40
59
  };
41
60
 
61
+ export type MapMapkaPopup = {
62
+ id: string;
63
+ container: HTMLElement;
64
+ options: MapkaPopupOptions;
65
+ popup: Popup;
66
+ };
67
+
68
+ export type MapMapkaMarker = {
69
+ id: string;
70
+ options: MapkaMarkerOptions;
71
+ marker: Marker;
72
+ };
73
+
42
74
  export class MapkaMap extends maplibregl.Map {
43
75
  static env: string = "prod";
44
76
 
45
- public constructor({ transformRequest, apiKey, ...options }: MapkaMapOptions) {
77
+ public markers: MapMapkaMarker[] = [];
78
+
79
+ public maxPopups: number = 1;
80
+ public popups: MapMapkaPopup[] = [];
81
+
82
+ public constructor({ transformRequest, apiKey, maxPopups = 1, ...options }: MapkaMapOptions) {
46
83
  super({
47
84
  ...options,
48
85
  transformRequest: createTransformRequest(apiKey, transformRequest),
49
86
  });
50
87
 
51
- super.on("styledata", (_: maplibregl.MapStyleDataEvent) => {
52
- addMarkers(this);
53
- });
88
+ this.maxPopups = maxPopups;
54
89
 
55
90
  super.on("styleimagemissing", (event: MapStyleImageMissingEvent) => {
56
91
  loadLayersIcons(this, event);
57
92
  });
93
+ super.on("click", () => {
94
+ closeOnMapClickPopups(this);
95
+ });
96
+
97
+ super.on("style.load", () => {
98
+ addStyleMarkers(this);
99
+ });
58
100
  }
59
101
 
60
102
  public setStyle(
@@ -66,11 +108,35 @@ export class MapkaMap extends maplibregl.Map {
66
108
  super.setStyle(style, {
67
109
  ...rest,
68
110
  transformStyle: (prev, next) => {
69
- addMarkersStyleDiff(this, next);
111
+ addStyleDiffMarkers(this, next);
70
112
  return transformStyle(prev, next);
71
113
  },
72
114
  });
73
115
 
74
116
  return this;
75
117
  }
118
+
119
+ public addMarkers(markers: MapkaMarkerOptions[]) {
120
+ addMarkers(this, markers);
121
+ }
122
+
123
+ public updateMarkers(markers: MapkaMarkerOptions[]) {
124
+ updateMarkers(this, markers);
125
+ }
126
+
127
+ public clearMarkers() {
128
+ clearMarkers(this);
129
+ }
130
+
131
+ public openPopup(popup: MapkaPopupOptions, id: string = getPopupId(popup)) {
132
+ return openPopup(this, popup, id);
133
+ }
134
+
135
+ public closePopup(id: string) {
136
+ closePopupsById(this, id);
137
+ }
138
+
139
+ public updatePopup(popup: MapkaPopupOptions, id: string = getPopupId(popup)) {
140
+ return updatePopup(this, popup, id);
141
+ }
76
142
  }