@mapcreator/sdk 0.0.7 → 0.0.9

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.
Files changed (123) hide show
  1. package/dist/{HighlightManager.d.ts → esm/HighlightManager.d.ts} +2 -2
  2. package/dist/esm/HighlightManager.js +203 -0
  3. package/dist/{MCMap.d.ts → esm/MCMap.d.ts} +3 -5
  4. package/dist/esm/MCMap.js +254 -0
  5. package/dist/{PopupManager.d.ts → esm/PopupManager.d.ts} +4 -4
  6. package/dist/esm/PopupManager.js +297 -0
  7. package/dist/{Registry.d.ts → esm/Registry.d.ts} +3 -3
  8. package/dist/esm/Registry.js +74 -0
  9. package/dist/{adornments → esm/adornments}/categoricalLegend.d.ts +1 -1
  10. package/dist/esm/adornments/categoricalLegend.js +141 -0
  11. package/dist/{adornments → esm/adornments}/connectedLegend.d.ts +2 -2
  12. package/dist/esm/adornments/connectedLegend.js +393 -0
  13. package/dist/{adornments → esm/adornments}/customAdornment.d.ts +1 -1
  14. package/dist/esm/adornments/customAdornment.js +29 -0
  15. package/dist/{adornments → esm/adornments}/heading.d.ts +1 -1
  16. package/dist/esm/adornments/heading.js +71 -0
  17. package/dist/esm/adornments/insetMap.d.ts +3 -0
  18. package/dist/esm/adornments/insetMap.js +351 -0
  19. package/dist/{adornments → esm/adornments}/manualLegend.d.ts +1 -1
  20. package/dist/esm/adornments/manualLegend.js +15 -0
  21. package/dist/esm/adornments/northArrow.d.ts +3 -0
  22. package/dist/esm/adornments/northArrow.js +24 -0
  23. package/dist/esm/adornments/scalebar.d.ts +3 -0
  24. package/dist/esm/adornments/scalebar.js +176 -0
  25. package/dist/{constants → esm/constants}/index.d.ts +2 -2
  26. package/dist/esm/constants/index.js +53 -0
  27. package/dist/esm/controls/controls.js +7 -0
  28. package/dist/{controls → esm/controls}/fullscreenControls.d.ts +1 -1
  29. package/dist/esm/controls/fullscreenControls.js +29 -0
  30. package/dist/{controls → esm/controls}/geocoderControl.d.ts +1 -1
  31. package/dist/esm/controls/geocoderControl.js +202 -0
  32. package/dist/{controls → esm/controls}/geolocationControls.d.ts +1 -1
  33. package/dist/esm/controls/geolocationControls.js +65 -0
  34. package/dist/esm/controls/refreshMapControls.d.ts +3 -0
  35. package/dist/esm/controls/refreshMapControls.js +26 -0
  36. package/dist/esm/controls/webControls.d.ts +4 -0
  37. package/dist/esm/controls/webControls.js +40 -0
  38. package/dist/{controls → esm/controls}/zoomControls.d.ts +1 -1
  39. package/dist/esm/controls/zoomControls.js +23 -0
  40. package/dist/esm/i18n.js +21 -0
  41. package/dist/esm/index.d.ts +5 -0
  42. package/dist/esm/index.js +5 -0
  43. package/dist/esm/locales/da_DK/strings.json +7 -0
  44. package/dist/esm/locales/de_DE/strings.json +7 -0
  45. package/dist/esm/locales/en_GB/strings.json +7 -0
  46. package/dist/esm/locales/es_ES/strings.json +7 -0
  47. package/dist/esm/locales/fr_FR/strings.json +7 -0
  48. package/dist/esm/locales/it_IT/strings.json +7 -0
  49. package/dist/esm/locales/nl_NL/strings.json +7 -0
  50. package/dist/esm/models/area.d.ts +5 -0
  51. package/dist/esm/models/area.js +165 -0
  52. package/dist/esm/models/circle.d.ts +5 -0
  53. package/dist/esm/models/circle.js +110 -0
  54. package/dist/esm/models/dot.d.ts +3 -0
  55. package/dist/esm/models/dot.js +42 -0
  56. package/dist/esm/models/line.d.ts +4 -0
  57. package/dist/esm/models/line.js +117 -0
  58. package/dist/esm/models/marker.d.ts +5 -0
  59. package/dist/esm/models/marker.js +179 -0
  60. package/dist/esm/models/polygon.d.ts +5 -0
  61. package/dist/esm/models/polygon.js +80 -0
  62. package/dist/{renderAdornments.d.ts → esm/renderAdornments.d.ts} +3 -3
  63. package/dist/esm/renderAdornments.js +129 -0
  64. package/dist/esm/types/geometry.js +1 -0
  65. package/dist/{types → esm/types}/index.d.ts +1 -1
  66. package/dist/esm/types/index.js +1 -0
  67. package/dist/esm/types/jobObject.js +1 -0
  68. package/dist/{types → esm/types}/mapstyle.d.ts +6 -2
  69. package/dist/esm/types/mapstyle.js +1 -0
  70. package/dist/esm/utils/browser.js +6 -0
  71. package/dist/{utils → esm/utils}/choropleth.d.ts +3 -3
  72. package/dist/esm/utils/choropleth.js +110 -0
  73. package/dist/esm/utils/fullscreen.js +40 -0
  74. package/dist/{utils → esm/utils}/geolocation.d.ts +1 -1
  75. package/dist/esm/utils/geolocation.js +93 -0
  76. package/dist/{utils → esm/utils}/graphhopper.d.ts +1 -1
  77. package/dist/esm/utils/graphhopper.js +41 -0
  78. package/dist/{utils → esm/utils}/helpers.d.ts +2 -2
  79. package/dist/esm/utils/helpers.js +116 -0
  80. package/dist/{utils → esm/utils}/language.d.ts +1 -1
  81. package/dist/esm/utils/language.js +170 -0
  82. package/dist/{utils → esm/utils}/models.d.ts +4 -4
  83. package/dist/esm/utils/models.js +103 -0
  84. package/dist/{utils → esm/utils}/overlays.d.ts +1 -1
  85. package/dist/esm/utils/overlays.js +87 -0
  86. package/dist/esm/utils/scalebar.js +52 -0
  87. package/dist/{utils → esm/utils}/svgHelpers.d.ts +4 -3
  88. package/dist/esm/utils/svgHelpers.js +1512 -0
  89. package/dist/{utils → esm/utils}/template.d.ts +2 -2
  90. package/dist/esm/utils/template.js +120 -0
  91. package/dist/{utils → esm/utils}/youtube.d.ts +1 -1
  92. package/dist/esm/utils/youtube.js +64 -0
  93. package/dist/favicon-32x32.png +0 -0
  94. package/dist/mapcreator-sdk.umd.cjs +91 -91
  95. package/dist/report.html +4950 -0
  96. package/package.json +7 -7
  97. package/dist/adornments/insetMap.d.ts +0 -3
  98. package/dist/adornments/northArrow.d.ts +0 -3
  99. package/dist/adornments/scalebar.d.ts +0 -3
  100. package/dist/controls/refreshMapControls.d.ts +0 -3
  101. package/dist/controls/webControls.d.ts +0 -4
  102. package/dist/index.d.ts +0 -2
  103. package/dist/locales/da_DK/strings.json.d.ts +0 -10
  104. package/dist/locales/de_DE/strings.json.d.ts +0 -10
  105. package/dist/locales/en_GB/strings.json.d.ts +0 -10
  106. package/dist/locales/es_ES/strings.json.d.ts +0 -10
  107. package/dist/locales/fr_FR/strings.json.d.ts +0 -10
  108. package/dist/locales/it_IT/strings.json.d.ts +0 -10
  109. package/dist/locales/nl_NL/strings.json.d.ts +0 -10
  110. package/dist/mapcreator-sdk.js +0 -39590
  111. package/dist/models/area.d.ts +0 -5
  112. package/dist/models/circle.d.ts +0 -5
  113. package/dist/models/dot.d.ts +0 -3
  114. package/dist/models/line.d.ts +0 -4
  115. package/dist/models/marker.d.ts +0 -5
  116. package/dist/models/polygon.d.ts +0 -5
  117. /package/dist/{controls → esm/controls}/controls.d.ts +0 -0
  118. /package/dist/{i18n.d.ts → esm/i18n.d.ts} +0 -0
  119. /package/dist/{types → esm/types}/geometry.d.ts +0 -0
  120. /package/dist/{types → esm/types}/jobObject.d.ts +0 -0
  121. /package/dist/{utils → esm/utils}/browser.d.ts +0 -0
  122. /package/dist/{utils → esm/utils}/fullscreen.d.ts +0 -0
  123. /package/dist/{utils → esm/utils}/scalebar.d.ts +0 -0
