@dative-gpi/foundation-shared-components 0.0.214 → 0.0.215

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.
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <FSCard
3
3
  :width="$props.width"
4
+ :style="style"
4
5
  v-bind="$attrs"
5
6
  >
6
7
  <FSCol
@@ -12,6 +13,7 @@
12
13
  v-if="$slots['leftoverlay-header'] || $slots['leftoverlay-body']"
13
14
  :mode="$props.overlayMode"
14
15
  :height="$props.height"
16
+ :mapId="mapId"
15
17
  @update:mode="$emit('update:overlayMode', $event)"
16
18
  >
17
19
  <template
@@ -39,9 +41,7 @@
39
41
  @click="editingLocation = true"
40
42
  />
41
43
  </FSRow>
42
- <FSCol
43
- :style="style"
44
- >
44
+ <FSCol>
45
45
  <div
46
46
  class="fs-leaflet-container"
47
47
  :id="mapId"
@@ -129,7 +129,7 @@
129
129
  </template>
130
130
 
131
131
  <script lang="ts">
132
- import { computed, defineComponent, onMounted, type PropType, ref, watch } from "vue";
132
+ import { computed, defineComponent, onMounted, onUnmounted, type PropType, ref, watch } from "vue";
133
133
 
134
134
  import * as L from "leaflet";
135
135
  import "leaflet.markercluster";
@@ -139,7 +139,7 @@ import { type Address, type FSArea } from '@dative-gpi/foundation-shared-domain/
139
139
 
140
140
  import { clusterMarker, locationMarker, myLocationMarker } from "../../utils";
141
141
  import { ColorEnum, type FSLocation, type MapLayer } from "../../models";
142
- import { useColors, useAddress } from "../../composables";
142
+ import { useColors, useAddress, useBreakpoints } from "../../composables";
143
143
 
144
144
  import FSMapEditPointAddressOverlay from "./FSMapEditPointAddressOverlay.vue";
145
145
  import FSMapLayerButton from "./FSMapLayerButton.vue";
