@dative-gpi/foundation-shared-components 1.0.155 → 1.0.156-maps2

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.
@@ -6,47 +6,32 @@
6
6
  :disabled="false"
7
7
  :elevation="0"
8
8
  :style="style"
9
- :hideActions="$props.actionPosition === 'start'"
10
9
  v-bind="$attrs"
11
10
  >
12
- <v-expansion-panel-title>
13
- <template
14
- v-slot:default="{ expanded, collapseIcon, expandIcon }"
11
+ <template
12
+ #title
13
+ >
14
+ <slot
15
+ name="title"
15
16
  >
16
- <FSRow
17
- gap="4px"
18
- >
19
- <FSCol
20
- v-if="$props.actionPosition === 'start'"
21
- width="hug"
22
- align="center-center"
17
+ <FSRow>
18
+ <FSIcon
19
+ v-if="$props.prependIcon"
23
20
  >
24
- <FSIcon
25
- size="22.5px"
26
- :icon="expanded ? collapseIcon : expandIcon"
27
- />
28
- </FSCol>
29
- <slot
30
- name="title"
21
+ {{ $props.prependIcon }}
22
+ </FSIcon>
23
+ <FSSpan
24
+ class="fs-accordion-panel-title"
25
+ :lineClamp="$props.lineClampTitle"
31
26
  >
32
- <FSRow>
33
- <FSIcon
34
- v-if="$props.prependIcon"
35
- >
36
- {{ $props.prependIcon }}
37
- </FSIcon>
38
- <FSSpan
39
- class="fs-accordion-panel-title"
40
- :lineClamp="$props.lineClampTitle"
41
- >
42
- {{ $props.title }}
43
- </FSSpan>
44
- </FSRow>
45
- </slot>
27
+ {{ $props.title }}
28
+ </FSSpan>
46
29
  </FSRow>
47
- </template>
48
- </v-expansion-panel-title>
49
- <v-expansion-panel-text>
30
+ </slot>
31
+ </template>
32
+ <template
33
+ #text
34
+ >
50
35
  <slot
51
36
  name="content"
52
37
  >
@@ -62,7 +47,7 @@
62
47
  :modelValue="$props.content"
63
48
  />
64
49
  </slot>
65
- </v-expansion-panel-text>
50
+ </template>
66
51
  </v-expansion-panel>
67
52
  </template>
68
53
 
@@ -143,10 +128,6 @@ export default defineComponent({
143
128
  type: String as PropType<"standard" | "rich-text">,
144
129
  required: false,
145
130
  default: "standard"
146
- },
147
- actionPosition: {
148
- type: String as () => "start" | "end",
149
- default: "end"
150
131
  }
151
132
  },
