@defra/interactive-map 0.0.43-fmp-experimental → 0.0.44-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/assets/templates/add-polygons.njk +5 -1
- package/assets/templates/add-symbols.njk +5 -1
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/context.md +24 -0
- package/docs/api/keyboard-shortcut-definition.md +102 -0
- package/docs/api.md +18 -8
- package/docs/building-a-plugin.md +1 -0
- package/docs/plugins/draw.md +2 -2
- package/docs/plugins/plugin-manifest.md +9 -0
- package/package.json +4 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/api/newPolygon.js +1 -0
- package/plugins/beta/draw-es/src/events.js +17 -8
- package/plugins/beta/draw-es/src/reducer.js +8 -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-ol/dist/css/index.css +1 -13
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/frame/dist/css/index.css +1 -11
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +28 -2
- package/plugins/beta/map-styles/dist/css/index.css +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/scale-bar/dist/css/index.css +1 -8
- package/plugins/datasets/dist/css/5648.index.css +1 -5
- package/plugins/datasets/dist/css/index.css +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/src/components/LayersMenu/Layers.module.scss +1 -0
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +2 -2
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +6 -5
- package/plugins/datasets/src/registry/dataset.js +8 -12
- package/plugins/datasets/src/registry/dataset.test.js +17 -0
- package/plugins/datasets/src/registry/datasetRegistry.js +4 -5
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/esm/index.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/dist/umd/index.js +1 -1
- package/plugins/draw/src/DrawInit.jsx +41 -26
- package/plugins/draw/src/DrawInit.test.jsx +55 -12
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +113 -114
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +82 -9
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +3 -1
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +18 -9
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +9 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +11 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +23 -2
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +29 -19
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +17 -2
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.js +35 -29
- package/plugins/draw/src/adapters/maplibre/modes/drawPointMode.test.js +24 -6
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/keyboardHandlers.js +22 -20
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode/pointOperations.js +1 -1
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editPointMode.test.js +3 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +31 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +8 -1
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +38 -30
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +20 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +5 -5
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +2 -2
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +3 -3
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +7 -7
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +4 -4
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +14 -14
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +12 -32
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +10 -10
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +48 -22
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +74 -6
- package/plugins/draw/src/adapters/maplibre/utils/snapMovement.js +2 -2
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +6 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +9 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +15 -2
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +27 -1
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +1 -1
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +40 -17
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +55 -1
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +13 -7
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +7 -15
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +36 -4
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +41 -1
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +35 -25
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +40 -2
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +2 -2
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +2 -2
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.js +40 -54
- package/plugins/draw/src/adapters/openlayers/point/drawPointMode.test.js +32 -13
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.js +1 -1
- package/plugins/draw/src/adapters/openlayers/point/editPointMode.test.js +17 -1
- package/plugins/draw/src/events.js +4 -4
- package/plugins/draw/src/events.test.js +1 -1
- package/plugins/draw/src/hooks/useSpatialList.js +180 -0
- package/plugins/draw/src/hooks/useSpatialList.test.js +339 -0
- package/plugins/draw/src/manifest.js +4 -1
- package/plugins/draw/src/manifest.test.js +19 -7
- package/plugins/draw/src/utils/spatial.js +1 -31
- package/plugins/draw/src/utils/spatial.test.js +1 -24
- 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/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +2 -2
- package/plugins/interact/src/InteractInit.test.js +3 -3
- package/plugins/interact/src/events.js +11 -2
- package/plugins/interact/src/events.test.js +32 -0
- package/plugins/interact/src/hooks/useAttachEvents.js +20 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +44 -1
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +9 -4
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +7 -7
- package/plugins/interact/src/hooks/useSpatialList.js +251 -0
- package/plugins/interact/src/hooks/{useMapItemList.test.js → useSpatialList.test.js} +216 -70
- package/plugins/interact/src/manifest.js +12 -1
- package/plugins/interact/src/manifest.test.js +20 -0
- package/plugins/interact/src/utils/spatial.js +24 -1
- package/plugins/interact/src/utils/spatial.test.js +26 -1
- package/plugins/map-key/dist/css/index.css +1 -106
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -1
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +1 -1
- package/plugins/map-key/dist/umd/index.js +1 -1
- package/plugins/map-key/src/components/Key/Key.jsx +3 -4
- package/plugins/map-key/src/components/Key/Key.module.scss +10 -8
- package/plugins/map-key/src/components/Key/KeyGroupItem.test.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeyItem.jsx +2 -2
- package/plugins/map-key/src/components/Key/KeySvg.jsx +9 -7
- package/plugins/map-key/src/components/Key/KeySvg.test.jsx +4 -4
- package/plugins/map-key/src/components/Key/MapKey.jsx +5 -3
- package/plugins/map-key/src/manifest.js +1 -1
- package/plugins/map-key/src/utils/groupStyles.js +7 -0
- package/plugins/map-key/src/utils/mergeKeyGroupItems.js +12 -0
- package/plugins/map-key/src/utils/mergeKeyGroupItems.test.js +53 -0
- package/plugins/menu/dist/css/index.css +1 -80
- package/plugins/menu/dist/esm/im-menu-plugin.js +1 -1
- package/plugins/menu/dist/umd/im-menu-plugin.js +1 -1
- package/plugins/menu/dist/umd/index.js +1 -1
- package/plugins/menu/src/initialise/MenuInit.jsx +15 -3
- package/plugins/menu/src/initialise/MenuInit.test.jsx +25 -0
- package/plugins/menu/src/initialise/createMenu.js +3 -1
- package/plugins/menu/src/initialise/createMenu.test.js +20 -5
- package/plugins/menu/src/initialise/updateUrl.js +44 -0
- package/plugins/menu/src/initialise/updateUrl.test.js +91 -0
- package/plugins/menu/src/reducers/menuStateReducer.js +19 -1
- package/plugins/menu/src/reducers/menuStateReducer.test.js +53 -20
- package/plugins/menu/src/reducers/pluginState.js +1 -6
- package/plugins/menu/src/reducers/pluginState.test.js +0 -25
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/src/manifest.js +5 -9
- package/plugins/search/src/manifest.test.js +5 -0
- package/providers/beta/esri/dist/css/index.css +1 -34
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/src/openlayersProvider.js +3 -3
- package/providers/beta/openlayers/src/openlayersProvider.test.js +6 -3
- package/providers/beta/openlayers/src/utils/queryFeatures.js +72 -0
- package/providers/beta/openlayers/src/utils/queryFeatures.test.js +122 -1
- 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/dist/umd/index.js +1 -1
- package/providers/maplibre/src/defaults.test.js +26 -0
- package/providers/maplibre/src/utils/labels.js +2 -2
- package/providers/maplibre/src/utils/labels.test.js +5 -5
- package/providers/maplibre/src/utils/spatial.js +0 -57
- package/providers/maplibre/src/utils/spatial.test.js +0 -27
- package/rollup.esm.mjs +2 -1
- package/scripts/publish-package.sh +36 -0
- package/src/App/App.jsx +1 -1
- package/src/App/App.test.jsx +6 -1
- package/src/App/components/CrossHair/CrossHair.jsx +25 -11
- package/src/App/components/CrossHair/CrossHair.module.scss +37 -12
- package/src/App/components/CrossHair/CrossHair.test.jsx +40 -11
- package/src/App/components/Hints/Hints.jsx +3 -3
- package/src/App/components/Hints/Hints.test.jsx +3 -3
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +4 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +17 -0
- package/src/App/components/MapButton/MapButton.jsx +19 -7
- package/src/App/components/MapButton/MapButton.module.scss +1 -2
- package/src/App/components/MapButton/MapButton.test.jsx +42 -4
- package/src/App/components/{MoveControls/MoveControls.jsx → MapControls/MapControls.jsx} +10 -10
- package/src/App/components/{MoveControls/MoveControls.module.scss → MapControls/MapControls.module.scss} +25 -25
- package/src/App/components/{MoveControls/MoveControls.test.jsx → MapControls/MapControls.test.jsx} +48 -48
- package/src/App/components/Markers/Markers.jsx +2 -2
- package/src/App/components/Markers/Markers.test.jsx +3 -3
- package/src/App/components/Markers/SymbolMarker.jsx +2 -3
- package/src/App/components/Markers/SymbolMarker.test.jsx +8 -9
- package/src/App/components/Panel/Panel.jsx +19 -20
- package/src/App/components/Panel/Panel.module.scss +5 -0
- package/src/App/components/Panel/Panel.test.jsx +69 -4
- package/src/App/components/PopupMenu/PopupMenu.jsx +7 -3
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +71 -0
- package/src/App/components/PopupMenu/usePopupMenu.js +10 -9
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/SpatialList.jsx +47 -0
- package/src/App/components/Viewport/{Features.module.scss → SpatialList.module.scss} +6 -6
- package/src/App/components/Viewport/SpatialList.test.jsx +285 -0
- package/src/App/components/Viewport/Viewport.jsx +21 -11
- package/src/App/components/Viewport/Viewport.test.jsx +18 -2
- package/src/App/controls/keyboardMappings.js +7 -2
- package/src/App/controls/keyboardMappings.test.js +21 -0
- package/src/App/controls/keyboardShortcuts.js +16 -2
- package/src/App/controls/keyboardShortcuts.test.js +21 -0
- package/src/App/hooks/useCrossHairAPI.js +29 -1
- package/src/App/hooks/useCrossHairAPI.test.js +17 -1
- package/src/App/hooks/useHintsAPI.js +5 -5
- package/src/App/hooks/useInterfaceAPI.js +2 -1
- package/src/App/hooks/useInterfaceAPI.test.js +9 -0
- package/src/App/hooks/useKeyboardShortcuts.js +66 -35
- package/src/App/hooks/useKeyboardShortcuts.test.js +52 -0
- package/src/App/hooks/useModalPanelBehaviour.js +48 -34
- package/src/App/hooks/useModalPanelBehaviour.test.js +59 -0
- package/src/App/hooks/useSpatialListFocus.js +296 -0
- package/src/App/hooks/useSpatialListFocus.test.js +957 -0
- package/src/App/hooks/useSpatialListItems.js +51 -0
- package/src/App/hooks/useSpatialListItems.test.js +217 -0
- package/src/App/initialiseApp.js +6 -3
- package/src/App/initialiseApp.test.js +11 -0
- package/src/App/layout/Layout.jsx +4 -2
- package/src/App/layout/Layout.test.jsx +21 -0
- package/src/App/layout/layout.module.scss +7 -1
- package/src/App/registry/keyboardShortcutRegistry.js +2 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +16 -0
- package/src/App/registry/pluginRegistry.js +13 -0
- package/src/App/registry/pluginRegistry.test.js +15 -0
- package/src/App/registry/spatialListRegistry.js +104 -0
- package/src/App/registry/spatialListRegistry.test.js +218 -0
- package/src/App/renderer/HtmlElementHost.jsx +12 -32
- package/src/App/renderer/mapButtons.js +4 -0
- package/src/App/renderer/mapButtons.test.js +20 -0
- package/src/App/renderer/mapPanels.js +33 -53
- package/src/App/renderer/mapPanels.test.js +29 -4
- package/src/App/renderer/slotHelpers.js +33 -0
- package/src/App/renderer/slotHelpers.test.js +79 -2
- package/src/App/store/AppProvider.jsx +3 -2
- package/src/App/store/ServiceProvider.jsx +5 -3
- package/src/App/store/ServiceProvider.test.jsx +20 -1
- package/src/InteractiveMap/InteractiveMap.js +13 -2
- package/src/InteractiveMap/InteractiveMap.test.js +33 -0
- package/src/config/appConfig.js +16 -16
- package/src/config/appConfig.test.js +19 -19
- package/src/config/defaults.js +2 -2
- package/src/config/events.js +8 -8
- package/src/scss/main.scss +2 -2
- package/src/test-utils.js +8 -0
- package/src/types.js +51 -9
- package/src/utils/findNearestItemInDirection.js +29 -0
- package/src/utils/findNearestItemInDirection.test.js +67 -0
- package/src/utils/getPanelElementId.js +4 -0
- package/src/utils/getPanelElementId.test.js +11 -0
- package/src/utils/getPanelRole.js +22 -0
- package/src/utils/getPanelRole.test.js +56 -0
- package/src/utils/globalAltShortcuts.js +12 -0
- package/src/utils/globalAltShortcuts.test.js +27 -0
- package/src/utils/spatialNavigate.js +49 -0
- package/src/utils/spatialNavigate.test.js +42 -0
- package/webpack.umd.mjs +6 -1
- package/plugins/interact/src/hooks/useMapItemList.js +0 -187
- package/src/App/components/Viewport/Features.jsx +0 -35
- package/src/App/components/Viewport/Features.test.jsx +0 -124
- package/src/App/hooks/useFeatureFocus.js +0 -199
- package/src/App/hooks/useFeatureFocus.test.js +0 -635
- package/src/App/hooks/useFeatureItems.js +0 -39
- package/src/App/hooks/useFeatureItems.test.js +0 -155
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react'
|
|
2
|
+
import { useSpatialList } from './useSpatialList.js'
|
|
3
|
+
import { createSpatialListRegistry } from '../../../../src/App/registry/spatialListRegistry.js'
|
|
4
|
+
import { ADAPTER_EVENTS } from '../adapterEvents.js'
|
|
5
|
+
|
|
6
|
+
const SET_FEATURES = 'map:setspatiallist'
|
|
7
|
+
const SET_ACTIVE = 'map:setactiveitem'
|
|
8
|
+
const CONFIRM = 'map:selectitem'
|
|
9
|
+
const MOVE_END = 'map:moveend'
|
|
10
|
+
|
|
11
|
+
const vertexItem = (overrides) => expect.objectContaining({ isMidpoint: false, ...overrides })
|
|
12
|
+
const midpointItem = (overrides) => expect.objectContaining({ isMidpoint: true, ...overrides })
|
|
13
|
+
|
|
14
|
+
const makeEventBus = () => {
|
|
15
|
+
const listeners = {}
|
|
16
|
+
return {
|
|
17
|
+
on: jest.fn((eventName, fn) => { listeners[eventName] = fn }),
|
|
18
|
+
off: jest.fn(),
|
|
19
|
+
emit: jest.fn((eventName, payload) => listeners[eventName]?.(payload))
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A minimal stand-in for the adapter surface useSpatialList.js actually calls
|
|
24
|
+
// (getVertexItems/selectVertex/insertVertexAtMidpoint), plus its own on/off/UPDATE bus.
|
|
25
|
+
const makeDrawAdapter = ({ vertices = [], midpoints = [] } = {}) => {
|
|
26
|
+
const listeners = {}
|
|
27
|
+
return {
|
|
28
|
+
getVertexItems: jest.fn(() => ({ vertices, midpoints })),
|
|
29
|
+
selectVertex: jest.fn(),
|
|
30
|
+
insertVertexAtMidpoint: jest.fn(),
|
|
31
|
+
on: jest.fn((eventName, fn) => { listeners[eventName] = fn }),
|
|
32
|
+
off: jest.fn(),
|
|
33
|
+
fireUpdate: () => listeners[ADAPTER_EVENTS.UPDATE]?.()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const makeMapProvider = (draw) => ({
|
|
38
|
+
draw,
|
|
39
|
+
mapToScreen: jest.fn(() => ({ x: 10, y: 20 }))
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const makeViewportRef = () => ({ current: { focus: jest.fn() } })
|
|
43
|
+
|
|
44
|
+
const setup = ({ mode = 'edit_vertex', draw, eventBus, mapSize = 'small', registry, viewportRef, announce } = {}) => {
|
|
45
|
+
const eb = eventBus ?? makeEventBus()
|
|
46
|
+
const adapter = draw ?? makeDrawAdapter()
|
|
47
|
+
const mp = makeMapProvider(adapter)
|
|
48
|
+
const vpRef = viewportRef ?? makeViewportRef()
|
|
49
|
+
const announceSpy = announce ?? jest.fn()
|
|
50
|
+
// A real registry, not a mock — spatialListRegistry.test.js already covers its own
|
|
51
|
+
// aggregation/exclusive-claim behaviour generically; using the real thing here proves this
|
|
52
|
+
// hook actually drives it correctly, same convention as interact's own useSpatialList.test.js.
|
|
53
|
+
const spatialListRegistry = registry ?? createSpatialListRegistry({ eventBus: eb })
|
|
54
|
+
const { result, unmount, rerender } = renderHook(
|
|
55
|
+
({ mode }) => useSpatialList({
|
|
56
|
+
mapState: { mapSize },
|
|
57
|
+
pluginState: { mode },
|
|
58
|
+
services: { eventBus: eb, announce: announceSpy },
|
|
59
|
+
mapProvider: mp,
|
|
60
|
+
spatialListRegistry,
|
|
61
|
+
viewportRef: vpRef
|
|
62
|
+
}),
|
|
63
|
+
{ initialProps: { mode } }
|
|
64
|
+
)
|
|
65
|
+
return { eb, mp, adapter, result, unmount, rerender, spatialListRegistry, viewportRef: vpRef, announce: announceSpy }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── useSpatialList — lifecycle ──────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
describe('useSpatialList — lifecycle', () => {
|
|
71
|
+
it('registers a "draw" item provider with spatialListRegistry on mount', () => {
|
|
72
|
+
const eb = makeEventBus()
|
|
73
|
+
const spatialListRegistry = createSpatialListRegistry({ eventBus: eb })
|
|
74
|
+
const registerSpy = jest.spyOn(spatialListRegistry, 'registerItemProvider')
|
|
75
|
+
const { unmount } = setup({ eventBus: eb, registry: spatialListRegistry })
|
|
76
|
+
expect(registerSpy).toHaveBeenCalledWith('draw', { getItems: expect.any(Function), exclusive: true })
|
|
77
|
+
unmount()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('unregisters the "draw" provider on unmount', () => {
|
|
81
|
+
const eb = makeEventBus()
|
|
82
|
+
const spatialListRegistry = createSpatialListRegistry({ eventBus: eb })
|
|
83
|
+
const unregisterSpy = jest.spyOn(spatialListRegistry, 'unregisterItemProvider')
|
|
84
|
+
const { unmount } = setup({ eventBus: eb, registry: spatialListRegistry })
|
|
85
|
+
unmount()
|
|
86
|
+
expect(unregisterSpy).toHaveBeenCalledWith('draw')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('subscribes to the adapter\'s UPDATE event while edit_vertex is active', () => {
|
|
90
|
+
const { adapter, unmount } = setup({ mode: 'edit_vertex' })
|
|
91
|
+
expect(adapter.on).toHaveBeenCalledWith(ADAPTER_EVENTS.UPDATE, expect.any(Function))
|
|
92
|
+
unmount()
|
|
93
|
+
expect(adapter.off).toHaveBeenCalledWith(ADAPTER_EVENTS.UPDATE, expect.any(Function))
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('is safe when mapProvider.draw does not exist yet', () => {
|
|
97
|
+
const eb = makeEventBus()
|
|
98
|
+
const mp = { draw: null, mapToScreen: jest.fn() }
|
|
99
|
+
expect(() => renderHook(() => useSpatialList({
|
|
100
|
+
mapState: { mapSize: 'small' },
|
|
101
|
+
pluginState: { mode: 'edit_vertex' },
|
|
102
|
+
services: { eventBus: eb },
|
|
103
|
+
mapProvider: mp,
|
|
104
|
+
spatialListRegistry: createSpatialListRegistry({ eventBus: eb })
|
|
105
|
+
}))).not.toThrow()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// ─── useSpatialList — item building ──────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
describe('useSpatialList — item building', () => {
|
|
112
|
+
it('emits no items and label "Shape points" is never surfaced (no claim) outside edit_vertex', () => {
|
|
113
|
+
const eb = makeEventBus()
|
|
114
|
+
setup({ eventBus: eb, mode: 'draw_polygon' })
|
|
115
|
+
// Not the exclusive claimant, so its own empty items don't even reach the emit.
|
|
116
|
+
expect(eb.emit).not.toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({ label: 'Shape points' }))
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('builds vertex items — id, "Point N" label, and screen position — on entering edit_vertex', () => {
|
|
120
|
+
const eb = makeEventBus()
|
|
121
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [] })
|
|
122
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
123
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, {
|
|
124
|
+
items: [
|
|
125
|
+
vertexItem({ id: '0', label: 'Point 1', x: 10, y: 20 }),
|
|
126
|
+
vertexItem({ id: '1', label: 'Point 2', x: 10, y: 20 })
|
|
127
|
+
],
|
|
128
|
+
multiselectable: false,
|
|
129
|
+
label: 'Shape points',
|
|
130
|
+
focusable: false
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('interleaves each midpoint after its vertex, with a flat id continuing the vertex index space', () => {
|
|
135
|
+
const eb = makeEventBus()
|
|
136
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1], [2, 2]], midpoints: [[0.5, 0.5], [1.5, 1.5], [1, 1]] })
|
|
137
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
138
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, {
|
|
139
|
+
items: [
|
|
140
|
+
vertexItem({ id: '0' }),
|
|
141
|
+
midpointItem({ id: '3', label: 'Insert 1-2' }),
|
|
142
|
+
vertexItem({ id: '1' }),
|
|
143
|
+
midpointItem({ id: '4', label: 'Insert 2-3' }),
|
|
144
|
+
vertexItem({ id: '2' }),
|
|
145
|
+
midpointItem({ id: '5', label: 'Insert 3-1' })
|
|
146
|
+
],
|
|
147
|
+
multiselectable: false,
|
|
148
|
+
label: 'Shape points',
|
|
149
|
+
focusable: false
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('declares focusable: false — no item is ever a real Tab stop for this list', () => {
|
|
154
|
+
const eb = makeEventBus()
|
|
155
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
156
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
157
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({ focusable: false }))
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('rebuilds on the adapter\'s UPDATE event (e.g. a vertex was inserted/moved)', () => {
|
|
161
|
+
const eb = makeEventBus()
|
|
162
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
163
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
164
|
+
eb.emit.mockClear()
|
|
165
|
+
|
|
166
|
+
draw.getVertexItems.mockReturnValue({ vertices: [[0, 0], [1, 1]], midpoints: [] })
|
|
167
|
+
act(() => draw.fireUpdate())
|
|
168
|
+
|
|
169
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({
|
|
170
|
+
items: [vertexItem({ id: '0' }), vertexItem({ id: '1' })]
|
|
171
|
+
}))
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('rebuilds on MAP_MOVE_END (pan/zoom) — the underlying coordinate is unchanged but its screen position moves', () => {
|
|
175
|
+
const eb = makeEventBus()
|
|
176
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
177
|
+
const { mp } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
178
|
+
eb.emit.mockClear()
|
|
179
|
+
|
|
180
|
+
mp.mapToScreen.mockReturnValue({ x: 99, y: 88 })
|
|
181
|
+
act(() => eb.emit(MOVE_END))
|
|
182
|
+
|
|
183
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({
|
|
184
|
+
items: [vertexItem({ id: '0', x: 99, y: 88 })]
|
|
185
|
+
}))
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('reports no items when leaving edit_vertex for another mode', () => {
|
|
189
|
+
const eb = makeEventBus()
|
|
190
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
191
|
+
const { rerender } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
192
|
+
eb.emit.mockClear()
|
|
193
|
+
|
|
194
|
+
rerender({ mode: 'disabled' })
|
|
195
|
+
|
|
196
|
+
// Once draw releases its exclusive claim (no other provider registered here), the
|
|
197
|
+
// registry has nothing left to emit for — items are simply gone, not merely empty.
|
|
198
|
+
expect(eb.emit).not.toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({ label: 'Shape points' }))
|
|
199
|
+
})
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
// ─── useSpatialList — exclusive claim ────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
describe('useSpatialList — exclusive claim', () => {
|
|
205
|
+
it('claims exclusive ownership on entering edit_vertex, excluding an additive provider\'s own items', () => {
|
|
206
|
+
const eb = makeEventBus()
|
|
207
|
+
const registry = createSpatialListRegistry({ eventBus: eb })
|
|
208
|
+
registry.registerItemProvider('interact', { getItems: () => ({ items: [{ id: 'feature-1' }], label: 'Map features' }) })
|
|
209
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
210
|
+
|
|
211
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex', registry })
|
|
212
|
+
|
|
213
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, expect.objectContaining({ label: 'Shape points' }))
|
|
214
|
+
const lastEmit = eb.emit.mock.calls.at(-1)[1]
|
|
215
|
+
expect(lastEmit.items.some(i => i.id === 'feature-1')).toBe(false)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('releases the claim on leaving edit_vertex, reverting to the additive provider\'s items', () => {
|
|
219
|
+
const eb = makeEventBus()
|
|
220
|
+
const registry = createSpatialListRegistry({ eventBus: eb })
|
|
221
|
+
registry.registerItemProvider('interact', { getItems: () => ({ items: [{ id: 'feature-1' }], label: 'Map features' }) })
|
|
222
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
223
|
+
|
|
224
|
+
const { rerender } = setup({ eventBus: eb, draw, mode: 'edit_vertex', registry })
|
|
225
|
+
eb.emit.mockClear()
|
|
226
|
+
rerender({ mode: 'disabled' })
|
|
227
|
+
|
|
228
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, { items: [{ id: 'feature-1' }], multiselectable: false, label: 'Map features' })
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('releases the claim on unmount too', () => {
|
|
232
|
+
const eb = makeEventBus()
|
|
233
|
+
const registry = createSpatialListRegistry({ eventBus: eb })
|
|
234
|
+
registry.registerItemProvider('interact', { getItems: () => ({ items: [{ id: 'feature-1' }], label: 'Map features' }) })
|
|
235
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
236
|
+
|
|
237
|
+
const { unmount } = setup({ eventBus: eb, draw, mode: 'edit_vertex', registry })
|
|
238
|
+
eb.emit.mockClear()
|
|
239
|
+
unmount()
|
|
240
|
+
|
|
241
|
+
expect(eb.emit).toHaveBeenCalledWith(SET_FEATURES, { items: [{ id: 'feature-1' }], multiselectable: false, label: 'Map features' })
|
|
242
|
+
})
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// ─── useSpatialList — active item preview (MAP_SET_ACTIVE_ITEM) ─────────
|
|
246
|
+
|
|
247
|
+
describe('useSpatialList — active item preview', () => {
|
|
248
|
+
it('previews a real vertex via selectVertex, by numeric index', () => {
|
|
249
|
+
const eb = makeEventBus()
|
|
250
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [] })
|
|
251
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
252
|
+
act(() => eb.emit(SET_ACTIVE, { id: '1' }))
|
|
253
|
+
expect(draw.selectVertex).toHaveBeenCalledWith(1)
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('previews a midpoint via selectVertex too — preview alone never inserts', () => {
|
|
257
|
+
const eb = makeEventBus()
|
|
258
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [[0.5, 0.5]] })
|
|
259
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
260
|
+
act(() => eb.emit(SET_ACTIVE, { id: '2' }))
|
|
261
|
+
expect(draw.selectVertex).toHaveBeenCalledWith(2)
|
|
262
|
+
expect(draw.insertVertexAtMidpoint).not.toHaveBeenCalled()
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('a null id (blur) does not call selectVertex — the selection must survive losing listbox focus', () => {
|
|
266
|
+
const eb = makeEventBus()
|
|
267
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
268
|
+
setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
269
|
+
act(() => eb.emit(SET_ACTIVE, { id: null }))
|
|
270
|
+
expect(draw.selectVertex).not.toHaveBeenCalled()
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('does nothing outside edit_vertex, even if a stale event arrives', () => {
|
|
274
|
+
const eb = makeEventBus()
|
|
275
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [] })
|
|
276
|
+
setup({ eventBus: eb, draw, mode: 'draw_polygon' })
|
|
277
|
+
act(() => eb.emit(SET_ACTIVE, { id: '0' }))
|
|
278
|
+
expect(draw.selectVertex).not.toHaveBeenCalled()
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
// ─── useSpatialList — confirm (MAP_SELECT_ITEM) ──────────────────────────
|
|
283
|
+
|
|
284
|
+
describe('useSpatialList — confirm', () => {
|
|
285
|
+
it('inserts a vertex when the active item is a midpoint, announces it, and hands focus back to the viewport', () => {
|
|
286
|
+
const eb = makeEventBus()
|
|
287
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [[0.5, 0.5]] })
|
|
288
|
+
const { viewportRef, announce } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
289
|
+
act(() => eb.emit(SET_ACTIVE, { id: '2' })) // the midpoint
|
|
290
|
+
act(() => eb.emit(CONFIRM))
|
|
291
|
+
expect(draw.insertVertexAtMidpoint).toHaveBeenCalledWith(2)
|
|
292
|
+
expect(announce).toHaveBeenCalledWith('New point inserted', 'action')
|
|
293
|
+
expect(viewportRef.current.focus).toHaveBeenCalled()
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('does not insert anything for a real vertex — already the live selection — but still announces it and hands focus back', () => {
|
|
297
|
+
const eb = makeEventBus()
|
|
298
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [[0.5, 0.5]] })
|
|
299
|
+
const { viewportRef, announce } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
300
|
+
act(() => eb.emit(SET_ACTIVE, { id: '0' }))
|
|
301
|
+
act(() => eb.emit(CONFIRM))
|
|
302
|
+
expect(draw.insertVertexAtMidpoint).not.toHaveBeenCalled()
|
|
303
|
+
expect(announce).toHaveBeenCalledWith('Point 1 selected', 'action')
|
|
304
|
+
expect(viewportRef.current.focus).toHaveBeenCalled()
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
it('is a no-op — no insert, no announce, no refocus — when nothing is active yet', () => {
|
|
308
|
+
const eb = makeEventBus()
|
|
309
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [[0.5, 0.5]] })
|
|
310
|
+
const { viewportRef, announce } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
311
|
+
act(() => eb.emit(CONFIRM))
|
|
312
|
+
expect(draw.insertVertexAtMidpoint).not.toHaveBeenCalled()
|
|
313
|
+
expect(announce).not.toHaveBeenCalled()
|
|
314
|
+
expect(viewportRef.current.focus).not.toHaveBeenCalled()
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
it('is a no-op when the active id no longer matches any built item (e.g. the geometry changed underneath it)', () => {
|
|
318
|
+
const eb = makeEventBus()
|
|
319
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0], [1, 1]], midpoints: [] })
|
|
320
|
+
const { viewportRef, announce } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
321
|
+
act(() => eb.emit(SET_ACTIVE, { id: '99' })) // never a built item
|
|
322
|
+
act(() => eb.emit(CONFIRM))
|
|
323
|
+
expect(draw.insertVertexAtMidpoint).not.toHaveBeenCalled()
|
|
324
|
+
expect(announce).not.toHaveBeenCalled()
|
|
325
|
+
expect(viewportRef.current.focus).not.toHaveBeenCalled()
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('is a no-op outside edit_vertex, even with a stale active id', () => {
|
|
329
|
+
const eb = makeEventBus()
|
|
330
|
+
const draw = makeDrawAdapter({ vertices: [[0, 0]], midpoints: [[0.5, 0.5]] })
|
|
331
|
+
const { rerender, viewportRef, announce } = setup({ eventBus: eb, draw, mode: 'edit_vertex' })
|
|
332
|
+
act(() => eb.emit(SET_ACTIVE, { id: '1' })) // the midpoint, while still in edit_vertex
|
|
333
|
+
rerender({ mode: 'disabled' })
|
|
334
|
+
act(() => eb.emit(CONFIRM))
|
|
335
|
+
expect(draw.insertVertexAtMidpoint).not.toHaveBeenCalled()
|
|
336
|
+
expect(announce).not.toHaveBeenCalled()
|
|
337
|
+
expect(viewportRef.current.focus).not.toHaveBeenCalled()
|
|
338
|
+
})
|
|
339
|
+
})
|
|
@@ -20,6 +20,9 @@ const isEditMode = (mode) => EDIT_MODES.has(mode)
|
|
|
20
20
|
|
|
21
21
|
// Show the platform-appropriate undo modifier (⌘ on macOS, Ctrl elsewhere).
|
|
22
22
|
const undoCommand = isMac() ? '<kbd>Command</kbd> + <kbd>Z</kbd>' : '<kbd>Ctrl</kbd> + <kbd>Z</kbd>'
|
|
23
|
+
// Show the platform-appropriate label for the Alt modifier — macOS keyboards and
|
|
24
|
+
// menus call it Option, not Alt.
|
|
25
|
+
const altKeyHtml = isMac() ? '<kbd>Option</kbd>' : '<kbd>Alt</kbd>'
|
|
23
26
|
|
|
24
27
|
const createButtonSlots = (showLabel) => ({
|
|
25
28
|
mobile: { slot: 'actions', showLabel },
|
|
@@ -131,7 +134,7 @@ export const manifest = {
|
|
|
131
134
|
id: 'drawSelectAdjacentPoint',
|
|
132
135
|
group: 'Drawing',
|
|
133
136
|
title: 'Select adjacent point (edit)',
|
|
134
|
-
command:
|
|
137
|
+
command: `${altKeyHtml} + <kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> or <kbd>→</kbd>`
|
|
135
138
|
}, {
|
|
136
139
|
id: 'drawMovePoint',
|
|
137
140
|
group: 'Drawing',
|
|
@@ -152,13 +152,13 @@ describe('drawMenu', () => {
|
|
|
152
152
|
})
|
|
153
153
|
})
|
|
154
154
|
|
|
155
|
-
describe('
|
|
156
|
-
const
|
|
155
|
+
describe('platform-specific keyboard shortcut commands', () => {
|
|
156
|
+
const loadCommand = (mac, id) => {
|
|
157
157
|
let command
|
|
158
158
|
jest.isolateModules(() => {
|
|
159
159
|
jest.doMock('../../../src/utils/isMac.js', () => ({ isMac: () => mac }))
|
|
160
160
|
const { manifest: reloaded } = require('./manifest.js')
|
|
161
|
-
command = reloaded.keyboardShortcuts.find((s) => s.id ===
|
|
161
|
+
command = reloaded.keyboardShortcuts.find((s) => s.id === id).command
|
|
162
162
|
})
|
|
163
163
|
return command
|
|
164
164
|
}
|
|
@@ -168,11 +168,23 @@ describe('drawUndo keyboard shortcut (platform-specific command)', () => {
|
|
|
168
168
|
jest.resetModules()
|
|
169
169
|
})
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
describe('drawUndo', () => {
|
|
172
|
+
test('uses Command on macOS', () => {
|
|
173
|
+
expect(loadCommand(true, 'drawUndo')).toBe('<kbd>Command</kbd> + <kbd>Z</kbd>')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('uses Ctrl on non-mac platforms', () => {
|
|
177
|
+
expect(loadCommand(false, 'drawUndo')).toBe('<kbd>Ctrl</kbd> + <kbd>Z</kbd>')
|
|
178
|
+
})
|
|
173
179
|
})
|
|
174
180
|
|
|
175
|
-
|
|
176
|
-
|
|
181
|
+
describe('drawSelectAdjacentPoint', () => {
|
|
182
|
+
test('uses Option on macOS', () => {
|
|
183
|
+
expect(loadCommand(true, 'drawSelectAdjacentPoint')).toBe('<kbd>Option</kbd> + <kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> or <kbd>→</kbd>')
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test('uses Alt on non-mac platforms', () => {
|
|
187
|
+
expect(loadCommand(false, 'drawSelectAdjacentPoint')).toBe('<kbd>Alt</kbd> + <kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> or <kbd>→</kbd>')
|
|
188
|
+
})
|
|
177
189
|
})
|
|
178
190
|
})
|
|
@@ -202,35 +202,6 @@ const isValidClick = (coords) => {
|
|
|
202
202
|
return coords[0].length <= 1 || isNewCoordinate(coords)
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
-
const spatialNavigate = (start, pixels, direction) => {
|
|
206
|
-
const quadrant = pixels.filter((p) => {
|
|
207
|
-
const offsetX = Math.abs(p[0] - start[0])
|
|
208
|
-
const offsetY = Math.abs(p[1] - start[1])
|
|
209
|
-
let isQuadrant = false
|
|
210
|
-
if (direction === 'ArrowUp') {
|
|
211
|
-
isQuadrant = p[1] <= start[1] && offsetY >= offsetX
|
|
212
|
-
} else if (direction === 'ArrowDown') {
|
|
213
|
-
isQuadrant = p[1] > start[1] && offsetY >= offsetX
|
|
214
|
-
} else if (direction === 'ArrowLeft') {
|
|
215
|
-
isQuadrant = p[0] <= start[0] && offsetY < offsetX
|
|
216
|
-
} else if (direction === 'ArrowRight') {
|
|
217
|
-
isQuadrant = p[0] > start[0] && offsetY < offsetX
|
|
218
|
-
} else {
|
|
219
|
-
isQuadrant = true
|
|
220
|
-
}
|
|
221
|
-
return isQuadrant && (JSON.stringify(p) !== JSON.stringify(start))
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
if (!quadrant.length) {
|
|
225
|
-
quadrant.push(start)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const pythagorean = (a, b) => Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2))
|
|
229
|
-
const distances = quadrant.map(p => pythagorean(Math.abs(start[0] - p[0]), Math.abs(start[1] - p[1])))
|
|
230
|
-
const closest = quadrant[distances.indexOf(Math.min(...distances))]
|
|
231
|
-
return pixels.findIndex(i => JSON.stringify(i) === JSON.stringify(closest))
|
|
232
|
-
}
|
|
233
|
-
|
|
234
205
|
export {
|
|
235
206
|
toTurfGeometry,
|
|
236
207
|
splitPolygon,
|
|
@@ -238,6 +209,5 @@ export {
|
|
|
238
209
|
extendLine,
|
|
239
210
|
isNewCoordinate,
|
|
240
211
|
isValidClick,
|
|
241
|
-
isValidLineClick
|
|
242
|
-
spatialNavigate
|
|
212
|
+
isValidLineClick
|
|
243
213
|
}
|
|
@@ -7,8 +7,7 @@ import {
|
|
|
7
7
|
extendLine,
|
|
8
8
|
isNewCoordinate,
|
|
9
9
|
isValidClick,
|
|
10
|
-
isValidLineClick
|
|
11
|
-
spatialNavigate
|
|
10
|
+
isValidLineClick
|
|
12
11
|
} from './spatial.js'
|
|
13
12
|
|
|
14
13
|
jest.mock('polygon-splitter', () => jest.fn())
|
|
@@ -219,25 +218,3 @@ describe('isValidLineClick', () => {
|
|
|
219
218
|
expect(isValidLineClick([[0, 0], [0, 0]])).toBe(false)
|
|
220
219
|
})
|
|
221
220
|
})
|
|
222
|
-
|
|
223
|
-
describe('spatialNavigate', () => {
|
|
224
|
-
const start = [0, 0]
|
|
225
|
-
const pixels = [[0, 0], [0, -10], [0, 10], [-10, 0], [10, 0]]
|
|
226
|
-
|
|
227
|
-
test.each([
|
|
228
|
-
['ArrowUp', 1],
|
|
229
|
-
['ArrowDown', 2],
|
|
230
|
-
['ArrowLeft', 3],
|
|
231
|
-
['ArrowRight', 4]
|
|
232
|
-
])('finds the nearest pixel for %s', (direction, expectedIndex) => {
|
|
233
|
-
expect(spatialNavigate(start, pixels, direction)).toBe(expectedIndex)
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
test('considers all pixels for an unrecognised direction', () => {
|
|
237
|
-
expect(spatialNavigate(start, pixels, 'Tab')).toBe(1)
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
test('falls back to the start point when no pixel is in the quadrant', () => {
|
|
241
|
-
expect(spatialNavigate(start, [[0, 0]], 'ArrowUp')).toBe(0)
|
|
242
|
-
})
|
|
243
|
-
})
|