Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/search/src/datasets.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function createDatasets({ customDatasets = [], osNamesURL, crs }) {
urlTemplate: osNamesURL,
parseResults: (json, query) => parseOsNamesResults(json, query, crs),
includeRegex: /^[a-zA-Z0-9\s,-]+$/,
excludeRegex: /^(?:[A-Za-z]{2}\s*(?:\d{3}\s*\d{3}|\d{4}\s*\d{4}|\d{5}\s*\d{5})|\d+\s*,?\s*\d+)$/i // exclude gridrefs/numeric coords
excludeRegex: /^(?:[a-z]{2}\s*(?:\d{3}\s*\d{3}|\d{4}\s*\d{4}|\d{5}\s*\d{5})|\d+\s*,?\s*\d+)$/i // NOSONAR - complexity unavoidable for gridref/coordinate matching
}]

return [...defaultDatasets, ...customDatasets]
Expand Down
2 changes: 1 addition & 1 deletion providers/beta/esri/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const arrayFindLast = {
const webGL = getWebGL(['webgl2', 'webgl1'])

// ESRI provider descriptor
export default function (config = {}) {
export default function createEsriProvider (config = {}) {
return {
checkDeviceCapabilities: () => {
return {
Expand Down
2 changes: 1 addition & 1 deletion providers/maplibre/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function supportsModernMaplibre() {
* @param {Partial<MapProviderConfig>} [config={}] - Optional provider configuration overrides.
* @returns {MapProviderDescriptor} The map provider descriptor.
*/
export default function (config = {}) {
export default function createMapLibreProvider (config = {}) {
return {
checkDeviceCapabilities: () => {
const webGL = getWebGL(['webgl2', 'webgl1'])
Expand Down
3 changes: 1 addition & 2 deletions providers/maplibre/src/utils/detectWebgl.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export const getWebGL = (names) => {
isEnabled: true
}
}
} catch (e) {
// No action required
} catch (_) { // NOSONAR - getContext may throw; failure is handled by the loop fallthrough
}
}
// WebGL is supported, but disabled
Expand Down
31 changes: 14 additions & 17 deletions providers/maplibre/src/utils/spatial.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,26 +123,23 @@ const getCardinalMove = (from, to) => {
* @param {Array<[number, number]>} pixels - Array of pixel coordinates.
* @returns {number} Index of the closest pixel in the given direction.
*/
const isInDirection = (direction, dx, dy) => {
switch (direction) {
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
default: return false
}
}

const spatialNavigate = (direction, start, pixels) => {
const [sx, sy] = start

// Direction filters
const candidates = pixels.filter(([x, y]) => {
if (x === sx && y === sy) {
return false
}

const dx = x - sx
const dy = y - sy

switch (direction) {
case 'ArrowUp': return dy < 0 && Math.abs(dy) >= Math.abs(dx)
case 'ArrowDown': return dy > 0 && Math.abs(dy) >= Math.abs(dx)
case 'ArrowLeft': return dx < 0 && Math.abs(dx) > Math.abs(dy)
case 'ArrowRight': return dx > 0 && Math.abs(dx) > Math.abs(dy)
default: return false
}
})
const candidates = pixels.filter(([x, y]) =>
(x !== sx || y !== sy) && isInDirection(direction, x - sx, y - sy)
)

if (!candidates.length) {
return pixels.findIndex(p => p[0] === sx && p[1] === sy)
Expand All @@ -169,7 +166,7 @@ const getResolution = (center, zoom) => {
const TILE_SIZE = 512
const lat = center.lat
const scale = Math.pow(2, zoom)
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE)
const resolution = (EARTH_CIRCUMFERENCE * Math.cos((lat * Math.PI) / 180)) / (scale * TILE_SIZE) // NOSONAR - 180 is degrees-to-radians conversion
return resolution
}

Expand Down
2 changes: 1 addition & 1 deletion src/App/components/Markers/Markers.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Markers = () => {
const { markers, markerRef } = useMarkers()

if (!mapStyle) {
return
return undefined
}

const defaultSvgPaths = markerSvgPaths.find(m => m.shape === markerShape)
Expand Down
2 changes: 1 addition & 1 deletion src/App/components/Viewport/MapController.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const MapController = ({ mapContainerRef }) => {
// Update padding when breakpoint or mapSize change
useEffect(() => {
if (!isMapReady || !syncMapPadding) {
return undefined
return
}
mapProvider.setPadding(scalePoints(safeZoneInset, scaleFactor[mapSize]))
}, [isMapReady, mapSize, breakpoint, safeZoneInset])
Expand Down
5 changes: 3 additions & 2 deletions src/App/controls/keyboardActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ export const createKeyboardActions = (mapProvider, announce, {

zoomOut: (e) => mapProvider.zoomOut(getZoom(e.shiftKey)),

getInfo: async (e) => {
getInfo: async (_e) => {
const coord = mapProvider.getCenter()
const place = await reverseGeocode(mapProvider.getZoom(), coord)
const area = mapProvider.getAreaDimensions?.()
announce(`${place}.${area ? ' Covering ' + area + '.' : ''}`, 'core')
const message = area ? `${place}. Covering ${area}.` : `${place}.`
announce(message, 'core')
},

highlightNextLabel: (e) => {
Expand Down
96 changes: 49 additions & 47 deletions src/App/hooks/useCrossHairAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,53 @@ import { useService } from '../store/serviceContext.js'
import { scaleFactor } from '../../config/appConfig.js'
import { EVENTS as events } from '../../config/events.js'

const assignCrossHairAPI = (crossHair, el, mapProvider, dispatch, updatePosition) => {
crossHair.pinToMap = (coords, state) => {
const { x, y } = mapProvider.mapToScreen(coords)
crossHair.coords = coords
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: true, isVisible: true, coords, state } })
updatePosition(el, x, y)
}

crossHair.fixAtCenter = () => {
el.style.left = '50%'
el.style.top = '50%'
el.style.transform = 'translate(0,0)'
el.style.display = 'block'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: false, isVisible: true } })
}

crossHair.remove = () => {
el.style.display = 'none'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: false, isVisible: false } })
}

crossHair.show = () => {
el.style.display = 'block'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isVisible: true } })
}

crossHair.hide = () => {
el.style.display = 'none'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isVisible: false } })
}

crossHair.setStyle = (state) => {
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { state } })
}

