@rural-environmental-registry/map_component 1.1.0 → 2.0.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.
Files changed (38) hide show
  1. package/README.md +1 -0
  2. package/dist/handlers/measureToolsHandler.d.ts +1 -0
  3. package/dist/index.css +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.mjs +16337 -14954
  6. package/dist/types/index.d.ts +118 -15
  7. package/dist/utils/geometryFileParseResult.d.ts +8 -0
  8. package/dist/utils/layersConfigNormalizer.d.ts +16 -0
  9. package/dist/utils/menuHistory.d.ts +3 -3
  10. package/dist/utils/normalizeGeoJsonToFeatures.d.ts +2 -0
  11. package/dist/utils/parseGeoJsonFile.d.ts +2 -0
  12. package/dist/utils/parseKmlFile.d.ts +2 -0
  13. package/dist/utils/parseShapefileZip.d.ts +2 -8
  14. package/dist/utils/{validateShapefileGeometry.d.ts → validateImportedPolygonGeometry.d.ts} +2 -2
  15. package/package.json +3 -2
  16. package/src/components/MapDPG.vue +90 -2
  17. package/src/components/coordinate/CoordinatePanel.vue +31 -8
  18. package/src/components/fa-icon/FontAwesomeIcon.vue +35 -5
  19. package/src/components/map/LeafletMap.vue +3 -1
  20. package/src/components/menu/ChildMenu.vue +307 -27
  21. package/src/components/menu/LayerMenu.vue +232 -37
  22. package/src/components/menu/ParentMenu.vue +261 -27
  23. package/src/components/menu/SectionMenu.vue +512 -0
  24. package/src/constants/coordinatePanel.ts +6 -5
  25. package/src/handlers/mapToolsHandler.ts +3 -1
  26. package/src/handlers/measureToolsHandler.ts +36 -27
  27. package/src/handlers/toolsConstants.ts +1 -0
  28. package/src/index.ts +47 -0
  29. package/src/types/index.ts +149 -14
  30. package/src/utils/geometryFileParseResult.ts +5 -0
  31. package/src/utils/layersConfigNormalizer.ts +273 -0
  32. package/src/utils/menuHistory.ts +35 -19
  33. package/src/utils/normalizeGeoJsonToFeatures.ts +52 -0
  34. package/src/utils/parseGeoJsonFile.ts +23 -0
  35. package/src/utils/parseKmlFile.ts +31 -0
  36. package/src/utils/parseShapefileZip.ts +4 -31
  37. package/src/utils/validateImportedPolygonGeometry.ts +62 -0
  38. package/src/utils/validateShapefileGeometry.ts +0 -54
@@ -8,21 +8,26 @@
8
8
  <FontAwesomeIcon :iconName="iconButton" />
9
9
  </ElButton>
10
10
  <ElMenu
11
+ :key="menuOpenedsKey"
11
12
  :class="customClasses.customMenu"
13
+ :default-openeds="defaultOpeneds"
12
14
  mode="vertical"
15
+ @close="onGroupMenuClose"
16
+ @open="onGroupMenuOpen"
13
17
  >
14
18
  <div id="external-id-top-menu"></div>
15
- <template
16
- v-for="group in props.layersConfig"
17
- :key="group.key"
18
- >
19
- <ParentMenu
20
- :groupData="group"
21
- :persist="!!props.options?.persist"
22
- @onChildLayerToggle="onChildLayerChange"
23
- @onGroupLayerToggle="onGroupLayerToggle"
24
- />
25
- </template>
19
+ <SectionMenu
20
+ v-if="normalizedSections.length"
21
+ :editingLayerKey="activeEditingLayerKey"
22
+ :persist="!!props.options?.persist"
23
+ :sections="normalizedSections"
24
+ :selectedSectionKey="resolvedSelectedSectionKey"
25
+ :visibilityMode="visibilityMode"
26
+ @onChildLayerToggle="onChildLayerChange"
27
+ @onGroupLayerToggle="onGroupLayerToggle"
28
+ @onLayerAction="onLayerAction"
29
+ @onSelectedSectionChange="onSelectedSectionChange"
30
+ />
26
31
  <div id="external-id-bottom-menu"></div>
27
32
  </ElMenu>
28
33
  </div>
@@ -33,9 +38,26 @@
33
38
  import L from 'leaflet'
34
39
  import { computed, nextTick, ref, watch } from 'vue'