@@ -0,0 +1,297 @@
1
+ import { Popup, } from '@mapcreator/maplibre-gl';
2
+ import { buildTemplate, closeButtonHtml, cycleButtonsHtml } from '@/utils/template';
3
+ import { Registry } from '@/Registry';
4
+ import { HighlightManager } from '@/HighlightManager';
5
+ export class PopupManager {
6
+ map;
7
+ popupPosition = 'bottomLeft';
8
+ mapContainer;
9
+ popupContainer;
10
+ feature;
11
+ currentFeatureId;
12
+ currentModelId;
13
+ popupElements = [];
14
+ language = 'en';
15
+ promoteIdCache = new Map();
16
+ publication;
17
+ floatingPopup;
18
+ registry;
19
+ highlightManager;
20
+ mode;
21
+ restrictMapMovement;
22
+ center;
23
+ constructor(map, mode) {
24
+ this.map = map;
25
+ this.mode = mode;
26
+ this.mapContainer = this.map.getContainer();
27
+ this.popupContainer = document.createElement('div');
28
+ this.popupContainer.classList.add('popup-container');
29
+ this.map.on('mousemove', e => this.onMouseMove(e));
30
+ this.map.on('click', e => this.onClick(e));
31
+ }
32
+ setJobObject(jobObject) {
33
+ this.registry = new Registry(jobObject);
34
+ }
35
+ setParams(popupPosition, language, restrictMapMovement, center) {
36
+ this.popupPosition = popupPosition;
37
+ this.language = language;
38
+ this.restrictMapMovement = restrictMapMovement;
39
+ this.center = center;
40
+ }
41
+ setPublication(publication) {
42
+ this.publication = publication;
43
+ }
44
+ setpopupElements() {
45
+ this.popupElements = this.registry.getPopupElements();
46
+ }
47
+ setHighlightManager() {
48
+ this.highlightManager = new HighlightManager(this.map, this.registry.getPopupElements());
49
+ }
50
+ onMouseMove(e) {
51
+ this.feature = this.getInteractiveFeature(e);
52
+ if (this.feature) {
53
+ this.map.getCanvas().style.cursor = 'pointer';
54
+ }
55
+ else {
56
+ this.map.getCanvas().style.cursor = '';
57
+ }
58
+ }
59
+ onClick(e) {
60
+ this.feature = this.getInteractiveFeature(e);
61
+ this.currentModelId = this.feature?.properties?.id;
62
+ if (this.feature &&
63
+ (!this.mapContainer.contains(this.popupContainer) ||
64
+ this.feature?.id !== this.currentFeatureId)) {
65
+ this.addPopup(e);
66
+ this.highlightManager?.highlight(this.getPopupElementFromFeature(this.feature));
67
+ }
68
+ else {
69
+ this.hidePopup();
70
+ this.highlightManager?.highlight(undefined);
71
+ }
72
+ }
73
+ getPopupElementFromFeature(feature) {
74
+ return this.popupElements.find(item => item.modelId === feature.properties.id);
75
+ }
76
+ getInteractiveFeature(e) {
77
+ const features = this.map.queryRenderedFeatures(e.point);
78
+ return features.find(feature => {
79
+ // Overlay interactive feature
80
+ if (Object.keys(feature.layer.metadata ?? {}).some(key => key.startsWith('mc-interactivity-template'))) {
81
+ return true;
82
+ }
83
+ // User-created interactive feature
84
+ const model = this.registry.getModel(feature);
85
+ return !!(model?.popup ?? model?.popupMedia);
86
+ });
87
+ }
88
+ addPopup(e) {
89
+ if (!this.feature) {
90
+ return;
91
+ }
92
+ this.floatingPopup?.remove();
93
+ const toNumber = Number(this.feature.id);
94
+ this.currentFeatureId = isNaN(toNumber) ? this.feature.id : toNumber;
95
+ const template = this.getTemplate(this.feature);
96
+ if (!template) {
97
+ return;
98
+ }
99
+ if (this.popupPosition === 'mapElement') {
100
+ if (this.feature.geometry.type === 'Point') {
101
+ this.addFloatingPopup(template, this.feature.geometry.coordinates);
102
+ }
103
+ else {
104
+ const { lng, lat } = this.map.unproject(e.point);
105
+ this.addFloatingPopup(template, [lng, lat]);
106
+ }
107
+ return;
108
+ }
109
+ this.popupContainer.innerHTML = template;
110
+ const currentPopup = this.popupElements.find(popup => popup.modelId === this.currentModelId);
111
+ if (currentPopup) {
112
+ this.addPopupControls();
113
+ }
114
+ this.mapContainer.appendChild(this.popupContainer);
115
+ }
116
+ addFloatingPopup(template, lngLat) {
117
+ const anchor = this.restrictMapMovement ? this.getAnchor(lngLat) : undefined;
118
+ this.floatingPopup = new Popup({
119
+ maxWidth: 'none',
120
+ closeButton: false,
121
+ offset: 20,
122
+ ...(anchor !== undefined && { anchor }),
123
+ })
124
+ .setLngLat(lngLat)
125
+ .setHTML(template)
126
+ .addTo(this.map);
127
+ }
128
+ getAnchor(lngLat) {
129
+ const point = this.map.projectWithScaleCorrection(lngLat);
130
+ const width = this.map.getContainer().clientWidth;
131
+ const height = this.map.getContainer().clientHeight;
132
+ let vertical;
133
+ let horizontal;
134
+ if (point.y < height * 0.33) {
135
+ vertical = 'top';
136
+ }
137
+ else if (point.y > height * 0.66) {
138
+ vertical = 'bottom';
139
+ }
140
+ else {
141
+ vertical = 'center';
142
+ }
143
+ if (point.x < width * 0.33) {
144
+ horizontal = 'left';
145
+ }
146
+ else if (point.x > width * 0.66) {
147
+ horizontal = 'right';
148
+ }
149
+ else {
150
+ horizontal = 'center';
151
+ }
152
+ if (vertical === 'center' && horizontal === 'center') {
153
+ return 'bottom';
154
+ }
155
+ else if (vertical === 'center') {
156
+ return horizontal;
157
+ }
158
+ else if (horizontal === 'center') {
159
+ return vertical;
160
+ }
161
+ return `${vertical}-${horizontal}`;
162
+ }
163
+ hidePopup() {
164
+ if (this.popupContainer) {
165
+ this.popupContainer.remove();
166
+ this.feature = undefined;
167
+ this.currentFeatureId = undefined;
168
+ }
169
+ if (this.restrictMapMovement && this.center) {
170
+ this.map.easeTo({ center: this.center });
171
+ }
172
+ }
173
+ getTemplate(feature) {
174
+ const { layer } = feature;
175
+ let template;
176
+ const model = this.registry.getModel(feature);
177
+ const modelTemplate = model?.popup || model?.popupMedia;
178
+ if (modelTemplate) {
179
+ template = buildTemplate(model.popup, model.popupMedia, model.dataBindings ?? {}, this.mode);
180
+ }
181
+ else {
182
+ const options = [];
183
+ if (this.publication) {
184
+ options.push(`:${this.publication}-${this.language}`);
185
+ options.push(`:${this.publication}-:en`);
186
+ }
187
+ options.push(`:${this.language}`);
188
+ options.push(`:en`);
189
+ options.push('');
190
+ for (const option of options) {
191
+ const layerTemplate = layer.metadata?.[`mc-interactivity-template${option}`];
192
+ if (layerTemplate) {
193
+ template = this.fillDatalayerTemplate(feature, layerTemplate, option);
194
+ break;
195
+ }
196
+ }
197
+ if (template) {
198
+ template = `
199
+ <div class="popup-scroll-wrapper">
200
+ <div class="mc-popup data-layer text-content">${template}</div>
201
+ </div>
202
+ `;
203
+ }
204
+ }
205
+ return template;
206
+ }
207
+ htmlToElement(html) {
208
+ const template = document.createElement('template');
209
+ template.innerHTML = html.trim();
210
+ return template.content.firstElementChild;
211
+ }
212
+ addPopupControls() {
213
+ const closeButton = this.htmlToElement(closeButtonHtml);
214
+ if (closeButton) {
215
+ closeButton.addEventListener('click', () => {
216
+ this.hidePopup();
217
+ this.highlightManager.highlight(undefined);
218
+ });
219
+ this.popupContainer.appendChild(closeButton);
220
+ }
221
+ if (this.popupElements.length < 2) {
222
+ return;
223
+ }
224
+ const cycleButtons = this.htmlToElement(cycleButtonsHtml);
225
+ if (cycleButtons) {
226
+ cycleButtons.querySelector('.prev')?.addEventListener('click', () => this.cyclePopups(false));
227
+ cycleButtons.querySelector('.next')?.addEventListener('click', () => this.cyclePopups());
228
+ this.popupContainer.appendChild(cycleButtons);
229
+ }
230
+ }
231
+ fillDatalayerTemplate(feature, html, language) {
232
+ const attr = 'data-mention-id';
233
+ const dom = new DOMParser().parseFromString(html, 'text/html').body;
234
+ for (const el of dom.querySelectorAll(`[${attr}]`)) {
235
+ const value = this.getProperty(feature, el.getAttribute(attr), language);
236
+ if (value !== undefined) {
237
+ el.innerText = value;
238
+ }
239
+ }
240
+ return dom.innerHTML;
241
+ }
242
+ getProperty(feature, name, language) {
243
+ if (!name) {
244
+ return undefined;
245
+ }
246
+ const options = name.endsWith(':en') ? [name.replace(':en', language), name] : [name];
247
+ for (const option of options) {
248
+ const property = feature.properties[option] ?? this.getVectorProperty(feature, option);
249
+ if (property !== undefined) {
250
+ return property;
251
+ }
252
+ }
253
+ return undefined;
254
+ }
255
+ getVectorProperty(feature, name) {
256
+ let promoteId = this.promoteIdCache.get(feature.source);
257
+ if (promoteId === undefined) {
258
+ promoteId = (this.map.getSource(feature.source)?.serialize().promoteId || 'id');
259
+ this.promoteIdCache.set(feature.source, promoteId);
260
+ }
261
+ try {
262
+ const id = feature.properties[promoteId];
263
+ return feature.layer.metadata.properties[name][id];
264
+ }
265
+ catch (e) {
266
+ return undefined;
267
+ }
268
+ }
269
+ cyclePopups(forward = true) {
270
+ const index = this.popupElements?.findIndex(model => model.modelId === this.currentModelId);
271
+ if (index !== -1) {
272
+ let nextIndex = 0;
273
+ if (forward) {
274
+ nextIndex = index === this.popupElements.length - 1 ? 0 : index + 1;
275
+ }
276
+ else {
277
+ nextIndex = index === 0 ? this.popupElements.length - 1 : index - 1;
278
+ }
279
+ const next = this.popupElements?.[nextIndex];
280
+ this.highlightManager.highlight(next);
281
+ const template = buildTemplate(next.popup, next.popupMedia, next.dataBindings ?? {}, this.mode);
282
+ const temp = this.popupContainer;
283
+ this.popupContainer.remove();
284
+ temp.innerHTML = template;
285
+ this.mapContainer.appendChild(temp);
286
+ this.currentModelId = next.modelId;
287
+ this.addPopupControls();
288
+ if (forward) {
289
+ this.popupContainer.querySelector('.mc-popup-button.next')?.focus();
290
+ }
291
+ else {
292
+ this.popupContainer.querySelector('.mc-popup-button.prev')?.focus();
293
+ }
294
+ this.currentFeatureId = undefined;
295
+ }
296
+ }
297
+ }
@@ -1,6 +1,6 @@
1
- import { Position } from './types/geometry';
2
- import { JobObject, JobObjectArea, JobObjectCircle, JobObjectDot, JobObjectLine, JobObjectMarker, JobObjectPolygon, PopupMedia, JobObjectDataBindings } from './types/jobObject';
3
- import { LngLat, MapGeoJSONFeature } from '@mapcreator/maplibre-gl';
1
+ import type { Position } from '@/types/geometry';
2
+ import type { JobObject, JobObjectArea, JobObjectCircle, JobObjectDot, JobObjectLine, JobObjectMarker, JobObjectPolygon, PopupMedia, JobObjectDataBindings } from '@/types/jobObject';
3
+ import type { LngLat, MapGeoJSONFeature } from '@mapcreator/maplibre-gl';
4
4
  type Model = JobObjectArea | JobObjectCircle | JobObjectDot | JobObjectLine | JobObjectMarker | JobObjectPolygon;