@@ -247,6 +247,7 @@ export default defineComponent({
247
247
  const { $tr } = useTranslationsProvider();
248
248
  const { reverseSearch } = useAddress();
249
249
  const { getColors } = useColors();
250
+ const { isExtraSmall } = useBreakpoints();
250
251
 
251
252
  const LL = window.L;
252
253
 
@@ -254,6 +255,9 @@ export default defineComponent({
254
255
  const innerModelValue = ref(props.modelValue);
255
256
  const editingLocation = ref(false);
256
257
  const fullScreen = ref(false);
258
+ const leftOverlayHeight = ref<number>();
259
+ const leftOverlayWidth = ref<number>();
260
+ const resizeObserver = ref<ResizeObserver | null>(null);
257
261
 
258
262
  const mapId = `map-${Math.random().toString(36).substring(7)}`;
259
263
  const defaultZoom = 15;
@@ -266,19 +270,6 @@ export default defineComponent({
266
270
  let map: L.Map;
267
271
  let markerLayerGroup: L.FeatureGroup | any;
268
272
 
269
- if (props.editable) {
270
- markerLayerGroup = new LL.FeatureGroup();
271
- }
272
- else {
273
- markerLayerGroup = new LL.MarkerClusterGroup({
274
- spiderfyOnMaxZoom: false,
275
- showCoverageOnHover: false,
276
- disableClusteringAtZoom: 17,
277
- iconCreateFunction: function (cluster: any) {
278
- return clusterMarker(cluster.getChildCount());
279
- }
280
- });
281
- }
282
273
  const mapLayers: MapLayer[] = [
283
274
  {
284
275
  name: "map",
@@ -302,11 +293,29 @@ export default defineComponent({
302
293
  }
303
294
  ];
304
295
 
296
+ const bottomMargin = computed(() => {
297
+ let margin = 0;
298
+ if (props.overlayMode !== 'expand' && leftOverlayHeight.value && isExtraSmall.value) {
299
+ margin += leftOverlayHeight.value;
300
+ }
301
+ return margin;
302
+ });
303
+
304
+ const leftMargin = computed(() => {
305
+ let margin = 0;
306
+ if (leftOverlayWidth.value && !isExtraSmall.value) {
307
+ margin += leftOverlayWidth.value;
308
+ }
309
+ return margin;
310
+ });
311
+
305
312
  const style = computed((): { [key: string]: string | undefined } => {
306
313
  return {
307
314
  "--fs-map-location-pin-color": getColors(ColorEnum.Primary).base,
315
+ "--fs-map-mylocation-pin-color": getColors(ColorEnum.Primary).base,
308
316
  "--fs-map-mylocation-pin-color-alpha": getColors(ColorEnum.Primary).base + "50",
309
317
  "--fs-map-leaflet-container-height": props.height as string,
318
+ "--fs-map-leaflet-bottom-overlay-margin": `${bottomMargin.value}px`,
310
319
  "--fs-map-container-grayscale": props.grayscale ? '0.9' : '0'
311
320
  };
312
321
  });
@@ -314,7 +323,7 @@ export default defineComponent({
314
323
  const displayLocations = () => {
315
324
  markerLayerGroup.clearLayers();
316
325
  innerModelValue.value.forEach((location) => {
317
- const icon = locationMarker(location.icon, getColors(location.color).base);
326
+ const icon = locationMarker(location.icon, getColors(location.color).base, L);
318
327
  const marker = LL.marker([location.address.latitude, location.address.longitude], { icon }).addTo(markerLayerGroup);
319
328
  markers[location.id] = marker;
320
329
  marker.on('click', () => emit('update:selectedLocationId', location.id));
@@ -351,6 +360,19 @@ export default defineComponent({
351
360
  };
352
361
 
353
362
  const initMap = () => {
363
+ if (props.editable) {
364
+ markerLayerGroup = new LL.FeatureGroup();
365
+ }
366
+ else {
367
+ markerLayerGroup = new LL.MarkerClusterGroup({
368
+ spiderfyOnMaxZoom: false,
369
+ showCoverageOnHover: false,
370
+ disableClusteringAtZoom: 17,
371
+ iconCreateFunction: function (cluster: any) {
372
+ return clusterMarker(cluster.getChildCount(), L);
373
+ }
374
+ });
375
+ }
354
376
  const mapOptions = {
355
377
  zoomControl: false,
356
378
  scrollWheelZoom: props.enableScrollWheelZoom,
@@ -392,7 +414,7 @@ export default defineComponent({
392
414
  return;
393
415
  }
394
416
  modifyLocationAddress(props.selectedLocationId, address);
395
- map.panTo([address.latitude, address.longitude]);
417
+ map.panTo(calculateTargetPosition(new L.LatLng(address.latitude, address.longitude)));
396
418
  };
397
419
 
398
420
  const onNewCoordEntered = async (lat: number, lng: number) => {
@@ -425,13 +447,18 @@ export default defineComponent({
425
447
  }
426
448
  map.locate();
427
449
  map.on('locationfound', (e: L.LocationEvent) => {
428
- map.panTo(e.latlng);
429
- const icon = myLocationMarker();
450
+ map.panTo(calculateTargetPosition(e.latlng));
451
+ const icon = myLocationMarker(L);
430
452
  myLocationLayerGroup.clearLayers();
431
453
  LL.marker(e.latlng, { icon }).addTo(myLocationLayerGroup);
432
454
  });
433
455
  };
434
456
 
457
+ const calculateTargetPosition = (target: L.LatLng, zoom: number = map.getZoom()) => {
458
+ const targetPoint = map.project(target, zoom).subtract([leftMargin.value / 2, -bottomMargin.value / 2]);
459
+ return map.unproject(targetPoint, zoom);
460
+ }
461
+
435
462
  const onCancel = () => {
436
463
  editingLocation.value = false;
437
464
  innerModelValue.value = props.modelValue;
@@ -443,7 +470,7 @@ export default defineComponent({
443
470
  map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
444
471
  }
445
472
  else {
446
- map.panTo([props.center[0], props.center[1]]);
473
+ map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
447
474
  }
448
475
  if (props.modelValue.length > 1) {
449
476
  emit('update:selectedLocationId', null);
@@ -460,7 +487,7 @@ export default defineComponent({
460
487
  map.fitBounds(markerLayerGroup.getBounds(), { maxZoom: defaultZoom });
461
488
  }
462
489
  else {
463
- map.flyTo([props.center[0], props.center[1]], map.getZoom() ?? defaultZoom, { animate: false });
490
+ map.panTo(calculateTargetPosition(new L.LatLng(props.center[0], props.center[1])), { animate: false });
464
491
  }
465
492
  if (props.modelValue.length > 1) {
466
493
  emit('update:selectedLocationId', null);
@@ -472,6 +499,29 @@ export default defineComponent({
472
499
  if (props.selectedLocationId && props.modelValue.length === 1) {
473
500
  editingLocation.value = true;
474
501
  }
502
+
503
+ resizeObserver.value = new ResizeObserver(entries => {
504
+ entries.forEach((entry) => {
505
+ if (entry.target.id === `left-overlay-${mapId}`) {
506
+ leftOverlayWidth.value = entry.contentRect.width;
507
+ }
508
+ if(entry.target.id === `left-overlay-mobile-${mapId}`) {
509
+ leftOverlayHeight.value = entry.contentRect.height;
510
+ }
511
+ });
512
+ });
513
+ if (document.querySelector(`#left-overlay-mobile-${mapId}`)) {
514
+ resizeObserver.value.observe(document.querySelector(`#left-overlay-mobile-${mapId}`)!);
515
+ }
516
+ if (document.querySelector(`#left-overlay-${mapId}`)) {
517
+ resizeObserver.value.observe(document.querySelector(`#left-overlay-${mapId}`)!);
518
+ }
519
+ });
520
+
521
+ onUnmounted((): void => {
522
+ if (resizeObserver.value) {
523
+ resizeObserver.value.disconnect();
524
+ }
475
525
  });
476
526
 
477
527
  watch(() => innerModelValue.value, () => {
@@ -488,7 +538,7 @@ export default defineComponent({
488
538
  });
489
539
 
490
540
  const marker = markers[props.selectedLocationId];
491
- map.flyTo(marker.getLatLng(), 17, { animate: false });
541
+ map.flyTo(calculateTargetPosition(marker.getLatLng(), 17), 17, { animate: false });
492
542
  marker.getElement()?.classList.add('fs-map-location-selected');
493
543
  })
494
544
 
@@ -507,10 +557,11 @@ export default defineComponent({
507
557
  });
508
558
 
509
559
  return {
510
- innerSelectedLayer,
560
+ bottomMargin,
511
561
  editingLocation,
512
- innerModelValue,
513
562
  fullScreen,
563
+ innerModelValue,
564
+ innerSelectedLayer,
514
565
  mapLayers,
515
566
  mapId,
516
567
  style,
@@ -7,20 +7,24 @@
7
7
  <FSDialog
8
8
  v-model="dialog"
9
9
  title="Select Layers"
10
- width="460px"
10
+ width="442px"
11
11
  >
12
12
  <template
13
13
  v-slot:body
14
14
  >
15
- <FSImageCard
16
- v-for="layer in layers"
17
- :variant="modelValue === layer.name ? 'full' : 'background'"
18
- :color="modelValue === layer.name ? 'primary' : 'light'"
19
- :label="layer.label"
20
- :src="layer.image"
21
- :key="layer.name"
22
- @click="onLayerClick(layer.name)"
23
- />
15
+ <FSRow
16
+ align="center-center"
17
+ >
18
+ <FSImageCard
19
+ v-for="layer in layers"
20
+ :variant="modelValue === layer.name ? 'full' : 'background'"
21
+ :color="modelValue === layer.name ? 'primary' : 'light'"
22
+ :label="layer.label"
23
+ :src="layer.image"
24
+ :key="layer.name"
25
+ @click="onLayerClick(layer.name)"
26
+ />
27
+ </FSRow>
24
28
  </template>
25
29
  </FSDialog>
26
30
  </template>
@@ -33,13 +37,15 @@ import { type MapLayer } from "../../models";
33
37
  import FSImageCard from "../FSImageCard.vue";
34
38
  import FSButton from "../FSButton.vue";
35
39
  import FSDialog from "../FSDialog.vue";
40
+ import FSRow from "../FSRow.vue";
36
41
 
37
42
  export default defineComponent({
38
43
  name: "FSMapLayerButton",
39
44
  components: {
40
45
  FSImageCard,
41
46
  FSButton,
42
- FSDialog
47
+ FSDialog,
48
+ FSRow
43
49
  },
44
50
  props: {
45
51
  layers: {
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <FSCol
3
- v-if="isExtraSmall"
3
+ v-show="isExtraSmall"
4
+ :id="`left-overlay-mobile-${$props.mapId}`"
4
5
  :height="$props.mode === 'expand' ? '100%' : ($props.mode === 'half' ? '50%' : 'hug')"
5
6
  :style="style"
6
7
  class="fs-map-overlay-left-mobile"
@@ -49,7 +50,9 @@
49
50
  </FSCard>
50
51
  </FSCol>
51
52
  <FSCol
52
- v-else
53
+ v-show="!isExtraSmall"
54
+ :style="style"
55
+ :id="`left-overlay-${$props.mapId}`"
53
56
  class="fs-map-overlay-left"
54
57
  width="hug"
55
58
  gap="2px"
@@ -60,7 +63,7 @@
60
63
  :border="false"
61
64
  >
62
65
  <FSCol
63
- :height="`calc(${$props.height} - 40px)`"
66
+ class="fs-map-overlay-left-content"
64
67
  >
65
68
  <slot
66
69
  name="leftoverlay-header"
@@ -101,6 +104,10 @@ export default defineComponent({
101
104
  type: String as PropType<"collapse" | "half" | "expand">,
102
105
  required: false,
103
106
  default: "collapse"
107
+ },
108
+ mapId: {
109
+ type: String,
110
+ required: true
104
111
  }
105
112
  },
106
113
  components: {
@@ -120,11 +127,13 @@ export default defineComponent({
120
127
  const style = computed((): { [key: string]: string | null | undefined } => {
121
128
  if (props.mode === "expand") {
122
129
  return {
130
+ "--fs-map-overlay-max-height": `calc(${props.height} - 40px)`,
123
131
  "--fs-map-overlay-card-height": "95%",
124
132
  };
125
133
  }
126
134
  else {
127
135
  return {
136
+ "--fs-map-overlay-max-height": `calc(${props.height} - 40px)`,
128
137
  "--fs-map-overlay-card-height": "100%",
129
138
  };
130
139
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dative-gpi/foundation-shared-components",
3
3
  "sideEffects": false,
4
- "version": "0.0.214",
4
+ "version": "0.0.215",
5
5
  "description": "",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -10,8 +10,8 @@
10
10
  "author": "",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
- "@dative-gpi/foundation-shared-domain": "0.0.214",
14
- "@dative-gpi/foundation-shared-services": "0.0.214"
13
+ "@dative-gpi/foundation-shared-domain": "0.0.215",
14
+ "@dative-gpi/foundation-shared-services": "0.0.215"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@dative-gpi/bones-ui": "^0.0.75",
@@ -35,5 +35,5 @@
35
35
  "sass": "1.71.1",
36
36
  "sass-loader": "13.3.2"
37
37
  },
38
- "gitHead": "d13e07184215ee5604d7d0ee124c889edd741b0b"
38
+ "gitHead": "0d0aafb99e7a818b3606c3ac8bea917213e389ad"
39
39
  }
@@ -9,14 +9,6 @@
9
9
  filter: grayscale(var(--fs-map-container-grayscale));
10
10
  }
11
11
 
12
- .fs-map-overlay-left {
13
- position: absolute;
14
- top: 0;
15
- left: 0;
16
- z-index: 950;
17
- margin: 4px;
18
- }
19
-
20
12
  .fs-map-overlay-edit-button {
21
13
  position: absolute;
22
14
  top: 0;
@@ -46,7 +38,7 @@
46
38
  bottom: 100%;
47
39
  right: 0;
48
40
  z-index: 1001;
49
- margin-bottom: 8px;
41
+ margin-bottom: calc(var(--fs-map-leaflet-bottom-overlay-margin) + 8px);
50
42
 
51
43
  button.fs-map-zoom-plus > * {
52
44
  border-bottom-left-radius: 0 !important;
@@ -65,10 +57,11 @@
65
57
 
66
58
  .fs-map-mylocation {
67
59
  border: 3px solid white;
60
+ background-color: var(--fs-map-mylocation-pin-color);
68
61
  border-radius: 100%;
69
- animation: fs-map-shadow 1.4s linear infinite;
62
+ animation: fs-map-shadow-mylocation 1.4s linear infinite;
70
63
 
71
- @keyframes fs-map-shadow {
64
+ @keyframes fs-map-shadow-mylocation {
72
65
  0% {
73
66
  box-shadow: 0 0 0px 0px var(--fs-map-mylocation-pin-color-alpha);
74
67
  }
@@ -108,9 +101,9 @@
108
101
  }
109
102
 
110
103
  .fs-map-location-selected > div {
111
- animation: fs-map-shadow 1.4s linear infinite;
104
+ animation: fs-map-shadow-location 1.4s linear infinite;
112
105
 
113
- @keyframes fs-map-shadow {
106
+ @keyframes fs-map-shadow-location {
114
107
  0% {
115
108
  box-shadow: 0 0 0px 0px var(--fs-map-mylocation-pin-color-alpha);
116
109
  }
@@ -1,3 +1,15 @@
1
+ .fs-map-overlay-left {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ z-index: 950;
6
+ margin: 4px;
7
+
8
+ .fs-map-overlay-left-content {
9
+ max-height: var(--fs-map-overlay-max-height);
10
+ }
11
+ }
12
+
1
13
  .fs-map-overlay-left-mobile {
2
14
  position: absolute;
3
15
  bottom: 0;
@@ -1,11 +1,7 @@
1
- import L from 'leaflet';
2
-
3
- export const locationMarker = (icon: string, color: string, size = 36) => {
1
+ export const locationMarker = (icon: string, color: string, L: any, size = 36) => {
4
2
  const iconHtml = `
5
3
  <div style="--fs-map-mylocation-pin-color-alpha:${color}50;--fs-map-location-pin-color: ${color}">
6
- <div class="fs-map-location-pin">
7
- <i class="${icon} mdi v-icon notranslate v-theme--DefaultTheme fs-icon" aria-hidden="true" style="--fs-icon-font-size: 22px;" />
8
- </div>
4
+ <i class="${icon} mdi notranslate v-theme--DefaultTheme fs-icon" aria-hidden="true" style="--fs-icon-font-size: 22px;" />
9
5
  </div>`;
10
6
 
11
7
  return L.divIcon({
@@ -16,7 +12,7 @@ export const locationMarker = (icon: string, color: string, size = 36) => {
16
12
  });
17
13
  }
18
14
 
19
- export const clusterMarker = (label: string, size = 36) => {
15
+ export const clusterMarker = (label: string, L: any, size = 36) => {
20
16
  const iconHtml = `
21
17
  <div class="fs-map-cluster">
22
18
  <span>${label}</span>
@@ -30,7 +26,7 @@ export const clusterMarker = (label: string, size = 36) => {
30
26
  })
31
27
  }
32
28
 
33
- export const myLocationMarker = (size = 16) => {
29
+ export const myLocationMarker = (L: any, size = 16) => {
34
30
  const iconHtml = `<div class="fs-map-mylocation-pin" />`;
35
31
 
36
32
  return L.divIcon({