Skip to content
Closed
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 demo/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const interactiveMap = new InteractiveMap('map', {
transformRequest: transformTileRequest,
enableZoomControls: true,
readMapText: true,
enableFullscreen: true,
// enableFullscreen: true,
// hasExitButton: true,
// markers: [{
// id: 'location',
Expand Down
18 changes: 16 additions & 2 deletions demo/js/planning.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ const interactiveMap = new InteractiveMap('map', {
mapStylesPlugin({
mapStyles: vtsMapStyles27700,
manifest: {
buttons: [{ id: 'mapStyles', desktop: { slot: 'right-top', showLabel: false }}],
panels: [{ id: 'mapStyles', desktop: { slot: 'map-styles-button', width: '400px', modal: false }}]
buttons: [{ id: 'mapStyles', desktop: { slot: 'right-top', showLabel: false, modal: true }}],
panels: [{ id: 'mapStyles', desktop: { slot: 'map-styles-button', width: '400px' }}]
}
}),
scaleBarPlugin({
Expand Down Expand Up @@ -134,6 +134,20 @@ interactiveMap.on('app:ready', function (e) {
tablet: { slot: 'inset', width: '260px', open: false, exclusive: true },
desktop: { slot: 'inset', width: '280px', open: false, exclusive: true }
})
interactiveMap.addPanel('test', {
label: 'test',
html: '<p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p>',
mobile: { slot: 'inset', open: true, modal: true },
tablet: { slot: 'left-top', open: true },
desktop: { slot: 'left-top', open: true }
})
interactiveMap.addPanel('test2', {
label: 'test2',
html: '<p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p><p>Test</p>',
mobile: { slot: 'inset', open: true, modal: true },
tablet: { slot: 'left-bottom', open: true },
desktop: { slot: 'left-bottom', open: true }
})
})

interactiveMap.on('map:ready', function (e) {
Expand Down
15 changes: 9 additions & 6 deletions plugins/search/src/events/fetchSuggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@

export const sanitiseQuery = (value) => value.replace(/[^a-zA-Z0-9\s\-.,]/g, '').trim()

const getRequestConfig = (ds, query, transformRequest) => {
const getRequestConfig = async (ds, query, transformRequest) => {
const defaultRequest = {
url: ds.urlTemplate?.replace('{query}', encodeURIComponent(query)),
options: { method: 'GET' }
}

if (typeof ds.buildRequest === 'function') {
if (ds.buildRequest) {
return ds.buildRequest(query, () => defaultRequest)
}

return typeof transformRequest === 'function'
? transformRequest(defaultRequest, query)
: defaultRequest
if (transformRequest) {
const transformedRequest = await transformRequest(defaultRequest, query)
return transformedRequest
}

return defaultRequest
}

/**
Expand Down Expand Up @@ -50,7 +53,7 @@ export const fetchSuggestions = async (value, datasets, dispatch, transformReque
let finalResults = []

for (const ds of activeDatasets) {
const request = getRequestConfig(ds, sanitisedValue, transformRequest)
const request = await getRequestConfig(ds, sanitisedValue, transformRequest)
const results = await fetchDatasetResults(ds, request, sanitisedValue)

// Check if we have results to add
Expand Down
5 changes: 5 additions & 0 deletions providers/beta/esri/src/esriProvider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,9 @@ body, html {
letter-spacing: inherit !important;
line-height: inherit !important;
}
}

// Hide the ESRI Attribution, manage this elsewhere
.esri-view-attribution {
display: none;
}
1 change: 1 addition & 0 deletions src/App/components/MapButton/MapButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export const MapButton = ({
return (
<div
className={buildWrapperClassNames(buttonId, showLabel)}
data-button-slot={panelId ? `${stringToKebab(buttonId)}-button` : undefined}
style={isHidden ? { display: 'none' } : undefined}
>
{showLabel ? buttonEl : <Tooltip content={label}>{buttonEl}</Tooltip>}
Expand Down
27 changes: 14 additions & 13 deletions src/App/components/Panel/Panel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ const computePanelState = (bpConfig, triggeringElement) => {
const isAside = bpConfig.slot === 'side' && bpConfig.open && !bpConfig.modal
const isDialog = !isAside && bpConfig.dismissible
const isModal = bpConfig.modal === true
const isDismissable = bpConfig.dismissible !== false
const isDismissible = bpConfig.dismissible !== false
const shouldFocus = Boolean(isModal || triggeringElement)
const buttonContainerEl = bpConfig.slot.endsWith('button') ? triggeringElement?.parentNode : undefined
return { isAside, isDialog, isModal, isDismissable, shouldFocus, buttonContainerEl }
return { isAside, isDialog, isModal, isDismissible, shouldFocus, buttonContainerEl }
}

const getPanelRole = (isDialog, isDismissable) => {
const getPanelRole = (isDialog, isDismissible) => {
if (isDialog) {
return 'dialog'
}
if (isDismissable) {
if (isDismissible) {
return 'complementary'
}
return 'region'
Expand All @@ -32,19 +32,20 @@ const buildPanelClassNames = (slot, showLabel) => [
!showLabel && 'im-c-panel--no-heading'
].filter(Boolean).join(' ')

const buildPanelBodyClassNames = (showLabel, isDismissable) => [
const buildPanelBodyClassNames = (showLabel, isDismissible) => [
'im-c-panel__body',
!showLabel && isDismissable && 'im-c-panel__body--offset'
!showLabel && isDismissible && 'im-c-panel__body--offset'
].filter(Boolean).join(' ')

const buildPanelProps = ({ elementId, shouldFocus, isDialog, isDismissable, isModal, width, panelClass }) => ({
const buildPanelProps = ({ elementId, shouldFocus, isDialog, isDismissible, isModal, width, panelClass, slot }) => ({
id: elementId,
'aria-labelledby': `${elementId}-label`,
tabIndex: shouldFocus ? -1 : undefined, // nosonar
role: getPanelRole(isDialog, isDismissable),
role: getPanelRole(isDialog, isDismissible),
'aria-modal': isDialog && isModal ? 'true' : undefined,
style: width ? { width } : undefined,
className: panelClass
className: panelClass,
'data-slot': slot
})

const buildBodyProps = ({ bodyRef, panelBodyClass, isBodyScrollable, elementId }) => ({
Expand All @@ -65,7 +66,7 @@ export const Panel = ({ panelId, panelConfig, props, WrappedChild, label, html,
const bpConfig = panelConfig[breakpoint]
const elementId = `${id}-panel-${stringToKebab(panelId)}`

const { isAside, isDialog, isModal, isDismissable, shouldFocus, buttonContainerEl } = computePanelState(bpConfig, props?.triggeringElement)
const { isAside, isDialog, isModal, isDismissible, shouldFocus, buttonContainerEl } = computePanelState(bpConfig, props?.triggeringElement)

// For persistent panels, gate modal behaviour on open state
const isModalActive = isModal && isOpen
Expand Down Expand Up @@ -97,10 +98,10 @@ export const Panel = ({ panelId, panelConfig, props, WrappedChild, label, html,
}, [isOpen])

const panelClass = buildPanelClassNames(bpConfig.slot, bpConfig.showLabel ?? true)
const panelBodyClass = buildPanelBodyClassNames(bpConfig.showLabel ?? true, isDismissable)
const panelBodyClass = buildPanelBodyClassNames(bpConfig.showLabel ?? true, isDismissible)
const innerHtmlProp = useMemo(() => html ? { __html: html } : null, [html])

const panelProps = buildPanelProps({ elementId, shouldFocus, isDialog, isDismissable, isModal, width: bpConfig.width, panelClass })
const panelProps = buildPanelProps({ elementId, shouldFocus, isDialog, isDismissible, isModal, width: bpConfig.width, panelClass, slot: bpConfig.slot })
const bodyProps = buildBodyProps({ bodyRef, panelBodyClass, isBodyScrollable, elementId })

return (
Expand All @@ -115,7 +116,7 @@ export const Panel = ({ panelId, panelConfig, props, WrappedChild, label, html,
{label}
</h2>

{isDismissable && (
{isDismissible && (
<button
aria-label={`Close ${label}`}
className='im-c-panel__close'
Expand Down
57 changes: 39 additions & 18 deletions src/App/hooks/useLayoutMeasurements.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export function useLayoutMeasurements () {
topRightColRef,
insetRef,
footerRef,
actionsRef
actionsRef,
leftTopRef,
leftBottomRef,
rightTopRef,
rightBottomRef
} = layoutRefs

// -----------------------------
Expand All @@ -38,31 +42,48 @@ export function useLayoutMeasurements () {
}

const root = document.documentElement
const dividerGap = Number.parseInt(getComputedStyle(root).getPropertyValue('--divider-gap'), 10)
const styles = getComputedStyle(root)
const dividerGap = Number.parseInt(styles.getPropertyValue('--divider-gap'), 10)

// === Inset offsets ===
const insetOffsetTop = topLeftCol.offsetHeight + top.offsetTop
const insetMaxHeight = main.offsetHeight - insetOffsetTop - top.offsetTop
appContainer.style.setProperty('--inset-offset-top', `${insetOffsetTop}px`)
appContainer.style.setProperty('--inset-max-height', `${insetMaxHeight}px`)
// === Top column width ===
const leftWidth = topLeftCol.offsetWidth || 0
const rightWidth = topRightCol.offsetWidth || 0
const finalWidth = leftWidth || rightWidth ? Math.max(leftWidth, rightWidth) : 0
appContainer.style.setProperty('--top-col-width', `${finalWidth}px`)

// === Bottom left offset ===
const insetBottom = inset.offsetHeight + insetOffsetTop
const bottomOffsetTop = Math.min(bottom.offsetTop, actions.offsetTop)
const bottomOffsetLeft = bottomOffsetTop - dividerGap > insetBottom ? 0 : inset.offsetLeft + inset.offsetWidth
appContainer.style.setProperty('--offset-left', `${bottomOffsetLeft}px`)
// === Left container offsets ===
const leftOffsetTop = topLeftCol.offsetHeight + top.offsetTop
const leftOffsetBottom = main.offsetHeight - bottom.offsetTop + dividerGap
appContainer.style.setProperty('--left-offset-top', `${leftOffsetTop}px`)
appContainer.style.setProperty('--left-offset-bottom', `${leftOffsetBottom}px`)
const leftColumnHeight = bottom.offsetTop - leftOffsetTop - dividerGap
appContainer.style.setProperty('--left-top-max-height', `${leftColumnHeight}px`)

// === Right container offsets ===
const rightOffsetTop = topRightCol.offsetHeight + top.offsetTop
const rightOffsetBottom = main.offsetHeight - bottom.offsetTop + dividerGap
appContainer.style.setProperty('--right-offset-top', `${rightOffsetTop}px`)
appContainer.style.setProperty('--right-offset-bottom', `${rightOffsetBottom}px`)
const rightColumnHeight = bottom.offsetTop - rightOffsetTop - dividerGap
appContainer.style.setProperty('--right-top-max-height', `${rightColumnHeight}px`)

// === Top column width ===
const leftWidth = topLeftCol.offsetWidth || 0
const rightWidth = topRightCol.offsetWidth || 0
const finalWidth = leftWidth || rightWidth ? Math.max(leftWidth, rightWidth) : 0
appContainer.style.setProperty('--top-col-width', `${finalWidth}px`)
// === Sub-slot panel max-heights ===
// Panel max-height = column height minus the sibling's button height (and a gap if non-zero)
const leftTopButtons = leftTopRef.current?.offsetHeight ?? 0
const leftBottomButtons = leftBottomRef.current?.offsetHeight ?? 0
appContainer.style.setProperty('--left-top-panel-max-height', `${leftColumnHeight - (leftBottomButtons ? leftBottomButtons + dividerGap : 0)}px`)
appContainer.style.setProperty('--left-bottom-panel-max-height', `${leftColumnHeight - (leftTopButtons ? leftTopButtons + dividerGap : 0)}px`)

const rightTopButtons = rightTopRef.current?.offsetHeight ?? 0
const rightBottomButtons = rightBottomRef.current?.offsetHeight ?? 0
appContainer.style.setProperty('--right-top-panel-max-height', `${rightColumnHeight - (rightBottomButtons ? rightBottomButtons + dividerGap : 0)}px`)
appContainer.style.setProperty('--right-bottom-panel-max-height', `${rightColumnHeight - (rightTopButtons ? rightTopButtons + dividerGap : 0)}px`)

// === Bottom left offset ===
const insetBottom = inset.offsetHeight + leftOffsetTop
const bottomOffsetTop = Math.min(bottom.offsetTop, actions.offsetTop)
const bottomOffsetLeft = bottomOffsetTop - dividerGap > insetBottom ? 0 : inset.offsetLeft + inset.offsetWidth
appContainer.style.setProperty('--offset-left', `${bottomOffsetLeft}px`)
}

// --------------------------------
Expand All @@ -81,7 +102,7 @@ export function useLayoutMeasurements () {
// --------------------------------
// 3. Recaluclate CSS vars when elements resize
// --------------------------------
useResizeObserver([bannerRef, mainRef, topRef, topLeftColRef, topRightColRef, actionsRef, footerRef], () => {
useResizeObserver([bannerRef, mainRef, topRef, topLeftColRef, topRightColRef, actionsRef, footerRef, leftTopRef, leftBottomRef, rightTopRef, rightBottomRef], () => {
requestAnimationFrame(() => {
calculateLayout()
})
Expand Down
28 changes: 23 additions & 5 deletions src/App/hooks/useLayoutMeasurements.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ const refs = (o = {}) => ({
topRightColRef: { current: el({ offsetHeight: 40, offsetWidth: 180, ...o.topRightCol }) },
insetRef: { current: o.inset === null ? null : el({ offsetHeight: 100, offsetLeft: 20, offsetWidth: 300, ...o.inset }) },
footerRef: { current: o.footer === null ? null : el({ offsetTop: 400, ...o.footer }) },
actionsRef: { current: el({ offsetTop: 450, ...o.actions }) }
actionsRef: { current: el({ offsetTop: 450, ...o.actions }) },
leftTopRef: { current: el({ offsetHeight: 0, ...o.leftTop }) },
leftBottomRef: { current: el({ offsetHeight: 0, ...o.leftBottom }) },
rightTopRef: { current: el({ offsetHeight: 0, ...o.rightTop }) },
rightBottomRef: { current: el({ offsetHeight: 0, ...o.rightBottom }) }
})

const setup = (o = {}) => {
Expand Down Expand Up @@ -62,13 +66,11 @@ describe('useLayoutMeasurements', () => {
const { layoutRefs } = setup()
renderHook(() => useLayoutMeasurements())
const spy = layoutRefs.appContainerRef.current.style.setProperty
;['--inset-offset-top', '--inset-max-height', '--offset-left', '--right-offset-top', '--right-offset-bottom', '--top-col-width']
;['--offset-left', '--right-offset-top', '--right-offset-bottom', '--top-col-width']
.forEach(prop => expect(spy).toHaveBeenCalledWith(prop, expect.any(String)))
})

test.each([
['inset-offset-top', { main: { offsetHeight: 500 }, top: { offsetTop: 20 }, topLeftCol: { offsetHeight: 50 } }, '70px'],
['inset-max-height', { main: { offsetHeight: 500 }, top: { offsetTop: 20 }, topLeftCol: { offsetHeight: 50 } }, '410px'],
['offset-left with overlap', { inset: { offsetHeight: 200, offsetLeft: 30, offsetWidth: 150 }, footer: { offsetTop: 100 }, actions: { offsetTop: 120 }, topLeftCol: { offsetHeight: 50 }, top: { offsetTop: 10 } }, '180px'],
['offset-left without overlap', { inset: { offsetHeight: 50, offsetLeft: 30, offsetWidth: 150 }, footer: { offsetTop: 200 }, actions: { offsetTop: 220 }, topLeftCol: { offsetHeight: 50 }, top: { offsetTop: 10 } }, '0px'],
['right-offset-top', { topRightCol: { offsetHeight: 80 }, top: { offsetTop: 15 } }, '95px'],
Expand All @@ -80,6 +82,22 @@ describe('useLayoutMeasurements', () => {
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith(varName, expected)
})

// leftColumnHeight = 400 - (50+10) - 8 = 332; rightColumnHeight = 400 - (40+10) - 8 = 342
test.each([
['--left-top-panel-max-height', {}, '332px'],
['--left-top-panel-max-height', { leftBottom: { offsetHeight: 50 } }, '274px'], // 332 - 50 - 8
['--left-bottom-panel-max-height', {}, '332px'],
['--left-bottom-panel-max-height', { leftTop: { offsetHeight: 40 } }, '284px'], // 332 - 40 - 8
['--right-top-panel-max-height', {}, '342px'],
['--right-top-panel-max-height', { rightBottom: { offsetHeight: 60 } }, '274px'], // 342 - 60 - 8
['--right-bottom-panel-max-height', {}, '342px'],
['--right-bottom-panel-max-height', { rightTop: { offsetHeight: 30 } }, '304px'] // 342 - 30 - 8
])('calculates %s with sibling buttons=%o correctly', (varName, refOverrides, expected) => {
const { layoutRefs } = setup({ refs: refOverrides })
renderHook(() => useLayoutMeasurements())
expect(layoutRefs.appContainerRef.current.style.setProperty).toHaveBeenCalledWith(varName, expected)
})

test.each([
[{ offsetWidth: 250 }, { offsetWidth: 200 }, '250px'],
[{ offsetWidth: 0 }, { offsetWidth: 200 }, '200px'],
Expand Down Expand Up @@ -114,7 +132,7 @@ describe('useLayoutMeasurements', () => {
const { layoutRefs } = setup()
renderHook(() => useLayoutMeasurements())
expect(useResizeObserver).toHaveBeenCalledWith(
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.footerRef],
[layoutRefs.bannerRef, layoutRefs.mainRef, layoutRefs.topRef, layoutRefs.topLeftColRef, layoutRefs.topRightColRef, layoutRefs.actionsRef, layoutRefs.footerRef, layoutRefs.leftTopRef, layoutRefs.leftBottomRef, layoutRefs.rightTopRef, layoutRefs.rightBottomRef],
expect.any(Function)
)
layoutRefs.appContainerRef.current.style.setProperty.mockClear()
Expand Down
Loading
Loading