152
133
  setup(props) {
@@ -68,11 +68,6 @@ export default defineComponent({
68
68
  required: false,
69
69
  default: null
70
70
  },
71
- maxWidth: {
72
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
73
- required: false,
74
- default: null
75
- },
76
71
  padding: {
77
72
  type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
78
73
  required: false,
@@ -108,11 +103,6 @@ export default defineComponent({
108
103
  required: false,
109
104
  default: "solid"
110
105
  },
111
- borderColor: {
112
- type: [Array, String] as PropType<ColorBase | null | string>,
113
- required: false,
114
- default: null
115
- },
116
106
  elevation: {
117
107
  type: Boolean,
118
108
  required: false,
@@ -139,10 +129,6 @@ export default defineComponent({
139
129
  const darks = getColors(ColorEnum.Dark);
140
130
 
141
131
  const borderColor = computed((): ColorBase => {
142
- if (props.borderColor) {
143
- return getColors(props.borderColor).base;
144
- }
145
-
146
132
  switch (props.variant) {
147
133
  case "background":
148
134
  return lights.dark;
@@ -168,7 +154,6 @@ export default defineComponent({
168
154
  "--fs-card-padding" : sizeToVar(props.padding),
169
155
  "--fs-card-height" : sizeToVar(props.height),
170
156
  "--fs-card-width" : sizeToVar(props.width),
171
- "--fs-card-max-width" : sizeToVar(props.maxWidth, "unset"),
172
157
  "--fs-card-background-color": backgrounds.base,
173
158
  "--fs-card-border-color" : borderColor.value,
174
159
  "--fs-card-color" : darks.base,
@@ -181,7 +166,6 @@ export default defineComponent({
181
166
  "--fs-card-padding" : sizeToVar(props.padding),
182
167
  "--fs-card-height" : sizeToVar(props.height),
183
168
  "--fs-card-width" : sizeToVar(props.width),
184
- "--fs-card-max-width" : sizeToVar(props.maxWidth, "unset"),
185
169
  "--fs-card-background-color": colors.value.light,
186
170
  "--fs-card-border-color" : borderColor.value,
187
171
  "--fs-card-color" : colors.value.lightContrast!,
@@ -194,7 +178,6 @@ export default defineComponent({
194
178
  "--fs-card-padding" : sizeToVar(props.padding),
195
179
  "--fs-card-height" : sizeToVar(props.height),
196
180
  "--fs-card-width" : sizeToVar(props.width),
197
- "--fs-card-max-width" : sizeToVar(props.maxWidth, "unset"),
198
181
  "--fs-card-background-color": colors.value.base,
199
182
  "--fs-card-border-color" : borderColor.value,
200
183
  "--fs-card-color" : colors.value.baseContrast!,
@@ -207,7 +190,6 @@ export default defineComponent({
207
190
  "--fs-card-padding" : sizeToVar(props.padding),
208
191
  "--fs-card-height" : sizeToVar(props.height),
209
192
  "--fs-card-width" : sizeToVar(props.width),
210
- "--fs-card-max-width" : sizeToVar(props.maxWidth, "unset"),
211
193
  "--fs-card-background-color": gradients.value.base,
212
194
  "--fs-card-border-color" : borderColor.value,
213
195
  "--fs-card-color" : colors.value.lightContrast!,
@@ -1,9 +1,7 @@
1
1
  <template>
2
2
  <component
3
- :is="$props.to && $props.clickable ? 'FSRouterLink' : 'div'"
4
- class="fs-chip-container"
3
+ :is="$props.to ? 'FSRouterLink' : 'div'"
5
4
  v-bind="$props.to ? { to: $props.to } : {}"
6
- @click="$emit('click', $event)"
7
5
  >
8
6
  <FSRow
9
7
  :align="$props.align"
@@ -71,6 +69,7 @@ export default defineComponent({
71
69
  FSSpan,
72
70
  FSRow
73
71
  },
72
+ inheritsAttrs: false,
74
73
  props: {
75
74
  prependIcon: {
76
75
  type: String as PropType<string | null>,
@@ -123,8 +122,6 @@ export default defineComponent({
123
122
  default: null
124
123
  },
125
124
  },
126
- inheritsAttrs: false,
127
- emits: ['click'],
128
125
  setup(props) {
129
126
  const { getColors } = useColors();
130
127
 
@@ -139,9 +136,9 @@ export default defineComponent({
139
136
  "--fs-chip-background-color" : backgrounds.base,
140
137
  "--fs-chip-border-color" : colors.value.base,
141
138
  "--fs-chip-color" : colors.value.base,
142
- "--fs-chip-hover-background-color" : colors.value.base,
139
+ "--fs-chip-hover-background-color" : backgrounds.base,
143
140
  "--fs-chip-hover-border-color" : colors.value.base,
144
- "--fs-chip-hover-color" : colors.value.baseContrast!,
141
+ "--fs-chip-hover-color" : colors.value.base,
145
142
  "--fs-chip-active-background-color": backgrounds.base,
146
143
  "--fs-chip-active-border-color" : colors.value.dark,
147
144
  "--fs-chip-active-color" : colors.value.dark
@@ -151,8 +148,8 @@ export default defineComponent({
151
148
  "--fs-chip-background-color" : colors.value.base,
152
149
  "--fs-chip-border-color" : colors.value.base,
153
150
  "--fs-chip-color" : colors.value.baseContrast!,
154
- "--fs-chip-hover-background-color" : colors.value.soft,
155
- "--fs-chip-hover-border-color" : colors.value.soft,
151
+ "--fs-chip-hover-background-color" : colors.value.base,
152
+ "--fs-chip-hover-border-color" : colors.value.base,
156
153
  "--fs-chip-hover-color" : colors.value.baseContrast!,
157
154
  "--fs-chip-active-background-color": colors.value.dark,
158
155
  "--fs-chip-active-border-color" : colors.value.darkContrast!,
@@ -13,7 +13,7 @@
13
13
  import { computed, defineComponent, type PropType, type StyleValue } from "vue";
14
14
 
15
15
  import { useBreakpoints, useColors } from "@dative-gpi/foundation-shared-components/composables";
16
- import { type ColorBase, type ColorBaseVariations } from "@dative-gpi/foundation-shared-components/models";
16
+ import { type ColorBase } from "@dative-gpi/foundation-shared-components/models";
17
17
 
18
18
  import { sizeToVar } from "../utils";
19
19
 
@@ -31,7 +31,7 @@ export default defineComponent({
31
31
  default: null
32
32
  },
33
33
  variant: {
34
- type: String as PropType<ColorBaseVariations>,
34
+ type: String as PropType<"base" | "baseContrast" | "soft" | "softContrast" | "light" | "lightContrast" | "dark" | "darkContrast">,
35
35
  required: false,
36
36
  default: "base"
37
37
  }
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <FSCard
3
3
  :variant="$props.backgroundVariant"
4
- :borderColor="$props.backgroundVariant === 'background' ? ColorEnum.Light : null"
5
4
  :color="$props.backgroundColor"
6
5
  :border="$props.border"
7
6
  :height="$props.size"
@@ -11,8 +10,8 @@
11
10
  align="center-center"
12
11
  >
13
12
  <FSIcon
14
- :variant="iconVariant"
15
- :color="iconColor"
13
+ :variant="$props.iconVariant"
14
+ :color="contrastedIconColor"
16
15
  :size="actualIconSize"
17
16
  >
18
17
  {{ $props.icon }}
@@ -24,9 +23,10 @@
24
23
  <script lang="ts">
25
24
  import { defineComponent, type PropType, computed } from "vue";
26
25
 
27
- import { ColorEnum, type ColorBase, type ColorBaseVariations } from "@dative-gpi/foundation-shared-components/models";
26
+ import { ColorEnum, type ColorBase } from "@dative-gpi/foundation-shared-components/models";
28
27
 
29
28
  import { sizeToVar } from "../utils";
29
+ import { useColors } from "../composables";
30
30
 
31
31
  import FSCard from "./FSCard.vue";
32
32
  import FSIcon from "./FSIcon.vue";
@@ -46,7 +46,7 @@ export default defineComponent({
46
46
  backgroundColor: {
47
47
  type: [Array, String] as PropType<ColorBase | ColorBase[]>,
48
48
  required: false,
49
- default: ColorEnum.Background
49
+ default: null
50
50
  },
51
51
  backgroundVariant: {
52
52
  type: String as PropType<"background" | "standard" | "full" | "gradient">,
@@ -60,12 +60,13 @@ export default defineComponent({
60
60
  },
61
61
  iconColor: {
62
62
  type: String as PropType<ColorBase>,
63
- required: false
63
+ required: false,
64
+ default: ColorEnum.Light
64
65
  },
65
66
  iconVariant: {
66
- type: String as PropType<ColorBaseVariations | null>,
67
+ type: String as PropType<"base" | "baseContrast" | "soft" | "softContrast" | "light" | "lightContrast" | "dark" | "darkContrast">,
67
68
  required: false,
68
- default: null
69
+ default: "base"
69
70
  },
70
71
  iconSize: {
71
72
  type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
@@ -79,6 +80,14 @@ export default defineComponent({
79
80
  }
80
81
  },
81
82
  setup(props){
83
+ const { getColors } = useColors();
84
+
85
+ const colors = computed(() => {
86
+ return Array.isArray(props.backgroundColor)
87
+ ? getColors(props.backgroundColor[Math.floor(props.backgroundColor.length/2)])
88
+ : getColors(props.backgroundColor)
89
+ });
90
+
82
91
  const actualIconSize = computed(() => {
83
92
  if (props.iconSize){
84
93
  return props.iconSize;
@@ -89,47 +98,22 @@ export default defineComponent({
89
98
  return "42px";
90
99
  });
91
100
 
92
- const iconVariant = computed((): ColorBaseVariations | undefined => {
93
- if (props.iconVariant) {
94
- return props.iconVariant;
95
- }
96
- if (props.iconColor) {
97
- return "base";
98
- }
99
-
101
+ const contrastedIconColor = computed(() => {
100
102
  switch (props.backgroundVariant) {
101
- case "background":
102
- return "lightContrast";
103
103
  case "standard":
104
- return "lightContrast";
105
- case "full":
106
- return "baseContrast";
107
- case "gradient":
108
- return "baseContrast";
109
- default:
110
- return "base";
111
- }
112
- });
113
-
114
- const iconColor = computed((): ColorBase | undefined => {
115
- if (props.iconColor) {
116
- return props.iconColor;
117
- }
118
-
119
- if(Array.isArray(props.backgroundColor)) {
120
- return props.backgroundColor[Math.floor(props.backgroundColor.length/2)];
121
- }
122
- if(props.backgroundVariant === "background") {
123
- return ColorEnum.Light;
104
+ switch (props.iconColor) {
105
+ case ColorEnum.Dark :
106
+ case ColorEnum.Light:
107
+ default: return colors.value.lightContrast!
108
+ };
109
+ case "background": return colors.value.base
110
+ default: return colors.value.baseContrast!
124
111
  }
125
- return props.backgroundColor;
126
112
  });
127
113
 
128
114
  return {
129
- actualIconSize,
130
- iconVariant,
131
- ColorEnum,
132
- iconColor,
115
+ contrastedIconColor,
116
+ actualIconSize
133
117
  };
134
118
  }
135
119
  });
@@ -15,7 +15,7 @@
15
15
  >
16
16
  <FSButton
17
17
  v-bind="props"
18
- :color="$props.iconColor"
18
+ :color="lightColors.dark"
19
19
  :iconSize="$props.iconSize"
20
20
  variant="icon"
21
21
  icon="mdi-information-outline"
@@ -26,7 +26,6 @@
26
26
  name="menuContent"
27
27
  >
28
28
  <FSCard
29
- :maxWidth="$props.maxWidth"
30
29
  :width="$props.width"
31
30
  :padding="$props.padding"
32
31
  :elevation="true"
@@ -39,15 +38,14 @@
39
38
  align="center-center"
40
39
  >
41
40
  <FSText
42
- font="text-body"
43
- :lineClamp="$props.lineClamp"
41
+ font="text-overline"
42
+ :lineClamp="4"
44
43
  >
45
44
  {{ $props.content }}
46
45
  </FSText>
47
46
  </FSRow>
48
47
  </slot>
49
48
  <template
50
- v-if="$props.showCloseButton"
51
49
  #top-right
52
50
  >
53
51
  <FSButton
@@ -97,11 +95,7 @@ export default defineComponent({
97
95
  },
98
96
  width: {
99
97
  type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
100
- default: null
101
- },
102
- maxWidth: {
103
- type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
104
- default: null
98
+ default: 182
105
99
  },
106
100
  padding: {
107
101
  type: [Array, String, Number] as PropType<string[] | number[] | string | number | null>,
@@ -110,18 +104,6 @@ export default defineComponent({
110
104
  iconSize: {
111
105
  type: String,
112
106
  default: "18px"
113
- },
114
- iconColor: {
115
- type: String as PropType<ColorBase>,
116
- default: ColorEnum.Primary
117
- },
118
- showCloseButton: {
119
- type: Boolean,
120
- default: true
121
- },
122
- lineClamp: {
123
- type: Number,
124
- default: 4
125
107
  }
126
108
  },
127
109
  emits: ["update:modelValue"],
@@ -28,7 +28,8 @@
28
28
 
29
29
  <FSMapLayerButton
30
30
  v-if="$props.allowedLayers?.length && $props.allowedLayers.length > 1"
31
- :layers="mapLayers.filter((layer) => $props.allowedLayers?.includes(layer.name) ?? true)"
31
+ :disabled="$props.disabled"
32
+ :layers="layers.filter((layer) => $props.allowedLayers?.includes(layer.name) ?? true)"
32
33
  :modelValue="$props.currentLayer"
33
34
  @update:model-value="$emit('update:currentLayer', $event)"
34
35
  />
@@ -39,6 +40,7 @@
39
40
  >
40
41
  <FSButton
41
42
  v-if="$props.showMyLocation"
43
+ :disabled="$props.disabled"
42
44
  icon="mdi-crosshairs-gps"
43
45
  color="primary"
44
46
  variant="full"
@@ -53,12 +55,14 @@
53
55
  gap="0"
54
56
  >
55
57
  <FSButton
58
+ :disabled="$props.disabled"
56
59
  class="fs-map-zoom-plus-button"
57
60
  icon="mdi-plus"
58
61
  @click="() => map!.zoomIn()"
59
62
  :border="false"
60
63
  />
61
64
  <FSButton
65
+ :disabled="$props.disabled"
62
66
  class="fs-map-zoom-minus-button"
63
67
  icon="mdi-minus"
64
68
  @click="() => map!.zoomOut()"
@@ -92,12 +96,10 @@
92
96
  import { computed, defineComponent, onMounted, type Ref, provide, type PropType, ref, type StyleValue, watch, onUnmounted, markRaw } from "vue";
93
97
 
94
98
  import type {} from "leaflet.markercluster";
95
- import { map as createMap, control, tileLayer, latLngBounds, latLng, type LatLng, type FitBoundsOptions, type ZoomPanOptions, type LatLngBounds } from "leaflet";
99
+ import { map as createMap, control, latLngBounds, latLng, type LatLng, type FitBoundsOptions, type ZoomPanOptions, type LatLngBounds } from "leaflet";
96
100
 
97
- import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
98
-
99
- import { useBreakpoints, useColors, useSlots } from "../../composables";
100
- import { ColorEnum, MapLayers, MapOverlayPositions, type MapLayer } from "../../models";
101
+ import { useBreakpoints, useColors, useMapLayers, useSlots } from "../../composables";
102
+ import { ColorEnum, MapLayers, MapOverlayPositions } from "../../models";
101
103
 
102
104
  import FSMapLayerButton from "./FSMapLayerButton.vue";
103
105
  import FSMapOverlay from "./FSMapOverlay.vue";
@@ -130,6 +132,11 @@ export default defineComponent({
130
132
  required: false,
131
133
  default: '100%'
132
134
  },
135
+ disabled: {
136
+ type: Boolean,
137
+ required: false,
138
+ default: false
139
+ },
133
140
  grayscale: {
134
141
  type: Boolean,
135
142
  required: false,
@@ -175,22 +182,20 @@ export default defineComponent({
175
182
  required: false,
176
183
  default: () => [MapLayers.Map, MapLayers.Imagery]
177
184
  },
178
- dirtyZoom: {
185
+ zoom: {
179
186
  type: Number,
180
187
  required: false,
181
188
  default: 16
182
189
  }
183
190
  },
184
- emits: ["update:modelValue", "update:selectedLocationId", "update:selectedAreaId", 'update:overlayMode', 'update:currentLayer', "click:latlng"],
191
+ emits: ['update:overlayMode', 'update:currentLayer', "click:latlng", "update:zoom", "update:center"],
185
192
  setup(props, { emit }) {
186
- const { $tr } = useTranslationsProvider();
193
+ const { layers } = useMapLayers();
187
194
  const { isExtraSmall } = useBreakpoints();
188
195
  const { getColors } = useColors();
189
196
  const { slots } = useSlots();
190
197
 
191
198
  const leafletContainer = ref<HTMLElement>();
192
- const locationGroupBounds = ref<LatLngBounds>();
193
- const areaGroupBounds = ref<LatLngBounds>();
194
199
  const gpsPosition : Ref<LatLng | null> = ref(null);
195
200
  const map: Ref<L.Map | null> = ref(null);
196
201
  const overlayHeight = ref<number>();
@@ -198,7 +203,6 @@ export default defineComponent({
198
203
 
199
204
  provide('map', map);
200
205
 
201
- const defaultZoom = ref(props.dirtyZoom);
202
206
  const mapResizeObserver = new ResizeObserver(() => {
203
207
  if(!map.value) {
204
208
  return;
@@ -206,53 +210,6 @@ export default defineComponent({
206
210
  map.value.invalidateSize();
207
211
  });
208
212
 
209
- const mapLayers: MapLayer[] = [
210
- {
211
- name: MapLayers.Map,
212
- label: $tr("ui.map-layer.map", "Map"),
213
- image: new URL("../../assets/images/map/map.png", import.meta.url).href,
214
- layers: [
215
- tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
216
- maxZoom: 22,
217
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
218
- attribution: '© Google Map Data',
219
- className: 'fs-map-tile-base-layer'
220
- })
221
- ]
222
- },
223
- {
224
- name: MapLayers.Imagery,
225
- label: $tr("ui.map-layer.imagery", "Imagery"),
226
- image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
227
- layers: [
228
- tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
229
- maxZoom: 22,
230
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
231
- attribution: '© Google Map Data',
232
- className: 'fs-map-tile-base-layer'
233
- })
234
- ]
235
- },
236
- {
237
- name: MapLayers.Snow,
238
- label: $tr("ui.map-layer.snow", "Snow ski map"),
239
- image: new URL("../../assets/images/map/snow.png", import.meta.url).href,
240
- layers: [
241
- tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? ""}`, {
242
- maxZoom: 22,
243
- subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
244
- attribution: '© Google Map Data',
245
- className: 'fs-map-tile-base-layer fs-map-tile-grayscale-layer'
246
- }),
247
- tileLayer(`https://tiles.opensnowmap.org/pistes/{z}/{x}/{y}.png`, {
248
- maxZoom: 18,
249
- attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors & ODbL, &copy; <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
250
- className: 'fs-map-tile-base-layer'
251
- })
252
- ]
253
- }
254
- ];
255
-
256
213
  const bottomOffset = computed(() => {
257
214
  if (props.overlayMode !== MapOverlayPositions.Expand && overlayHeight.value && isExtraSmall.value) {
258
215
  return overlayHeight.value;
@@ -276,7 +233,7 @@ export default defineComponent({
276
233
  }));
277
234
 
278
235
  const actualLayer = computed(() => {
279
- return mapLayers.find((mapLayer) => mapLayer.name === props.currentLayer)?.layers ?? mapLayers[0].layers;
236
+ return layers.find((mapLayer) => mapLayer.name === props.currentLayer)?.layers ?? layers[0].layers;
280
237
  });
281
238
 
282
239
  const overlaySlots = computed(() => {
@@ -295,7 +252,7 @@ export default defineComponent({
295
252
  return map.value.unproject(targetPoint, zoom);
296
253
  }
297
254
 
298
- const flyTo = (lat: number, lng: number, zoom: number = defaultZoom.value, options?: ZoomPanOptions) => {
255
+ const flyTo = (lat: number, lng: number, zoom: number, options?: ZoomPanOptions) => {
299
256
  if(!map.value) {
300
257
  return;
301
258
  }
@@ -316,7 +273,6 @@ export default defineComponent({
316
273
  } else {
317
274
  map.value.flyTo(calculateTargetPosition(latLng(lat, lng), zoom), zoom, options);
318
275
  }
319
-
320
276
  }
321
277
 
322
278
  const setView = (lat: number, lng: number, zoom: number) => {
@@ -329,13 +285,13 @@ export default defineComponent({
329
285
  const fitBounds = (bounds: LatLngBounds, options?: FitBoundsOptions) => {
330
286
  if (!map.value) {return;}
331
287
  const paddingTopLeft: [number, number] = [
332
- leftOffset.value,
333
- 0
288
+ leftOffset.value + 24,
289
+ 24
334
290
  ];
335
291
 
336
292
  const paddingBottomRight: [number, number] = [
337
- 0,
338
- bottomOffset.value
293
+ 24,
294
+ bottomOffset.value + 24
339
295
  ];
340
296
  const paddingOptions = {
341
297
  paddingTopLeft,
@@ -353,14 +309,16 @@ export default defineComponent({
353
309
 
354
310
  const mapOptions = {
355
311
  zoomControl: false,
356
- scrollWheelZoom: props.enableScrollWheelZoom,
312
+ scrollWheelZoom: props.enableScrollWheelZoom && !props.disabled,
313
+ dragging: !props.disabled,
314
+ doubleClickZoom: false,
357
315
  minZoom: 2,
358
316
  maxZoom: 22,
359
317
  maxBounds: latLngBounds(latLng(-90, -180), latLng(90, 180)),
360
318
  maxBoundsViscosity: 1.0,
361
- zoom: defaultZoom.value,
319
+ zoom: props.zoom,
362
320
  center: props.center ? latLng(props.center[0], props.center[1]) : latLng(48.85782, 2.29521)
363
- };
321
+ } satisfies L.MapOptions;
364
322
 
365
323
  map.value = markRaw(createMap(leafletContainer.value, mapOptions));
366
324
 
@@ -368,6 +326,21 @@ export default defineComponent({
368
326
  emit('click:latlng', e.latlng);
369
327
  });
370
328
 
329
+ map.value.on('zoomend', () => {
330
+ if(!map.value) {
331
+ return;
332
+ }
333
+ emit('update:zoom', map.value.getZoom());
334
+ });
335
+
336
+ map.value.on('moveend', () => {
337
+ if(!map.value) {
338
+ return;
339
+ }
340
+ const center = map.value.getCenter();
341
+ emit('update:center', [center.lat, center.lng]);
342
+ });
343
+
371
344
  map.value.attributionControl.remove();
372
345
  control.attribution({ position: 'bottomleft' }).addTo(map.value);
373
346
 
@@ -382,7 +355,7 @@ export default defineComponent({
382
355
  return;
383
356
  }
384
357
 
385
- flyTo(e.latlng.lat, e.latlng.lng);
358
+ flyTo(e.latlng.lat, e.latlng.lng, 14);
386
359
  });
387
360
 
388
361
  mapResizeObserver.observe(leafletContainer.value);
@@ -392,38 +365,69 @@ export default defineComponent({
392
365
  mapResizeObserver.disconnect();
393
366
  });
394
367
 
395
- watch ([() => props.center, () => map.value], () => {
396
- if(!map.value || !props.center) {
368
+ watch ([() => props.center, () => props.zoom], ([newCenter, newZoom], [oldCenter, oldZoom]) => {
369
+ if(!map.value || !props.center || !newCenter) {
397
370
  return;
398
371
  }
399
- setView(props.center[0], props.center[1], defaultZoom.value);
372
+
373
+ if(map.value.getZoom() === newZoom && map.value.getCenter().equals(latLng(newCenter[0], newCenter[1]))) {
374
+ return;
375
+ }
376
+
377
+ if((newCenter[0] !== oldCenter?.[0] || newCenter[1] !== oldCenter?.[1]) && newZoom !== oldZoom) {
378
+ setView(newCenter[0], newCenter[1], newZoom);
379
+ }
380
+ else if ((newCenter[0] !== oldCenter?.[0] || newCenter[1] !== oldCenter?.[1])) {
381
+ setView(newCenter[0], newCenter[1], map.value.getZoom());
382
+ }
383
+ else if(newZoom !== oldZoom) {
384
+ map.value.setZoom(newZoom);
385
+ }
400
386
  }, { immediate: true });
401
387
 
402
388
  watch([() => props.bounds, () => map.value], () => {
403
389
  if(!map.value || !props.bounds) {
404
390
  return;
405
391
  }
406
- fitBounds(props.bounds, { maxZoom: defaultZoom.value });
392
+
393
+ //console.log("Bounds changed", props.bounds);
394
+ fitBounds(props.bounds, { maxZoom: 14 });
407
395
  });
408
396
 
409
- watch(() => props.dirtyZoom, (newZoom) => {
410
- defaultZoom.value = newZoom;
411
- if(map.value) {
412
- map.value.setZoom(newZoom);
397
+ watch(() => props.enableScrollWheelZoom, (newValue) => {
398
+ if(!map.value) {
399
+ return;
400
+ }
401
+ if(newValue) {
402
+ map.value.scrollWheelZoom.enable();
403
+ } else {
404
+ map.value.scrollWheelZoom.disable();
405
+ }
406
+ }, { immediate: true });
407
+
408
+ watch(() => props.disabled, (newValue) => {
409
+ if(!map.value) {
410
+ return;
411
+ }
412
+ if(newValue) {
413
+ map.value.dragging.disable();
414
+ map.value.scrollWheelZoom.disable();
415
+ } else {
416
+ map.value.dragging.enable();
417
+ if(props.enableScrollWheelZoom) {
418
+ map.value.scrollWheelZoom.enable();
419
+ }
413
420
  }
414
421
  }, { immediate: true });
415
422
 
416
423
  return {
417
424
  ColorEnum,
418
- defaultZoom,
419
425
  leafletContainer,
420
- locationGroupBounds,
421
426
  overlayHeight,
422
427
  overlayWidth,
423
- areaGroupBounds,
424
428
  map,
425
429
  actualLayer,
426
- mapLayers,
430
+ layers,
427
431
  gpsPosition,
428
432
  style,
429
433
  overlaySlots
@@ -3,7 +3,7 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import { inject, provide, ref, type Ref } from 'vue';
6
+ import { inject, provide, ref, type Ref, onUnmounted } from 'vue';
7
7
 
8
8
  import { type Map, FeatureGroup } from 'leaflet';
9
9
  import { MAP } from './keys';
@@ -46,6 +46,12 @@ export default {
46
46
  emit("update:bounds", featureGroup.value.getBounds());
47
47
  }
48
48
  });
49
+
50
+ onUnmounted(() => {
51
+ if (map.value && map.value.hasLayer(featureGroup.value as unknown as FeatureGroup)) {
52
+ map.value.removeLayer(featureGroup.value as unknown as FeatureGroup);
53
+ }
54
+ });
49
55
  }
50
56
  };
51
57
  </script>
@@ -4,6 +4,7 @@
4
4
  icon="mdi-layers-outline"
5
5
  :elevation="true"
6
6
  @click="dialog = true"
7
+ v-bind="$attrs"
7
8
  />
8
9
  <FSDialog
9
10
  v-model="dialog"
@@ -3,9 +3,9 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import { inject, type PropType, onMounted, type Ref, watch, ref } from 'vue';
6
+ import { inject, type PropType, type Ref, watch, ref, onUnmounted, onMounted } from 'vue';
7
7
 
8
- import { type Map, type DivIcon, divIcon, type LatLng, marker, type Marker, type MarkerClusterGroup } from 'leaflet';
8
+ import { type Map, divIcon, type LatLng, marker, type Marker, type MarkerClusterGroup, type LeafletMouseEvent } from 'leaflet';
9
9
 
10
10
  import { useColors } from "../../composables";
11
11
 
@@ -49,72 +49,90 @@ export default {
49
49
  const markerClusterGroup = inject<Ref<MarkerClusterGroup | null>>(MARKERCLUSTERGROUP, ref(null));
50
50
 
51
51
  const { getColors } = useColors();
52
-
53
- const lastMarker = ref<Marker | null>(null);
54
-
55
- if(!map) {
56
- throw new Error('FSMapTileLayer must be used inside a FSMap component');
57
- }
58
-
59
- if(!map.value) {
60
- throw new Error('FSMapTileLayer must be used inside a FSMap component with a map');
61
- }
62
-
63
- const updateMarker = () => {
64
- if(!map.value || !props.latlng) {
65
- return;
66
- }
67
-
68
- if(lastMarker.value) {
69
- if(markerClusterGroup && markerClusterGroup.value) {
70
- markerClusterGroup.value.removeLayer(lastMarker.value as Marker);
71
- } else {
72
- map.value.removeLayer(lastMarker.value as Marker);
73
- }
74
- }
75
-
76
- let icon: DivIcon | null = null;
52
+ const getMarkerIcon = () => {
77
53
  if(props.variant === 'gps') {
78
54
  const size = 16;
79
- icon = divIcon({
55
+ return divIcon({
80
56
  html: gpsMarkerHtml(),
81
57
  className: 'fs-map-mylocation',
82
58
  iconSize: [size, size],
83
59
  iconAnchor: [size / 2, size / 2],
84
60
  });
85
- } else if(props.variant === 'location') {
61
+ }
62
+
63
+ if(props.variant === 'location') {
86
64
  const size = 36;
87
- icon = divIcon({
65
+ return divIcon({
88
66
  html: locationMarkerHtml(props.icon ?? "mdi-map-marker", getColors(props.color).base, props.label),
89
67
  iconSize: [size, size],
90
68
  className: props.selected ? 'fs-map-marker fs-map-location fs-map-location-selected' : 'fs-map-marker fs-map-location',
91
69
  iconAnchor: [size / 2, size / 2],
92
70
  });
93
- } else {
94
- const size = 16;
95
- icon = divIcon({
96
- html: pinMarkerHtml(getColors(props.color).base, props.label),
97
- iconSize: [size, size],
98
- className: props.selected ? 'fs-map-marker fs-map-pin fs-map-pin-selected' : 'fs-map-marker fs-map-pin',
99
- iconAnchor: [size / 2, size / 2],
100
- });
101
71
  }
102
-
103
- lastMarker.value = marker(props.latlng, { icon });
104
- lastMarker.value.on('click', (e) => {
105
- emit('click', e);
72
+
73
+ const size = 16;
74
+ return divIcon({
75
+ html: pinMarkerHtml(getColors(props.color).base, props.label),
76
+ iconSize: [size, size],
77
+ className: props.selected ? 'fs-map-marker fs-map-pin fs-map-pin-selected' : 'fs-map-marker fs-map-pin',
78
+ iconAnchor: [size / 2, size / 2],
106
79
  });
80
+ }
81
+
82
+ const actualMarker = ref(marker(props.latlng ?? [0, 0], { icon: getMarkerIcon() }));
83
+
84
+ if(!map) {
85
+ throw new Error('FSMapTileLayer must be used inside a FSMap component');
86
+ }
87
+
88
+ if(!map.value) {
89
+ throw new Error('FSMapTileLayer must be used inside a FSMap component with a map');
90
+ }
91
+
92
+ watch(map, () => {
93
+ if(!map.value) {
94
+ return;
95
+ }
107
96
 
108
97
  if(markerClusterGroup && markerClusterGroup.value) {
109
- lastMarker.value.addTo(markerClusterGroup.value);
98
+ actualMarker.value.addTo(markerClusterGroup.value);
110
99
  } else {
111
- lastMarker.value.addTo(map.value);
100
+ actualMarker.value.addTo(map.value);
112
101
  }
113
- };
102
+ }, { immediate: true });
114
103
 
115
- onMounted(updateMarker);
104
+ watch([() => props.variant, () => props.color, () => props.selected], () => {
105
+ if(!actualMarker.value || !map.value) {
106
+ return;
107
+ }
108
+
109
+ const icon = getMarkerIcon();
110
+ actualMarker.value?.setIcon(icon);
111
+ });
112
+
113
+ watch([() => props.latlng?.lat, () => props.latlng?.lng], () => {
114
+ if(!actualMarker.value || !map.value || !props.latlng) {
115
+ return;
116
+ }
117
+
118
+ actualMarker.value.setLatLng(props.latlng);
119
+ });
116
120
 
117
- watch(() => [props.variant, props.color, props.latlng, props.selected], updateMarker);
121
+ onMounted(() => {
122
+ actualMarker.value.on('click', (event: LeafletMouseEvent) => {
123
+ emit('click', event);
124
+ });
125
+ });
126
+
127
+ onUnmounted(() => {
128
+ if(actualMarker.value && map.value) {
129
+ if(markerClusterGroup && markerClusterGroup.value) {
130
+ markerClusterGroup.value.removeLayer(actualMarker.value as Marker);
131
+ } else {
132
+ map.value.removeLayer(actualMarker.value as Marker);
133
+ }
134
+ }
135
+ })
118
136
  }
119
137
  };
120
138
  </script>
@@ -3,9 +3,9 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import { inject, provide, ref, type Ref } from 'vue';
6
+ import { inject, provide, ref, type Ref, onUnmounted } from 'vue';
7
7
 
8
- import { type Map, MarkerClusterGroup, divIcon } from 'leaflet';
8
+ import { LatLngBounds, type Map, MarkerClusterGroup, divIcon } from 'leaflet';
9
9
 
10
10
  import { clusterMarkerHtml } from '../../utils/leafletMarkers';
11
11
  import { MAP } from './keys';
@@ -54,17 +54,42 @@ export default {
54
54
 
55
55
  provide('markerClusterGroup', markerClusterGroup);
56
56
 
57
- markerClusterGroup.value.on("layeradd", () => {
58
- if(!map.value) {
57
+ const handleLayerChange = () => {
58
+ if (!map.value) {
59
59
  return;
60
60
  }
61
61
 
62
62
  const layers = markerClusterGroup.value.getLayers();
63
63
 
64
- if(layers.length === props.expectedLayers && !added) {
64
+ if (layers.length === 0 && added) {
65
+ map.value.removeLayer(markerClusterGroup.value as unknown as L.Layer);
66
+ added = false;
67
+ return;
68
+ }
69
+ if (layers.length === props.expectedLayers && !added) {
65
70
  markerClusterGroup.value.addTo(map.value);
66
71
  added = true;
67
- emit("update:bounds", markerClusterGroup.value.getBounds());
72
+ }
73
+ if (layers.length === props.expectedLayers) {
74
+ const bounds = new LatLngBounds([]);
75
+ for (const layer of layers as any[]) {
76
+ if (layer.getBounds) {
77
+ bounds.extend(layer.getBounds());
78
+ } else if (layer.getLatLng) {
79
+ bounds.extend(layer.getLatLng());
80
+ }
81
+ }
82
+
83
+ emit("update:bounds", layers.length > 0 ? bounds : null);
84
+ }
85
+ };
86
+
87
+ markerClusterGroup.value.on("layeradd", handleLayerChange);
88
+ markerClusterGroup.value.on("layerremove", handleLayerChange);
89
+
90
+ onUnmounted(() => {
91
+ if (map.value && map.value.hasLayer(markerClusterGroup.value as unknown as L.Layer)) {
92
+ map.value.removeLayer(markerClusterGroup.value as unknown as L.Layer);
68
93
  }
69
94
  });
70
95
  }
@@ -3,7 +3,7 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import { inject, type PropType, onMounted, type Ref, watch, ref } from 'vue';
6
+ import { inject, type PropType, onMounted, type Ref, watch, ref, onUnmounted } from 'vue';
7
7
 
8
8
  import { type Map, type LatLng, type Polygon, type FeatureGroup, polygon } from 'leaflet';
9
9
 
@@ -75,7 +75,19 @@ export default {
75
75
 
76
76
  onMounted(updatePolygon);
77
77
 
78
- watch(() => [props.color, props.latlngs], updatePolygon);
78
+ onUnmounted(() => {
79
+ if (lastPolygon.value) {
80
+ if (featureGroup?.value) {
81
+ featureGroup.value.removeLayer(lastPolygon.value);
82
+ } else if (map.value?.hasLayer(lastPolygon.value)) {
83
+ map.value.removeLayer(lastPolygon.value);
84
+ }
85
+ lastPolygon.value = null;
86
+ }
87
+ });
88
+
89
+
90
+ watch([() => props.color, () => props.latlngs], updatePolygon);
79
91
  }
80
92
  };
81
93
  </script>
@@ -3,7 +3,7 @@
3
3
  </template>
4
4
 
5
5
  <script lang="ts">
6
- import { inject, type PropType, onMounted, type Ref, watch } from 'vue';
6
+ import { inject, type PropType, onMounted, type Ref, watch, onUnmounted } from 'vue';
7
7
 
8
8
  import type { Map, Layer } from 'leaflet';
9
9
 
@@ -50,6 +50,17 @@ export default {
50
50
 
51
51
  onMounted(updateLayer);
52
52
 
53
+ onUnmounted(() => {
54
+ if (lastLayers && map.value) {
55
+ lastLayers.forEach(layer => {
56
+ if (map.value.hasLayer(layer)) {
57
+ map.value.removeLayer(layer);
58
+ }
59
+ });
60
+ lastLayers = [];
61
+ }
62
+ });
63
+
53
64
  watch(() => props.layers, updateLayer);
54
65
  }
55
66
  };
File without changes
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <FSTile
3
- :width="['296px','336px']"
3
+ :width="['324px','272px']"
4
4
  :height="['132px', '116px']"
5
5
  :padding="['16px', '12px']"
6
6
  borderRadius="8px"
@@ -107,11 +107,13 @@ export default defineComponent({
107
107
  },
108
108
  iconBackgroundVariant: {
109
109
  type: String as PropType<"background" | "standard" | "full" | "gradient">,
110
- required: false
110
+ required: false,
111
+ default: "standard"
111
112
  },
112
113
  iconBackgroundColor: {
113
114
  type: [Array, String] as PropType<ColorBase | ColorBase[]>,
114
- required: false
115
+ required: false,
116
+ default: ColorEnum.Background
115
117
  },
116
118
  iconBorder: {
117
119
  type: Boolean as PropType<boolean>,
@@ -120,7 +122,8 @@ export default defineComponent({
120
122
  },
121
123
  iconColor: {
122
124
  type: String as PropType<ColorBase>,
123
- required: false
125
+ required: false,
126
+ default: ColorEnum.Light
124
127
  },
125
128
  activeColor: {
126
129
  type: String as PropType<ColorBase>,
@@ -4,6 +4,7 @@ export * from "./useBreakpoints";
4
4
  export * from "./useColors";
5
5
  export * from "./useDebounce";
6
6
  export * from "./useMagicFieldProvider";
7
+ export * from "./useMapLayers";
7
8
  export * from "./useRules";
8
9
  export * from "./useSlots";
9
10
  export * from "./useTables";
@@ -0,0 +1,62 @@
1
+ import { tileLayer } from 'leaflet';
2
+
3
+ import { useTranslations as useTranslationsProvider } from "@dative-gpi/bones-ui/composables";
4
+
5
+ import { MapLayers } from '@dative-gpi/foundation-shared-components/models';
6
+
7
+ export const useMapLayers = () => {
8
+ const { $tr } = useTranslationsProvider();
9
+
10
+ const apiKey = import.meta.env.VITE_GOOGLE_MAPS_API_KEY ?? "";
11
+
12
+ const layers = [
13
+ {
14
+ name: MapLayers.Map,
15
+ label: $tr("ui.map-layer.map", "Map"),
16
+ image: new URL("../../assets/images/map/map.png", import.meta.url).href,
17
+ layers: [
18
+ tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${apiKey}`, {
19
+ maxZoom: 22,
20
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
21
+ attribution: '© Google Map Data',
22
+ className: 'fs-map-tile-base-layer'
23
+ })
24
+ ]
25
+ },
26
+ {
27
+ name: MapLayers.Imagery,
28
+ label: $tr("ui.map-layer.imagery", "Imagery"),
29
+ image: new URL("../../assets/images/map/imagery.png", import.meta.url).href,
30
+ layers: [
31
+ tileLayer(`https://{s}.google.com/vt/lyrs=s,h&x={x}&y={y}&z={z}&key=${apiKey}`, {
32
+ maxZoom: 22,
33
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
34
+ attribution: '© Google Map Data',
35
+ className: 'fs-map-tile-base-layer'
36
+ })
37
+ ]
38
+ },
39
+ {
40
+ name: MapLayers.Snow,
41
+ label: $tr("ui.map-layer.snow", "Snow ski map"),
42
+ image: new URL("../../assets/images/map/snow.png", import.meta.url).href,
43
+ layers: [
44
+ tileLayer(`https://{s}.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key=${apiKey}`, {
45
+ maxZoom: 22,
46
+ subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
47
+ attribution: '© Google Map Data',
48
+ className: 'fs-map-tile-base-layer fs-map-tile-grayscale-layer'
49
+ }),
50
+ tileLayer(`https://tiles.opensnowmap.org/pistes/{z}/{x}/{y}.png`, {
51
+ maxZoom: 18,
52
+ attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors & ODbL, &copy; <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
53
+ className: 'fs-map-tile-base-layer'
54
+ })
55
+ ]
56
+ }
57
+ ];
58
+
59
+ return {
60
+ layers
61
+ };
62
+ }
package/models/colors.ts CHANGED
@@ -19,5 +19,4 @@ export interface ColorVariations {
19
19
  darkContrast?: string | null;
20
20
  }
21
21
 
22
- export type ColorBase = (string | ColorEnum);
23
- export type ColorBaseVariations = "base" | "baseContrast" | "soft" | "softContrast" | "light" | "lightContrast" | "dark" | "darkContrast";
22
+ export type ColorBase = (string | ColorEnum);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dative-gpi/foundation-shared-components",
3
3
  "sideEffects": false,
4
- "version": "1.0.155",
4
+ "version": "1.0.156-maps2",
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": "1.0.155",
14
- "@dative-gpi/foundation-shared-services": "1.0.155"
13
+ "@dative-gpi/foundation-shared-domain": "1.0.156-maps2",
14
+ "@dative-gpi/foundation-shared-services": "1.0.156-maps2"
15
15
  },
16
16
  "peerDependencies": {
17
17
  "@dative-gpi/bones-ui": "^1.0.0",
@@ -35,5 +35,5 @@
35
35
  "sass": "1.71.1",
36
36
  "sass-loader": "13.3.2"
37
37
  },
38
- "gitHead": "1cec447dda0a3f4af92ee9e6457134a5b4cf64e1"
38
+ "gitHead": "e47363fc9a738d138f94cbf4948da4947d35e0df"
39
39
  }
@@ -4,7 +4,6 @@
4
4
  padding: var(--fs-card-padding);
5
5
  height: var(--fs-card-height);
6
6
  width: var(--fs-card-width);
7
- max-width: var(--fs-card-max-width);
8
7
  position: relative;
9
8
  display: flex;
10
9
 
@@ -1,7 +1,3 @@
1
- .fs-chip-container {
2
- text-decoration: none;
3
- }
4
-
5
1
  .fs-chip {
6
2
  user-select: none;
7
3
  transition: all 0.28s cubic-bezier(0.4, 0, 0.2, 1);
@@ -1,42 +1,7 @@
1
1
  const MinusOperator = "-";
2
2
 
3
- /**
4
- * Splits an expression by operators while keeping unary minus signs.
5
- */
6
- const splitByOperators = (expression: string): string[] => {
7
- const tokens: string[] = [];
8
- let current = '';
9
- let expectOperand = true;
10
-
11
- for (let i = 0; i < expression.length; i++) {
12
- const char = expression[i];
13
- if ('+-*/'.includes(char)) {
14
- const isUnaryMinus = char === '-' && expectOperand;
15
- if (isUnaryMinus) {
16
- current += char;
17
- expectOperand = true;
18
- } else {
19
- if (expectOperand) {
20
- // 2 consecutive operators or operator at the start
21
- return [];
22
- }
23
- if (current !== '') {
24
- tokens.push(current);
25
- current = '';
26
- }
27
- tokens.push(char);
28
- expectOperand = true;
29
- }
30
- } else {
31
- current += char;
32
- expectOperand = false;
33
- }
34
- }
35
- if (current !== '') {
36
- tokens.push(current);
37
- }
38
- return tokens;
39
- };
3
+ // Matches one of the three operators (+, *, /) or the - operator if it is preceded by something that is not another operator
4
+ const operatorsRegex = new RegExp(/[\+\*\/]|(?<=.)(?<![-\+\*\/])[-]/gm);
40
5
 
41
6
  // Matches a nested block of parenthesis
42
7
  const parenthesisRegex = new RegExp(/\([^)(]+\)/gm);
@@ -49,11 +14,7 @@ const validateBlock = (block: string, operands: string[] = [], variables: string
49
14
  block = block.replaceAll("(", "").replaceAll(")", "");
50
15
 
51
16
  // Split block on operators (Leave negative signs)
52
- const tokens = splitByOperators(block);
53
- if (tokens.length === 0) {
54
- return false;
55
- }
56
- const components = tokens.filter(token => !'+-*/'.includes(token));
17
+ const components = block.split(operatorsRegex);
57
18
 
58
19
  // Check if each bit is a valid operand
59
20
  for (let i = 0; i < components.length; i++) {