@defra/interactive-map 0.0.6-alpha → 0.0.7-alpha
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.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/docs/api.md +10 -5
- package/package.json +56 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +2 -0
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/draw-es/dist/esm/index.js +2 -0
- package/plugins/beta/draw-es/dist/esm/index.js.LICENSE.txt +1 -0
- package/plugins/beta/draw-ml/dist/css/index.css +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +15 -0
- package/plugins/beta/draw-ml/src/api/deleteFeature.js +4 -4
- package/plugins/beta/draw-ml/src/api/editFeature.js +10 -5
- package/plugins/beta/draw-ml/src/draw.scss +1 -0
- package/plugins/beta/draw-ml/src/events.js +2 -0
- package/plugins/beta/draw-ml/src/manifest.js +18 -43
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +35 -1
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +17 -1
- package/plugins/interact/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/src/defaults.js +1 -0
- package/plugins/interact/src/events.js +6 -1
- package/plugins/interact/src/events.test.js +19 -5
- package/plugins/interact/src/hooks/useInteractionHandlers.js +7 -2
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +28 -0
- package/plugins/interact/src/interact.scss +1 -0
- package/plugins/interact/src/manifest.js +22 -16
- package/plugins/interact/src/manifest.test.js +1 -1
- package/plugins/interact/src/reducer.js +1 -0
- package/plugins/interact/src/reducer.test.js +1 -0
- package/providers/beta/esri/dist/css/im-esri-provider.css +1 -0
- package/providers/beta/esri/dist/esm/im-esri-provider.js +2 -0
- package/providers/beta/esri/dist/esm/im-esri-provider.js.LICENSE.txt +1 -0
- package/providers/beta/esri/dist/esm/index.js +2 -0
- package/providers/beta/esri/dist/esm/index.js.LICENSE.txt +1 -0
- package/providers/beta/esri/src/esriProvider.js +12 -0
- package/providers/beta/esri/src/mapEvents.js +2 -2
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/maplibreProvider.js +11 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +6 -2
- package/src/App/components/Actions/Actions.jsx +2 -1
- package/src/App/components/Actions/Actions.module.scss +14 -0
- package/src/App/components/Actions/Actions.test.jsx +2 -2
- package/src/App/components/MapButton/MapButton.module.scss +19 -6
- package/src/App/components/PopupMenu/PopupMenu.jsx +69 -14
- package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -7
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +102 -16
- package/src/App/hooks/useButtonStateEvaluator.js +2 -2
- package/src/App/hooks/useInterfaceAPI.js +26 -32
- package/src/App/registry/pluginRegistry.js +2 -2
- package/src/App/renderer/mapButtons.js +6 -1
- package/src/App/renderer/mapButtons.test.js +20 -0
- package/src/App/store/MapProvider.jsx +2 -2
- package/src/App/store/mapActionsMap.js +3 -2
- package/src/App/store/mapReducer.js +1 -0
- package/src/scss/settings/_dimensions.scss +1 -0
- package/webpack.esm.mjs +19 -10
|
@@ -91,8 +91,8 @@ describe('PopupMenu', () => {
|
|
|
91
91
|
Object.defineProperty(ev, 'target', { value: target, enumerable: true })
|
|
92
92
|
document.dispatchEvent(ev)
|
|
93
93
|
}
|
|
94
|
-
const expectSelected = (text) => expect(screen.getByText(text).parentElement).toHaveClass('
|
|
95
|
-
const expectNotSelected = (text) => expect(screen.getByText(text).parentElement).not.toHaveClass('
|
|
94
|
+
const expectSelected = (text) => expect(screen.getByText(text).parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
95
|
+
const expectNotSelected = (text) => expect(screen.getByText(text).parentElement).not.toHaveClass('im-c-popup-menu__item--selected')
|
|
96
96
|
|
|
97
97
|
it('renders items with labels and icons', () => {
|
|
98
98
|
renderMenu()
|
|
@@ -104,7 +104,7 @@ describe('PopupMenu', () => {
|
|
|
104
104
|
it('applies selected class to active index', () => {
|
|
105
105
|
renderMenu({ startIndex: 1 })
|
|
106
106
|
expect(screen.getByText('Item 2').parentElement).toHaveClass(
|
|
107
|
-
'
|
|
107
|
+
'im-c-popup-menu__item--selected'
|
|
108
108
|
)
|
|
109
109
|
})
|
|
110
110
|
|
|
@@ -112,15 +112,15 @@ describe('PopupMenu', () => {
|
|
|
112
112
|
renderMenu()
|
|
113
113
|
const ul = screen.getByRole('menu')
|
|
114
114
|
fireEvent.keyDown(ul, { key: 'ArrowDown' })
|
|
115
|
-
expect(screen.getByText('Item 2').parentElement).toHaveClass('
|
|
115
|
+
expect(screen.getByText('Item 2').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
116
116
|
fireEvent.keyDown(ul, { key: 'ArrowDown' })
|
|
117
|
-
expect(screen.getByText('Item 1').parentElement).toHaveClass('
|
|
117
|
+
expect(screen.getByText('Item 1').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
118
118
|
fireEvent.keyDown(ul, { key: 'ArrowUp' })
|
|
119
|
-
expect(screen.getByText('Item 2').parentElement).toHaveClass('
|
|
119
|
+
expect(screen.getByText('Item 2').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
120
120
|
fireEvent.keyDown(ul, { key: 'Home' })
|
|
121
|
-
expect(screen.getByText('Item 1').parentElement).toHaveClass('
|
|
121
|
+
expect(screen.getByText('Item 1').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
122
122
|
fireEvent.keyDown(ul, { key: 'End' })
|
|
123
|
-
expect(screen.getByText('Item 2').parentElement).toHaveClass('
|
|
123
|
+
expect(screen.getByText('Item 2').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
124
124
|
})
|
|
125
125
|
|
|
126
126
|
it('handles Enter key to call onClick and close menu', () => {
|
|
@@ -159,6 +159,32 @@ describe('PopupMenu', () => {
|
|
|
159
159
|
expect(mockSetIsOpen).toHaveBeenCalled()
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
+
it('does not call onClick or close menu when clicking a disabled item', () => {
|
|
163
|
+
mockUseApp.disabledButtons = new Set(['item1'])
|
|
164
|
+
renderMenu()
|
|
165
|
+
fireEvent.click(screen.getByText('Item 1'))
|
|
166
|
+
expect(items[0].onClick).not.toHaveBeenCalled()
|
|
167
|
+
expect(mockSetIsOpen).not.toHaveBeenCalled()
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('Enter calls buttonConfig.onClick with evaluateProp context', () => {
|
|
171
|
+
const mockOnClick = jest.fn()
|
|
172
|
+
mockUseApp.buttonConfig = { item1: { onClick: mockOnClick } }
|
|
173
|
+
renderMenu({ startIndex: 0 })
|
|
174
|
+
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Enter' })
|
|
175
|
+
expect(mockOnClick).toHaveBeenCalled()
|
|
176
|
+
expect(mockEvaluateProp).toHaveBeenCalled()
|
|
177
|
+
expect(mockSetIsOpen).toHaveBeenCalled()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('Enter on a disabled item closes menu but does not call onClick', () => {
|
|
181
|
+
mockUseApp.disabledButtons = new Set(['item1'])
|
|
182
|
+
renderMenu({ startIndex: 0 })
|
|
183
|
+
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Enter' })
|
|
184
|
+
expect(items[0].onClick).not.toHaveBeenCalled()
|
|
185
|
+
expect(mockSetIsOpen).toHaveBeenCalled()
|
|
186
|
+
})
|
|
187
|
+
|
|
162
188
|
it('does nothing if click is inside menu', () => {
|
|
163
189
|
renderMenu()
|
|
164
190
|
const element = document.createElement('div')
|
|
@@ -215,22 +241,22 @@ describe('PopupMenu', () => {
|
|
|
215
241
|
renderMenu({ startIndex: -1 })
|
|
216
242
|
const ul = screen.getByRole('menu')
|
|
217
243
|
fireEvent.keyDown(ul, { key: 'ArrowDown' })
|
|
218
|
-
expect(screen.getByText('Item 1').parentElement).toHaveClass('
|
|
244
|
+
expect(screen.getByText('Item 1').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
219
245
|
fireEvent.keyDown(ul, { key: 'ArrowUp' })
|
|
220
|
-
expect(screen.getByText('Item 2').parentElement).toHaveClass('
|
|
246
|
+
expect(screen.getByText('Item 2').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
221
247
|
})
|
|
222
248
|
|
|
223
249
|
it('ArrowUp from no selection picks last visible', () => {
|
|
224
250
|
renderMenu({ startIndex: -1 })
|
|
225
251
|
const ul = screen.getByRole('menu')
|
|
226
252
|
fireEvent.keyDown(ul, { key: 'ArrowUp' })
|
|
227
|
-
expect(screen.getByText('Item 2').parentElement).toHaveClass('
|
|
253
|
+
expect(screen.getByText('Item 2').parentElement).toHaveClass('im-c-popup-menu__item--selected')
|
|
228
254
|
})
|
|
229
255
|
|
|
230
256
|
it('initializes with no selection when neither startIndex nor startPos provided', () => {
|
|
231
257
|
renderMenu({ startIndex: undefined, startPos: undefined })
|
|
232
|
-
expect(screen.getByText('Item 1').parentElement).not.toHaveClass('
|
|
233
|
-
expect(screen.getByText('Item 2').parentElement).not.toHaveClass('
|
|
258
|
+
expect(screen.getByText('Item 1').parentElement).not.toHaveClass('im-c-popup-menu__item--selected')
|
|
259
|
+
expect(screen.getByText('Item 2').parentElement).not.toHaveClass('im-c-popup-menu__item--selected')
|
|
234
260
|
})
|
|
235
261
|
|
|
236
262
|
it('does nothing when navigating and no visible items', () => {
|
|
@@ -238,8 +264,8 @@ describe('PopupMenu', () => {
|
|
|
238
264
|
renderMenu({ startIndex: -1 })
|
|
239
265
|
const ul = screen.getByRole('menu')
|
|
240
266
|
fireEvent.keyDown(ul, { key: 'ArrowDown' })
|
|
241
|
-
expect(screen.getByText('Item 1').parentElement).not.toHaveClass('
|
|
242
|
-
expect(screen.getByText('Item 2').parentElement).not.toHaveClass('
|
|
267
|
+
expect(screen.getByText('Item 1').parentElement).not.toHaveClass('im-c-popup-menu__item--selected')
|
|
268
|
+
expect(screen.getByText('Item 2').parentElement).not.toHaveClass('im-c-popup-menu__item--selected')
|
|
243
269
|
})
|
|
244
270
|
|
|
245
271
|
it('Enter with no selection closes but does not call any item onClick', () => {
|
|
@@ -291,7 +317,7 @@ describe('PopupMenu', () => {
|
|
|
291
317
|
const item1 = screen.getByText('Item 1').parentElement
|
|
292
318
|
const item2 = screen.getByText('Item 2').parentElement
|
|
293
319
|
expect(item1).toHaveStyle('display: none')
|
|
294
|
-
expect(item2).toHaveAttribute('aria-
|
|
320
|
+
expect(item2).toHaveAttribute('aria-checked', 'true')
|
|
295
321
|
})
|
|
296
322
|
|
|
297
323
|
it('ignores unrelated keys (covers Enter false branch)', () => {
|
|
@@ -301,4 +327,64 @@ describe('PopupMenu', () => {
|
|
|
301
327
|
expect(items[1].onClick).not.toHaveBeenCalled()
|
|
302
328
|
expect(mockSetIsOpen).not.toHaveBeenCalled()
|
|
303
329
|
})
|
|
330
|
+
|
|
331
|
+
it('activateItem does nothing when item has no onClick and no buttonConfig.onClick (line 123 false branch)', () => {
|
|
332
|
+
const noHandlerItems = [{ id: 'noHandler', label: 'No Handler' }]
|
|
333
|
+
render(
|
|
334
|
+
<PopupMenu
|
|
335
|
+
pluginId='plugin1'
|
|
336
|
+
instigatorId='instigator'
|
|
337
|
+
startIndex={0}
|
|
338
|
+
menuRef={menuRef}
|
|
339
|
+
items={noHandlerItems}
|
|
340
|
+
setIsOpen={mockSetIsOpen}
|
|
341
|
+
/>
|
|
342
|
+
)
|
|
343
|
+
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Enter' })
|
|
344
|
+
expect(mockSetIsOpen).toHaveBeenCalledWith(false)
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
it('activateItem skips synthetic click when element is not found in DOM (line 132 false branch)', () => {
|
|
348
|
+
const getByIdSpy = jest.spyOn(document, 'getElementById').mockReturnValue(null)
|
|
349
|
+
renderMenu({ startIndex: 0 })
|
|
350
|
+
fireEvent.keyDown(screen.getByRole('menu'), { key: 'Enter' })
|
|
351
|
+
expect(mockSetIsOpen).toHaveBeenCalledWith(false)
|
|
352
|
+
getByIdSpy.mockRestore()
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
describe('Space key', () => {
|
|
356
|
+
it('Space on a menuitem calls onClick and closes menu', () => {
|
|
357
|
+
renderMenu({ startIndex: 0 })
|
|
358
|
+
const ul = screen.getByRole('menu')
|
|
359
|
+
fireEvent.keyDown(ul, { key: ' ' })
|
|
360
|
+
expect(items[0].onClick).toHaveBeenCalled()
|
|
361
|
+
expect(mockSetIsOpen).toHaveBeenCalledWith(false)
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('Space on a menuitemcheckbox calls onClick but does not close menu', () => {
|
|
365
|
+
renderMenu({ startIndex: 1 })
|
|
366
|
+
const ul = screen.getByRole('menu')
|
|
367
|
+
fireEvent.keyDown(ul, { key: ' ' })
|
|
368
|
+
expect(items[1].onClick).toHaveBeenCalled()
|
|
369
|
+
expect(mockSetIsOpen).not.toHaveBeenCalled()
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
it('Space on a disabled item does nothing', () => {
|
|
373
|
+
mockUseApp.disabledButtons = new Set(['item1'])
|
|
374
|
+
renderMenu({ startIndex: 0 })
|
|
375
|
+
const ul = screen.getByRole('menu')
|
|
376
|
+
fireEvent.keyDown(ul, { key: ' ' })
|
|
377
|
+
expect(items[0].onClick).not.toHaveBeenCalled()
|
|
378
|
+
expect(mockSetIsOpen).not.toHaveBeenCalled()
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
it('Space with no selection does nothing', () => {
|
|
382
|
+
renderMenu({ startIndex: -1 })
|
|
383
|
+
const ul = screen.getByRole('menu')
|
|
384
|
+
fireEvent.keyDown(ul, { key: ' ' })
|
|
385
|
+
expect(items[0].onClick).not.toHaveBeenCalled()
|
|
386
|
+
expect(items[1].onClick).not.toHaveBeenCalled()
|
|
387
|
+
expect(mockSetIsOpen).not.toHaveBeenCalled()
|
|
388
|
+
})
|
|
389
|
+
})
|
|
304
390
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/core/hooks/useButtonStateEvaluator.js
|
|
2
|
-
import {
|
|
2
|
+
import { useLayoutEffect, useContext } from 'react'
|
|
3
3
|
import { useApp } from '../store/appContext.js'
|
|
4
4
|
import { useConfig } from '../store/configContext.js'
|
|
5
5
|
import { PluginContext } from '../store/PluginProvider.jsx'
|
|
@@ -55,7 +55,7 @@ export function useButtonStateEvaluator (evaluateProp) {
|
|
|
55
55
|
const { pluginRegistry } = useConfig()
|
|
56
56
|
const pluginContext = useContext(PluginContext)
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
59
|
if (!appState?.dispatch || !pluginContext) {
|
|
60
60
|
return
|
|
61
61
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/hooks/usePanels.js
|
|
2
|
-
import { useEffect } from 'react'
|
|
2
|
+
import { useEffect, useRef } from 'react'
|
|
3
3
|
import { EVENTS as events } from '../../config/events.js'
|
|
4
4
|
import { useApp } from '../store/appContext.js'
|
|
5
5
|
import { useService } from '../store/serviceContext.js'
|
|
@@ -8,38 +8,45 @@ export const useInterfaceAPI = () => {
|
|
|
8
8
|
const { dispatch, hiddenButtons, disabledButtons, pressedButtons, expandedButtons } = useApp()
|
|
9
9
|
const { eventBus } = useService()
|
|
10
10
|
|
|
11
|
+
// Refs so event handlers always use the latest dispatch/state without re-subscribing
|
|
12
|
+
const dispatchRef = useRef(dispatch)
|
|
13
|
+
const stateRef = useRef({ hiddenButtons, disabledButtons, pressedButtons, expandedButtons })
|
|
14
|
+
dispatchRef.current = dispatch
|
|
15
|
+
stateRef.current = { hiddenButtons, disabledButtons, pressedButtons, expandedButtons }
|
|
16
|
+
|
|
11
17
|
useEffect(() => {
|
|
12
18
|
const handleAddButton = ({ id, config }) => {
|
|
13
19
|
// Add the button
|
|
14
|
-
|
|
15
|
-
// Add all optional menu items as individual buttons
|
|
20
|
+
dispatchRef.current({ type: 'ADD_BUTTON', payload: { id, config } })
|
|
21
|
+
// Add all optional menu items as individual buttons (isMenuItem prevents slot rendering)
|
|
16
22
|
if (Array.isArray(config.menuItems)) {
|
|
17
23
|
config.menuItems.forEach(item => {
|
|
18
|
-
|
|
24
|
+
dispatchRef.current({ type: 'ADD_BUTTON', payload: { id: item.id, config: { ...item, isMenuItem: true } } })
|
|
19
25
|
})
|
|
20
26
|
}
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
const handleToggleButtonState = ({ id, prop, value }) => {
|
|
30
|
+
const { hiddenButtons: hidden, disabledButtons: disabled, pressedButtons: pressed, expandedButtons: expanded } = stateRef.current
|
|
24
31
|
switch (prop) {
|
|
25
32
|
case 'hidden': {
|
|
26
|
-
const isHidden = typeof value === 'boolean' ? value : !
|
|
27
|
-
|
|
33
|
+
const isHidden = typeof value === 'boolean' ? value : !hidden.has(id)
|
|
34
|
+
dispatchRef.current({ type: 'TOGGLE_BUTTON_HIDDEN', payload: { id, isHidden } })
|
|
28
35
|
break
|
|
29
36
|
}
|
|
30
37
|
case 'disabled': {
|
|
31
|
-
const isDisabled = typeof value === 'boolean' ? value : !
|
|
32
|
-
|
|
38
|
+
const isDisabled = typeof value === 'boolean' ? value : !disabled.has(id)
|
|
39
|
+
dispatchRef.current({ type: 'TOGGLE_BUTTON_DISABLED', payload: { id, isDisabled } })
|
|
33
40
|
break
|
|
34
41
|
}
|
|
35
42
|
case 'pressed': {
|
|
36
|
-
const isPressed = typeof value === 'boolean' ? value : !
|
|
37
|
-
|
|
43
|
+
const isPressed = typeof value === 'boolean' ? value : !pressed.has(id)
|
|
44
|
+
dispatchRef.current({ type: 'TOGGLE_BUTTON_PRESSED', payload: { id, isPressed } })
|
|
38
45
|
break
|
|
39
46
|
}
|
|
40
47
|
case 'expanded': {
|
|
41
|
-
const isExpanded = typeof value === 'boolean' ? value : !
|
|
42
|
-
|
|
48
|
+
const isExpanded = typeof value === 'boolean' ? value : !expanded.has(id)
|
|
49
|
+
dispatchRef.current({ type: 'TOGGLE_BUTTON_EXPANDED', payload: { id, isExpanded } })
|
|
43
50
|
break
|
|
44
51
|
}
|
|
45
52
|
default:
|
|
@@ -47,25 +54,11 @@ export const useInterfaceAPI = () => {
|
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
const handleAddPanel = ({ id, config }) => {
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
dispatch({ type: 'REMOVE_PANEL', payload: id })
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const handleShowPanel = (id) => {
|
|
59
|
-
dispatch({ type: 'OPEN_PANEL', payload: { panelId: id } })
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const handleHidePanel = (id) => {
|
|
63
|
-
dispatch({ type: 'CLOSE_PANEL', payload: id })
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const handleAddControl = ({ id, config }) => {
|
|
67
|
-
dispatch({ type: 'ADD_CONTROL', payload: { id, config } })
|
|
68
|
-
}
|
|
57
|
+
const handleAddPanel = ({ id, config }) => dispatchRef.current({ type: 'ADD_PANEL', payload: { id, config } })
|
|
58
|
+
const handleRemovePanel = (id) => dispatchRef.current({ type: 'REMOVE_PANEL', payload: id })
|
|
59
|
+
const handleShowPanel = (id) => dispatchRef.current({ type: 'OPEN_PANEL', payload: { panelId: id } })
|
|
60
|
+
const handleHidePanel = (id) => dispatchRef.current({ type: 'CLOSE_PANEL', payload: id })
|
|
61
|
+
const handleAddControl = ({ id, config }) => dispatchRef.current({ type: 'ADD_CONTROL', payload: { id, config } })
|
|
69
62
|
|
|
70
63
|
eventBus.on(events.APP_ADD_BUTTON, handleAddButton)
|
|
71
64
|
eventBus.on(events.APP_TOGGLE_BUTTON_STATE, handleToggleButtonState)
|
|
@@ -77,11 +70,12 @@ export const useInterfaceAPI = () => {
|
|
|
77
70
|
|
|
78
71
|
return () => {
|
|
79
72
|
eventBus.off(events.APP_ADD_BUTTON, handleAddButton)
|
|
73
|
+
eventBus.off(events.APP_TOGGLE_BUTTON_STATE, handleToggleButtonState)
|
|
80
74
|
eventBus.off(events.APP_ADD_PANEL, handleAddPanel)
|
|
81
75
|
eventBus.off(events.APP_REMOVE_PANEL, handleRemovePanel)
|
|
82
76
|
eventBus.off(events.APP_SHOW_PANEL, handleShowPanel)
|
|
83
77
|
eventBus.off(events.APP_HIDE_PANEL, handleHidePanel)
|
|
84
78
|
eventBus.off(events.APP_ADD_CONTROL, handleAddControl)
|
|
85
79
|
}
|
|
86
|
-
}, [
|
|
80
|
+
}, [eventBus])
|
|
87
81
|
}
|
|
@@ -19,9 +19,9 @@ export function createPluginRegistry ({ registerButton, registerPanel, registerC
|
|
|
19
19
|
if (manifest.buttons) {
|
|
20
20
|
asArray(manifest.buttons).forEach(button => {
|
|
21
21
|
registerButton({ [button.id]: { ...pluginConfig, ...button } })
|
|
22
|
-
// Flat button registry including
|
|
22
|
+
// Flat button registry including any menu items (isMenuItem prevents slot rendering)
|
|
23
23
|
button?.menuItems?.forEach(menuItem => {
|
|
24
|
-
registerButton({ [menuItem.id]: { ...pluginConfig, ...menuItem } })
|
|
24
|
+
registerButton({ [menuItem.id]: { ...pluginConfig, ...menuItem, isMenuItem: true } })
|
|
25
25
|
})
|
|
26
26
|
})
|
|
27
27
|
}
|
|
@@ -8,9 +8,14 @@ function getMatchingButtons ({ appState, buttonConfig, slot, evaluateProp }) {
|
|
|
8
8
|
return []
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
return Object.entries(buttonConfig).filter(([_, config]) => {
|
|
11
|
+
return Object.entries(buttonConfig).filter(([_, config]) => { // NOSONAR, extractig to a hleper wouldn't necessarily improve readability
|
|
12
12
|
const bpConfig = config[breakpoint]
|
|
13
13
|
|
|
14
|
+
// Skip menu items — they render inside a parent button's popup, not in a slot
|
|
15
|
+
if (config.isMenuItem) {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
// Dynamic exclusion
|
|
15
20
|
if (typeof config.excludeWhen === 'function' && evaluateProp(config.excludeWhen, config.pluginId)) {
|
|
16
21
|
return false
|
|
@@ -82,6 +82,16 @@ describe('mapButtons module', () => {
|
|
|
82
82
|
const state = { ...appState, isFullscreen: false }
|
|
83
83
|
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState: state, evaluateProp }).length).toBe(1)
|
|
84
84
|
})
|
|
85
|
+
|
|
86
|
+
it('filters out buttons with isMenuItem:true', () => {
|
|
87
|
+
const config = { b1: { ...baseBtn, isMenuItem: true } }
|
|
88
|
+
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp }).length).toBe(0)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('does not filter out buttons without isMenuItem', () => {
|
|
92
|
+
const config = { b1: baseBtn, b2: { ...baseBtn, isMenuItem: false } }
|
|
93
|
+
expect(getMatchingButtons({ buttonConfig: config, slot: 'header', appState, evaluateProp }).length).toBe(2)
|
|
94
|
+
})
|
|
85
95
|
})
|
|
86
96
|
|
|
87
97
|
// -------------------------
|
|
@@ -203,5 +213,15 @@ describe('mapButtons module', () => {
|
|
|
203
213
|
appState.buttonConfig = ({ b1: { ...baseBtn, desktop: { slot: 'header' } } })
|
|
204
214
|
expect(map()[0].order).toBe(0)
|
|
205
215
|
})
|
|
216
|
+
|
|
217
|
+
it('excludes menu items from slot rendering even when they have a matching slot', () => {
|
|
218
|
+
appState.buttonConfig = ({
|
|
219
|
+
parent: baseBtn,
|
|
220
|
+
child: { ...baseBtn, isMenuItem: true }
|
|
221
|
+
})
|
|
222
|
+
const result = map()
|
|
223
|
+
expect(result).toHaveLength(1)
|
|
224
|
+
expect(result[0].id).toBe('parent')
|
|
225
|
+
})
|
|
206
226
|
})
|
|
207
227
|
})
|
|
@@ -8,8 +8,8 @@ export const MapContext = createContext(null)
|
|
|
8
8
|
export const MapProvider = ({ options, children }) => {
|
|
9
9
|
const [state, dispatch] = useReducer(reducer, initialState(options))
|
|
10
10
|
|
|
11
|
-
const handleMapReady = () => {
|
|
12
|
-
dispatch({ type: 'SET_MAP_READY' })
|
|
11
|
+
const handleMapReady = (mapProvider) => {
|
|
12
|
+
dispatch({ type: 'SET_MAP_READY', payload: mapProvider })
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const handleInitMapStyles = (mapStyles) => {
|
package/webpack.esm.mjs
CHANGED
|
@@ -86,15 +86,23 @@ const createESMConfig = (entryName, entryPath, outDir, isCore = false) => {
|
|
|
86
86
|
},
|
|
87
87
|
externals: isCore
|
|
88
88
|
? {}
|
|
89
|
-
:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
: [
|
|
90
|
+
{
|
|
91
|
+
react: 'react',
|
|
92
|
+
'react-dom': 'react-dom',
|
|
93
|
+
'react/jsx-runtime': 'react/jsx-runtime',
|
|
94
|
+
preact: 'preact',
|
|
95
|
+
'preact/compat': 'preact/compat',
|
|
96
|
+
'preact/hooks': 'preact/hooks',
|
|
97
|
+
'preact/jsx-runtime': 'preact/jsx-runtime'
|
|
98
|
+
},
|
|
99
|
+
({ request }, callback) => {
|
|
100
|
+
if (request.startsWith('@arcgis/core')) {
|
|
101
|
+
return callback(null, `module ${request}`)
|
|
102
|
+
}
|
|
103
|
+
return callback()
|
|
104
|
+
}
|
|
105
|
+
],
|
|
98
106
|
module: {
|
|
99
107
|
rules: [
|
|
100
108
|
{ test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ },
|
|
@@ -119,7 +127,7 @@ const ALL_BUILDS = [
|
|
|
119
127
|
// Providers
|
|
120
128
|
{ entryPath: './providers/maplibre/src/index.js', outDir: 'providers/maplibre/dist/esm' },
|
|
121
129
|
{ entryPath: './providers/beta/open-names/src/index.js', outDir: 'providers/beta/open-names/dist/esm' },
|
|
122
|
-
|
|
130
|
+
{ entryPath: './providers/beta/esri/src/index.js', outDir: 'providers/beta/esri/dist/esm' },
|
|
123
131
|
|
|
124
132
|
// Plugins
|
|
125
133
|
{ entryPath: './plugins/beta/scale-bar/src/index.js', outDir: 'plugins/beta/scale-bar/dist/esm' },
|
|
@@ -129,6 +137,7 @@ const ALL_BUILDS = [
|
|
|
129
137
|
{ entryPath: './plugins/beta/datasets/src/index.js', outDir: 'plugins/beta/datasets/dist/esm' },
|
|
130
138
|
{ entryPath: './plugins/beta/map-styles/src/index.js', outDir: 'plugins/beta/map-styles/dist/esm' },
|
|
131
139
|
{ entryPath: './plugins/beta/draw-ml/src/index.js', outDir: 'plugins/beta/draw-ml/dist/esm' },
|
|
140
|
+
{ entryPath: './plugins/beta/draw-es/src/index.js', outDir: 'plugins/beta/draw-es/dist/esm' },
|
|
132
141
|
{ entryPath: './plugins/beta/frame/src/index.js', outDir: 'plugins/beta/frame/dist/esm' }
|
|
133
142
|
]
|
|
134
143
|
|