@rural-environmental-registry/map_component 2.0.0 → 2.0.2

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.
@@ -19,6 +19,7 @@ export type LayerActionDefaultItem = {
19
19
  key?: string;
20
20
  name: string;
21
21
  icon?: FaIconName;
22
+ tooltip?: string;
22
23
  style?: LayerActionStyle;
23
24
  disabled?: boolean;
24
25
  visible?: boolean;
@@ -37,6 +38,7 @@ export type LayerActionConfig = {
37
38
  type?: 'edit' | 'import' | 'delete' | 'visibility' | 'custom' | 'cancel' | 'conclude';
38
39
  disabled?: boolean;
39
40
  visible?: boolean;
41
+ tooltip?: string;
40
42
  style?: LayerActionStyle;
41
43
  };
42
44
  export type LayerMetricsConfig = {
@@ -73,7 +75,7 @@ export type LayerData = {
73
75
  name: string;
74
76
  active: boolean;
75
77
  activeDefault: boolean;
76
- role?: 'layer' | 'separator';
78
+ role?: 'layer' | 'separator' | 'subgroup';
77
79
  tooltip?: string;
78
80
  toggle?: {
79
81
  active: string;
@@ -102,6 +104,8 @@ export type LayerData = {
102
104
  infoIcons?: LayerInfoIconConfig[];
103
105
  required?: boolean;
104
106
  editActions?: LayerActionConfig[];
107
+ children?: LayerData[];
108
+ collapsed?: boolean;
105
109
  meta?: Record<string, unknown>;
106
110
  };
107
111
  export type GroupLayerData = {
@@ -5,6 +5,8 @@ export declare const normalizeLayersConfig: (config?: LayersConfig | null) => Se
5
5
  export declare const resolveLayerActions: (layer: LayerData, defaults?: LayerActionDefaults) => LayerActionConfig[];
6
6
  export declare const resolveEditControlAction: (defaults: LayerActionDefaults | undefined, kind: "cancel" | "conclude") => LayerActionConfig;
7
7
  export declare const isMenuSeparator: (layer: LayerData | null | undefined) => boolean;
8
+ export declare const isMenuSubgroup: (layer: LayerData | null | undefined) => boolean;
9
+ export declare const walkMenuLayers: (layers: LayerData[] | undefined, visitor: (layer: LayerData) => void) => void;
8
10
  export declare const shouldApplyMapOverlay: (layer: LayerData) => boolean;
9
11
  export declare const collectLayerMetricsMap: (config?: LayersConfig | null) => LayerMetricsMap;
10
12
  export declare const resolveLayerMetrics: (layer: LayerData | null | undefined) => LayerMetricsConfig[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rural-environmental-registry/map_component",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Map component for Vue.js applications",
5
5
  "main": "dist/index.mjs",
6
6
  "module": "dist/index.mjs",
@@ -84,19 +84,26 @@
84
84
  v-if="!hideControls && visibleActions.length"
85
85
  class="child-menu-actions"
86
86
  >
87
- <button
87
+ <ElTooltip
88
88
  v-for="action in visibleActions"
89
89
  :key="action.key"
90
- type="button"
91
- class="child-action-button"
92
- :disabled="action.disabled"
93
- @click.stop="onActionClick(action)"
90
+ :content="action.tooltip || action.name"
91
+ :disabled="!(action.tooltip || action.name)"
92
+ placement="top"
94
93
  >
95
- <FontAwesomeIcon
96
- v-if="action.icon"
97
- :iconName="action.icon"
98
- />
99
- </button>
94
+ <button
95
+ type="button"
96
+ class="child-action-button"
97
+ :disabled="action.disabled"
98
+ :aria-label="action.tooltip || action.name"
99
+ @click.stop="onActionClick(action)"
100
+ >
101
+ <FontAwesomeIcon
102
+ v-if="action.icon"
103
+ :iconName="action.icon"
104
+ />
105
+ </button>
106
+ </ElTooltip>
100
107
  </div>
101
108
  <template v-else-if="!hideControls && visibilityMode !== 'eye'">
102
109
  <ElSwitch
@@ -367,7 +374,7 @@
367
374
  align-items: center;
368
375
  justify-content: center;
369
376
  border: none !important;
370
- background: transparent !important;;
377
+ background: transparent !important;
371
378
  color: var(--mapa-base-green);
372
379
  cursor: pointer;
373
380
  padding: var(--mapa-size-base-2);
@@ -49,8 +49,10 @@
49
49
  SectionSelectPayload
50
50
  } from '../../types'
51
51
  import {
52
+ isMenuSubgroup,
52
53
  normalizeLayersConfig,
53
- shouldApplyMapOverlay
54
+ shouldApplyMapOverlay,
55
+ walkMenuLayers
54
56
  } from '../../utils/layersConfigNormalizer'
55
57
  import {
56
58
  resolveGroupCollapsedState,
@@ -152,6 +154,15 @@
152
154
  ? group.collapsed
153
155
  : resolveGroupCollapsedState(group.key, undefined, persist)
154
156
  if (!collapsed) openeds.push(group.key)
157
+
158
+ ;(group.layers || []).forEach((layer) => {
159
+ if (!isMenuSubgroup(layer)) return
160
+ const subCollapsed =
161
+ layer.collapsed !== undefined
162
+ ? layer.collapsed
163
+ : resolveGroupCollapsedState(layer.key, true, persist)
164
+ if (!subCollapsed) openeds.push(layer.key)
165
+ })
155
166
  })
156
167
  })
157
168
 
@@ -279,7 +290,7 @@
279
290
 
280
291
  normalizedSections.value.forEach((section) => {
281
292
  section.groups.forEach((group) => {
282
- group.layers?.forEach((layer) => {
293
+ walkMenuLayers(group.layers, (layer) => {
283
294
  if (initializedLayerKeys.has(layer.key)) return
284
295
  if (!shouldApplyMapOverlay(layer)) return
285
296
  if (!shouldInitLayerOnMap(layer, persist)) return
@@ -313,7 +324,7 @@
313
324
  }
314
325
 
315
326
  const onGroupLayerToggle = (parent: GroupLayerData): void => {
316
- parent.layers.forEach((childLayer: LayerData) => {
327
+ walkMenuLayers(parent.layers, (childLayer) => {
317
328
  applyLayerOverlay(childLayer)
318
329
  })
319
330
 
@@ -363,7 +374,8 @@
363
374
 
364
375
  const handleWmsLayer = (layer: LayerData): void => {
365
376
  if (convertedLayers.value[layer.key]) {
366
- if (!layer.active) return removeWmsLayer(layer)
377
+ if (!layer.active) removeWmsLayer(layer)
378
+ return
367
379
  }
368
380
 
369
381
  if (!layer.active) return
@@ -415,7 +427,9 @@
415
427
 
416
428
  const handleGeoJsonLayer = (layer: LayerData): void => {
417
429
  if (convertedGeoJsonLayers.value[layer.key]) {
418
- if (!layer.active) return removeGeoJsonLayer(layer)
430
+ // Já no mapa: só remove se desativada — evita GeoJSON órfão ao reaplicar active=true.
431
+ if (!layer.active) removeGeoJsonLayer(layer)
432
+ return
419
433
  }
420
434
 
421
435
  if (!layer.active) return
@@ -469,8 +483,7 @@
469
483
  .layer-menu > .map-custom-menu {
470
484
  box-shadow: var(--mapa-base-box-shadow);
471
485
  padding: var(--mapa-size-base-10);
472
- overflow-y: scroll;
473
- scrollbar-width: none;
486
+ overflow-y: auto;
474
487
  height: 100%;
475
488
  }
476
489
 
@@ -51,8 +51,29 @@
51
51
  v-for="(child, idx) in childrenLayers"
52
52
  :key="child.key"
53
53
  >
54
+ <ElSubMenu
55
+ v-if="isMenuSubgroup(child)"
56
+ :index="child.key"
57
+ class="parent-menu-subgroup-accordion"
58
+ >
59
+ <template #title>
60
+ <div class="parent-menu-subgroup-title">{{ child.name }}</div>
61
+ </template>
62
+ <ChildMenu
63
+ v-for="(nested, nestedIdx) in child.children || []"
64
+ :key="nested.key"
65
+ :actionDefaults="actionDefaults"
66
+ :data="nested"
67
+ :groupKey="groupData.key"
68
+ :persist="props.persist"
69
+ :sectionKey="sectionKey"
70
+ :visibilityMode="visibilityMode"
71
+ @onChildLayerToggle="onNestedChildChange($event, idx, nestedIdx)"
72
+ @onLayerAction="emit('onLayerAction', $event)"
73
+ />
74
+ </ElSubMenu>
54
75
  <ElMenuItem
55
- v-if="isMenuSeparator(child)"
76
+ v-else-if="isMenuSeparator(child)"
56
77
  :index="child.key"
57
78
  class="parent-menu-subgroup-header"
58
79
  disabled
@@ -114,6 +135,7 @@
114
135
  } from '../../types'
115
136
  import {
116
137
  isMenuSeparator,
138
+ isMenuSubgroup,
117
139
  resolveActiveGroupActions
118
140
  } from '../../utils/layersConfigNormalizer'
119
141
  import { resolveLayerActiveState, setHistory } from '../../utils/menuHistory.ts'
@@ -154,16 +176,26 @@
154
176
  onLayerAction: [LayerActionPayload]
155
177
  }>()
156
178
 
179
+ const resolveMenuEntry = (layer: LayerData): LayerData => {
180
+ if (isMenuSubgroup(layer)) {
181
+ return {
182
+ ...layer,
183
+ children: (layer.children || []).map((child) =>
184
+ resolveLayerActiveState(child, props.persist)
185
+ )
186
+ }
187
+ }
188
+ return resolveLayerActiveState(layer, props.persist)
189
+ }
190
+
157
191
  const childrenLayers = ref<LayerData[]>(
158
- (props.groupData.layers ?? []).map((layer) => resolveLayerActiveState(layer, props.persist))
192
+ (props.groupData.layers ?? []).map((layer) => resolveMenuEntry(layer))
159
193
  )
160
194
 
161
195
  watch(
162
196
  () => props.groupData.layers,
163
197
  (layers) => {
164
- childrenLayers.value = (layers ?? []).map((layer) =>
165
- resolveLayerActiveState(layer, props.persist)
166
- )
198
+ childrenLayers.value = (layers ?? []).map((layer) => resolveMenuEntry(layer))
167
199
  },
168
200
  { deep: true }
169
201
  )
@@ -190,9 +222,20 @@
190
222
  'Ocultar'
191
223
  )
192
224
 
193
- const toggleableLayers = computed(() =>
194
- childrenLayers.value.filter((layer: LayerData) => !isMenuSeparator(layer))
195
- )
225
+ const collectToggleableLeaves = (layers: LayerData[]): LayerData[] => {
226
+ const leaves: LayerData[] = []
227
+ for (const layer of layers) {
228
+ if (isMenuSeparator(layer)) continue
229
+ if (isMenuSubgroup(layer)) {
230
+ leaves.push(...collectToggleableLeaves(layer.children || []))
231
+ continue
232
+ }
233
+ leaves.push(layer)
234
+ }
235
+ return leaves
236
+ }
237
+
238
+ const toggleableLayers = computed(() => collectToggleableLeaves(childrenLayers.value))
196
239
 
197
240
  const toggleVisibleAllLayers = (): void => {
198
241
  if (props.persist) {
@@ -210,6 +253,12 @@
210
253
  set: (active: boolean) => {
211
254
  childrenLayers.value = (props.groupData.layers ?? []).map((layer: LayerData) => {
212
255
  if (isMenuSeparator(layer)) return resolveLayerActiveState(layer, props.persist)
256
+ if (isMenuSubgroup(layer)) {
257
+ return {
258
+ ...layer,
259
+ children: (layer.children || []).map((child) => ({ ...child, active }))
260
+ }
261
+ }
213
262
  return { ...layer, active }
214
263
  })
215
264
 
@@ -222,6 +271,19 @@
222
271
  emit('onChildLayerToggle', layer)
223
272
  }
224
273
 
274
+ const onNestedChildChange = (
275
+ layer: LayerData,
276
+ subgroupIdx: number,
277
+ nestedIdx: number
278
+ ): void => {
279
+ const subgroup = childrenLayers.value[subgroupIdx]
280
+ if (!subgroup) return
281
+ const children = [...(subgroup.children || [])]
282
+ children[nestedIdx] = layer
283
+ childrenLayers.value[subgroupIdx] = { ...subgroup, children }
284
+ emit('onChildLayerToggle', layer)
285
+ }
286
+
225
287
  const onGroupActionClick = (action: GroupActionConfig): void => {
226
288
  if (action.disabled) return
227
289
 
@@ -337,7 +399,6 @@
337
399
  margin: 0 !important;
338
400
  }
339
401
 
340
- /* Título de subgrupo: separador visual, sem accordion */
341
402
  .parent-menu .parent-menu-subgroup-header {
342
403
  height: auto !important;
343
404
  line-height: normal !important;
@@ -355,6 +416,21 @@
355
416
  opacity: 1 !important;
356
417
  }
357
418
 
419
+ .parent-menu .parent-menu-subgroup-accordion {
420
+ padding-left: 0 !important;
421
+ }
422
+
423
+ .parent-menu .parent-menu-subgroup-accordion > .el-sub-menu__title {
424
+ height: auto !important;
425
+ line-height: normal !important;
426
+ white-space: normal !important;
427
+ padding: var(--mapa-size-base-10) var(--mapa-size-base-20) var(--mapa-size-base-5) !important;
428
+ }
429
+
430
+ .parent-menu .parent-menu-subgroup-accordion > .el-sub-menu__title:hover {
431
+ background-color: transparent !important;
432
+ }
433
+
358
434
  .parent-menu .parent-menu-subgroup-title {
359
435
  font-size: var(--mapa-fs-12);
360
436
  font-weight: 700;
package/src/index.ts CHANGED
@@ -40,6 +40,8 @@ export {
40
40
  resolveEditControlAction,
41
41
  shouldApplyMapOverlay,
42
42
  isMenuSeparator,
43
+ isMenuSubgroup,
44
+ walkMenuLayers,
43
45
  collectLayerMetricsMap,
44
46
  resolveLayerMetrics,
45
47
  collectLayerInfoIconsMap,
@@ -47,6 +47,7 @@ export type LayerActionDefaultItem = {
47
47
  key?: string
48
48
  name: string
49
49
  icon?: FaIconName
50
+ tooltip?: string
50
51
  style?: LayerActionStyle
51
52
  disabled?: boolean
52
53
  visible?: boolean
@@ -67,6 +68,7 @@ export type LayerActionConfig = {
67
68
  type?: 'edit' | 'import' | 'delete' | 'visibility' | 'custom' | 'cancel' | 'conclude'
68
69
  disabled?: boolean
69
70
  visible?: boolean
71
+ tooltip?: string
70
72
  style?: LayerActionStyle
71
73
  }
72
74
 
@@ -115,7 +117,7 @@ export type LayerData = {
115
117
  name: string
116
118
  active: boolean
117
119
  activeDefault: boolean
118
- role?: 'layer' | 'separator'
120
+ role?: 'layer' | 'separator' | 'subgroup'
119
121
  tooltip?: string
120
122
  toggle?: {
121
123
  active: string
@@ -145,6 +147,8 @@ export type LayerData = {
145
147
  infoIcons?: LayerInfoIconConfig[]
146
148
  required?: boolean
147
149
  editActions?: LayerActionConfig[]
150
+ children?: LayerData[]
151
+ collapsed?: boolean
148
152
  meta?: Record<string, unknown>
149
153
  }
150
154
 
@@ -110,6 +110,7 @@ export const resolveLayerActions = (
110
110
  name: defaults.edit.name,
111
111
  icon: defaults.edit.icon ?? 'pencil',
112
112
  type: 'edit',
113
+ tooltip: defaults.edit.tooltip,
113
114
  style: defaults.edit.style,
114
115
  disabled: defaults.edit.disabled
115
116
  })
@@ -121,6 +122,7 @@ export const resolveLayerActions = (
121
122
  name: defaults.import.name,
122
123
  icon: defaults.import.icon ?? 'upload',
123
124
  type: 'import',
125
+ tooltip: defaults.import.tooltip,
124
126
  style: defaults.import.style,
125
127
  disabled: defaults.import.disabled
126
128
  })
@@ -132,6 +134,7 @@ export const resolveLayerActions = (
132
134
  name: defaults.delete.name,
133
135
  icon: defaults.delete.icon ?? 'trash',
134
136
  type: 'delete',
137
+ tooltip: defaults.delete.tooltip,
135
138
  style: defaults.delete.style,
136
139
  disabled: defaults.delete.disabled
137
140
  })
@@ -163,8 +166,26 @@ export const isMenuSeparator = (layer: LayerData | null | undefined): boolean =>
163
166
  return layer?.role === 'separator' || !!layer?.meta?.isSeparator
164
167
  }
165
168
 
169
+ export const isMenuSubgroup = (layer: LayerData | null | undefined): boolean => {
170
+ return layer?.role === 'subgroup' || !!layer?.meta?.isSubgroup
171
+ }
172
+
173
+ export const walkMenuLayers = (
174
+ layers: LayerData[] | undefined,
175
+ visitor: (layer: LayerData) => void
176
+ ): void => {
177
+ for (const layer of layers || []) {
178
+ if (isMenuSubgroup(layer) && layer.children?.length) {
179
+ walkMenuLayers(layer.children, visitor)
180
+ continue
181
+ }
182
+ if (isMenuSeparator(layer)) continue
183
+ visitor(layer)
184
+ }
185
+ }
186
+
166
187
  export const shouldApplyMapOverlay = (layer: LayerData): boolean => {
167
- if (isMenuSeparator(layer)) return false
188
+ if (isMenuSeparator(layer) || isMenuSubgroup(layer)) return false
168
189
  if (layer.mapSource === 'none' || layer.mapSource === 'consumer') return false
169
190
  if (layer.mapSource === 'wms' || layer.mapSource === 'geojson') return true
170
191
  if (layer.geojson) return true