5
5
  export type PopupElement = {
6
6
  groupId: string;
@@ -0,0 +1,74 @@
1
+ const allModelTypes = ['area', 'circle', 'dot', 'line', 'marker', 'polygon'];
2
+ export class Registry {
3
+ data;
4
+ customGroupIds;
5
+ constructor(jobObject) {
6
+ this.data = {
7
+ area: {},
8
+ circle: {},
9
+ dot: {},
10
+ line: {},
11
+ marker: {},
12
+ polygon: {},
13
+ };
14
+ for (const modelType of allModelTypes) {
15
+ for (const group of jobObject.registry?.models?.[modelType] ?? []) {
16
+ this.data[modelType][group.id] = {};
17
+ for (const model of group.models) {
18
+ // MC-2870 Convert the old format area without multiple vector-source group support to the new format
19
+ if (modelType === 'area') {
20
+ const area = model;
21
+ if (area.featureId === undefined) {
22
+ area.featureId = Number(area.id);
23
+ area.id = `${area.vectorUrl}-${area.id}`;
24
+ }
25
+ }
26
+ this.data[modelType][group.id][model.id] = model;
27
+ }
28
+ }
29
+ }
30
+ const { beforeWaters = [], beforeBoundaries = [], beforeNames = [], beforeNone = [], } = jobObject.registry?.slots ?? {};
31
+ this.customGroupIds = [...beforeWaters, ...beforeBoundaries, ...beforeNames, ...beforeNone].map(item => item.groupId);
32
+ }
33
+ getModel(feature) {
34
+ let modelId = String(feature.properties?.id);
35
+ const metadata = feature.layer.metadata;
36
+ const groupId = metadata?.['mc-group-id'];
37
+ const modelType = metadata?.['mc-model-type'];
38
+ const prefix = metadata?.['mc-model-id-prefix'];
39
+ if (prefix) {
40
+ modelId = `${prefix}-${modelId}`;
41
+ }
42
+ if (groupId === undefined || modelId === undefined || modelType === undefined) {
43
+ return undefined;
44
+ }
45
+ return this.data[modelType]?.[groupId]?.[modelId];
46
+ }
47
+ getPopupElements() {
48
+ const result = [];
49
+ const elementTypes = ['circle', 'dot', 'line', 'marker', 'polygon'];
50
+ for (const groupId of this.customGroupIds) {
51
+ for (const type of elementTypes) {
52
+ const groupModels = this.data[type]?.[groupId];
53
+ if (groupModels) {
54
+ for (const [modelId, model] of Object.entries(groupModels)) {
55
+ if (model.popup || model.popupMedia) {
56
+ result.push({
57
+ groupId,
58
+ modelId,
59
+ popup: model.popup,
60
+ popupMedia: model.popupMedia,
61
+ lngLat: model.lngLat,
62
+ center: model.center,
63
+ radius: model.radius,
64
+ anchorPoints: model.anchorPoints,
65
+ dataBindings: model.dataBindings,
66
+ });
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ return result.reverse();
73
+ }
74
+ }
@@ -1,2 +1,2 @@
1
- import { JobObjectManualLegend } from '../types/jobObject';
1
+ import type { JobObjectManualLegend } from '@/types/jobObject';
2
2
  export declare function getCategoricalLegendSvg(legend: JobObjectManualLegend, defaultFont: string): string;
@@ -0,0 +1,141 @@
1
+ import { escapeXML, measureTextBlock } from '@/utils/svgHelpers';
2
+ export function getCategoricalLegendSvg(legend, defaultFont) {
3
+ const padding = 15;
4
+ const swatchSize = 15;
5
+ const entryMargin = 20;
6
+ const swatchMargin = 10;
7
+ const { title = '', titleFontColor = '#000000', titleFont = defaultFont, titleFontSize = 24, entries, entryFontColor = '#000000', entryFont = defaultFont, entryFontSize = 13, entryShape = 'rectangle', layout = 'vertical', background = '#ffffff', showBackground = false, } = legend;
8
+ const titleBox = measureTextBlock([title], titleFont, titleFontSize);
9
+ const entryBoxes = entries.map(entry => measureTextBlock([entry.label], entryFont, entryFontSize));
10
+ const hasTitle = title.trim().length > 0;
11
+ let width;
12
+ let height;
13
+ if (layout === 'vertical') {
14
+ width =
15
+ Math.max(titleBox.width, ...entryBoxes.map(b => b.width + swatchSize + swatchMargin)) +
16
+ padding * 2;
17
+ height =
18
+ padding * 2 +
19
+ (hasTitle ? titleBox.height + entryMargin : 0) +
20
+ entryBoxes.reduce((sum, box) => sum + box.height, 0) +
21
+ Math.max(entryBoxes.length - 1, 0) * entryMargin;
22
+ }
23
+ else {
24
+ width = Math.max(titleBox.width + padding * 2, padding * 2 +
25
+ entryBoxes.reduce((sum, box) => sum + box.width + swatchSize + swatchMargin, 0) +
26
+ Math.max(entryBoxes.length - 1, 0) * entryMargin);
27
+ height =
28
+ padding * 2 +
29
+ (hasTitle ? titleBox.height + entryMargin : 0) +
30
+ Math.max(0, ...entryBoxes.map(b => b.height));
31
+ }
32
+ const children = [];
33
+ if (showBackground) {
34
+ children.push(`
35
+ <rect
36
+ x="0"
37
+ y="0"
38
+ rx="10"
39
+ ry="10"
40
+ width="${width}"
41
+ height="${height}"
42
+ fill="${background}"
43
+ />
44
+ `);
45
+ }
46
+ let x = padding;
47
+ let y = padding;
48
+ if (hasTitle) {
49
+ children.push(`
50
+ <text
51
+ x="${x}"
52
+ y="${y + titleBox.ascent}"
53
+ font-family="${titleFont}"
54
+ font-size="${titleFontSize}"
55
+ fill="${titleFontColor}"
56
+ >
57
+ ${escapeXML(title)}
58
+ </text>
59
+ `);
60
+ y += titleBox.height + entryMargin;
61
+ }
62
+ for (let i = 0; i < entries.length; i++) {
63
+ const entry = entries[i];
64
+ const entryBox = entryBoxes[i];
65
+ if (entryShape === 'rectangle') {
66
+ children.push(`
67
+ <rect
68
+ x="${x}"
69
+ y="${y + entryBox.height / 2 - swatchSize / 2}"
70
+ rx="2"
71
+ ry="2"
72
+ width="${swatchSize}"
73
+ height="${swatchSize}"
74
+ fill="${entry.color}"
75
+ />
76
+ `);
77
+ }
78
+ else if (entryShape === 'circle') {
79
+ children.push(`
80
+ <circle
81
+ cx="${x + swatchSize / 2}"
82
+ cy="${y + entryBox.height / 2}"
83
+ r="${swatchSize / 2}"
84
+ fill="${entry.color}"
85
+ />
86
+ `);
87
+ }
88
+ else if (entryShape === 'line') {
89
+ children.push(`
90
+ <line
91
+ x1="${x}"
92
+ y1="${y + entryBox.height / 2}"
93
+ x2="${x + swatchSize}"
94
+ y2="${y + entryBox.height / 2}"
95
+ stroke="${entry.color}"
96
+ stroke-width="2"
97
+ stroke-linecap="round"
98
+ />
99
+ `);
100
+ }
101
+ else if (entryShape === 'icon' && entry.svg.length > 0) {
102
+ const svgEl = new DOMParser()
103
+ .parseFromString(entry.svg, 'image/svg+xml')
104
+ .querySelector('svg');
105
+ if (svgEl) {
106
+ svgEl.setAttribute('width', String(swatchSize));
107
+ svgEl.setAttribute('height', String(swatchSize));
108
+ svgEl.setAttribute('transform', `translate(${x}, ${y + entryBox.height / 2 - swatchSize / 2})`);
109
+ children.push(svgEl.outerHTML);
110
+ }
111
+ }
112
+ children.push(`
113
+ <text
114
+ x="${x + swatchSize + swatchMargin}"
115
+ y="${y + entryBox.ascent}"
116
+ font-family="${entryFont}"
117
+ font-size="${entryFontSize}"
118
+ fill="${entryFontColor}"
119
+ >
120
+ ${escapeXML(entry.label)}
121
+ </text>
122
+ `);
123
+ if (layout === 'vertical') {
124
+ y += entryBox.height + entryMargin;
125
+ }
126
+ else {
127
+ x += entryBox.width + swatchSize + swatchMargin + entryMargin;
128
+ }
129
+ }
130
+ return `
131
+ <svg
132
+ xmlns="http://www.w3.org/2000/svg"
133
+ width="${width}"
134
+ height="${height}"
135
+ viewBox="0 0 ${width} ${height}"
136
+ style="display: block;"
137
+ >
138
+ ${children.join('')}
139
+ </svg>
140
+ `;
141
+ }
@@ -1,3 +1,3 @@
1
- import { Map as MapLibre } from '@mapcreator/maplibre-gl';
2
- import { JobObject, JobObjectConnectedLegend } from '../types/jobObject';
1
+ import type { Map as MapLibre } from '@mapcreator/maplibre-gl';
2
+ import type { JobObject, JobObjectConnectedLegend } from '@/types/jobObject';
3
3
  export declare function useConnectedLegend(legend: JobObjectConnectedLegend, jobObject: JobObject, map: MapLibre, vapiUrl: string, accessToken: string): HTMLElement;