@atlaskit/navigation-system 11.0.0 → 11.1.0

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/cjs/entry-points/layout/chat-panel.js +12 -0
  3. package/dist/cjs/entry-points/layout/layout-sizing.js +44 -0
  4. package/dist/cjs/ui/page-layout/calculate-layout-area-states.js +179 -0
  5. package/dist/cjs/ui/page-layout/chat-panel.compiled.css +27 -0
  6. package/dist/cjs/ui/page-layout/chat-panel.js +216 -0
  7. package/dist/cjs/ui/page-layout/constants.js +6 -1
  8. package/dist/cjs/ui/page-layout/layout-area-sizing-context.js +13 -0
  9. package/dist/cjs/ui/page-layout/layout-area-sizing-provider.js +304 -0
  10. package/dist/cjs/ui/page-layout/layout-area-state-contexts.js +13 -0
  11. package/dist/cjs/ui/page-layout/main/main.js +11 -2
  12. package/dist/cjs/ui/page-layout/panel-splitter/panel-splitter.js +73 -7
  13. package/dist/cjs/ui/page-layout/panel-splitter/provider.js +7 -1
  14. package/dist/cjs/ui/page-layout/resolve-layout-width.js +13 -0
  15. package/dist/cjs/ui/page-layout/ribbon.js +1 -0
  16. package/dist/cjs/ui/page-layout/root.compiled.css +4 -2
  17. package/dist/cjs/ui/page-layout/root.js +10 -4
  18. package/dist/cjs/ui/page-layout/side-nav/side-nav.compiled.css +2 -1
  19. package/dist/cjs/ui/page-layout/side-nav/side-nav.js +152 -14
  20. package/dist/cjs/ui/page-layout/use-layout-area-sizing.js +73 -0
  21. package/dist/cjs/ui/page-layout/use-layout-main-sizing.js +15 -0
  22. package/dist/cjs/ui/page-layout/use-occupied-layout-width.js +111 -0
  23. package/dist/es2019/entry-points/layout/chat-panel.js +1 -0
  24. package/dist/es2019/entry-points/layout/layout-sizing.js +3 -0
  25. package/dist/es2019/ui/page-layout/calculate-layout-area-states.js +128 -0
  26. package/dist/es2019/ui/page-layout/chat-panel.compiled.css +27 -0
  27. package/dist/es2019/ui/page-layout/chat-panel.js +199 -0
  28. package/dist/es2019/ui/page-layout/constants.js +5 -0
  29. package/dist/es2019/ui/page-layout/layout-area-sizing-context.js +8 -0
  30. package/dist/es2019/ui/page-layout/layout-area-sizing-provider.js +279 -0
  31. package/dist/es2019/ui/page-layout/layout-area-state-contexts.js +7 -0
  32. package/dist/es2019/ui/page-layout/main/main.js +10 -2
  33. package/dist/es2019/ui/page-layout/panel-splitter/panel-splitter.js +68 -8
  34. package/dist/es2019/ui/page-layout/panel-splitter/provider.js +7 -1
  35. package/dist/es2019/ui/page-layout/resolve-layout-width.js +7 -0
  36. package/dist/es2019/ui/page-layout/ribbon.js +1 -0
  37. package/dist/es2019/ui/page-layout/root.compiled.css +4 -2
  38. package/dist/es2019/ui/page-layout/root.js +10 -4
  39. package/dist/es2019/ui/page-layout/side-nav/side-nav.compiled.css +2 -1
  40. package/dist/es2019/ui/page-layout/side-nav/side-nav.js +142 -14
  41. package/dist/es2019/ui/page-layout/use-layout-area-sizing.js +58 -0
  42. package/dist/es2019/ui/page-layout/use-layout-main-sizing.js +7 -0
  43. package/dist/es2019/ui/page-layout/use-occupied-layout-width.js +67 -0
  44. package/dist/esm/entry-points/layout/chat-panel.js +1 -0
  45. package/dist/esm/entry-points/layout/layout-sizing.js +3 -0
  46. package/dist/esm/ui/page-layout/calculate-layout-area-states.js +173 -0
  47. package/dist/esm/ui/page-layout/chat-panel.compiled.css +27 -0
  48. package/dist/esm/ui/page-layout/chat-panel.js +207 -0
  49. package/dist/esm/ui/page-layout/constants.js +5 -0
  50. package/dist/esm/ui/page-layout/layout-area-sizing-context.js +8 -0
  51. package/dist/esm/ui/page-layout/layout-area-sizing-provider.js +295 -0
  52. package/dist/esm/ui/page-layout/layout-area-state-contexts.js +7 -0
  53. package/dist/esm/ui/page-layout/main/main.js +11 -2
  54. package/dist/esm/ui/page-layout/panel-splitter/panel-splitter.js +74 -8
  55. package/dist/esm/ui/page-layout/panel-splitter/provider.js +7 -1
  56. package/dist/esm/ui/page-layout/resolve-layout-width.js +7 -0
  57. package/dist/esm/ui/page-layout/ribbon.js +1 -0
  58. package/dist/esm/ui/page-layout/root.compiled.css +4 -2
  59. package/dist/esm/ui/page-layout/root.js +10 -4
  60. package/dist/esm/ui/page-layout/side-nav/side-nav.compiled.css +2 -1
  61. package/dist/esm/ui/page-layout/side-nav/side-nav.js +153 -15
  62. package/dist/esm/ui/page-layout/use-layout-area-sizing.js +67 -0
  63. package/dist/esm/ui/page-layout/use-layout-main-sizing.js +9 -0
  64. package/dist/esm/ui/page-layout/use-occupied-layout-width.js +105 -0
  65. package/dist/types/entry-points/layout/chat-panel.d.ts +1 -0
  66. package/dist/types/entry-points/layout/layout-sizing.d.ts +4 -0
  67. package/dist/types/ui/page-layout/calculate-layout-area-states.d.ts +20 -0
  68. package/dist/types/ui/page-layout/chat-panel.d.ts +48 -0
  69. package/dist/types/ui/page-layout/constants.d.ts +4 -0
  70. package/dist/types/ui/page-layout/layout-area-sizing-context.d.ts +55 -0
  71. package/dist/types/ui/page-layout/layout-area-sizing-provider.d.ts +5 -0
  72. package/dist/types/ui/page-layout/layout-area-state-contexts.d.ts +3 -0
  73. package/dist/types/ui/page-layout/main/main.d.ts +5 -1
  74. package/dist/types/ui/page-layout/panel-splitter/panel-splitter-context.d.ts +15 -1
  75. package/dist/types/ui/page-layout/panel-splitter/provider.d.ts +1 -1
  76. package/dist/types/ui/page-layout/resolve-layout-width.d.ts +2 -0
  77. package/dist/types/ui/page-layout/side-nav/side-nav.d.ts +11 -2
  78. package/dist/types/ui/page-layout/use-layout-area-sizing.d.ts +12 -0
  79. package/dist/types/ui/page-layout/use-layout-main-sizing.d.ts +2 -0
  80. package/dist/types/ui/page-layout/use-occupied-layout-width.d.ts +8 -0
  81. package/layout/chat-panel/package.json +10 -0
  82. package/layout/layout-sizing/package.json +10 -0
  83. package/package.json +11 -3