crossHair.getDetail = () => {
const coords = crossHair.isPinnedToMap ? crossHair.coords : mapProvider.getCenter()

return {
state: crossHair.state,
point: mapProvider.mapToScreen(coords),
zoom: mapProvider.getZoom(),
coords
}
}
}

export const useCrossHair = () => {
const { mapProvider } = useConfig()
const { safeZoneInset } = useApp()
Expand All @@ -25,55 +72,10 @@ export const useCrossHair = () => {

const crossHairRef = useCallback(el => {
if (!el) {
return
}

// --- API ---

crossHair.pinToMap = (coords, state) => {
const { x, y } = mapProvider.mapToScreen(coords)
crossHair.coords = coords
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: true, isVisible: true, coords, state } })
updatePosition(el, x, y)
}

crossHair.fixAtCenter = () => {
el.style.left = '50%'
el.style.top = '50%'
el.style.transform = 'translate(0,0)'
el.style.display = 'block'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: false, isVisible: true } })
}

crossHair.remove = () => {
el.style.display = 'none'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isPinnedToMap: false, isVisible: false } })
return undefined
}

crossHair.show = () => {
el.style.display = 'block'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isVisible: true } })
}

crossHair.hide = () => {
el.style.display = 'none'
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { isVisible: false } })
}

crossHair.setStyle = (state) => {
dispatch({ type: 'UPDATE_CROSS_HAIR', payload: { state } })
}

crossHair.getDetail = () => {
const coords = crossHair.isPinnedToMap ? crossHair.coords : mapProvider.getCenter()

return {
state: crossHair.state,
point: mapProvider.mapToScreen(coords),
zoom: mapProvider.getZoom(),
coords
}
}
assignCrossHairAPI(crossHair, el, mapProvider, dispatch, updatePosition)