35
40
  import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
36
- import ParentMenu from './ParentMenu.vue'
37
- import { GroupLayerData, LayerData, LayersConfig, LayersMenuConfig } from '../../types'
38
- import { resolveLayerActiveState, shouldInitLayerOnMap } from '../../utils/menuHistory.ts'
41
+ import SectionMenu from './SectionMenu.vue'
42
+ import {
43
+ GroupLayerData,
44
+ LayerActionPayload,
45
+ LayerData,
46
+ LayersConfig,
47
+ LayersMenuConfig,
48
+ SectionEditStatePayload,
49
+ SectionSelectPayload
50
+ } from '../../types'
51
+ import {
52
+ normalizeLayersConfig,
53
+ shouldApplyMapOverlay
54
+ } from '../../utils/layersConfigNormalizer'
55
+ import {
56
+ resolveGroupCollapsedState,
57
+ resolveLayerActiveState,
58
+ setGroupCollapsedHistory,
59
+ shouldInitLayerOnMap
60
+ } from '../../utils/menuHistory.ts'
39
61
 
40
62
  type MenuProps = {
41
63
  layersConfig: LayersConfig
@@ -57,13 +79,112 @@
57
79
  (e: 'stopLoading'): void
58
80
  (e: 'onChildLayerToggle', data: LayerData): void
59
81
  (e: 'onGroupLayerToggle', data: GroupLayerData): void
82
+ (e: 'onLayerAction', payload: LayerActionPayload): void
83
+ (e: 'onSectionEditStateChange', payload: SectionEditStatePayload): void
84
+ (e: 'onSelectedSectionChange', payload: SectionSelectPayload): void
60
85
  }>()
61
86
 
62
87
  const props = defineProps<MenuProps>()
63
88
 
64
- const isMenuOpen = ref<boolean>(false)
89
+ const isMenuOpen = ref<boolean>(!!props.options?.defaultOpen)
65
90
  const convertedLayers = ref<ConvertedLayers>({})
66
91
  const convertedGeoJsonLayers = ref<ConvertedGeoJsonLayers>({})
92
+ const internalEditingLayerKey = ref<string | null>(null)
93
+ const internalSelectedSectionKey = ref<string | null>(null)
94
+
95
+ const normalizedSections = computed(() => normalizeLayersConfig(props.layersConfig))
96
+
97
+ const visibilityMode = computed(() => props.options?.visibilityMode ?? 'switch')
98
+
99
+ const isEditingControlled = computed(() => props.options?.editingLayerKey !== undefined)
100
+
101
+ const activeEditingLayerKey = computed(() => {
102
+ if (isEditingControlled.value) {
103
+ return props.options?.editingLayerKey ?? null
104
+ }
105
+ return internalEditingLayerKey.value
106
+ })
107
+
108
+ const isSectionSelectionControlled = computed(
109
+ () => props.options?.selectedSectionKey !== undefined
110
+ )
111
+
112
+ const activeSelectedSectionKey = computed(() => {
113
+ if (isSectionSelectionControlled.value) {
114
+ return props.options?.selectedSectionKey ?? null
115
+ }
116
+ return internalSelectedSectionKey.value
117
+ })
118
+
119
+ const resolvedSelectedSectionKey = computed((): string | null => {
120
+ const sections = normalizedSections.value
121
+ if (!sections.length) return null
122
+
123
+ const current = activeSelectedSectionKey.value
124
+ if (current && sections.some((section) => section.key === current)) {
125
+ return current
126
+ }
127
+
128
+ return sections[0].key
129
+ })
130
+
131
+ watch(
132
+ normalizedSections,
133
+ (sections) => {
134
+ if (isSectionSelectionControlled.value) return
135
+
136
+ const current = internalSelectedSectionKey.value
137
+ if (current && sections.some((section) => section.key === current)) return
138
+
139
+ internalSelectedSectionKey.value = sections[0]?.key ?? null
140
+ },
141
+ { immediate: true }
142
+ )
143
+
144
+ const defaultOpeneds = computed(() => {
145
+ const persist = !!props.options?.persist
146
+ const openeds: string[] = []
147
+
148
+ normalizedSections.value.forEach((section) => {
149
+ section.groups.forEach((group) => {
150
+ const collapsed =
151
+ group.collapsed !== undefined
152
+ ? group.collapsed
153
+ : resolveGroupCollapsedState(group.key, undefined, persist)
154
+ if (!collapsed) openeds.push(group.key)
155
+ })
156
+ })
157
+
158
+ return openeds
159
+ })
160
+
161
+ const menuOpenedsKey = computed(() => {
162
+ const stepOpenKeys: string[] = []
163
+ normalizedSections.value.forEach((section) => {
164
+ section.groups.forEach((group) => {
165
+ if (group.collapsed === false && group.meta?.isCurrentStep) {
166
+ stepOpenKeys.push(group.key)
167
+ }
168
+ })
169
+ })
170
+ return stepOpenKeys.slice().sort().join('|') || 'none'
171
+ })
172
+
173
+ watch(
174
+ () => menuOpenedsKey.value,
175
+ () => {
176
+ if (!props.options?.persist) return
177
+
178
+ const openSet = new Set(defaultOpeneds.value)
179
+ normalizedSections.value.forEach((section) => {
180
+ section.groups.forEach((group) => {
181
+ if (group.collapsed === undefined) return
182
+ setGroupCollapsedHistory(group.key, !openSet.has(group.key))
183
+ })
184
+ })
185
+ },
186
+ { flush: 'post' }
187
+ )
67
188
 