@@ -0,0 +1,27 @@
1
+ ._12xt1ym0._12xt1ym0{grid-area:chat-panel}
2
+ ._12xtquub._12xtquub{grid-area:2/1/-1/-1}
3
+ ._nd5lquub{grid-area:2/1/-1/-1}
4
+ ._191wglyw{border-inline-start:none}
5
+ ._i8luglyw._i8luglyw{border-inline-start:none}
6
+ ._i8luia51._i8luia51{border-inline-start:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}._10fph9n0{justify-self:end}
7
+ ._152t1nws{inset-block-start:var(--n_bnrM,0)}
8
+ ._16hg1bhr._16hg1bhr{background-color:var(--ds-surface-overlay,#fff)}
9
+ ._16hgvuon._16hgvuon{background-color:var(--ds-surface,#fff)}
10
+ ._16qs130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}
11
+ ._18m91wug{overflow-y:auto}
12
+ ._1bsb1ky5{width:var(--n_cPnlRsz,var(--n_cPnlOverlayW))}
13
+ ._1pbyqfx8{z-index:5}
14
+ ._1reo1wug{overflow-x:auto}
15
+ ._4t3i1osq{height:100%}
16
+ ._4t3i56xv{height:calc(100vh - var(--n_bnrM, 0px))}
17
+ ._bfhk1bhr{background-color:var(--ds-surface-overlay,#fff)}
18
+ ._kqsw1if8{position:sticky}
19
+ ._p12f1osq{max-width:100%}
20
+ ._qo371wug._qo371wug{z-index:auto}
21
+ ._qo37qfx8._qo37qfx8{z-index:5}
22
+ ._so1e130s._so1e130s{box-shadow:var(--ds-shadow-overlay,0 8px 9pt #1e1f2126,0 0 1px #1e1f214f)}
23
+ ._so1eglyw._so1eglyw{box-shadow:none}
24
+ ._vchhusvi{box-sizing:border-box}
25
+ ._w1ap1ky5._w1ap1ky5{width:var(--n_cPnlRsz,var(--n_cPnlOverlayW))}
26
+ ._w1ap8rg1._w1ap8rg1{width:var(--n_cPnlRsz,var(--n_cPnlW))}
27
+ @media (min-width:40rem){._19dmia51{border-inline-start:var(--ds-border-width,1px) solid var(--ds-border,#0b120e24)}._1ao7glyw{box-shadow:none}._qhdr1ym0{grid-area:chat-panel}._m5h51wug{z-index:auto}._1ttbvuon{background-color:var(--ds-surface,#fff)}._1mxh8rg1{width:var(--n_cPnlRsz,var(--n_cPnlW))}}
@@ -0,0 +1,199 @@
1
+ /* chat-panel.tsx generated by @compiled/babel-plugin v3.0.2 */
2
+ import "./chat-panel.compiled.css";
3
+ import * as React from 'react';
4
+ import { ax, ix } from "@compiled/react/runtime";
5
+ import { useCallback, useEffect, useRef, useState } from 'react';
6
+ import { bind } from 'bind-event-listener';
7
+ import { fg } from '@atlaskit/platform-feature-flags/fg';
8
+ import { useSkipLinkInternal } from '../../context/skip-links/use-skip-link-internal';
9
+ import { chatPanelLiveWidthVar, chatPanelVar, localSlotLayers, mainMinimumWidthVar } from './constants';
10
+ import { convertResizeBoundToPixels } from './panel-splitter/convert-resize-bound-to-pixels';
11
+ import { PanelSplitterProvider } from './panel-splitter/provider';
12
+ import { resolveLayoutWidth } from './resolve-layout-width';
13
+ import { gridRootId } from './root';
14
+ import { useLayoutAreaSizing } from './use-layout-area-sizing';
15
+ import { useLayoutId } from './use-layout-id';
16
+ import { useResizingWidthCssVarOnRootElement } from './use-resizing-width-css-var-on-root-element';
17
+ import { useSafeDefaultWidth } from './use-safe-default-width';
18
+ const chatPanelPanelSplitterId = Symbol('ChatPanel PanelSplitter');
19
+ const panelSplitterResizingVar = '--n_cPnlRsz';
20
+ const chatPanelOverlayWidthVar = '--n_cPnlOverlayW';
21
+ const fallbackDefaultWidth = 400;
22
+ const fallbackCompactDefaultWidth = 320;
23
+ const fallbackMinWidth = '320px';
24
+ const inlineMediaQuery = '(min-width: 40rem)';
25
+ function getPixelCustomProperty(element, propertyName, fallback) {
26
+ const raw = getComputedStyle(element).getPropertyValue(propertyName).trim();
27
+ const value = Number.parseFloat(raw);
28
+ return Number.isFinite(value) ? resolveLayoutWidth(raw.endsWith('vw') ? `${value}vw` : value, window.innerWidth) : fallback;
29
+ }
30
+ const styles = {
31
+ root: "_nd5lquub _10fph9n0 _vchhusvi _p12f1osq _1pbyqfx8 _4t3i56xv _kqsw1if8 _152t1nws _bfhk1bhr _1bsb1ky5 _qhdr1ym0 _m5h51wug _1ttbvuon _1mxh8rg1",
32
+ managedInline: "_12xt1ym0 _qo371wug _16hgvuon _w1ap8rg1",
33
+ managedOverlay: "_12xtquub _qo37qfx8 _16hg1bhr _w1ap1ky5",
34
+ scrollContainer: "_1reo1wug _18m91wug _4t3i1osq",
35
+ border: "_191wglyw _16qs130s _19dmia51 _1ao7glyw",
36
+ managedInlineBorder: "_i8luia51 _so1eglyw",
37
+ managedOverlayBorder: "_i8luglyw _so1e130s"
38
+ };
39
+
40
+ /**
41
+ * The ChatPanel layout area is rendered below the banner and to the right (inline end) of the app.
42
+ *
43
+ * Below 40rem it overlays the app. At 40rem and above it is rendered as the final
44
+ * full-height column in the page layout grid.
45
+ *
46
+ * You can optionally render a `PanelSplitter` as a child to make the chat panel resizable.
47
+ */
48
+ export function ChatPanel({
49
+ children,
50
+ defaultWidth: defaultWidthProp,
51
+ minWidth = fallbackMinWidth,
52
+ maxWidth,
53
+ label = 'Chat panel',
54
+ skipLinkLabel = label,
55
+ testId,
56
+ id: providedId,
57
+ xcss,
58
+ hasBorder = true,
59
+ onClose
60
+ }) {
61
+ var _managedSizing$mode, _managedSizing$width;
62
+ const id = useLayoutId({
63
+ providedId
64
+ });
65
+ const defaultWidth = useSafeDefaultWidth({
66
+ defaultWidthProp: defaultWidthProp !== null && defaultWidthProp !== void 0 ? defaultWidthProp : fallbackDefaultWidth,
67
+ fallbackDefaultWidth,
68
+ slotName: 'ChatPanel'
69
+ });
70
+ const compactDefaultWidth = defaultWidthProp === undefined ? fallbackCompactDefaultWidth : defaultWidth;
71
+ useSkipLinkInternal({
72
+ id,
73
+ label: skipLinkLabel,
74
+ isHidden: defaultWidth === 0 || fg('platform_dst_nav4_skip_link_a11y_1')
75
+ });
76
+ const ref = useRef(null);
77
+ const [inlineWidth, setInlineWidth] = useState(defaultWidth);
78
+ const [overlayWidth, setOverlayWidth] = useState(compactDefaultWidth);
79
+ const [isInline, setIsInline] = useState(false);
80
+ const {
81
+ state: managedSizing,
82
+ getResizeBounds: getManagedResizeBounds,
83
+ startResize,
84
+ resize,
85
+ completeResize
86
+ } = useLayoutAreaSizing({
87
+ area: 'chat-panel',
88
+ config: {
89
+ defaultWidth,
90
+ compactDefaultWidth,
91
+ minWidth,
92
+ maxWidth,
93
+ isOpen: true,
94
+ onRequestClose: onClose
95
+ }
96
+ });
97
+ useEffect(() => {
98
+ const mediaQuery = window.matchMedia(inlineMediaQuery);
99
+ setIsInline(mediaQuery.matches);
100
+ const onChange = event => {
101
+ setIsInline(event.matches);
102
+ };
103
+ return bind(mediaQuery, {
104
+ type: 'change',
105
+ listener: onChange
106
+ });
107
+ }, []);
108
+ const mode = (_managedSizing$mode = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) !== null && _managedSizing$mode !== void 0 ? _managedSizing$mode : isInline ? 'inline' : 'overlay';
109
+ const renderedWidth = (_managedSizing$width = managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.width) !== null && _managedSizing$width !== void 0 ? _managedSizing$width : mode === 'inline' ? inlineWidth : overlayWidth;
110
+ useResizingWidthCssVarOnRootElement({
111
+ isEnabled: !managedSizing,
112
+ cssVar: panelSplitterResizingVar,
113
+ panelId: chatPanelPanelSplitterId
114
+ });
115
+ const getResizeBounds = useCallback(() => {
116
+ const managedBounds = getManagedResizeBounds();
117
+ if (managedBounds) {
118
+ return managedBounds;
119
+ }
120
+ const chatPanel = ref.current;
121
+ const layoutRoot = chatPanel === null || chatPanel === void 0 ? void 0 : chatPanel.parentElement;
122
+ const main = layoutRoot === null || layoutRoot === void 0 ? void 0 : layoutRoot.querySelector(':scope > [data-layout-slot][role="main"]');
123
+ if (!chatPanel || !main || getComputedStyle(chatPanel).gridArea !== 'chat-panel') {
124
+ return {
125
+ min: minWidth,
126
+ max: maxWidth !== null && maxWidth !== void 0 ? maxWidth : '90vw'
127
+ };
128
+ }
129
+ const chatPanelWidth = chatPanel.getBoundingClientRect().width;
130
+ const mainWidth = main.getBoundingClientRect().width;
131
+ if (chatPanelWidth === 0 || mainWidth === 0) {
132
+ return {
133
+ min: minWidth,
134
+ max: maxWidth !== null && maxWidth !== void 0 ? maxWidth : '90vw'
135
+ };
136
+ }
137
+ const localPanel = main.querySelector('[data-layout-with-panel-slot]');
138
+ const isLocalPanelInline = localPanel && getComputedStyle(localPanel).gridArea === 'panel';
139
+ const localPanelWidth = isLocalPanelInline ? localPanel.getBoundingClientRect().width : 0;
140
+ const mainMinimumWidth = getPixelCustomProperty(main, mainMinimumWidthVar, 320);
141
+ const mainContentWidth = mainWidth - localPanelWidth;
142
+ const availableWidth = Math.max(0, mainContentWidth - mainMinimumWidth);
143
+ const availableInlineWidth = Math.floor(chatPanelWidth + availableWidth);
144
+ const constrainedMaxWidth = maxWidth ? Math.min(convertResizeBoundToPixels(maxWidth), availableInlineWidth) : availableInlineWidth;
145
+ return {
146
+ min: minWidth,
147
+ max: `${Math.max(convertResizeBoundToPixels(minWidth), constrainedMaxWidth)}px`
148
+ };
149
+ }, [getManagedResizeBounds, maxWidth, minWidth]);
150
+ const configuredMaxWidth = maxWidth !== null && maxWidth !== void 0 ? maxWidth : '100vw';
151
+ const chatPanelVariableWidth = managedSizing ? `${renderedWidth}px` : `clamp(${minWidth}, ${inlineWidth}px, ${configuredMaxWidth})`;
152
+ const chatPanelOverlayVariableWidth = managedSizing ? `${renderedWidth}px` : `clamp(${minWidth}, ${overlayWidth}px, min(90vw, ${configuredMaxWidth}))`;
153
+ const onCompleteResize = useCallback(finalWidth => {
154
+ if (mode === 'inline') {
155
+ setInlineWidth(finalWidth);
156
+ } else {
157
+ setOverlayWidth(finalWidth);
158
+ }
159
+ completeResize(mode, finalWidth);
160
+ }, [completeResize, mode]);
161
+ const onResizeStartInternal = useCallback(() => {
162
+ startResize(mode);
163
+ }, [mode, startResize]);
164
+ const onResizeInternal = useCallback(width => resize(mode, width), [mode, resize]);
165
+ return /*#__PURE__*/React.createElement("section", {
166
+ id: id,
167
+ "data-layout-slot": true,
168
+ "aria-label": label,
169
+ className: ax([styles.root, hasBorder && styles.border, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInline, (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlay, hasBorder && (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'inline' && styles.managedInlineBorder, hasBorder && (managedSizing === null || managedSizing === void 0 ? void 0 : managedSizing.mode) === 'overlay' && styles.managedOverlayBorder, xcss]),
170
+ style: {
171
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values -- Shared layout CSS variable name.
172
+ [chatPanelVar]: chatPanelVariableWidth,
173
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop, @atlaskit/ui-styling-standard/no-unsafe-values
174
+ [chatPanelOverlayWidthVar]: chatPanelOverlayVariableWidth
175
+ },
176
+ "data-testid": testId,
177
+ "data-layout-chat-panel": "",
178
+ ref: ref
179
+ },
180
+ /*#__PURE__*/
181
+ // Hoist the committed or actively resizing inline width so SideNav can yield
182
+ // space to ChatPanel without allowing the page layout to overflow.
183
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
184
+ React.createElement("style", null, `
185
+ #${gridRootId} { ${chatPanelLiveWidthVar}: var(${panelSplitterResizingVar}, ${chatPanelVariableWidth}); }
186
+ `), /*#__PURE__*/React.createElement(PanelSplitterProvider, {
187
+ panelId: chatPanelPanelSplitterId,
188
+ panelRef: ref,
189
+ panelWidth: renderedWidth,
190
+ onCompleteResize: onCompleteResize,
191
+ onResizeStartInternal: managedSizing ? onResizeStartInternal : undefined,
192
+ onResizeInternal: managedSizing ? onResizeInternal : undefined,
193
+ getResizeBounds: getResizeBounds,
194
+ resizingCssVar: panelSplitterResizingVar,
195
+ position: "start"
196
+ }, /*#__PURE__*/React.createElement("div", {
197
+ className: ax([styles.scrollContainer])
198
+ }, children)));
199
+ }
@@ -8,6 +8,9 @@
8
8
  export const sideNavVar = '--n_sNvw';
9
9
  export const asideVar = '--n_asDw';
10
10
  export const panelVar = '--n_pnlW';
11
+ export const chatPanelVar = '--n_cPnlW';
12
+ export const chatPanelLiveWidthVar = '--n_cPnlLw';
13
+ export const mainMinimumWidthVar = '--n_mainMinW';
11
14
  export const bannerMountedVar = '--n_bnrM';
12
15
  export const topNavMountedVar = '--n_tNvM';
13
16
  export const ribbonVar = '--n_rbnW';
@@ -53,6 +56,8 @@ export const UNSAFE_MAIN_INLINE_END_FOR_LEGACY_PAGES_ONLY = `calc(var(${UNSAFE_a
53
56
  */
54
57
  // eslint-disable-next-line @atlaskit/volt-strict-mode/no-multiple-exports
55
58
  export const localSlotLayers = {
59
+ // Chat overlays the app, including TopNav and SideNav, below the banner.
60
+ chatPanelOverlay: 5,
56
61
  ribbon: 4,
57
62
  // The side nav panel splitter is layered above the top nav when FHS is enabled.
58
63
  // It has the same z-index value, but is rendered after the top nav in the DOM so is stacked above.
@@ -0,0 +1,8 @@
1
+ import { createContext } from 'react';
2
+
3
+ /**
4
+ * Effective sibling widths held fixed during and after a user resize, until the
5
+ * layout dimensions or configuration change. This is not a resize history.
6
+ */
7
+
8
+ export const LayoutAreaSizingContext = /*#__PURE__*/createContext(null);
@@ -0,0 +1,279 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { bind } from 'bind-event-listener';
3
+ import { calculateLayoutAreaStates } from './calculate-layout-area-states';
4
+ import { LayoutAreaSizingContext } from './layout-area-sizing-context';
5
+ import { layoutAreaStateContexts } from './layout-area-state-contexts';
6
+ import { useOccupiedLayoutWidth } from './use-occupied-layout-width';
7
+ const defaultMainMinWidth = 320;
8
+ function useStableAreaState(state) {
9
+ const {
10
+ mode,
11
+ width,
12
+ minWidth
13
+ } = state !== null && state !== void 0 ? state : {};
14
+ return useMemo(() => mode !== undefined && width !== undefined && minWidth !== undefined ? {
15
+ mode,
16
+ width,
17
+ minWidth
18
+ } : undefined, [mode, width, minWidth]);
19
+ }
20
+ export function LayoutAreaSizingProvider({
21
+ children,
22
+ layoutRef
23
+ }) {
24
+ const occupiedWidth = useOccupiedLayoutWidth(layoutRef);
25
+ const [dimensions, setDimensions] = useState(null);
26
+ const [mainMinWidth, setMainMinWidth] = useState(defaultMainMinWidth);
27
+ const [areas, setAreas] = useState({});
28
+ const [resizeSession, setResizeSession] = useState(null);
29
+ const areaStatesRef = React.useRef({});
30
+ const configsRef = React.useRef({});
31
+ const openSequence = React.useRef(0);
32
+ useEffect(() => {
33
+ setResizeSession(null);
34
+ }, [mainMinWidth, occupiedWidth]);
35
+ useEffect(() => {
36
+ const root = layoutRef === null || layoutRef === void 0 ? void 0 : layoutRef.current;
37
+ let previous = null;
38
+ // Match the actual rem-based grid breakpoint, including browser font settings.
39
+ const inlineChatQuery = window.matchMedia('(min-width: 40rem)');
40
+ const inlineSideNavQuery = window.matchMedia('(min-width: 64rem)');
41
+ const updateDimensions = contentWidth => {
42
+ var _style$paddingLeft, _style$paddingRight, _previous;
43
+ const style = root && contentWidth === undefined ? getComputedStyle(root) : null;
44
+ const layoutWidth = contentWidth !== null && contentWidth !== void 0 ? contentWidth : root ? Math.max(0, root.clientWidth - (parseFloat((_style$paddingLeft = style === null || style === void 0 ? void 0 : style.paddingLeft) !== null && _style$paddingLeft !== void 0 ? _style$paddingLeft : '') || 0) - (parseFloat((_style$paddingRight = style === null || style === void 0 ? void 0 : style.paddingRight) !== null && _style$paddingRight !== void 0 ? _style$paddingRight : '') || 0)) : window.innerWidth;
45
+ const next = {
46
+ viewportWidth: window.innerWidth,
47
+ layoutWidth,
48
+ isChatPanelOverlay: !inlineChatQuery.matches,
49
+ isSideNavOverlay: !inlineSideNavQuery.matches
50
+ };
51
+ if (((_previous = previous) === null || _previous === void 0 ? void 0 : _previous.viewportWidth) === next.viewportWidth && previous.layoutWidth === next.layoutWidth && previous.isChatPanelOverlay === next.isChatPanelOverlay && previous.isSideNavOverlay === next.isSideNavOverlay) {
52
+ return;
53
+ }
54
+ previous = next;
55
+ setDimensions(next);
56
+ setResizeSession(null);
57
+ };
58
+ updateDimensions();
59
+ const observer = new ResizeObserver(entries => {
60
+ const entry = entries.find(entry => entry.target === root);
61
+ if (entry) {
62
+ updateDimensions(entry.contentRect.width);
63
+ }
64
+ });
65
+ if (root) {
66
+ observer.observe(root);
67
+ }
68
+ const unbindResize = bind(window, {
69
+ type: 'resize',
70
+ listener: () => updateDimensions()
71
+ });
72
+ const unbindQuery = bind(inlineChatQuery, {
73
+ type: 'change',
74
+ listener: () => updateDimensions()
75
+ });
76
+ const unbindSideNavQuery = bind(inlineSideNavQuery, {
77
+ type: 'change',
78
+ listener: () => updateDimensions()
79
+ });
80
+ return () => {
81
+ observer.disconnect();
82
+ unbindResize();
83
+ unbindQuery();
84
+ unbindSideNavQuery();
85
+ };
86
+ }, [layoutRef]);
87
+ const registerArea = useCallback((area, config) => {
88
+ const previous = configsRef.current[area];
89
+ configsRef.current[area] = config;
90
+ setResizeSession(current => {
91
+ // A changed layout/configuration needs a fresh allocation. A controlled
92
+ // acknowledgement for the resized area keeps its siblings fixed.
93
+ return (previous === null || previous === void 0 ? void 0 : previous.isOpen) === config.isOpen && previous.minWidth === config.minWidth && previous.maxWidth === config.maxWidth && previous.defaultWidth === config.defaultWidth && previous.compactDefaultWidth === config.compactDefaultWidth && (previous.requestedWidth === config.requestedWidth || (current === null || current === void 0 ? void 0 : current.area) === area) ? current : null;
94
+ });
95
+ // Allocate once per registration, outside the updater React may replay. Gaps are
96
+ // harmless: only relative ordering of closed-to-open transitions is significant.
97
+ const openedAt = ++openSequence.current;
98
+ setAreas(current => {
99
+ const existing = current[area];
100
+ return {
101
+ ...current,
102
+ [area]: {
103
+ ...existing,
104
+ ...config,
105
+ liveResize: config.isOpen ? existing === null || existing === void 0 ? void 0 : existing.liveResize : undefined,
106
+ openedAt: config.isOpen && !(existing !== null && existing !== void 0 && existing.isOpen) ? openedAt : existing === null || existing === void 0 ? void 0 : existing.openedAt
107
+ }
108
+ };
109
+ });
110
+ }, []);
111
+ const unregisterArea = useCallback(area => {
112
+ delete configsRef.current[area];
113
+ setResizeSession(null);
114
+ setAreas(current => {
115
+ if (!current[area]) {
116
+ return current;
117
+ }
118
+ // Closing uses isOpen: false and retains preferences. Unregistering means
119
+ // the owning component unmounted; a replacement must start with its own defaults.
120
+ const next = {
121
+ ...current
122
+ };
123
+ delete next[area];
124
+ return next;
125
+ });
126
+ }, []);
127
+ const freezeSiblingWidths = useCallback(area => {
128
+ const baselineWidths = {};
129
+ for (const areaName of ['side-nav', 'panel', 'chat-panel']) {
130
+ const state = areaStatesRef.current[areaName];
131
+ if ((state === null || state === void 0 ? void 0 : state.mode) === 'inline') {
132
+ baselineWidths[areaName] = state.width;
133
+ }
134
+ }
135
+ // Keep the same siblings fixed after completion, including while a controlled
136
+ // consumer rejects or asynchronously accepts the requested width.
137
+ setResizeSession({
138
+ area,
139
+ baselineWidths
140
+ });
141
+ }, []);
142
+ const startResize = useCallback((area, mode) => {
143
+ if (mode !== 'inline') {
144
+ return;
145
+ }
146
+ freezeSiblingWidths(area);
147
+
148
+ // Start from the effective width, which may already be compressed below the preferred width.
149
+ // Reapplying the preferred width here would move the splitter away from the pointer.
150
+ const renderedState = areaStatesRef.current[area];
151
+ if ((renderedState === null || renderedState === void 0 ? void 0 : renderedState.mode) === 'inline') {
152
+ setAreas(current => {
153
+ const existing = current[area];
154
+ return existing ? {
155
+ ...current,
156
+ [area]: {
157
+ ...existing,
158
+ liveResize: {
159
+ mode,
160
+ width: renderedState.width
161
+ }
162
+ }
163
+ } : current;
164
+ });
165
+ }
166
+ }, [freezeSiblingWidths]);
167
+ const resize = useCallback((area, mode, width) => {
168
+ setAreas(current => {
169
+ var _existing$liveResize;
170
+ const existing = current[area];
171
+ if ((existing === null || existing === void 0 ? void 0 : (_existing$liveResize = existing.liveResize) === null || _existing$liveResize === void 0 ? void 0 : _existing$liveResize.mode) === mode && existing.liveResize.width === width) {
172
+ return current;
173
+ }
174
+ return existing ? {
175
+ ...current,
176
+ [area]: {
177
+ ...existing,
178
+ liveResize: {
179
+ mode,
180
+ width
181
+ }
182
+ }
183
+ } : current;
184
+ });
185
+ }, []);
186
+ const completeResize = useCallback((area, mode, width) => {
187
+ if (mode === 'inline') {
188
+ // Keyboard resizing completes without a preceding pointer-drag session.
189
+ freezeSiblingWidths(area);
190
+ }
191
+ setAreas(current => {
192
+ const existing = current[area];
193
+ if (!existing) {
194
+ return current;
195
+ }
196
+ if (mode === 'overlay') {
197
+ return {
198
+ ...current,
199
+ [area]: {
200
+ ...existing,
201
+ liveResize: undefined,
202
+ preferredOverlayWidth: width
203
+ }
204
+ };
205
+ }
206
+ return {
207
+ ...current,
208
+ [area]: {
209
+ ...existing,
210
+ liveResize: undefined,
211
+ preferredInlineWidth: width
212
+ }
213
+ };
214
+ });
215
+ }, [freezeSiblingWidths]);
216
+ const areaStates = useMemo(() => dimensions === null ? {} : calculateLayoutAreaStates({
217
+ ...dimensions,
218
+ occupiedWidth,
219
+ mainMinWidth,
220
+ areas,
221
+ resizeSession
222
+ }), [areas, mainMinWidth, resizeSession, dimensions, occupiedWidth]);
223
+ useEffect(() => {
224
+ areaStatesRef.current = areaStates;
225
+ }, [areaStates]);
226
+ useEffect(() => {
227
+ var _areaStates$sideNav, _areaStates$chatPane, _sideNav$openedAt, _chatPanel$openedAt;
228
+ if (((_areaStates$sideNav = areaStates['side-nav']) === null || _areaStates$sideNav === void 0 ? void 0 : _areaStates$sideNav.mode) !== 'overlay' || ((_areaStates$chatPane = areaStates['chat-panel']) === null || _areaStates$chatPane === void 0 ? void 0 : _areaStates$chatPane.mode) !== 'overlay') {
229
+ return;
230
+ }
231
+ const sideNav = areas['side-nav'];
232
+ const chatPanel = areas['chat-panel'];
233
+ if (!(sideNav !== null && sideNav !== void 0 && sideNav.isOpen) || !(chatPanel !== null && chatPanel !== void 0 && chatPanel.isOpen)) {
234
+ return;
235
+ }
236
+ if (((_sideNav$openedAt = sideNav.openedAt) !== null && _sideNav$openedAt !== void 0 ? _sideNav$openedAt : 0) > ((_chatPanel$openedAt = chatPanel.openedAt) !== null && _chatPanel$openedAt !== void 0 ? _chatPanel$openedAt : 0)) {
237
+ var _chatPanel$onRequestC;
238
+ (_chatPanel$onRequestC = chatPanel.onRequestClose) === null || _chatPanel$onRequestC === void 0 ? void 0 : _chatPanel$onRequestC.call(chatPanel);
239
+ } else {
240
+ var _sideNav$onRequestClo;
241
+ (_sideNav$onRequestClo = sideNav.onRequestClose) === null || _sideNav$onRequestClo === void 0 ? void 0 : _sideNav$onRequestClo.call(sideNav);
242
+ }
243
+ }, [areas, areaStates]);
244
+
245
+ // Read bounds lazily: changing a sibling's capacity must not publish new render
246
+ // state or recreate drag listeners for a region whose own width did not change.
247
+ const getResizeBounds = useCallback(area => {
248
+ var _areaStatesRef$curren;
249
+ const bounds = (_areaStatesRef$curren = areaStatesRef.current[area]) === null || _areaStatesRef$curren === void 0 ? void 0 : _areaStatesRef$curren.resizeBounds;
250
+ return bounds ? {
251
+ min: `${bounds.min}px`,
252
+ max: `${bounds.max}px`
253
+ } : undefined;
254
+ }, []);
255
+ const value = useMemo(() => ({
256
+ getResizeBounds,
257
+ registerArea,
258
+ unregisterArea,
259
+ startResize,
260
+ resize,
261
+ completeResize,
262
+ setMainMinWidth
263
+ }), [completeResize, getResizeBounds, registerArea, resize, startResize, unregisterArea]);
264
+ const sideNavState = useStableAreaState(areaStates['side-nav']);
265
+ const panelState = useStableAreaState(areaStates.panel);
266
+ const chatPanelState = useStableAreaState(areaStates['chat-panel']);
267
+ const SideNavStateProvider = layoutAreaStateContexts['side-nav'].Provider;
268
+ const PanelStateProvider = layoutAreaStateContexts.panel.Provider;
269
+ const ChatPanelStateProvider = layoutAreaStateContexts['chat-panel'].Provider;
270
+ return /*#__PURE__*/React.createElement(LayoutAreaSizingContext.Provider, {
271
+ value: value
272
+ }, /*#__PURE__*/React.createElement(SideNavStateProvider, {
273
+ value: sideNavState
274
+ }, /*#__PURE__*/React.createElement(PanelStateProvider, {
275
+ value: panelState
276
+ }, /*#__PURE__*/React.createElement(ChatPanelStateProvider, {
277
+ value: chatPanelState
278
+ }, children))));
279
+ }
@@ -0,0 +1,7 @@
1
+ import { createContext } from 'react';
2
+ // Width changes should notify only the affected region, not every sizing consumer.
3
+ export const layoutAreaStateContexts = {
4
+ 'side-nav': /*#__PURE__*/createContext(undefined),
5
+ panel: /*#__PURE__*/createContext(undefined),
6
+ 'chat-panel': /*#__PURE__*/createContext(undefined)
7
+ };
@@ -5,7 +5,10 @@ import { ax, ix } from "@compiled/react/runtime";
5
5
  import { Fragment } from 'react';
6
6
  import { fg } from '@atlaskit/platform-feature-flags/fg';
7
7
  import { useSkipLink } from '../../../context/skip-links/use-skip-link';
8
+ import { mainMinimumWidthVar } from '../constants';
9
+ import { gridRootId } from '../root';
8
10
  import { useLayoutId } from '../use-layout-id';
11
+ import { useLayoutMainSizing } from '../use-layout-main-sizing';
9
12
  const contentInsetBlockStartStatic = `calc(var(--n_bnrM, 0px) + var(--n_tNvM, 0px))`;
10
13
  const contentHeightWhenFixedStatic = `calc(100vh - var(--n_bnrM, 0px) - var(--n_tNvM, 0px))`;
11
14
  const mainElementStyles = {
@@ -20,13 +23,18 @@ export function Main({
20
23
  xcss,
21
24
  skipLinkLabel = fg('platform_dst_nav4_skip_link_a11y_1') ? 'Main content' : 'Main Content',
22
25
  testId,
23
- id: providedId
26
+ id: providedId,
27
+ minWidth = 320
24
28
  }) {
25
29
  const id = useLayoutId({
26
30
  providedId
27
31
  });
32
+ useLayoutMainSizing(minWidth);
28
33
  useSkipLink(id, skipLinkLabel);
29
- return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement("div", {
34
+ return /*#__PURE__*/React.createElement(Fragment, null, fg('platform-dst-chat-panel-layout') &&
35
+ /*#__PURE__*/
36
+ // eslint-disable-next-line @atlaskit/ui-styling-standard/no-global-styles
37
+ React.createElement("style", null, `#${gridRootId} { ${mainMinimumWidthVar}: ${minWidth}px; }`), /*#__PURE__*/React.createElement("div", {
30
38
  id: id,
31
39
  "data-layout-slot": true,
32
40
  className: ax([mainElementStyles.root, xcss]),