const handleRender = () => {
if (crossHair.coords && crossHair.isPinnedToMap) {
Expand Down
4 changes: 2 additions & 2 deletions src/App/hooks/useFocusVisible.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function useFocusVisible () {
useEffect(() => {
const scope = layoutRefs.appContainerRef.current
if (!scope) {
return
return undefined
}

function handleFocusIn (e) {
Expand All @@ -22,7 +22,7 @@ export function useFocusVisible () {
delete e.target.dataset.focusVisible
}

function handlePointerdown (e) {
function handlePointerdown () {
delete document.activeElement.dataset.focusVisible
}

Expand Down
2 changes: 1 addition & 1 deletion src/App/hooks/useKeyboardHint.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function useKeyboardHint ({

useEffect(() => {
if (!showHint || !containerRef.current) {
return
return undefined
}

const containerEl = containerRef.current
Expand Down
66 changes: 34 additions & 32 deletions src/App/hooks/useMapAnnouncements.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@ import { useService } from '../store/serviceContext.js'
import { getMapStatusMessage } from '../../utils/getMapStatusMessage.js'
import { EVENTS as events } from '../../config/events.js'

const resolveMessage = (previous, current, mapProvider) => {
const zoomChanged = previous.zoom !== current.zoom
const centerChanged =
previous.center[0] !== current.center[0] ||
previous.center[1] !== current.center[1]

const areaDimensions = mapProvider.getAreaDimensions()

// Panned only
if (centerChanged && !zoomChanged) {
const direction = mapProvider.getCardinalMove(previous.center, current.center)
return getMapStatusMessage.moved({ direction, areaDimensions })
}

// Zoomed only
if (!centerChanged && zoomChanged) {
return getMapStatusMessage.zoomed({
...current,
from: previous.zoom,
to: current.zoom,
areaDimensions
})
}

// No change
if (!centerChanged && !zoomChanged) {
return getMapStatusMessage.noChange({ ...current })
}

// Panned and zoomed
return getMapStatusMessage.newArea({ ...current, areaDimensions })
}

export function useMapAnnouncements () {
const { mapProvider } = useConfig()
const { eventBus, announce } = useService()
Expand All @@ -15,38 +48,7 @@ export function useMapAnnouncements () {
return
}

const zoomChanged = previous.zoom !== current.zoom
const centerChanged =
previous.center[0] !== current.center[0] ||
previous.center[1] !== current.center[1]

const areaDimensions = mapProvider.getAreaDimensions()
let message

if (centerChanged && !zoomChanged) {
const direction = mapProvider.getCardinalMove(previous.center, current.center)
message = getMapStatusMessage.moved({
direction,
areaDimensions
})
} else if (!centerChanged && zoomChanged) {
message = getMapStatusMessage.zoomed({
...current,
from: previous.zoom,
to: current.zoom,
areaDimensions
})
} else if (!centerChanged && !zoomChanged) {
message = getMapStatusMessage.noChange({
...current
})
} else {
message = getMapStatusMessage.newArea({
...current,
areaDimensions
})
}

const message = resolveMessage(previous, current, mapProvider)
if (message) {
announce(message, 'core')
}
Expand Down
13 changes: 12 additions & 1 deletion src/App/hooks/useMapEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ import { useEffect } from 'react'
import { useConfig } from '../store/configContext.js'
import { useService } from '../store/serviceContext.js'

/**
* Subscribes to map events via the shared event bus for the lifetime of
* the consuming component. Handlers are automatically unsubscribed on
* unmount or when the eventMap reference changes.
*
* Supported events include clicks, pans, zooms, style changes and more
* — see `EVENTS` in `config/events.js` for the full list.
*
* @param {Object<string, Function>} [eventMap={}] - A map of event names to
* callback functions.
*/
export function useMapEvents (eventMap = {}) {
const { mapProvider } = useConfig()
const { eventBus } = useService()

useEffect(() => {
if (!mapProvider) {
return
return undefined
}

const handlers = {}
Expand Down
8 changes: 7 additions & 1 deletion src/App/hooks/useMapStateSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { useMap } from '../store/mapContext.js'
import { useService } from '../store/serviceContext.js'
import { EVENTS as events } from '../../config/events.js'

/**
* Keeps React state in sync with the map provider by listening to move,
* move-end and first-idle events. On each move-end it also emits a
* MAP_STATE_UPDATED event carrying both the previous and current state,
* which other hooks (e.g. useMapAnnouncements) use to determine what changed.
*/
export function useMapStateSync () {
const { mapProvider } = useConfig()
const { dispatch } = useMap()
Expand All @@ -14,7 +20,7 @@ export function useMapStateSync () {

useEffect(() => {
if (!mapProvider) {
return
return undefined
}

// Handle map move
Expand Down
Loading