@defra/interactive-map 0.0.12-alpha → 0.0.15-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/package.json +9 -4
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/src/manifest.js +4 -4
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/src/manifest.js +2 -2
- 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/events/fetchSuggestions.js +10 -7
- package/plugins/search/src/events/fetchSuggestions.test.js +4 -4
- package/plugins/search/src/search.scss +8 -3
- package/providers/beta/esri/dist/css/index.css +4 -0
- package/providers/beta/esri/src/esriProvider.scss +5 -0
- package/src/App/components/MapButton/MapButton.jsx +1 -0
- package/src/App/components/Panel/Panel.jsx +14 -13
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/hooks/useLayoutMeasurements.js +31 -23
- package/src/App/hooks/useLayoutMeasurements.test.js +39 -10
- package/src/App/hooks/useModalPanelBehaviour.js +85 -21
- package/src/App/hooks/useModalPanelBehaviour.test.js +126 -18
- package/src/App/hooks/useVisibleGeometry.js +7 -13
- package/src/App/hooks/useVisibleGeometry.test.js +72 -47
- package/src/App/layout/Layout.jsx +11 -6
- package/src/App/layout/Layout.test.jsx +0 -1
- package/src/App/layout/layout.module.scss +83 -10
- package/src/App/renderer/HtmlElementHost.jsx +10 -4
- package/src/App/renderer/HtmlElementHost.test.jsx +32 -11
- package/src/App/renderer/SlotRenderer.jsx +1 -1
- package/src/App/renderer/mapPanels.js +1 -2
- package/src/App/renderer/mapPanels.test.js +3 -3
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +3 -3
- package/src/App/renderer/slots.js +11 -8
- package/src/App/store/AppProvider.jsx +5 -2
- package/src/App/store/appDispatchMiddleware.test.js +2 -2
- package/src/config/appConfig.js +4 -4
- package/src/utils/getSafeZoneInset.js +139 -39
- package/src/utils/getSafeZoneInset.test.js +301 -81
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/core/renderers/mapPanels.js
|
|
2
2
|
import React from 'react'
|
|
3
|
-
import { stringToKebab } from '../../utils/stringToKebab.js'
|
|
4
3
|
import { withPluginContexts } from './pluginWrapper.js'
|
|
5
4
|
import { Panel } from '../components/Panel/Panel.jsx'
|
|
6
5
|
import { allowedSlots } from './slots.js'
|
|
@@ -12,7 +11,7 @@ import { resolveTargetSlot, isModeAllowed, isConsumerHtml } from './slotHelpers.
|
|
|
12
11
|
* and ensures only the topmost modal panel is shown.
|
|
13
12
|
*/
|
|
14
13
|
const isPanelVisible = (panelId, config, bpConfig, { targetSlot, slot, mode, isFullscreen, allowedModalPanelId }) => {
|
|
15
|
-
const isNextToButton =
|
|
14
|
+
const isNextToButton = targetSlot.endsWith('-button')
|
|
16
15
|
if (!allowedSlots.panel.includes(targetSlot) && !isNextToButton) {
|
|
17
16
|
return false
|
|
18
17
|
}
|
|
@@ -7,7 +7,7 @@ jest.mock('../registry/panelRegistry.js')
|
|
|
7
7
|
jest.mock('../registry/pluginRegistry.js', () => ({ registeredPlugins: [] }))
|
|
8
8
|
jest.mock('./pluginWrapper.js', () => ({ withPluginContexts: jest.fn((c) => c) }))
|
|
9
9
|
jest.mock('../components/Panel/Panel.jsx', () => ({ Panel: (props) => <div data-testid='panel' {...props} /> }))
|
|
10
|
-
jest.mock('./slots.js', () => ({ allowedSlots: { panel: ['header', 'modal', '
|
|
10
|
+
jest.mock('./slots.js', () => ({ allowedSlots: { panel: ['header', 'modal', 'left-top'] } }))
|
|
11
11
|
|
|
12
12
|
describe('mapPanels', () => {
|
|
13
13
|
const baseConfig = {
|
|
@@ -145,7 +145,7 @@ describe('mapPanels', () => {
|
|
|
145
145
|
expect(map()).toHaveLength(1)
|
|
146
146
|
})
|
|
147
147
|
|
|
148
|
-
it('replaces bottom slot with
|
|
148
|
+
it('replaces bottom slot with left-top on non-mobile breakpoints', () => {
|
|
149
149
|
defaultAppState.panelConfig = ({
|
|
150
150
|
p1: {
|
|
151
151
|
desktop: { slot: 'bottom' },
|
|
@@ -153,7 +153,7 @@ describe('mapPanels', () => {
|
|
|
153
153
|
}
|
|
154
154
|
})
|
|
155
155
|
|
|
156
|
-
const result = map(defaultAppState, '
|
|
156
|
+
const result = map(defaultAppState, 'left-top')
|
|
157
157
|
expect(result).toHaveLength(1)
|
|
158
158
|
expect(result[0].id).toBe('p1')
|
|
159
159
|
})
|
|
@@ -4,14 +4,14 @@ import { allowedSlots } from './slots.js'
|
|
|
4
4
|
/**
|
|
5
5
|
* Resolves the target slot for a panel based on its breakpoint config.
|
|
6
6
|
* Modal panels always render in the 'modal' slot, and the bottom slot
|
|
7
|
-
* is only available on mobile — tablet and desktop fall back to '
|
|
7
|
+
* is only available on mobile — tablet and desktop fall back to 'left-top'.
|
|
8
8
|
*/
|
|
9
9
|
export const resolveTargetSlot = (bpConfig, breakpoint) => {
|
|
10
10
|
if (bpConfig.modal) {
|
|
11
11
|
return 'modal'
|
|
12
12
|
}
|
|
13
13
|
if (bpConfig.slot === 'bottom' && ['tablet', 'desktop'].includes(breakpoint)) {
|
|
14
|
-
return '
|
|
14
|
+
return 'left-top'
|
|
15
15
|
}
|
|
16
16
|
return bpConfig.slot
|
|
17
17
|
}
|
|
@@ -7,9 +7,9 @@ describe('resolveTargetSlot', () => {
|
|
|
7
7
|
expect(resolveTargetSlot({ modal: true, slot: 'side' }, 'desktop')).toBe('modal')
|
|
8
8
|
})
|
|
9
9
|
|
|
10
|
-
it('replaces bottom with
|
|
11
|
-
expect(resolveTargetSlot({ slot: 'bottom' }, 'tablet')).toBe('
|
|
12
|
-
expect(resolveTargetSlot({ slot: 'bottom' }, 'desktop')).toBe('
|
|
10
|
+
it('replaces bottom with left-top on tablet and desktop', () => {
|
|
11
|
+
expect(resolveTargetSlot({ slot: 'bottom' }, 'tablet')).toBe('left-top')
|
|
12
|
+
expect(resolveTargetSlot({ slot: 'bottom' }, 'desktop')).toBe('left-top')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
it('keeps bottom on mobile', () => {
|
|
@@ -5,10 +5,11 @@ export const layoutSlots = Object.freeze({
|
|
|
5
5
|
TOP_LEFT: 'top-left',
|
|
6
6
|
TOP_MIDDLE: 'top-middle',
|
|
7
7
|
TOP_RIGHT: 'top-right',
|
|
8
|
-
|
|
8
|
+
LEFT_TOP: 'left-top',
|
|
9
|
+
LEFT_BOTTOM: 'left-bottom',
|
|
10
|
+
MIDDLE: 'middle',
|
|
9
11
|
RIGHT_TOP: 'right-top',
|
|
10
12
|
RIGHT_BOTTOM: 'right-bottom',
|
|
11
|
-
MIDDLE: 'middle',
|
|
12
13
|
FOOTER_RIGHT: 'footer-right',
|
|
13
14
|
BOTTOM: 'bottom',
|
|
14
15
|
ACTIONS: 'actions',
|
|
@@ -20,7 +21,6 @@ export const allowedSlots = Object.freeze({
|
|
|
20
21
|
layoutSlots.BANNER,
|
|
21
22
|
layoutSlots.TOP_LEFT,
|
|
22
23
|
layoutSlots.TOP_RIGHT,
|
|
23
|
-
layoutSlots.INSET,
|
|
24
24
|
layoutSlots.MIDDLE,
|
|
25
25
|
layoutSlots.FOOTER_RIGHT,
|
|
26
26
|
layoutSlots.BOTTOM,
|
|
@@ -29,17 +29,20 @@ export const allowedSlots = Object.freeze({
|
|
|
29
29
|
panel: [
|
|
30
30
|
layoutSlots.SIDE,
|
|
31
31
|
layoutSlots.BANNER,
|
|
32
|
-
layoutSlots.
|
|
33
|
-
layoutSlots.
|
|
32
|
+
layoutSlots.LEFT_TOP,
|
|
33
|
+
layoutSlots.LEFT_BOTTOM,
|
|
34
34
|
layoutSlots.MIDDLE,
|
|
35
|
-
layoutSlots.
|
|
36
|
-
layoutSlots.
|
|
37
|
-
layoutSlots.
|
|
35
|
+
layoutSlots.RIGHT_TOP,
|
|
36
|
+
layoutSlots.RIGHT_BOTTOM,
|
|
37
|
+
layoutSlots.BOTTOM, // Typicaly on mobile
|
|
38
|
+
layoutSlots.MODAL // Internal only
|
|
38
39
|
],
|
|
39
40
|
button: [
|
|
40
41
|
layoutSlots.TOP_LEFT,
|
|
41
42
|
layoutSlots.TOP_MIDDLE,
|
|
42
43
|
layoutSlots.TOP_RIGHT,
|
|
44
|
+
layoutSlots.LEFT_TOP,
|
|
45
|
+
layoutSlots.LEFT_BOTTOM,
|
|
43
46
|
layoutSlots.RIGHT_TOP,
|
|
44
47
|
layoutSlots.RIGHT_BOTTOM,
|
|
45
48
|
layoutSlots.ACTIONS
|
|
@@ -19,10 +19,13 @@ export const AppProvider = ({ options, children }) => {
|
|
|
19
19
|
topRef: useRef(null),
|
|
20
20
|
topLeftColRef: useRef(null),
|
|
21
21
|
topRightColRef: useRef(null),
|
|
22
|
-
|
|
22
|
+
leftRef: useRef(null),
|
|
23
|
+
leftTopRef: useRef(null),
|
|
24
|
+
leftBottomRef: useRef(null),
|
|
25
|
+
middleRef: useRef(null),
|
|
23
26
|
rightRef: useRef(null),
|
|
27
|
+
rightTopRef: useRef(null),
|
|
24
28
|
rightBottomRef: useRef(null),
|
|
25
|
-
middleRef: useRef(null),
|
|
26
29
|
bottomRef: useRef(null),
|
|
27
30
|
footerRef: useRef(null),
|
|
28
31
|
actionsRef: useRef(null),
|
|
@@ -189,7 +189,7 @@ describe('appDispatchMiddleware', () => {
|
|
|
189
189
|
|
|
190
190
|
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
191
191
|
events.APP_PANEL_OPENED,
|
|
192
|
-
{ panelId: 'newPanel', slot: '
|
|
192
|
+
{ panelId: 'newPanel', slot: 'left-top' }
|
|
193
193
|
)
|
|
194
194
|
})
|
|
195
195
|
|
|
@@ -204,7 +204,7 @@ describe('appDispatchMiddleware', () => {
|
|
|
204
204
|
|
|
205
205
|
expect(eventBus.emit).toHaveBeenCalledWith(
|
|
206
206
|
events.APP_PANEL_OPENED,
|
|
207
|
-
{ panelId: 'geoPanel', slot: '
|
|
207
|
+
{ panelId: 'geoPanel', slot: 'left-top', visibleGeometry }
|
|
208
208
|
)
|
|
209
209
|
})
|
|
210
210
|
|
package/src/config/appConfig.js
CHANGED
|
@@ -120,14 +120,14 @@ export const defaultPanelConfig = {
|
|
|
120
120
|
showLabel: true
|
|
121
121
|
},
|
|
122
122
|
tablet: {
|
|
123
|
-
slot: '
|
|
123
|
+
slot: 'left-top',
|
|
124
124
|
open: true,
|
|
125
125
|
dismissible: true,
|
|
126
126
|
modal: false,
|
|
127
127
|
showLabel: true
|
|
128
128
|
},
|
|
129
129
|
desktop: {
|
|
130
|
-
slot: '
|
|
130
|
+
slot: 'left-top',
|
|
131
131
|
open: true,
|
|
132
132
|
dismissible: true,
|
|
133
133
|
modal: false,
|
|
@@ -144,10 +144,10 @@ export const defaultControlConfig = {
|
|
|
144
144
|
slot: 'bottom'
|
|
145
145
|
},
|
|
146
146
|
tablet: {
|
|
147
|
-
slot: '
|
|
147
|
+
slot: 'left-top'
|
|
148
148
|
},
|
|
149
149
|
desktop: {
|
|
150
|
-
slot: '
|
|
150
|
+
slot: 'left-top'
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -1,56 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Calculates the safe zone inset — the unobscured region of the map viewport
|
|
3
|
-
* not hidden behind overlay panels
|
|
4
|
-
* for map operations like
|
|
3
|
+
* not hidden behind overlay panels or structural UI (button columns, footer,
|
|
4
|
+
* action bar). Used as padding for map operations like fitBounds or setView
|
|
5
|
+
* so the target location or extent is fully visible.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Each edge inset is driven by:
|
|
8
|
+
* - A structural baseline from the button columns, footer, and action bar.
|
|
9
|
+
* - A panel contribution when panels on that edge cover more than 1/RATIO
|
|
10
|
+
* of the available map dimension along that edge.
|
|
11
|
+
* - A cap so no single inset exceeds 1/MAX_RATIO of the map dimension,
|
|
12
|
+
* preventing fitBounds from zooming out to a corner when panels are large.
|
|
9
13
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Trigger logic per edge:
|
|
15
|
+
* - left / right : combined HEIGHT of panels in that column vs available height
|
|
16
|
+
* - top / bottom : WIDTH of panels in that row vs available width
|
|
17
|
+
*
|
|
18
|
+
* When a panel individually exceeds BOTH the height and width thresholds it
|
|
19
|
+
* would otherwise trigger both adjacent edges, pushing the safe zone into the
|
|
20
|
+
* opposite corner. To prevent this, such panels are classified as either
|
|
21
|
+
* "column-primary" (h/availableH ≥ w/availableW) or "row-primary" and only
|
|
22
|
+
* contribute to their dominant edge. Panels that exceed at most one threshold
|
|
23
|
+
* are never in conflict and contribute to both axes freely.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} refs - React refs from layoutRefs.
|
|
26
|
+
* @param {React.RefObject} refs.mainRef - Main map container.
|
|
27
|
+
* @param {React.RefObject} refs.leftRef - Left button column.
|
|
28
|
+
* @param {React.RefObject} refs.rightRef - Right button column.
|
|
29
|
+
* @param {React.RefObject} refs.actionsRef - Bottom action bar.
|
|
30
|
+
* @param {React.RefObject} refs.footerRef - Footer (logo, copyright, etc).
|
|
31
|
+
* @param {React.RefObject} [refs.leftTopRef] - Top-left panel slot.
|
|
32
|
+
* @param {React.RefObject} [refs.leftBottomRef] - Bottom-left panel slot.
|
|
33
|
+
* @param {React.RefObject} [refs.rightTopRef] - Top-right panel slot.
|
|
34
|
+
* @param {React.RefObject} [refs.rightBottomRef] - Bottom-right panel slot.
|
|
16
35
|
* @returns {{ top: number, right: number, left: number, bottom: number } | undefined}
|
|
17
|
-
* Pixel insets from each edge of the main area, or undefined if any ref is missing.
|
|
36
|
+
* Pixel insets from each edge of the main area, or undefined if any required ref is missing.
|
|
18
37
|
*/
|
|
38
|
+
|
|
39
|
+
const RATIO = 2 // panels covering more than 1/RATIO of an edge trigger that edge's inset
|
|
40
|
+
const MAX_RATIO = 3 // each inset is capped at 1/MAX_RATIO of its map dimension
|
|
41
|
+
|
|
42
|
+
// Query the panel element directly within its slot container.
|
|
43
|
+
// Only the first panel matters, and only when it is also the first element in the slot
|
|
44
|
+
// (i.e. no buttons or other elements precede it — a panel pushed down by buttons is
|
|
45
|
+
// already within the button-column structural inset and should not add extra padding).
|
|
46
|
+
const getPanelDimensions = (slotRef) => {
|
|
47
|
+
if (!slotRef?.current) {
|
|
48
|
+
return { offsetWidth: 0, offsetHeight: 0 }
|
|
49
|
+
}
|
|
50
|
+
const first = slotRef.current.firstElementChild
|
|
51
|
+
if (!first?.classList.contains('im-c-panel') || first.offsetWidth === 0 || first.offsetHeight === 0) {
|
|
52
|
+
return { offsetWidth: 0, offsetHeight: 0 }
|
|
53
|
+
}
|
|
54
|
+
return { offsetWidth: first.offsetWidth, offsetHeight: first.offsetHeight }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Panels are in normal flow inside slot containers, expanding left/right offsetWidth.
|
|
58
|
+
// Query button groups directly to recover the button-only structural column width.
|
|
59
|
+
const getButtonColWidth = (colRef) => {
|
|
60
|
+
const group = colRef?.current?.querySelector('.im-c-button-group')
|
|
61
|
+
return group ? group.offsetWidth : 0
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Returns the panel's contribution to column (left/right) and row (top/bottom) edges.
|
|
65
|
+
//
|
|
66
|
+
// A panel can only trigger BOTH adjacent edges simultaneously when it individually
|
|
67
|
+
// exceeds both the height threshold (h > availableH/RATIO) and the width threshold
|
|
68
|
+
// (w > availableW/RATIO). In that case, only the dominant direction is used, preventing
|
|
69
|
+
// a corner panel from pushing the safe zone into the opposite corner.
|
|
70
|
+
// When only one (or neither) threshold is individually exceeded, the panel contributes
|
|
71
|
+
// its dimensions to both axes freely (combined thresholds still apply later).
|
|
72
|
+
const panelContrib = (w, h, availableW, availableH) => {
|
|
73
|
+
const hFrac = h / availableH
|
|
74
|
+
const wFrac = w / availableW
|
|
75
|
+
if (hFrac >= 1 / RATIO && wFrac >= 1 / RATIO) {
|
|
76
|
+
return hFrac >= wFrac
|
|
77
|
+
? { colH: h, colW: w, rowW: 0, rowH: 0 } // column-primary (left/right)
|
|
78
|
+
: { colH: 0, colW: 0, rowW: w, rowH: h } // row-primary (top/bottom)
|
|
79
|
+
}
|
|
80
|
+
return { colH: h, colW: w, rowW: w, rowH: h }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Determines the column (left/right) inset and the row-axis contributions for both panels.
|
|
84
|
+
//
|
|
85
|
+
// Column coverage uses raw heights so that two panels whose combined height exceeds the
|
|
86
|
+
// threshold always trigger the column inset, even if one is individually row-primary.
|
|
87
|
+
// Exception: a single row-primary panel (no sibling, colH=0) is suppressed so it
|
|
88
|
+
// contributes to top/bottom instead — giving a larger safe zone in the horizontal direction.
|
|
89
|
+
// When triggered, row contributions for both panels are zeroed to prevent double-padding.
|
|
90
|
+
const computeColumn = (panelA, panelB, availableW, availableH, hThreshold, columnLeft, gap) => {
|
|
91
|
+
const a = panelContrib(panelA.offsetWidth, panelA.offsetHeight, availableW, availableH)
|
|
92
|
+
const b = panelContrib(panelB.offsetWidth, panelB.offsetHeight, availableW, availableH)
|
|
93
|
+
const aSingleRowPrimary = panelB.offsetHeight === 0 && a.colH === 0
|
|
94
|
+
const bSingleRowPrimary = panelA.offsetHeight === 0 && b.colH === 0
|
|
95
|
+
const coverage = panelA.offsetHeight + panelB.offsetHeight + (panelA.offsetHeight > 0 && panelB.offsetHeight > 0 ? gap : 0)
|
|
96
|
+
const triggered = coverage > hThreshold && !aSingleRowPrimary && !bSingleRowPrimary
|
|
97
|
+
return {
|
|
98
|
+
panelInset: triggered ? columnLeft + Math.max(panelA.offsetWidth, panelB.offsetWidth) + gap : 0,
|
|
99
|
+
rowA: triggered ? { w: 0, h: 0 } : { w: a.rowW, h: a.rowH },
|
|
100
|
+
rowB: triggered ? { w: 0, h: 0 } : { w: b.rowW, h: b.rowH }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Determines the row-axis (top/bottom) panel inset driven by combined panel width.
|
|
105
|
+
// The combined width test subsumes the individual checks (dimensions are non-negative).
|
|
106
|
+
const computeRow = (leftW, rightW, leftH, rightH, wThreshold, baseInset, gap) =>
|
|
107
|
+
leftW + rightW > wThreshold ? baseInset + Math.max(leftH, rightH) + gap : 0
|
|
108
|
+
|
|
19
109
|
export const getSafeZoneInset = ({
|
|
20
|
-
mainRef,
|
|
21
|
-
|
|
22
|
-
rightRef,
|
|
23
|
-
actionsRef,
|
|
24
|
-
footerRef
|
|
110
|
+
mainRef, leftRef, rightRef, actionsRef, footerRef,
|
|
111
|
+
leftTopRef, leftBottomRef, rightTopRef, rightBottomRef
|
|
25
112
|
}) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (refs.some(ref => !ref.current)) {
|
|
113
|
+
if ([mainRef, leftRef, rightRef, actionsRef, footerRef].some(ref => !ref?.current)) {
|
|
29
114
|
return undefined
|
|
30
115
|
}
|
|
31
116
|
|
|
32
|
-
const
|
|
117
|
+
const main = mainRef.current; const left = leftRef.current
|
|
118
|
+
const actions = actionsRef.current; const footer = footerRef.current
|
|
119
|
+
|
|
120
|
+
const gap = Number.parseInt(getComputedStyle(document.documentElement).getPropertyValue('--divider-gap'), 10)
|
|
121
|
+
|
|
122
|
+
const rawTL = getPanelDimensions(leftTopRef); const rawBL = getPanelDimensions(leftBottomRef)
|
|
123
|
+
const rawTR = getPanelDimensions(rightTopRef); const rawBR = getPanelDimensions(rightBottomRef)
|
|
33
124
|
|
|
34
|
-
|
|
35
|
-
const
|
|
125
|
+
// Structural base insets — always present, never capped.
|
|
126
|
+
const colWidth = Math.max(getButtonColWidth(leftRef), getButtonColWidth(rightRef))
|
|
127
|
+
const baseLeft = main.offsetLeft + left.offsetLeft + colWidth + gap
|
|
128
|
+
const baseRight = left.offsetLeft + colWidth + gap
|
|
129
|
+
const baseTop = left.offsetTop
|
|
130
|
+
const footerInset = main.offsetHeight - footer.offsetTop + gap // mirrors --left/right-offset-bottom CSS var
|
|
131
|
+
const baseBottom = Math.max(main.offsetHeight - actions.offsetTop + gap, footerInset)
|
|
36
132
|
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const rightOffset = inset.offsetLeft + right.offsetWidth + dividerGap
|
|
40
|
-
const availableWidth = main.offsetWidth - rightOffset * 2
|
|
41
|
-
const insetOverlapWidth = inset.offsetWidth - rightOffset + inset.offsetLeft
|
|
42
|
-
const isLandscape = availableWidth - insetOverlapWidth > availableHeight - inset.offsetHeight
|
|
43
|
-
const topOffset = inset.offsetTop + (!isLandscape && inset.offsetHeight > 0 ? inset.offsetHeight + dividerGap : 0)
|
|
44
|
-
const leftOffset = isLandscape ? inset.offsetWidth + inset.offsetLeft + dividerGap : rightOffset
|
|
45
|
-
const actionsOffset = main.offsetHeight - actions.offsetTop
|
|
46
|
-
const footerOffset = main.offsetHeight - footer.offsetTop
|
|
133
|
+
const availableH = main.offsetHeight - baseTop - baseBottom
|
|
134
|
+
const availableW = main.offsetWidth - (baseLeft - main.offsetLeft) - baseRight
|
|
47
135
|
|
|
48
|
-
const
|
|
49
|
-
const
|
|
136
|
+
const leftCol = computeColumn(rawTL, rawBL, availableW, availableH, availableH / RATIO, main.offsetLeft + left.offsetLeft, gap)
|
|
137
|
+
const rightCol = computeColumn(rawTR, rawBR, availableW, availableH, availableH / RATIO, left.offsetLeft, gap)
|
|
50
138
|
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
139
|
+
const leftPanelInset = leftCol.panelInset
|
|
140
|
+
const rightPanelInset = rightCol.panelInset
|
|
141
|
+
const topPanelInset = computeRow(leftCol.rowA.w, rightCol.rowA.w, leftCol.rowA.h, rightCol.rowA.h, availableW / RATIO, baseTop, gap)
|
|
142
|
+
const bottomPanelInset = computeRow(leftCol.rowB.w, rightCol.rowB.w, leftCol.rowB.h, rightCol.rowB.h, availableW / RATIO, footerInset, gap)
|
|
54
143
|
|
|
55
|
-
|
|
144
|
+
const usableW = main.offsetWidth - 2 * gap
|
|
145
|
+
const usableH = main.offsetHeight - 2 * gap
|
|
146
|
+
const maxL = main.offsetLeft + usableW * (MAX_RATIO - 1) / MAX_RATIO
|
|
147
|
+
const maxR = usableW * (MAX_RATIO - 1) / MAX_RATIO
|
|
148
|
+
const maxV = usableH * (MAX_RATIO - 1) / MAX_RATIO
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
left: Math.max(baseLeft, Math.min(leftPanelInset, maxL)),
|
|
152
|
+
right: Math.max(baseRight, Math.min(rightPanelInset, maxR)),
|
|
153
|
+
top: Math.max(baseTop, Math.min(topPanelInset, maxV)),
|
|
154
|
+
bottom: Math.max(baseBottom, Math.min(bottomPanelInset, maxV))
|
|
155
|
+
}
|
|
56
156
|
}
|