68
189
  type CustomClasses = {
69
190
  layerMenu: string
@@ -86,12 +207,69 @@
86
207
  return isMenuOpen.value ? 'chevron-left' : 'chevron-right'
87
208
  })
88
209
 
89
- const onInitDefaultLayer = (layer: LayerData): void => {
90
- if (layer.geojson) return handleGeoJsonLayer(layer)
210
+ const findLayerContext = (
211
+ layerKey: string
212
+ ): { sectionKey: string; layer: LayerData } | null => {
213
+ for (const section of normalizedSections.value) {
214
+ for (const group of section.groups) {
215
+ const layer = group.layers?.find((item) => item.key === layerKey)
216
+ if (layer) return { sectionKey: section.key, layer }
217
+ }
218
+ }
219
+ return null
220
+ }
221
+
222
+ const setEditingState = (sectionKey: string | null, layerKey: string | null): void => {
223
+ if (!isEditingControlled.value) {
224
+ internalEditingLayerKey.value = layerKey
225
+ }
226
+
227
+ const context = layerKey ? findLayerContext(layerKey) : null
228
+
229
+ emit('onSectionEditStateChange', {
230
+ sectionKey,
231
+ layerKey,
232
+ layer: context?.layer ?? null
233
+ })
234
+ }
235
+
236
+ const setSelectedSection = (sectionKey: string): void => {
237
+ if (!isSectionSelectionControlled.value) {
238
+ internalSelectedSectionKey.value = sectionKey
239
+ }
240
+
241
+ emit('onSelectedSectionChange', { sectionKey })
242
+ }
243
+
244
+ const onSelectedSectionChange = (sectionKey: string): void => {
245
+ setSelectedSection(sectionKey)
246
+ }
247
+
248
+ const onGroupMenuOpen = (index: string): void => {
249
+ if (!props.options?.persist) return
250
+ setGroupCollapsedHistory(index, false)
251
+ }
252
+
253
+ const onGroupMenuClose = (index: string): void => {
254
+ if (!props.options?.persist) return
255
+ setGroupCollapsedHistory(index, true)
256
+ }
257
+
258
+ const applyLayerOverlay = (layer: LayerData): void => {
259
+ if (!shouldApplyMapOverlay(layer)) return
260
+
261
+ if (layer.geojson || layer.mapSource === 'geojson') {
262
+ handleGeoJsonLayer(layer)
263
+ return
264
+ }
91
265
 
92
266
  handleWmsLayer(layer)
93
267
  }
94
268
 
269
+ const onInitDefaultLayer = (layer: LayerData): void => {
270
+ applyLayerOverlay(layer)
271
+ }
272
+
95
273
  const initializedLayerKeys = new Set<string>()
96
274
 
97
275
  const initDefaultLayers = (): void => {
@@ -99,14 +277,17 @@
99
277
 
100
278
  const persist = !!props.options?.persist
101
279
 
102
- props.layersConfig?.forEach((group) => {
103
- group.layers?.forEach((layer) => {
104
- if (initializedLayerKeys.has(layer.key)) return
105
- if (!shouldInitLayerOnMap(layer, persist)) return
106
-
107
- const resolved = resolveLayerActiveState(layer, persist)
108
- onInitDefaultLayer(resolved)
109
- initializedLayerKeys.add(layer.key)
280
+ normalizedSections.value.forEach((section) => {
281
+ section.groups.forEach((group) => {
282
+ group.layers?.forEach((layer) => {
283
+ if (initializedLayerKeys.has(layer.key)) return
284
+ if (!shouldApplyMapOverlay(layer)) return
285
+ if (!shouldInitLayerOnMap(layer, persist)) return
286
+
287
+ const resolved = resolveLayerActiveState(layer, persist)
288
+ onInitDefaultLayer(resolved)
289
+ initializedLayerKeys.add(layer.key)
290
+ })
110
291
  })
111
292
  })
112
293
  }
@@ -127,29 +308,43 @@
127
308
  )
128
309
 
129
310
  const onChildLayerChange = (layer: LayerData): void => {
130
- if (layer.geojson) {
131
- handleGeoJsonLayer(layer)
132
- } else {
133
- handleWmsLayer(layer)
134
- }
135
-
311
+ applyLayerOverlay(layer)
136
312
  emit('onChildLayerToggle', layer)
137
313
  }
138
314
 
139
315
  const onGroupLayerToggle = (parent: GroupLayerData): void => {
140
316
  parent.layers.forEach((childLayer: LayerData) => {
141
- if (childLayer.geojson) {
142
- handleGeoJsonLayer(childLayer)
143
- } else {
144
- handleWmsLayer(childLayer)
145
- }
317
+ applyLayerOverlay(childLayer)
146
318
  })
147
319
 
148
320
  emit('onGroupLayerToggle', parent)
149
321
  }
150
322
 
323
+ const onLayerAction = (payload: LayerActionPayload): void => {
324
+ const isEditAction = payload.actionType === 'edit' || payload.actionKey === 'edit'
325
+ const isEditControlAction =
326
+ payload.source === 'edit-panel' &&
327
+ (payload.actionType === 'cancel' ||
328
+ payload.actionType === 'conclude' ||
329
+ payload.actionKey === 'cancel' ||
330
+ payload.actionKey === 'conclude')
331
+
332
+ if (isEditAction && payload.source === 'child-menu' && payload.layerKey) {
333
+ setEditingState(payload.sectionKey ?? null, payload.layerKey)
334
+ if (payload.sectionKey) {
335
+ setSelectedSection(payload.sectionKey)
336
+ }
337
+ }
338
+
339
+ if (isEditControlAction) {
340
+ setEditingState(null, null)
341
+ }
342
+
343
+ emit('onLayerAction', payload)
344
+ }
345
+
151
346
  const convertToWmsLayer = (layer: LayerData): L.TileLayer => {
152
- const wmsLayer = L.tileLayer.wms(layer.baseUrl, {
347
+ const wmsLayer = L.tileLayer.wms(layer.baseUrl ?? '', {
153
348
  layers: layer.layers,
154
349
  format: layer.format || 'image/png',
155
350
  transparent: layer.transparent
@@ -7,21 +7,39 @@
7
7
  <template #title>
8
8
  <div class="parent-menu-row">
9
9
  <div class="parent-layer-title">{{ groupData.name }}</div>
10
- <span class="switch-component">
11
- <ElSwitch
12
- v-model="allLayersActive"
13
- @click.stop
10
+ <span
11
+ v-if="showVisibilityControl"
12
+ class="visibility-control"
13
+ >
14
+ <button
15
+ v-if="visibilityMode === 'eye'"
16
+ type="button"
17
+ class="visibility-eye-button"
18
+ :aria-label="allLayersActive ? hideLabel : showLabel"
19
+ :title="allLayersActive ? hideLabel : showLabel"
20
+ @click.stop="allLayersActive = !allLayersActive"
14
21
  >
15
- <template #active-action>
16
- <FontAwesomeIcon iconName="check" />
17
- </template>
18
- <template #inactive-action>
19
- <FontAwesomeIcon iconName="xmark" />
20
- </template>
21
- </ElSwitch>
22
- <span class="parent-layer-status">
23
- {{ allLayersActive ? groupData.toggle.active : groupData.toggle.inactive }}
24
- </span>
22
+ <FontAwesomeIcon :iconName="allLayersActive ? 'eye' : 'eye-slash'" />
23
+ </button>
24
+ <template v-else>
25
+ <ElSwitch
26
+ v-model="allLayersActive"
27
+ @click.stop
28
+ >
29
+ <template #active-action>
30
+ <FontAwesomeIcon iconName="check" />
31
+ </template>
32
+ <template #inactive-action>
33
+ <FontAwesomeIcon iconName="xmark" />
34
+ </template>
35
+ </ElSwitch>
36
+ <span
37
+ v-if="groupData.toggle"
38
+ class="parent-layer-status"
39
+ >
40
+ {{ allLayersActive ? groupData.toggle.active : groupData.toggle.inactive }}
41
+ </span>
42
+ </template>
25
43
  <ElDivider
26
44
  class="divider-bar"
27
45
  direction="vertical"
@@ -33,43 +51,153 @@
33
51
  v-for="(child, idx) in childrenLayers"
34
52
  :key="child.key"
35
53
  >
54
+ <ElMenuItem
55
+ v-if="isMenuSeparator(child)"
56
+ :index="child.key"
57
+ class="parent-menu-subgroup-header"
58
+ disabled
59
+ >
60
+ <div class="parent-menu-subgroup-title">{{ child.name }}</div>
61
+ </ElMenuItem>
36
62
  <ChildMenu
37
- v-if="child.name"
63
+ v-else-if="child.name"
64
+ :actionDefaults="actionDefaults"
38
65
  :data="child"
66
+ :groupKey="groupData.key"
39
67
  :persist="props.persist"
68
+ :sectionKey="sectionKey"
69
+ :visibilityMode="visibilityMode"
40
70
  @onChildLayerToggle="onChildChange($event, idx)"
71
+ @onLayerAction="emit('onLayerAction', $event)"
41
72
  />
42
73
  </template>
74
+
75
+ <ElMenuItem
76
+ v-if="visibleGroupActions.length"
77
+ :index="`${groupData.key}__group-actions`"
78
+ class="parent-menu-group-actions-item"
79
+ >
80
+ <div class="parent-menu-group-actions">
81
+ <button
82
+ v-for="action in visibleGroupActions"
83
+ :key="action.key"
84
+ type="button"
85
+ class="parent-group-action-button"
86
+ :style="actionButtonStyle(action)"
87
+ :disabled="action.disabled"
88
+ @click.stop="onGroupActionClick(action)"
89
+ >
90
+ <FontAwesomeIcon
91
+ v-if="action.icon"
92
+ :iconName="action.icon"
93
+ />
94
+ <span>{{ action.name }}</span>
95
+ </button>
96
+ </div>
97
+ </ElMenuItem>
43
98
  </ElSubMenu>
44
99
  <ElDivider class="parent-menu-divider-row" />
45
100
  </template>
46
101
 
47
102
  <script lang="ts" setup>
48
- import { ElDivider, ElSubMenu, ElSwitch } from 'element-plus'
49
- import { computed, ref } from 'vue'
103
+ import { ElDivider, ElMenuItem, ElSubMenu, ElSwitch } from 'element-plus'
104
+ import { computed, ref, watch } from 'vue'
50
105
  import FontAwesomeIcon from '../fa-icon/FontAwesomeIcon.vue'
51
106
  import ChildMenu from './ChildMenu.vue'
52
- import { GroupLayerData, LayerData } from '../../types'
107
+ import {
108
+ GroupActionConfig,
109
+ GroupLayerData,
110
+ LayerActionDefaults,
111
+ LayerActionPayload,
112
+ LayerActionStyle,
113
+ LayerData
114
+ } from '../../types'
115
+ import {
116
+ isMenuSeparator,
117
+ resolveActiveGroupActions
118
+ } from '../../utils/layersConfigNormalizer'
53
119
  import { resolveLayerActiveState, setHistory } from '../../utils/menuHistory.ts'
54
120
 
121
+ const DEFAULT_ACTION_STYLE: Required<LayerActionStyle> = {
122
+ backgroundColor: 'var(--mapa-base-green)',
123
+ color: 'var(--mapa-base-white)',
124
+ borderColor: 'var(--mapa-base-white)'
125
+ }
126
+
127
+ const resolveActionStyle = (style?: LayerActionStyle): Required<LayerActionStyle> => {
128
+ const color = style?.color ?? DEFAULT_ACTION_STYLE.color
129
+ return {
130
+ backgroundColor: style?.backgroundColor ?? DEFAULT_ACTION_STYLE.backgroundColor,
131
+ color,
132
+ borderColor: style?.borderColor ?? color
133
+ }
134
+ }
135
+
136
+ const actionButtonStyle = (action: GroupActionConfig): Required<LayerActionStyle> =>
137
+ resolveActionStyle(action.style)
138
+
55
139
  type ParentMenuProps = {
56
140
  groupData: GroupLayerData
57
141
  persist: boolean
142
+ sectionKey?: string
143
+ actionDefaults?: LayerActionDefaults
144
+ visibilityMode?: 'switch' | 'eye'
58
145
  }
59
146
 
60
- const props = defineProps<ParentMenuProps>()
147
+ const props = withDefaults(defineProps<ParentMenuProps>(), {
148
+ visibilityMode: 'switch'
149
+ })
61
150
 
62
151
  const emit = defineEmits<{
63
152
  onChildLayerToggle: [LayerData]
64
153
  onGroupLayerToggle: [GroupLayerData]
154
+ onLayerAction: [LayerActionPayload]
65
155
  }>()
66
156
 
67
157
  const childrenLayers = ref<LayerData[]>(
68
158
  (props.groupData.layers ?? []).map((layer) => resolveLayerActiveState(layer, props.persist))
69
159
  )
70
160
 
161
+ watch(
162
+ () => props.groupData.layers,
163
+ (layers) => {
164
+ childrenLayers.value = (layers ?? []).map((layer) =>
165
+ resolveLayerActiveState(layer, props.persist)
166
+ )
167
+ },
168
+ { deep: true }
169
+ )
170
+
171
+ const visibleGroupActions = computed(() => resolveActiveGroupActions(props.groupData))
172
+
173
+ const showGroupEye = computed(() => props.groupData.visibility?.show !== false)
174
+
175
+ const showVisibilityControl = computed(() => {
176
+ if (props.visibilityMode === 'eye') return showGroupEye.value
177
+ return true
178
+ })
179
+
180
+ const showLabel = computed(
181
+ () =>
182
+ props.groupData.visibility?.labelShow ??
183
+ props.groupData.toggle?.active ??
184
+ 'Exibir'
185
+ )
186
+ const hideLabel = computed(
187
+ () =>
188
+ props.groupData.visibility?.labelHide ??
189
+ props.groupData.toggle?.inactive ??
190
+ 'Ocultar'
191
+ )
192
+
193
+ const toggleableLayers = computed(() =>
194
+ childrenLayers.value.filter((layer: LayerData) => !isMenuSeparator(layer))
195
+ )
196
+
71
197
  const toggleVisibleAllLayers = (): void => {
72
- if (props.persist) childrenLayers.value.forEach((layer: LayerData) => setHistory(layer))
198
+ if (props.persist) {
199
+ toggleableLayers.value.forEach((layer: LayerData) => setHistory(layer))
200
+ }
73
201
 
74
202
  emit('onGroupLayerToggle', {
75
203
  ...props.groupData,
@@ -78,13 +206,11 @@
78
206
  }
79
207
 
80
208
  const allLayersActive = computed<boolean>({
81
- get: () => childrenLayers.value.some((layer: LayerData) => layer.active),
209
+ get: () => toggleableLayers.value.some((layer: LayerData) => layer.active),
82
210
  set: (active: boolean) => {
83
- childrenLayers.value = props.groupData.layers.map((layer: LayerData) => {
84
- return {
85
- ...layer,
86
- active
87
- }
211
+ childrenLayers.value = (props.groupData.layers ?? []).map((layer: LayerData) => {
212
+ if (isMenuSeparator(layer)) return resolveLayerActiveState(layer, props.persist)
213
+ return { ...layer, active }
88
214
  })
89
215
 
90
216
  toggleVisibleAllLayers()
@@ -95,6 +221,18 @@
95
221
  childrenLayers.value[idx] = layer
96
222
  emit('onChildLayerToggle', layer)
97
223
  }
224
+
225
+ const onGroupActionClick = (action: GroupActionConfig): void => {
226
+ if (action.disabled) return
227
+
228
+ emit('onLayerAction', {
229
+ actionKey: action.key,
230
+ actionType: action.type,
231
+ sectionKey: props.sectionKey,
232
+ groupKey: props.groupData.key,
233
+ source: 'group-menu'
234
+ })
235
+ }
98
236
  </script>
99
237
 
100
238
  <style>
@@ -106,13 +244,24 @@
106
244
  font-size: var(--mapa-fs-12);
107
245
  }
108
246
 
109
- .parent-menu .switch-component {
247
+ .parent-menu .visibility-control {
110
248
  display: flex;
111
249
  justify-content: center;
112
250
  align-items: center;
113
251
  gap: var(--mapa-size-base-5);
114
252
  }
115
253
 
254
+ .parent-menu .visibility-eye-button {
255
+ display: inline-flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ border: none;
259
+ background: transparent;
260
+ color: var(--mapa-base-green);
261
+ cursor: pointer;
262
+ padding: var(--mapa-size-base-2);
263
+ }
264
+
116
265
  .parent-menu .parent-layer-title {
117
266
  word-wrap: break-word;
118
267
  white-space: normal;
@@ -126,7 +275,92 @@
126
275
  border-width: var(--mapa-size-base-2);
127
276
  }
128
277
 
278
+ .parent-menu-group-actions {
279
+ display: flex;
280
+ flex-direction: column;
281
+ align-items: stretch;
282
+ gap: var(--mapa-size-base-5);
283
+ width: 100%;
284
+ box-sizing: border-box;
285
+ padding: var(--mapa-size-base-10) 0;
286
+ }
287
+
288
+ .parent-menu .parent-menu-group-actions-item {
289
+ height: auto !important;
290
+ line-height: normal !important;
291
+ white-space: normal !important;
292
+ padding-left: var(--mapa-size-base-20) !important;
293
+ padding-right: var(--mapa-size-base-20) !important;
294
+ cursor: default !important;
295
+ }
296
+
297
+ .parent-menu .parent-menu-group-actions-item:hover {
298
+ background-color: transparent !important;
299
+ }
300
+
301
+ .parent-group-action-button {
302
+ display: inline-flex;
303
+ flex: 0 0 auto;
304
+ align-items: center;
305
+ justify-content: center;
306
+ gap: var(--mapa-size-base-5);
307
+ width: 100%;
308
+ height: var(--mapa-size-base-40);
309
+ padding: 0 var(--mapa-size-base-10);
310
+ border: 1px solid;
311
+ border-radius: 100em;
312
+ font-size: var(--mapa-fs-15);
313
+ font-weight: 600;
314
+ white-space: nowrap;
315
+ overflow: hidden;
316
+ text-overflow: ellipsis;
317
+ position: relative;
318
+ text-align: center;
319
+ vertical-align: middle;
320
+ cursor: pointer;
321
+ }
322
+
323
+ .parent-group-action-button:not(:disabled):hover {
324
+ background-image: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.16));
325
+ }
326
+
327
+ .parent-group-action-button:not(:disabled):active {
328
+ background-image: linear-gradient(rgba(0, 0, 0, 0.32), rgba(0, 0, 0, 0.32));
329
+ }
330
+
331
+ .parent-group-action-button:disabled {
332
+ opacity: 0.5;
333
+ cursor: not-allowed;
334
+ }
335
+
129
336
  .parent-menu-divider-row {
130
337
  margin: 0 !important;
131
338
  }
339
+
340
+ /* Título de subgrupo: separador visual, sem accordion */
341
+ .parent-menu .parent-menu-subgroup-header {
342
+ height: auto !important;
343
+ line-height: normal !important;
344
+ white-space: normal !important;
345
+ padding: var(--mapa-size-base-10) var(--mapa-size-base-20) var(--mapa-size-base-5) !important;
346
+ cursor: default !important;
347
+ opacity: 1 !important;
348
+ color: inherit !important;
349
+ }
350
+
351
+ .parent-menu .parent-menu-subgroup-header:hover,
352
+ .parent-menu .parent-menu-subgroup-header.is-disabled {
353
+ background-color: transparent !important;
354
+ cursor: default !important;
355
+ opacity: 1 !important;
356
+ }
357
+
358
+ .parent-menu .parent-menu-subgroup-title {
359
+ font-size: var(--mapa-fs-12);
360
+ font-weight: 700;
361
+ line-height: 150%;
362
+ color: var(--mapa-base-black, #000000);
363
+ word-wrap: break-word;
364
+ white-space: normal;
365
+ }
132
366
  </style>