@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
|
@@ -2,31 +2,20 @@ import React, { useRef, useEffect, useMemo } from 'react'
|
|
|
2
2
|
import { useConfig } from '../../store/configContext'
|
|
3
3
|
import { useApp } from '../../store/appContext'
|
|
4
4
|
import { stringToKebab } from '../../../utils/stringToKebab.js'
|
|
5
|
+
import { getPanelElementId } from '../../../utils/getPanelElementId.js'
|
|
5
6
|
import { useModalPanelBehaviour } from '../../hooks/useModalPanelBehaviour.js'
|
|
6
7
|
import { useIsScrollable } from '../../hooks/useIsScrollable.js'
|
|
8
|
+
import { classifyPanel, getPanelRole } from '../../../utils/getPanelRole.js'
|
|
7
9
|
import { Icon } from '../Icon/Icon'
|
|
8
10
|
import { Tabs } from '../Tabs/Tabs.jsx'
|
|
9
11
|
|
|
10
12
|
const computePanelState = (bpConfig, triggeringElement, focus, focusOnOpen) => {
|
|
11
|
-
const
|
|
12
|
-
const isDialog = !isAside && bpConfig.dismissible
|
|
13
|
-
const isModal = bpConfig.modal === true
|
|
14
|
-
const isDismissible = bpConfig.dismissible !== false
|
|
13
|
+
const { isModal, isAside, isDismissible, isDialog } = classifyPanel(bpConfig)
|
|
15
14
|
const shouldFocus = isModal || focusOnOpen === true || (focusOnOpen !== false && focus !== false && (focus === true || Boolean(triggeringElement)))
|
|
16
15
|
const buttonContainerEl = bpConfig.slot.endsWith('button') ? triggeringElement?.parentNode : undefined
|
|
17
16
|
return { isAside, isDialog, isModal, isDismissible, shouldFocus, buttonContainerEl }
|
|
18
17
|
}
|
|
19
18
|
|
|
20
|
-
const getPanelRole = (isDialog, isDismissible) => {
|
|
21
|
-
if (isDialog) {
|
|
22
|
-
return 'dialog'
|
|
23
|
-
}
|
|
24
|
-
if (isDismissible) {
|
|
25
|
-
return 'complementary'
|
|
26
|
-
}
|
|
27
|
-
return 'region'
|
|
28
|
-
}
|
|
29
|
-
|
|
30
19
|
const buildPanelClassNames = (slot, showLabel) => [
|
|
31
20
|
'im-c-panel',
|
|
32
21
|
`im-c-panel--${slot}`,
|
|
@@ -38,15 +27,17 @@ const buildPanelBodyClassNames = (showLabel, isDismissible) => [
|
|
|
38
27
|
!showLabel && isDismissible && 'im-c-panel__body--offset'
|
|
39
28
|
].filter(Boolean).join(' ')
|
|
40
29
|
|
|
41
|
-
const buildPanelProps = ({ elementId, shouldFocus, isDialog, isDismissible, isModal, width, panelClass, slot }) => ({
|
|
30
|
+
const buildPanelProps = ({ elementId, shouldFocus, isDialog, isDismissible, isModal, width, panelClass, slot, isOpen }) => ({
|
|
42
31
|
id: elementId,
|
|
43
32
|
'aria-labelledby': `${elementId}-label`,
|
|
44
33
|
tabIndex: shouldFocus ? -1 : undefined, // nosonar
|
|
45
|
-
role: getPanelRole(isDialog, isDismissible),
|
|
34
|
+
role: getPanelRole({ isDialog, isDismissible }),
|
|
46
35
|
'aria-modal': isDialog && isModal ? 'true' : undefined,
|
|
47
36
|
style: width ? { width } : undefined,
|
|
48
37
|
className: panelClass,
|
|
49
|
-
'data-slot': slot
|
|
38
|
+
'data-slot': slot,
|
|
39
|
+
// Panel is mounted permanently (see mapPanels.js); hidden is what actually opens/closes it.
|
|
40
|
+
hidden: !isOpen
|
|
50
41
|
})
|
|
51
42
|
|
|
52
43
|
const buildBodyProps = ({ bodyRef, panelBodyClass, isBodyScrollable, elementId }) => ({
|
|
@@ -112,7 +103,7 @@ export const Panel = ({ panelId, panelConfig, props, focusOnOpen, WrappedChild,
|
|
|
112
103
|
|
|
113
104
|
const rootEl = document.getElementById(`${id}-im-app`)
|
|
114
105
|
const bpConfig = panelConfig[breakpoint]
|
|
115
|
-
const elementId =
|
|
106
|
+
const elementId = getPanelElementId(id, panelId)
|
|
116
107
|
|
|
117
108
|
const { isAside, isDialog, isModal, isDismissible, shouldFocus, buttonContainerEl } = computePanelState(bpConfig, props?.triggeringElement, panelConfig.focus, focusOnOpen) // nosonar
|
|
118
109
|
|
|
@@ -129,7 +120,15 @@ export const Panel = ({ panelId, panelConfig, props, focusOnOpen, WrappedChild,
|
|
|
129
120
|
const panelRef = rootRef || internalPanelRef
|
|
130
121
|
|
|
131
122
|
const handleClose = () => {
|
|
132
|
-
requestAnimationFrame(() => {
|
|
123
|
+
requestAnimationFrame(() => {
|
|
124
|
+
// Re-checked here, not at handleClose-call time — the triggering element (a button, a
|
|
125
|
+
// Features-list item, ...) may have been removed from the DOM while the panel was open
|
|
126
|
+
// (e.g. the map panned and the feature it came from dropped out of the current list).
|
|
127
|
+
// document.contains() on a detached node is false, not a throw, so this is a plain check.
|
|
128
|
+
const triggeringElement = props?.triggeringElement
|
|
129
|
+
const focusTarget = triggeringElement && document.contains(triggeringElement) ? triggeringElement : layoutRefs.viewportRef.current
|
|
130
|
+
focusTarget?.focus?.({ preventScroll: interfaceType !== 'keyboard' })
|
|
131
|
+
})
|
|
133
132
|
dispatch({ type: 'CLOSE_PANEL', payload: panelId })
|
|
134
133
|
}
|
|
135
134
|
|
|
@@ -149,7 +148,7 @@ export const Panel = ({ panelId, panelConfig, props, focusOnOpen, WrappedChild,
|
|
|
149
148
|
const panelBodyClass = buildPanelBodyClassNames(bpConfig.showLabel ?? true, isDismissible)
|
|
150
149
|
const innerHtmlProp = useMemo(() => html ? { __html: html } : null, [html])
|
|
151
150
|
|
|
152
|
-
const panelProps = buildPanelProps({ elementId, shouldFocus, isDialog, isDismissible, isModal, width: bpConfig.width, panelClass, slot: bpConfig.slot })
|
|
151
|
+
const panelProps = buildPanelProps({ elementId, shouldFocus, isDialog, isDismissible, isModal, width: bpConfig.width, panelClass, slot: bpConfig.slot, isOpen })
|
|
153
152
|
const bodyProps = buildBodyProps({ bodyRef, panelBodyClass, isBodyScrollable, elementId })
|
|
154
153
|
// DOM anchor for controls DOM-projected via the JS/consumer-HTML API — see HtmlElementHost.jsx.
|
|
155
154
|
// Only present on the items-capable body below: dangerouslySetInnerHTML owns the static-html
|
|
@@ -20,6 +20,11 @@
|
|
|
20
20
|
pointer-events: auto;
|
|
21
21
|
color: var(--foreground-color);
|
|
22
22
|
background-color: var(--background-color);
|
|
23
|
+
|
|
24
|
+
// Override needed: display: flex above beats the browser's default [hidden] rule otherwise.
|
|
25
|
+
&[hidden] {
|
|
26
|
+
display: none;
|
|
27
|
+
}
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
// 2. Elements
|
|
@@ -102,6 +102,20 @@ describe('Panel', () => {
|
|
|
102
102
|
expect(dialog).toHaveAttribute('aria-modal', 'true')
|
|
103
103
|
expect(dialog).toHaveAttribute('tabIndex', '-1')
|
|
104
104
|
})
|
|
105
|
+
|
|
106
|
+
it('renders dialog role and aria-modal for a modal panel even when dismissible is left unset (WCAG requires a modal to be dismissible)', () => {
|
|
107
|
+
renderPanel({ desktop: { slot: 'overlay', modal: true } })
|
|
108
|
+
const dialog = screen.getByRole('dialog')
|
|
109
|
+
expect(dialog).toHaveAttribute('aria-modal', 'true')
|
|
110
|
+
expect(screen.getByRole('button', { name: 'Close Settings' })).toBeInTheDocument()
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('renders dialog role and aria-modal for a modal panel even when dismissible: false is set (a non-dismissible modal would trap keyboard/AT users)', () => {
|
|
114
|
+
renderPanel({ desktop: { slot: 'overlay', modal: true, dismissible: false } })
|
|
115
|
+
const dialog = screen.getByRole('dialog')
|
|
116
|
+
expect(dialog).toHaveAttribute('aria-modal', 'true')
|
|
117
|
+
expect(screen.getByRole('button', { name: 'Close Settings' })).toBeInTheDocument()
|
|
118
|
+
})
|
|
105
119
|
})
|
|
106
120
|
|
|
107
121
|
describe('focus behaviour', () => {
|
|
@@ -142,9 +156,18 @@ describe('Panel', () => {
|
|
|
142
156
|
})
|
|
143
157
|
|
|
144
158
|
describe('close functionality', () => {
|
|
159
|
+
// document.contains() needs a real Node — a plain { focus: jest.fn() } mock object throws
|
|
160
|
+
// ("parameter 1 is not of type 'Node'"), so triggeringElement here is a real element, spied
|
|
161
|
+
// on and attached to the document, matching what production always passes
|
|
162
|
+
// (e.currentTarget / document.activeElement).
|
|
163
|
+
const makeAttachedTriggeringElement = () => {
|
|
164
|
+
const el = document.createElement('button')
|
|
165
|
+
document.body.appendChild(el)
|
|
166
|
+
return { el, focusMock: jest.spyOn(el, 'focus') }
|
|
167
|
+
}
|
|
168
|
+
|
|
145
169
|
it('focuses triggeringElement on close for button slots', () => {
|
|
146
|
-
const focusMock =
|
|
147
|
-
const triggeringElement = { focus: focusMock, parentNode: document.createElement('div') }
|
|
170
|
+
const { el: triggeringElement, focusMock } = makeAttachedTriggeringElement()
|
|
148
171
|
|
|
149
172
|
renderPanel(
|
|
150
173
|
{ desktop: { slot: 'top-button', dismissible: true, open: false } },
|
|
@@ -157,8 +180,7 @@ describe('Panel', () => {
|
|
|
157
180
|
})
|
|
158
181
|
|
|
159
182
|
it('handles close for non-button slots', () => {
|
|
160
|
-
const focusMock =
|
|
161
|
-
const triggeringElement = { focus: focusMock, parentNode: document.createElement('div') }
|
|
183
|
+
const { el: triggeringElement, focusMock } = makeAttachedTriggeringElement()
|
|
162
184
|
|
|
163
185
|
renderPanel(
|
|
164
186
|
{ desktop: { slot: 'overlay', dismissible: true, modal: true } },
|
|
@@ -176,6 +198,20 @@ describe('Panel', () => {
|
|
|
176
198
|
expect(layoutRefs.viewportRef.current.focus).toHaveBeenCalled()
|
|
177
199
|
expect(dispatch).toHaveBeenCalledWith({ type: 'CLOSE_PANEL', payload: 'Settings' })
|
|
178
200
|
})
|
|
201
|
+
|
|
202
|
+
it('falls back to viewportRef focus when triggeringElement has been removed from the DOM', () => {
|
|
203
|
+
const triggeringElement = document.createElement('button') // never attached
|
|
204
|
+
const focusMock = jest.spyOn(triggeringElement, 'focus')
|
|
205
|
+
|
|
206
|
+
renderPanel(
|
|
207
|
+
{ desktop: { slot: 'side', dismissible: true, open: false } },
|
|
208
|
+
{ props: { triggeringElement } }
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
fireEvent.click(screen.getByRole('button', { name: 'Close Settings' }))
|
|
212
|
+
expect(focusMock).not.toHaveBeenCalled()
|
|
213
|
+
expect(layoutRefs.viewportRef.current.focus).toHaveBeenCalled()
|
|
214
|
+
})
|
|
179
215
|
})
|
|
180
216
|
|
|
181
217
|
describe('content rendering', () => {
|
|
@@ -308,4 +344,33 @@ describe('Panel', () => {
|
|
|
308
344
|
})
|
|
309
345
|
})
|
|
310
346
|
})
|
|
347
|
+
|
|
348
|
+
describe('isOpen (always-mounted, hidden-toggle)', () => {
|
|
349
|
+
it('renders hidden (not unmounted) when isOpen is false, so its id stays a real element', () => {
|
|
350
|
+
renderPanel({}, { isOpen: false })
|
|
351
|
+
const panel = document.getElementById('app-panel-settings')
|
|
352
|
+
expect(panel).toBeInTheDocument()
|
|
353
|
+
expect(panel).toHaveAttribute('hidden')
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
it('does not focus a closed panel, even one that would otherwise auto-focus (modal)', () => {
|
|
357
|
+
renderPanel({ desktop: { slot: 'overlay', dismissible: true, modal: true } }, { isOpen: false })
|
|
358
|
+
const panel = document.getElementById('app-panel-settings')
|
|
359
|
+
expect(document.activeElement).not.toBe(panel)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it('keeps the same DOM node (no swap/remount) across an isOpen transition, and un-hides it once open', () => {
|
|
363
|
+
const panelConfig = { desktop: { slot: 'side', open: true, dismissible: false, modal: false, showLabel: true } }
|
|
364
|
+
const { rerender } = render(<Panel panelId='Settings' panelConfig={panelConfig} label='Settings' isOpen={false} />)
|
|
365
|
+
const panelWhileClosed = document.getElementById('app-panel-settings')
|
|
366
|
+
expect(panelWhileClosed).toHaveAttribute('hidden')
|
|
367
|
+
|
|
368
|
+
rerender(<Panel panelId='Settings' panelConfig={panelConfig} label='Settings' isOpen items={[{ id: 'a', element: <p>Item</p> }]} />)
|
|
369
|
+
|
|
370
|
+
const panelWhileOpen = document.getElementById('app-panel-settings')
|
|
371
|
+
expect(panelWhileOpen).toBe(panelWhileClosed)
|
|
372
|
+
expect(panelWhileOpen).not.toHaveAttribute('hidden')
|
|
373
|
+
expect(screen.getByText('Item')).toBeInTheDocument()
|
|
374
|
+
})
|
|
375
|
+
})
|
|
311
376
|
})
|
|
@@ -21,7 +21,8 @@ const MenuItem = ({ item, isSelected, hiddenButtons, disabledButtons, pressedBut
|
|
|
21
21
|
</li>
|
|
22
22
|
)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// isOpen defaults to true so direct/standalone usage doesn't need to pass it.
|
|
25
|
+
export const PopupMenu = ({ popupMenuId, buttonId, instigatorId, pluginId, startPos, startIndex, menuRef, items, setIsOpen, buttonRect, isOpen = true }) => {
|
|
25
26
|
const { id } = useConfig()
|
|
26
27
|
const { buttonRefs, buttonConfig, hiddenButtons, disabledButtons, pressedButtons, layoutRefs } = useApp()
|
|
27
28
|
const instigatorKey = buttonId ?? instigatorId
|
|
@@ -43,7 +44,8 @@ export const PopupMenu = ({ popupMenuId, buttonId, instigatorId, pluginId, start
|
|
|
43
44
|
id,
|
|
44
45
|
menuRef,
|
|
45
46
|
setIsOpen,
|
|
46
|
-
buttonRect
|
|
47
|
+
buttonRect,
|
|
48
|
+
isOpen
|
|
47
49
|
})
|
|
48
50
|
|
|
49
51
|
return createPortal(
|
|
@@ -56,11 +58,13 @@ export const PopupMenu = ({ popupMenuId, buttonId, instigatorId, pluginId, start
|
|
|
56
58
|
style={menuStyle}
|
|
57
59
|
role='menu' // NOSONAR
|
|
58
60
|
tabIndex='-1'
|
|
61
|
+
hidden={!isOpen}
|
|
59
62
|
aria-labelledby={instigatorKey}
|
|
60
63
|
aria-activedescendant={index >= 0 ? `${id}-${stringToKebab(items[index].id)}` : undefined}
|
|
61
64
|
onKeyDown={handleMenuKeyDown}
|
|
62
65
|
>
|
|
63
|
-
{
|
|
66
|
+
{/* Items only mount once open — the <ul> above stays in the DOM either way. */}
|
|
67
|
+
{isOpen && items.map((item, i) => (
|
|
64
68
|
<MenuItem
|
|
65
69
|
key={item.id}
|
|
66
70
|
item={item}
|
|
@@ -651,4 +651,75 @@ describe('PopupMenu', () => {
|
|
|
651
651
|
window.dispatchEvent(new Event('resize'))
|
|
652
652
|
expect(mockSetIsOpen).toHaveBeenCalledWith(false)
|
|
653
653
|
})
|
|
654
|
+
|
|
655
|
+
describe('isOpen (always-mounted, hidden-toggle)', () => {
|
|
656
|
+
it('renders a hidden, empty menu when isOpen is false, without grabbing focus or item content', () => {
|
|
657
|
+
renderMenu({ isOpen: false })
|
|
658
|
+
const ul = screen.getByRole('menu', { hidden: true })
|
|
659
|
+
expect(ul).toHaveAttribute('hidden')
|
|
660
|
+
expect(screen.queryByText('Item 1')).not.toBeInTheDocument()
|
|
661
|
+
expect(document.activeElement).not.toBe(ul)
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it('keeps the same ul node across an isOpen transition (no swap/remount) and un-hides it once open', () => {
|
|
665
|
+
const { rerender } = renderMenu({ isOpen: false })
|
|
666
|
+
const ulWhileClosed = screen.getByRole('menu', { hidden: true })
|
|
667
|
+
|
|
668
|
+
rerender(
|
|
669
|
+
<PopupMenu
|
|
670
|
+
id='menu'
|
|
671
|
+
pluginId='plugin1'
|
|
672
|
+
instigatorId='instigator'
|
|
673
|
+
startPos='first'
|
|
674
|
+
menuRef={menuRef}
|
|
675
|
+
items={items}
|
|
676
|
+
setIsOpen={mockSetIsOpen}
|
|
677
|
+
isOpen
|
|
678
|
+
/>
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
const ulWhileOpen = screen.getByRole('menu')
|
|
682
|
+
expect(ulWhileOpen).toBe(ulWhileClosed)
|
|
683
|
+
expect(ulWhileOpen).not.toHaveAttribute('hidden')
|
|
684
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument()
|
|
685
|
+
expect(document.activeElement).toBe(ulWhileOpen)
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('resets selection on every open rather than carrying over a stale index from a previous open', () => {
|
|
689
|
+
const { rerender } = renderMenu({ startPos: 'first', startIndex: undefined })
|
|
690
|
+
expectSelected('Item 1')
|
|
691
|
+
|
|
692
|
+
// Close, then reopen via a plain click — selection should reset, not carry over.
|
|
693
|
+
rerender(
|
|
694
|
+
<PopupMenu
|
|
695
|
+
id='menu'
|
|
696
|
+
pluginId='plugin1'
|
|
697
|
+
instigatorId='instigator'
|
|
698
|
+
menuRef={menuRef}
|
|
699
|
+
items={items}
|
|
700
|
+
setIsOpen={mockSetIsOpen}
|
|
701
|
+
isOpen={false}
|
|
702
|
+
/>
|
|
703
|
+
)
|
|
704
|
+
rerender(
|
|
705
|
+
<PopupMenu
|
|
706
|
+
id='menu'
|
|
707
|
+
pluginId='plugin1'
|
|
708
|
+
instigatorId='instigator'
|
|
709
|
+
menuRef={menuRef}
|
|
710
|
+
items={items}
|
|
711
|
+
setIsOpen={mockSetIsOpen}
|
|
712
|
+
isOpen
|
|
713
|
+
/>
|
|
714
|
+
)
|
|
715
|
+
expectNotSelected('Item 1')
|
|
716
|
+
expectNotSelected('Item 2')
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
it('defaults isOpen to true for direct/standalone usage', () => {
|
|
720
|
+
renderMenu()
|
|
721
|
+
expect(screen.getByRole('menu')).not.toHaveAttribute('hidden')
|
|
722
|
+
expect(screen.getByText('Item 1')).toBeInTheDocument()
|
|
723
|
+
})
|
|
724
|
+
})
|
|
654
725
|
})
|
|
@@ -275,12 +275,14 @@ const createMenuKeyDownHandler = ({ items, visibleIndices, index, setIndex, disa
|
|
|
275
275
|
* @param {object} params.menuRef - Ref to the menu UL element.
|
|
276
276
|
* @param {Function} params.setIsOpen - Callback to open/close the menu.
|
|
277
277
|
* @param {DOMRect} params.buttonRect - Bounding rect of the trigger button for positioning.
|
|
278
|
+
* @param {boolean} [params.isOpen] - Whether the menu is currently open (PopupMenu stays
|
|
279
|
+
* mounted; this just toggles visibility, so setup/teardown here is keyed on isOpen, not mount).
|
|
278
280
|
* @returns {{ index: number, handleMenuKeyDown: Function, handleItemClick: Function,
|
|
279
281
|
* menuStyle: object, menuDirection: string, menuHAlign: string }}
|
|
280
282
|
*/
|
|
281
283
|
export const usePopupMenu = ({
|
|
282
284
|
items, hiddenButtons, startIndex, startPos, instigator, instigatorKey,
|
|
283
|
-
buttonRefs, buttonConfig, disabledButtons, pluginId, evaluateProp, id, menuRef, setIsOpen, buttonRect
|
|
285
|
+
buttonRefs, buttonConfig, disabledButtons, pluginId, evaluateProp, id, menuRef, setIsOpen, buttonRect, isOpen
|
|
284
286
|
}) => {
|
|
285
287
|
const { dispatch, layoutRefs } = useApp()
|
|
286
288
|
const viewportRef = layoutRefs.viewportRef
|
|
@@ -329,15 +331,14 @@ export const usePopupMenu = ({
|
|
|
329
331
|
}
|
|
330
332
|
}
|
|
331
333
|
|
|
334
|
+
// Keyed on isOpen (not mount) since PopupMenu stays mounted; resolveInitialIndex re-runs each
|
|
335
|
+
// open so selection resets instead of carrying over from a previous open.
|
|
332
336
|
useEffect(() => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
setIndex(visibleIndices[0] ?? -1)
|
|
336
|
-
} else if (startPos === 'last') {
|
|
337
|
-
setIndex(visibleIndices[visibleIndices.length - 1] ?? -1) // NOSONAR .length - 1 used instead of .at(-1) for wider browser support
|
|
338
|
-
} else {
|
|
339
|
-
// No action
|
|
337
|
+
if (!isOpen) {
|
|
338
|
+
return undefined
|
|
340
339
|
}
|
|
340
|
+
menuRef.current?.focus()
|
|
341
|
+
setIndex(resolveInitialIndex(startIndex, startPos, visibleIndices))
|
|
341
342
|
const handleResize = () => setIsOpen(false)
|
|
342
343
|
document.addEventListener('focusin', handleOutside)
|
|
343
344
|
document.addEventListener('pointerdown', handleOutside)
|
|
@@ -347,7 +348,7 @@ export const usePopupMenu = ({
|
|
|
347
348
|
document.removeEventListener('pointerdown', handleOutside)
|
|
348
349
|
window.removeEventListener('resize', handleResize)
|
|
349
350
|
}
|
|
350
|
-
}, [])
|
|
351
|
+
}, [isOpen])
|
|
351
352
|
|
|
352
353
|
const { style: menuStyle, direction: menuDirection, halign: menuHAlign } = getMenuStyle(buttonRect)
|
|
353
354
|
return { index, handleMenuKeyDown, handleItemClick, menuStyle, menuDirection, menuHAlign }
|
|
@@ -11,7 +11,7 @@ export const MapStatus = () => {
|
|
|
11
11
|
<div // NOSONAR: div + status role is semantically correct here, <output> implies a calculation result
|
|
12
12
|
ref={mapStatusRef}
|
|
13
13
|
role='status'
|
|
14
|
-
className='im-
|
|
14
|
+
className='im-u-visually-hidden'
|
|
15
15
|
/>
|
|
16
16
|
)
|
|
17
17
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { forwardRef } from 'react'
|
|
2
|
+
import { useConfig } from '../../store/configContext.js'
|
|
3
|
+
|
|
4
|
+
// Accessible spatial list: a listbox whose items are positioned to match wherever they are on
|
|
5
|
+
// the map, navigable both sequentially (Arrow/Home/End) and spatially (Alt+Arrow — see
|
|
6
|
+
// useSpatialListFocus.js). Content is whatever the current spatialListRegistry provider
|
|
7
|
+
// contributes; `label` names it for aria-label accordingly.
|
|
8
|
+
export const SpatialList = forwardRef(({ activeItemId, tabbableId, selectedIds = [], multiselectable = false, items = [], label = 'Map features', focusable = true, onFocus, onBlur, onSelectItem }, ref) => {
|
|
9
|
+
const { id } = useConfig()
|
|
10
|
+
const hasItems = items.length > 0
|
|
11
|
+
// Roving tabindex: exactly one option is a Tab stop at a time — see useSpatialListFocus.js.
|
|
12
|
+
const currentId = activeItemId ?? tabbableId
|
|
13
|
+
return (
|
|
14
|
+
<ul // NOSONAR: role='listbox' is correct for custom composite widget; native <select> cannot host SVG marker elements
|
|
15
|
+
id={`${id}-spatial-list`}
|
|
16
|
+
ref={ref}
|
|
17
|
+
role='listbox' // NOSONAR
|
|
18
|
+
aria-hidden={hasItems ? undefined : true}
|
|
19
|
+
aria-label={label}
|
|
20
|
+
aria-describedby={`${id}-keyboard-desc`}
|
|
21
|
+
aria-multiselectable={multiselectable || undefined}
|
|
22
|
+
className='im-c-spatial-list'
|
|
23
|
+
onFocus={onFocus}
|
|
24
|
+
onBlur={onBlur}
|
|
25
|
+
>
|
|
26
|
+
{items.map(item => (
|
|
27
|
+
<li // NOSONAR: role='option' overrides implicit listitem; this is the correct ARIA listbox child pattern.
|
|
28
|
+
// tabIndex/onClick let voice control and screen readers reach and activate each option
|
|
29
|
+
// directly. focusable: false (draw's vertex/midpoint items) keeps every item out of
|
|
30
|
+
// the Tab order — that population already has a full keyboard path via the map itself.
|
|
31
|
+
key={item.id} id={`${id}-spatial-list-item-${item.id}`} role='option' // NOSONAR
|
|
32
|
+
data-id={item.id}
|
|
33
|
+
tabIndex={focusable && item.id === currentId ? 0 : -1}
|
|
34
|
+
aria-selected={selectedIds.includes(item.id)}
|
|
35
|
+
// Positions the (visually clipped) option over its on-map feature, so AT overlays like
|
|
36
|
+
// Voice Control's "Show Numbers" number it at the right screen position.
|
|
37
|
+
style={item.x != null ? { left: item.x, top: item.y } : undefined}
|
|
38
|
+
onClick={() => onSelectItem?.(item.id)}
|
|
39
|
+
>
|
|
40
|
+
{item.label}
|
|
41
|
+
</li>
|
|
42
|
+
))}
|
|
43
|
+
</ul>
|
|
44
|
+
)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
SpatialList.displayName = 'SpatialList'
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
@use '../../../scss/tools/index' as tools;
|
|
2
2
|
|
|
3
3
|
// ===================================================
|
|
4
|
-
// Component:
|
|
4
|
+
// Component: SpatialList
|
|
5
5
|
// ===================================================
|
|
6
6
|
|
|
7
|
-
.im-c-
|
|
7
|
+
.im-c-spatial-list {
|
|
8
8
|
position: absolute;
|
|
9
9
|
inset: 0;
|
|
10
10
|
pointer-events: none;
|
|
@@ -16,10 +16,6 @@
|
|
|
16
16
|
$is-inset: true
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
-
&[data-focus-visible="true"][aria-activedescendant]::after {
|
|
20
|
-
opacity: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
19
|
[role="option"] {
|
|
24
20
|
position: absolute;
|
|
25
21
|
width: 1px;
|
|
@@ -27,5 +23,9 @@
|
|
|
27
23
|
overflow: hidden;
|
|
28
24
|
clip-path: inset(50%);
|
|
29
25
|
white-space: nowrap;
|
|
26
|
+
// Roving tabindex moves real focus (and click activation) onto individual options, so they
|
|
27
|
+
// need pointer events re-enabled — overridden off .im-c-spatial-list's pointer-events: none,
|
|
28
|
+
// which otherwise lets mouse interaction pass through to the map everywhere else.
|
|
29
|
+
pointer-events: auto;
|
|
30
30
|
}
|
|
31
31
|
}
|