phonux 0.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.
- package/DESIGN.md +985 -0
- package/LICENSE +21 -0
- package/Panel.d.ts +76 -0
- package/Panel.js +45 -0
- package/PanelFrame.d.ts +154 -0
- package/PanelFrame.js +160 -0
- package/PanelRow.d.ts +46 -0
- package/PanelRow.js +98 -0
- package/PanelRowSlot.d.ts +74 -0
- package/PanelRowSlot.js +68 -0
- package/PhoneDetectPrompt.d.ts +18 -0
- package/PhoneDetectPrompt.js +52 -0
- package/README.md +86 -0
- package/Workspace.d.ts +65 -0
- package/Workspace.js +47 -0
- package/defaultTheme.d.ts +10 -0
- package/defaultTheme.js +26 -0
- package/directionalTransition.d.ts +33 -0
- package/directionalTransition.js +24 -0
- package/dragToClose.d.ts +60 -0
- package/dragToClose.js +151 -0
- package/fakeHost.d.ts +33 -0
- package/fakeHost.js +85 -0
- package/hostApi.d.ts +247 -0
- package/hostApi.js +54 -0
- package/index.d.ts +46 -0
- package/index.js +30 -0
- package/package.json +30 -0
- package/panelCapacity.d.ts +15 -0
- package/panelCapacity.js +19 -0
- package/panelRowEntry.d.ts +37 -0
- package/panelRowEntry.js +11 -0
- package/panelRowLayout.d.ts +57 -0
- package/panelRowLayout.js +52 -0
- package/panelRowOrder.d.ts +46 -0
- package/panelRowOrder.js +105 -0
- package/panelTiers.d.ts +24 -0
- package/panelTiers.js +29 -0
- package/panelWindow.d.ts +170 -0
- package/panelWindow.js +243 -0
- package/panels/usePanelClosing.d.ts +58 -0
- package/panels/usePanelClosing.js +140 -0
- package/panels/usePanelManager.d.ts +74 -0
- package/panels/usePanelManager.js +403 -0
- package/panels/useProvidePanels.d.ts +82 -0
- package/panels/useProvidePanels.js +142 -0
- package/panels/useRowScrollGesture.d.ts +2 -0
- package/panels/useRowScrollGesture.js +70 -0
- package/panels/useWorkspacePersistence.d.ts +14 -0
- package/panels/useWorkspacePersistence.js +74 -0
- package/phoneModels.d.ts +26 -0
- package/phoneModels.js +43 -0
- package/snapshots.d.ts +58 -0
- package/snapshots.js +22 -0
- package/viewRegistry.d.ts +23 -0
- package/viewRegistry.js +30 -0
- package/viewState.d.ts +32 -0
- package/viewState.js +135 -0
- package/windowOverlay.d.ts +87 -0
- package/windowOverlay.js +137 -0
- package/workspaceState.d.ts +63 -0
- package/workspaceState.js +95 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jojos-Creations
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Panel.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared shell every column in the row renders through -- Settings, History and each live
|
|
3
|
+
* panel build their own content, then hand it to Panel for the `position: relative` +
|
|
4
|
+
* `width: phoneWidth` wrapper box and PanelHeaderRow's floating title pill above it. Deliberately
|
|
5
|
+
* a SHELL, not a content container: it does not render PanelFrame itself, so a caller's `children`
|
|
6
|
+
* (already one or more PanelFrame elements) needs no change to move under Panel.
|
|
7
|
+
*/
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import { type BoxProps } from '@mui/material/Box';
|
|
10
|
+
import { type PanelHeaderRowProps } from './PanelFrame.js';
|
|
11
|
+
import { type PanelSize } from './hostApi.js';
|
|
12
|
+
import { type ViewId } from './snapshots.js';
|
|
13
|
+
export { HostMissingProviderError, HostProvider, useHost } from './hostApi.js';
|
|
14
|
+
export type { Host } from './hostApi.js';
|
|
15
|
+
/** @deprecated use HostMissingProviderError, HostProvider, useHost */
|
|
16
|
+
export { PhoneAPIsMissingProviderError, PhoneAPIsProvider, usePhoneAPIs } from './hostApi.js';
|
|
17
|
+
/** @deprecated use Host */
|
|
18
|
+
export type { PhoneAPIs } from './hostApi.js';
|
|
19
|
+
export { DRAG_CLOSE, DragToClose, shouldCloseOnRelease, type DragHandleProps, type ReleaseMeasure, type ReorderHandle } from './dragToClose.js';
|
|
20
|
+
export { WindowOverlay, OverlaySlot, OVERLAY_Z, OverlayProvider, OverlayStore, type OverlaySlotName, type WindowOverlayProps, type Item, type Entry } from './windowOverlay.js';
|
|
21
|
+
/** @public */
|
|
22
|
+
export interface PanelProps extends Omit<BoxProps, 'children' | 'sx' | 'title'> {
|
|
23
|
+
/**
|
|
24
|
+
* The header pill's title. Omitted entirely (not passed at all) renders
|
|
25
|
+
* NO header row, rather than forcing the view into
|
|
26
|
+
* PanelHeaderRow's own "Untitled" placeholder. Pass an
|
|
27
|
+
* explicit falsy value (e.g. `''`) to get that placeholder on purpose.
|
|
28
|
+
*/
|
|
29
|
+
title?: React.ReactNode;
|
|
30
|
+
actions?: React.ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Extra PanelHeaderRow props beyond title/actions -- the drag handle
|
|
33
|
+
* of a DragToClose view (`onPointerDown`, `sx`), `data-*` attributes, etc. The `data-${string}` index signature is
|
|
34
|
+
* needed because TS's "data-* is always valid" leniency only applies to
|
|
35
|
+
* attributes written directly on a JSX element -- it does not extend to a
|
|
36
|
+
* plain object literal like this one being checked against
|
|
37
|
+
* PanelHeaderRowProps before it ever reaches JSX.
|
|
38
|
+
*/
|
|
39
|
+
headerProps?: Omit<PanelHeaderRowProps, 'title' | 'actions'> & {
|
|
40
|
+
[key: `data-${string}`]: string | boolean | undefined;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The grab handle for dragging this Panel (DragToClose's `onPointerDown`/`sx`), rendered as its OWN
|
|
44
|
+
* element filling the gap between the header and the frame -- never spread onto `headerProps` -- so a
|
|
45
|
+
* click on the title text or an action icon in the header is never mistaken for a drag start. Omitted
|
|
46
|
+
* (the usual case for a locked view, e.g. History) renders no grab bar at all.
|
|
47
|
+
*/
|
|
48
|
+
grabProps?: Omit<BoxProps, 'children'> & {
|
|
49
|
+
[key: `data-${string}`]: string | boolean | undefined;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Content positioned absolutely over this view (History's own "+" Fab,
|
|
53
|
+
* bottom-right) -- rendered as a plain sibling of `children` inside this
|
|
54
|
+
* component's own `position: relative` box, which is already the
|
|
55
|
+
* positioning context an `overlay` needs.
|
|
56
|
+
*/
|
|
57
|
+
overlay?: React.ReactNode;
|
|
58
|
+
sx?: BoxProps['sx'];
|
|
59
|
+
children: React.ReactNode;
|
|
60
|
+
/**
|
|
61
|
+
* Registers this Panel's own root element with the desktop's capture registry (see snapshots.ts's
|
|
62
|
+
* CaptureRegistryContext) so the host can later ask for a picture of it by id. Omitted: never registered.
|
|
63
|
+
*/
|
|
64
|
+
viewId?: ViewId;
|
|
65
|
+
/** 'never' opts a Panel with a viewId out of registration entirely -- default 'auto'. */
|
|
66
|
+
capture?: 'auto' | 'never';
|
|
67
|
+
/** This panel's own size instead of the shared useHost().device.size. Omitted: the width comes from device.size, read live -- see DESIGN.md. */
|
|
68
|
+
size?: PanelSize;
|
|
69
|
+
}
|
|
70
|
+
/** @public @deprecated use PanelProps */
|
|
71
|
+
export type PhoneViewProps = PanelProps;
|
|
72
|
+
/** @public */
|
|
73
|
+
export declare function Panel({ title, actions, headerProps, grabProps, overlay, sx, children, viewId, capture, size, ...rest }: PanelProps): React.JSX.Element;
|
|
74
|
+
/** @public @deprecated use Panel */
|
|
75
|
+
export declare const PhoneView: typeof Panel;
|
|
76
|
+
export default Panel;
|
package/Panel.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The shared shell every column in the row renders through -- Settings, History and each live
|
|
4
|
+
* panel build their own content, then hand it to Panel for the `position: relative` +
|
|
5
|
+
* `width: phoneWidth` wrapper box and PanelHeaderRow's floating title pill above it. Deliberately
|
|
6
|
+
* a SHELL, not a content container: it does not render PanelFrame itself, so a caller's `children`
|
|
7
|
+
* (already one or more PanelFrame elements) needs no change to move under Panel.
|
|
8
|
+
*/
|
|
9
|
+
import * as React from 'react';
|
|
10
|
+
import Box from '@mui/material/Box';
|
|
11
|
+
import { PANEL_HEADER_GAP_PX, PanelHeaderRow } from './PanelFrame.js';
|
|
12
|
+
import { useHost } from './hostApi.js';
|
|
13
|
+
import { CaptureRegistryContext } from './snapshots.js';
|
|
14
|
+
// One import path for a view author: the contracts, the provider and the hook.
|
|
15
|
+
export { HostMissingProviderError, HostProvider, useHost } from './hostApi.js';
|
|
16
|
+
/** @deprecated use HostMissingProviderError, HostProvider, useHost */
|
|
17
|
+
export { PhoneAPIsMissingProviderError, PhoneAPIsProvider, usePhoneAPIs } from './hostApi.js';
|
|
18
|
+
export { DRAG_CLOSE, DragToClose, shouldCloseOnRelease } from './dragToClose.js';
|
|
19
|
+
export { WindowOverlay, OverlaySlot, OVERLAY_Z, OverlayProvider, OverlayStore } from './windowOverlay.js';
|
|
20
|
+
/** @public */
|
|
21
|
+
export function Panel({ title, actions, headerProps, grabProps, overlay, sx, children, viewId, capture = 'auto', size, ...rest }) {
|
|
22
|
+
const { device } = useHost();
|
|
23
|
+
const registry = React.useContext(CaptureRegistryContext);
|
|
24
|
+
const rootRef = React.useRef(null);
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
// No registry (a bare <HostProvider>, as in most tests) or opted out: registering is a no-op, never a throw.
|
|
27
|
+
if (!registry || !viewId || capture === 'never' || !rootRef.current)
|
|
28
|
+
return undefined;
|
|
29
|
+
return registry.register(viewId, rootRef.current, 'viewport');
|
|
30
|
+
}, [registry, viewId, capture]);
|
|
31
|
+
return (_jsxs(Box, { sx: { position: 'relative', width: size?.width ?? device.size.width, ...sx }, ...rest, ref: rootRef, children: [title !== undefined && _jsx(PanelHeaderRow, { title: title, actions: actions, ...headerProps }), grabProps && (_jsx(Box, { "data-panel-grab-bar": "", ...grabProps, sx: {
|
|
32
|
+
position: 'absolute',
|
|
33
|
+
bottom: '100%',
|
|
34
|
+
left: 0,
|
|
35
|
+
right: 0,
|
|
36
|
+
height: `${PANEL_HEADER_GAP_PX}px`,
|
|
37
|
+
display: 'flex',
|
|
38
|
+
alignItems: 'center',
|
|
39
|
+
justifyContent: 'center',
|
|
40
|
+
...grabProps.sx,
|
|
41
|
+
}, children: _jsx(Box, { "aria-hidden": "true", sx: { width: 32, height: 4, borderRadius: 2, bgcolor: 'divider' } }) })), children, overlay] }));
|
|
42
|
+
}
|
|
43
|
+
/** @public @deprecated use Panel */
|
|
44
|
+
export const PhoneView = Panel;
|
|
45
|
+
export default Panel;
|
package/PanelFrame.d.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shared phone-shaped card every view renders inside, so every section reads as one
|
|
3
|
+
* silhouette instead of near-identical border/radius/background declarations drifting apart.
|
|
4
|
+
* Fixed size by default; `expanded` is only the tap-to-expand fullscreen overlay. The inset, scrollbar
|
|
5
|
+
* and header-pill rules below are explained in DESIGN.md ("PanelFrame's inset, scrollbar and header pill").
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { type BoxProps } from '@mui/material/Box';
|
|
9
|
+
import { type Theme } from '@mui/material/styles';
|
|
10
|
+
import { type PanelSize } from './hostApi.js';
|
|
11
|
+
/** @public */
|
|
12
|
+
export interface PanelFrameProps extends Omit<BoxProps, 'children'> {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
/** Fill the available space instead of the fixed phone size (the tap-to-expand overlay). */
|
|
15
|
+
expanded?: boolean;
|
|
16
|
+
/** This panel's own size instead of the shared useHost().device.size. Omitted: width and height come from device.size, read live -- see DESIGN.md. `height: 'fill'` renders as `100%` rather than a pixel value. */
|
|
17
|
+
size?: PanelSize;
|
|
18
|
+
}
|
|
19
|
+
/** @public @deprecated use PanelFrameProps */
|
|
20
|
+
export type PhoneFrameProps = PanelFrameProps;
|
|
21
|
+
/** The phone's corner radius in theme shape units (4 x 6px = 24px). The frame's corners and the header
|
|
22
|
+
* pills' semicircular ends both derive from it, so they cannot drift apart. @public */
|
|
23
|
+
export declare const PANEL_CORNER_RADIUS_UNITS = 4;
|
|
24
|
+
/** @public @deprecated use PANEL_CORNER_RADIUS_UNITS */
|
|
25
|
+
export declare const PHONE_CORNER_RADIUS_UNITS = 4;
|
|
26
|
+
/** That radius in px for `theme`; a header pill is twice this tall (exact semicircles). @public */
|
|
27
|
+
export declare const panelCornerRadiusPx: (theme: Theme) => number;
|
|
28
|
+
/** @public @deprecated use panelCornerRadiusPx */
|
|
29
|
+
export declare const phoneCornerRadiusPx: (theme: Theme) => number;
|
|
30
|
+
/** The header pill's gap above the frame, in px -- also the grab bar's own height (Panel.tsx),
|
|
31
|
+
* since the grab bar fills that whole strip. One constant so the two can't drift apart, and sized to WCAG
|
|
32
|
+
* 2.5.8's 24px minimum pointer-target height even though the drawn pill inside it is thinner. @internal */
|
|
33
|
+
export declare const PANEL_HEADER_GAP_PX = 24;
|
|
34
|
+
/**
|
|
35
|
+
* Keeps content inset the same from the border on every side, scrollbar or not: `scrollbar-gutter: stable`
|
|
36
|
+
* reserves the bar's strip, so the right padding is pre-shrunk by its width. A nested scroller needs its own
|
|
37
|
+
* copy of the `::-webkit-scrollbar` rules below, which style only the element they are set on.
|
|
38
|
+
* @public
|
|
39
|
+
*/
|
|
40
|
+
export declare const panelFrameScrollSx: {
|
|
41
|
+
readonly overflowX: "hidden";
|
|
42
|
+
readonly overflowY: "auto";
|
|
43
|
+
readonly scrollbarGutter: "stable";
|
|
44
|
+
readonly pt: "12px";
|
|
45
|
+
readonly pb: "12px";
|
|
46
|
+
readonly pl: "12px";
|
|
47
|
+
readonly pr: "4px";
|
|
48
|
+
readonly '&::-webkit-scrollbar': {
|
|
49
|
+
readonly width: 8;
|
|
50
|
+
};
|
|
51
|
+
readonly '&::-webkit-scrollbar-track': {
|
|
52
|
+
readonly background: "transparent";
|
|
53
|
+
};
|
|
54
|
+
readonly '&::-webkit-scrollbar-thumb': {
|
|
55
|
+
readonly background: "rgba(255, 255, 255, 0.25)";
|
|
56
|
+
readonly borderRadius: number;
|
|
57
|
+
};
|
|
58
|
+
readonly '&::-webkit-scrollbar-thumb:hover': {
|
|
59
|
+
readonly background: "rgba(255, 255, 255, 0.4)";
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/** @public @deprecated use panelFrameScrollSx */
|
|
63
|
+
export declare const phoneFrameScrollSx: {
|
|
64
|
+
readonly overflowX: "hidden";
|
|
65
|
+
readonly overflowY: "auto";
|
|
66
|
+
readonly scrollbarGutter: "stable";
|
|
67
|
+
readonly pt: "12px";
|
|
68
|
+
readonly pb: "12px";
|
|
69
|
+
readonly pl: "12px";
|
|
70
|
+
readonly pr: "4px";
|
|
71
|
+
readonly '&::-webkit-scrollbar': {
|
|
72
|
+
readonly width: 8;
|
|
73
|
+
};
|
|
74
|
+
readonly '&::-webkit-scrollbar-track': {
|
|
75
|
+
readonly background: "transparent";
|
|
76
|
+
};
|
|
77
|
+
readonly '&::-webkit-scrollbar-thumb': {
|
|
78
|
+
readonly background: "rgba(255, 255, 255, 0.25)";
|
|
79
|
+
readonly borderRadius: number;
|
|
80
|
+
};
|
|
81
|
+
readonly '&::-webkit-scrollbar-thumb:hover': {
|
|
82
|
+
readonly background: "rgba(255, 255, 255, 0.4)";
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* For a caller doing its own scrolling one level deeper (an embedded web view, a transcript under a
|
|
87
|
+
* pinned input bar): cancels PanelFrame's own padding AND its reserved scrollbar strip, so
|
|
88
|
+
* neither is reserved twice. Every property is a longhand -- MUI resolves `p`/`pr` and
|
|
89
|
+
* `overflow`/`overflowY` independently, so a shorthand spread over a longhand does not reliably win.
|
|
90
|
+
* @public
|
|
91
|
+
*/
|
|
92
|
+
export declare const panelFrameBareSx: {
|
|
93
|
+
readonly pt: 0;
|
|
94
|
+
readonly pr: 0;
|
|
95
|
+
readonly pb: 0;
|
|
96
|
+
readonly pl: 0;
|
|
97
|
+
readonly overflowX: "hidden";
|
|
98
|
+
readonly overflowY: "hidden";
|
|
99
|
+
readonly scrollbarGutter: "auto";
|
|
100
|
+
};
|
|
101
|
+
/** @public @deprecated use panelFrameBareSx */
|
|
102
|
+
export declare const phoneFrameBareSx: {
|
|
103
|
+
readonly pt: 0;
|
|
104
|
+
readonly pr: 0;
|
|
105
|
+
readonly pb: 0;
|
|
106
|
+
readonly pl: 0;
|
|
107
|
+
readonly overflowX: "hidden";
|
|
108
|
+
readonly overflowY: "hidden";
|
|
109
|
+
readonly scrollbarGutter: "auto";
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Two nested boxes: the OUTER owns border, radius and size and clips to the rounded corner; the INNER owns
|
|
113
|
+
* the padding and is the scroll container. `panelFrameScrollSx` forces a classic scrollbar, because an
|
|
114
|
+
* overlay one floats over content with an inset no padding can cancel (DESIGN.md).
|
|
115
|
+
* @public
|
|
116
|
+
*/
|
|
117
|
+
declare const PanelFrame: React.ForwardRefExoticComponent<Omit<PanelFrameProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
|
|
118
|
+
/** @public */
|
|
119
|
+
export interface PanelHeaderProps extends Omit<BoxProps, 'children'> {
|
|
120
|
+
children: React.ReactNode;
|
|
121
|
+
}
|
|
122
|
+
/** @public @deprecated use PanelHeaderProps */
|
|
123
|
+
export type PhoneFrameHeaderProps = PanelHeaderProps;
|
|
124
|
+
/**
|
|
125
|
+
* A header that floats ABOVE a PanelFrame (`position: absolute; bottom: calc(100% + gap)`), so its parent must
|
|
126
|
+
* be `position: relative`. Internal layout is not baked in (callers differ): pass `sx`, layered on top. A plain
|
|
127
|
+
* `Box`, since `Paper`'s polymorphic `component` typing collides with `Omit<BoxProps, ...>`. Drag wiring and
|
|
128
|
+
* any grab cursor belong on `Panel`'s `grabProps`, never here.
|
|
129
|
+
* @public
|
|
130
|
+
*/
|
|
131
|
+
export declare function PanelHeader({ children, sx, ...rest }: PanelHeaderProps): React.JSX.Element;
|
|
132
|
+
/** @public @deprecated use PanelHeader */
|
|
133
|
+
export declare const PhoneFrameHeader: typeof PanelHeader;
|
|
134
|
+
/** @public */
|
|
135
|
+
export interface PanelHeaderRowProps extends Omit<PanelHeaderProps, 'children' | 'title'> {
|
|
136
|
+
/** Left-aligned, ellipsis-truncated. Falsy renders a placeholder ("Untitled") instead of
|
|
137
|
+
* nothing, so the pill never blanks for a view with no title yet. */
|
|
138
|
+
title: React.ReactNode;
|
|
139
|
+
/** Right-aligned, never truncated. Falsy renders a placeholder icon slot instead of nothing,
|
|
140
|
+
* so a conditional like `isRunning && <IconButton .../>` still keeps the pill's full shape. */
|
|
141
|
+
actions?: React.ReactNode;
|
|
142
|
+
}
|
|
143
|
+
/** @public @deprecated use PanelHeaderRowProps */
|
|
144
|
+
export type PhoneFrameHeaderRowProps = PanelHeaderRowProps;
|
|
145
|
+
/**
|
|
146
|
+
* The title-left/actions-right row shape every PanelHeader caller wants, as one shared layout
|
|
147
|
+
* instead of each call site reimplementing near-identical flex/ellipsis markup. Placeholder
|
|
148
|
+
* substitution lives HERE, not in each caller, so every caller gets it for free.
|
|
149
|
+
* @public
|
|
150
|
+
*/
|
|
151
|
+
export declare function PanelHeaderRow({ title, actions, sx, ...rest }: PanelHeaderRowProps): React.JSX.Element;
|
|
152
|
+
/** @public @deprecated use PanelHeaderRow */
|
|
153
|
+
export declare const PhoneFrameHeaderRow: typeof PanelHeaderRow;
|
|
154
|
+
export default PanelFrame;
|
package/PanelFrame.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The shared phone-shaped card every view renders inside, so every section reads as one
|
|
4
|
+
* silhouette instead of near-identical border/radius/background declarations drifting apart.
|
|
5
|
+
* Fixed size by default; `expanded` is only the tap-to-expand fullscreen overlay. The inset, scrollbar
|
|
6
|
+
* and header-pill rules below are explained in DESIGN.md ("PanelFrame's inset, scrollbar and header pill").
|
|
7
|
+
*/
|
|
8
|
+
import * as React from 'react';
|
|
9
|
+
import Box from '@mui/material/Box';
|
|
10
|
+
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
11
|
+
import { useHost } from './hostApi.js';
|
|
12
|
+
/** The classic (non-overlay) scrollbar this app draws for itself, in px. */
|
|
13
|
+
const SCROLLBAR_PX = 8;
|
|
14
|
+
/** How far content sits from the frame's own border, on every side, in px. */
|
|
15
|
+
const CONTENT_INSET_PX = 12;
|
|
16
|
+
/** The phone's corner radius in theme shape units (4 x 6px = 24px). The frame's corners and the header
|
|
17
|
+
* pills' semicircular ends both derive from it, so they cannot drift apart. @public */
|
|
18
|
+
export const PANEL_CORNER_RADIUS_UNITS = 4;
|
|
19
|
+
/** @public @deprecated use PANEL_CORNER_RADIUS_UNITS */
|
|
20
|
+
export const PHONE_CORNER_RADIUS_UNITS = PANEL_CORNER_RADIUS_UNITS;
|
|
21
|
+
/** That radius in px for `theme`; a header pill is twice this tall (exact semicircles). @public */
|
|
22
|
+
export const panelCornerRadiusPx = (theme) => PANEL_CORNER_RADIUS_UNITS * Number(theme.shape.borderRadius);
|
|
23
|
+
/** @public @deprecated use panelCornerRadiusPx */
|
|
24
|
+
export const phoneCornerRadiusPx = panelCornerRadiusPx;
|
|
25
|
+
/** The header pill's gap above the frame, in px -- also the grab bar's own height (Panel.tsx),
|
|
26
|
+
* since the grab bar fills that whole strip. One constant so the two can't drift apart, and sized to WCAG
|
|
27
|
+
* 2.5.8's 24px minimum pointer-target height even though the drawn pill inside it is thinner. @internal */
|
|
28
|
+
export const PANEL_HEADER_GAP_PX = 24;
|
|
29
|
+
/**
|
|
30
|
+
* Keeps content inset the same from the border on every side, scrollbar or not: `scrollbar-gutter: stable`
|
|
31
|
+
* reserves the bar's strip, so the right padding is pre-shrunk by its width. A nested scroller needs its own
|
|
32
|
+
* copy of the `::-webkit-scrollbar` rules below, which style only the element they are set on.
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export const panelFrameScrollSx = {
|
|
36
|
+
overflowX: 'hidden',
|
|
37
|
+
overflowY: 'auto',
|
|
38
|
+
scrollbarGutter: 'stable',
|
|
39
|
+
pt: `${CONTENT_INSET_PX}px`,
|
|
40
|
+
pb: `${CONTENT_INSET_PX}px`,
|
|
41
|
+
pl: `${CONTENT_INSET_PX}px`,
|
|
42
|
+
pr: `${CONTENT_INSET_PX - SCROLLBAR_PX}px`,
|
|
43
|
+
// Forces a classic (non-overlay) scrollbar: an overlay one reserves no layout width, which
|
|
44
|
+
// would break the padding math above. Track transparent, thumb a low-contrast fill.
|
|
45
|
+
'&::-webkit-scrollbar': { width: SCROLLBAR_PX },
|
|
46
|
+
'&::-webkit-scrollbar-track': { background: 'transparent' },
|
|
47
|
+
'&::-webkit-scrollbar-thumb': {
|
|
48
|
+
background: 'rgba(255, 255, 255, 0.25)',
|
|
49
|
+
borderRadius: SCROLLBAR_PX / 2,
|
|
50
|
+
},
|
|
51
|
+
'&::-webkit-scrollbar-thumb:hover': { background: 'rgba(255, 255, 255, 0.4)' },
|
|
52
|
+
};
|
|
53
|
+
/** @public @deprecated use panelFrameScrollSx */
|
|
54
|
+
export const phoneFrameScrollSx = panelFrameScrollSx;
|
|
55
|
+
/**
|
|
56
|
+
* For a caller doing its own scrolling one level deeper (an embedded web view, a transcript under a
|
|
57
|
+
* pinned input bar): cancels PanelFrame's own padding AND its reserved scrollbar strip, so
|
|
58
|
+
* neither is reserved twice. Every property is a longhand -- MUI resolves `p`/`pr` and
|
|
59
|
+
* `overflow`/`overflowY` independently, so a shorthand spread over a longhand does not reliably win.
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
62
|
+
export const panelFrameBareSx = {
|
|
63
|
+
pt: 0,
|
|
64
|
+
pr: 0,
|
|
65
|
+
pb: 0,
|
|
66
|
+
pl: 0,
|
|
67
|
+
overflowX: 'hidden',
|
|
68
|
+
overflowY: 'hidden',
|
|
69
|
+
scrollbarGutter: 'auto',
|
|
70
|
+
};
|
|
71
|
+
/** @public @deprecated use panelFrameBareSx */
|
|
72
|
+
export const phoneFrameBareSx = panelFrameBareSx;
|
|
73
|
+
/**
|
|
74
|
+
* Two nested boxes: the OUTER owns border, radius and size and clips to the rounded corner; the INNER owns
|
|
75
|
+
* the padding and is the scroll container. `panelFrameScrollSx` forces a classic scrollbar, because an
|
|
76
|
+
* overlay one floats over content with an inset no padding can cancel (DESIGN.md).
|
|
77
|
+
* @public
|
|
78
|
+
*/
|
|
79
|
+
const PanelFrame = React.forwardRef(function PanelFrame({ children, expanded, size, sx, ...rest }, ref) {
|
|
80
|
+
const deviceSize = useHost().device.size;
|
|
81
|
+
const width = size?.width ?? deviceSize.width;
|
|
82
|
+
const height = size?.height === 'fill' ? '100%' : (size?.height ?? deviceSize.height);
|
|
83
|
+
return (_jsx(Box, { ref: ref, "data-phone-frame": "", ...rest, sx: {
|
|
84
|
+
// border-box: row-layout width math elsewhere treats device.size as this box's real
|
|
85
|
+
// rendered size; content-box would add border+padding on top, silently under-fitting.
|
|
86
|
+
boxSizing: 'border-box',
|
|
87
|
+
border: '1px solid',
|
|
88
|
+
borderColor: 'divider',
|
|
89
|
+
borderRadius: PANEL_CORNER_RADIUS_UNITS,
|
|
90
|
+
bgcolor: 'background.paper',
|
|
91
|
+
display: 'flex',
|
|
92
|
+
flexDirection: 'column',
|
|
93
|
+
overflow: 'hidden',
|
|
94
|
+
flexShrink: 0,
|
|
95
|
+
...(expanded ? { width: '100%', height: '100%' } : { width, height }),
|
|
96
|
+
}, children: _jsx(Box, { sx: {
|
|
97
|
+
flex: 1,
|
|
98
|
+
minHeight: 0,
|
|
99
|
+
display: 'flex',
|
|
100
|
+
flexDirection: 'column',
|
|
101
|
+
...panelFrameScrollSx,
|
|
102
|
+
...sx,
|
|
103
|
+
}, children: children }) }));
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* A header that floats ABOVE a PanelFrame (`position: absolute; bottom: calc(100% + gap)`), so its parent must
|
|
107
|
+
* be `position: relative`. Internal layout is not baked in (callers differ): pass `sx`, layered on top. A plain
|
|
108
|
+
* `Box`, since `Paper`'s polymorphic `component` typing collides with `Omit<BoxProps, ...>`. Drag wiring and
|
|
109
|
+
* any grab cursor belong on `Panel`'s `grabProps`, never here.
|
|
110
|
+
* @public
|
|
111
|
+
*/
|
|
112
|
+
export function PanelHeader({ children, sx, ...rest }) {
|
|
113
|
+
return (_jsx(Box, { ...rest, sx: {
|
|
114
|
+
position: 'absolute',
|
|
115
|
+
bottom: `calc(100% + ${PANEL_HEADER_GAP_PX}px)`,
|
|
116
|
+
left: 0,
|
|
117
|
+
right: 0,
|
|
118
|
+
// Twice the phone's own corner radius, so each end is an exact semicircle; from the theme, never a
|
|
119
|
+
// raw 48. Fixed, so however the content wraps the pill keeps its shape.
|
|
120
|
+
height: (theme) => 2 * panelCornerRadiusPx(theme),
|
|
121
|
+
display: 'flex',
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
// bgcolor + border: MUI Paper variant="outlined"'s look, shaped as a pill.
|
|
124
|
+
bgcolor: 'background.paper',
|
|
125
|
+
border: '1px solid',
|
|
126
|
+
borderColor: 'divider',
|
|
127
|
+
borderRadius: PANEL_CORNER_RADIUS_UNITS,
|
|
128
|
+
// 12px, PanelFrame's own content inset, so a header's text lines up with the frame's content below.
|
|
129
|
+
px: 1.5,
|
|
130
|
+
fontSize: 13,
|
|
131
|
+
color: 'common.white',
|
|
132
|
+
...sx,
|
|
133
|
+
}, children: children }));
|
|
134
|
+
}
|
|
135
|
+
/** @public @deprecated use PanelHeader */
|
|
136
|
+
export const PhoneFrameHeader = PanelHeader;
|
|
137
|
+
/** Deliberately narrow (mirrors JSX's own falsy-child rules, not a general emptiness check) so a
|
|
138
|
+
* real value like `0` or whitespace is never mistaken for "no content". */
|
|
139
|
+
function isEmptyHeaderContent(node) {
|
|
140
|
+
return node === undefined || node === null || node === false || node === '';
|
|
141
|
+
}
|
|
142
|
+
/** A dimmed "Untitled" reads as "intentionally blank" rather than a loading glitch or missing data. */
|
|
143
|
+
const PLACEHOLDER_TITLE = (_jsx(Box, { component: "span", "data-header-placeholder-title": "", sx: { color: 'text.disabled' }, children: "Untitled" }));
|
|
144
|
+
/** A dimmed "more" glyph reads as "no actions here" rather than a missing icon, and keeps the
|
|
145
|
+
* row's right edge occupied so the pill's shape never depends on whether any were passed. */
|
|
146
|
+
const PLACEHOLDER_ACTIONS = (_jsx(Box, { "data-header-placeholder-action": "", "aria-hidden": "true", sx: { display: 'flex', alignItems: 'center', color: 'text.disabled' }, children: _jsx(MoreHorizIcon, { fontSize: "small" }) }));
|
|
147
|
+
/**
|
|
148
|
+
* The title-left/actions-right row shape every PanelHeader caller wants, as one shared layout
|
|
149
|
+
* instead of each call site reimplementing near-identical flex/ellipsis markup. Placeholder
|
|
150
|
+
* substitution lives HERE, not in each caller, so every caller gets it for free.
|
|
151
|
+
* @public
|
|
152
|
+
*/
|
|
153
|
+
export function PanelHeaderRow({ title, actions, sx, ...rest }) {
|
|
154
|
+
const resolvedTitle = isEmptyHeaderContent(title) ? PLACEHOLDER_TITLE : title;
|
|
155
|
+
const resolvedActions = isEmptyHeaderContent(actions) ? PLACEHOLDER_ACTIONS : actions;
|
|
156
|
+
return (_jsxs(PanelHeader, { ...rest, sx: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 1, ...sx }, children: [_jsx(Box, { sx: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }, children: resolvedTitle }), _jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.25, flexShrink: 0 }, children: resolvedActions })] }));
|
|
157
|
+
}
|
|
158
|
+
/** @public @deprecated use PanelHeaderRow */
|
|
159
|
+
export const PhoneFrameHeaderRow = PanelHeaderRow;
|
|
160
|
+
export default PanelFrame;
|
package/PanelRow.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One AnimatePresence over ONE array: each PRESENT entry (an always-shown column regardless of `live`, a
|
|
3
|
+
* collapsible one only while `live`) in its own animated slot, in the array's order. Not a view. Renders ONE
|
|
4
|
+
* root, this Box.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { type NeighborWidths, type PanelRowEntry } from './panelRowEntry.js';
|
|
8
|
+
/** True only for a `collapsible` entry (e.g. a plain web panel): an always-shown column (the root, or a
|
|
9
|
+
* `permanent:true` custom column) is never a reorder source. Unprovided resolves to false: a view mounted
|
|
10
|
+
* with no `PanelRow` above it never reorders.
|
|
11
|
+
* @public */
|
|
12
|
+
export declare const usePanelReorderEligible: () => boolean;
|
|
13
|
+
/** False only for an always-shown column (`collapsible:false`): the built-in 'web' view must never wrap such
|
|
14
|
+
* an entry in a framework close handle (CreatePanelOptions.permanent's 'own control only' guarantee).
|
|
15
|
+
* Unprovided resolves to true: a view mounted directly (as tests do) is an ordinary collapsible panel.
|
|
16
|
+
* @public */
|
|
17
|
+
export declare const usePanelCloseEligible: () => boolean;
|
|
18
|
+
/** The widths this entry's drag can cross (reorderStepsForOffset's input). Unprovided: none, so no hop.
|
|
19
|
+
* @public */
|
|
20
|
+
export declare const usePanelNeighborWidths: () => NeighborWidths;
|
|
21
|
+
/** Anchored left of the row's root, read from the anchor every render so a drag across the root changes it too. Unprovided: false.
|
|
22
|
+
* @public */
|
|
23
|
+
export declare const usePanelPinned: () => boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The row: every present entry in its own animated slot, in the array's order, each rendered by its registered view.
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export declare function PanelRow({ entries, rootId, closingIds, enteringViaAnchorIds, exitRightIds, enterSideById, maxLiveWidth, }: {
|
|
29
|
+
/** The raw one-list array (PanelRowEntry, not PanelSummary): `view` alone picks the component (resolveView
|
|
30
|
+
* never returns nothing), independent of `layer` (stacking) and `anchoredEnter` (enter direction). */
|
|
31
|
+
entries: readonly PanelRowEntry[];
|
|
32
|
+
/** The row's root entry: an entry with no anchor orients against it, and `usePanelPinned` means left of it. */
|
|
33
|
+
rootId: string;
|
|
34
|
+
/** Ids being closed: each exits DOWN. Kept off PanelsAPI on purpose. */
|
|
35
|
+
closingIds: ReadonlySet<string>;
|
|
36
|
+
/** Ids (re)joining `live` by a reveal or an undo: each enters from its anchor's side. Any other entry
|
|
37
|
+
* enters from the right unless its own `anchoredEnter` says otherwise. */
|
|
38
|
+
enteringViaAnchorIds: ReadonlySet<string>;
|
|
39
|
+
/** Ids parking with an exit to the RIGHT; every other park exits LEFT. */
|
|
40
|
+
exitRightIds?: ReadonlySet<string>;
|
|
41
|
+
/** An explicit enter side per id, winning over the anchor formula: a scroll gesture knows which side a reveal
|
|
42
|
+
* must enter from, which the boolean anchor formula cannot express. */
|
|
43
|
+
enterSideById?: ReadonlyMap<string, 'left' | 'right'>;
|
|
44
|
+
/** The widest one live panel can be in this row (see computeMaxLiveWidth). Omitted: no cap. */
|
|
45
|
+
maxLiveWidth?: number;
|
|
46
|
+
}): React.ReactElement;
|
package/PanelRow.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* One AnimatePresence over ONE array: each PRESENT entry (an always-shown column regardless of `live`, a
|
|
4
|
+
* collapsible one only while `live`) in its own animated slot, in the array's order. Not a view. Renders ONE
|
|
5
|
+
* root, this Box.
|
|
6
|
+
*/
|
|
7
|
+
import * as React from 'react';
|
|
8
|
+
import { AnimatePresence } from 'motion/react';
|
|
9
|
+
import Box from '@mui/material/Box';
|
|
10
|
+
import { useHost } from './hostApi.js';
|
|
11
|
+
import { summarisePanel } from './panelRowEntry.js';
|
|
12
|
+
import { anchorTargetOf, resolveEnterDirection } from './panelRowOrder.js';
|
|
13
|
+
import { GAP, PanelRowSlot } from './PanelRowSlot.js';
|
|
14
|
+
import { panelWidthOf } from './panelTiers.js';
|
|
15
|
+
import { resolveView } from './viewRegistry.js';
|
|
16
|
+
import { useViewState } from './viewState.js';
|
|
17
|
+
/** A 'raised' slot sits above a 'base' one: a LEFT-exiting park crosses a raised panel's footprint on the way out (a close exits DOWN and never does). */
|
|
18
|
+
const RAISED_LAYER_Z_INDEX = 2;
|
|
19
|
+
const BASE_LAYER_Z_INDEX = 1;
|
|
20
|
+
const EMPTY_SET = new Set();
|
|
21
|
+
const EMPTY_MAP = new Map();
|
|
22
|
+
const NO_NEIGHBORS = { left: [], right: [] };
|
|
23
|
+
/** The view's summary with `size.width` at the width the row renders it, so a view passing `panel.size` to Panel stays inside its slot. */
|
|
24
|
+
function renderedSummary(p, width) {
|
|
25
|
+
const summary = summarisePanel(p);
|
|
26
|
+
return summary.size && summary.size.width !== width && (p.formFactor ?? 'phone') !== 'phone' ? { ...summary, size: { ...summary.size, width } } : summary;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Per-slot facts a view cannot read itself: it only receives `PanelSummary`, which has no `collapsible` or
|
|
30
|
+
* `anchor`, on purpose (hostApi.tsx). No provider above (a view mounted directly, as in tests) is `undefined`,
|
|
31
|
+
* deliberately not `false`: each hook below resolves that case to its OWN default.
|
|
32
|
+
*/
|
|
33
|
+
const PanelSlotContext = React.createContext(undefined);
|
|
34
|
+
/** True only for a `collapsible` entry (e.g. a plain web panel): an always-shown column (the root, or a
|
|
35
|
+
* `permanent:true` custom column) is never a reorder source. Unprovided resolves to false: a view mounted
|
|
36
|
+
* with no `PanelRow` above it never reorders.
|
|
37
|
+
* @public */
|
|
38
|
+
export const usePanelReorderEligible = () => React.useContext(PanelSlotContext)?.collapsible ?? false;
|
|
39
|
+
/** False only for an always-shown column (`collapsible:false`): the built-in 'web' view must never wrap such
|
|
40
|
+
* an entry in a framework close handle (CreatePanelOptions.permanent's 'own control only' guarantee).
|
|
41
|
+
* Unprovided resolves to true: a view mounted directly (as tests do) is an ordinary collapsible panel.
|
|
42
|
+
* @public */
|
|
43
|
+
export const usePanelCloseEligible = () => React.useContext(PanelSlotContext)?.collapsible ?? true;
|
|
44
|
+
/** The widths this entry's drag can cross (reorderStepsForOffset's input). Unprovided: none, so no hop.
|
|
45
|
+
* @public */
|
|
46
|
+
export const usePanelNeighborWidths = () => React.useContext(PanelSlotContext)?.neighborWidths ?? NO_NEIGHBORS;
|
|
47
|
+
/** Anchored left of the row's root, read from the anchor every render so a drag across the root changes it too. Unprovided: false.
|
|
48
|
+
* @public */
|
|
49
|
+
export const usePanelPinned = () => React.useContext(PanelSlotContext)?.pinned ?? false;
|
|
50
|
+
/**
|
|
51
|
+
* The row: every present entry in its own animated slot, in the array's order, each rendered by its registered view.
|
|
52
|
+
* @public
|
|
53
|
+
*/
|
|
54
|
+
export function PanelRow({ entries, rootId, closingIds, enteringViaAnchorIds, exitRightIds = EMPTY_SET, enterSideById = EMPTY_MAP, maxLiveWidth = Number.POSITIVE_INFINITY, }) {
|
|
55
|
+
const { device, panels } = useHost();
|
|
56
|
+
const phoneWidth = device.size.width;
|
|
57
|
+
const phoneHeight = device.size.height;
|
|
58
|
+
// Ids never repeat, so a panel in neither list can never be shown again and its view state is released.
|
|
59
|
+
useViewState.useRetainOnly([...entries, ...(panels.recentlyClosed ?? [])].map((p) => p.id));
|
|
60
|
+
const present = entries.filter((p) => !p.collapsible || p.live);
|
|
61
|
+
// Every present entry's OWN width, in row order. An always-shown column needs no special case: setFormFactor
|
|
62
|
+
// no-ops on a collapsible:false id, so panelWidthOf gives it `phoneWidth`.
|
|
63
|
+
const widths = present.map((p) => panelWidthOf(p, phoneWidth, maxLiveWidth));
|
|
64
|
+
// Each slot's rest x: every PRECEDING item's own width plus one gap, so a wider item pushes the rest right.
|
|
65
|
+
const leftOffsets = [];
|
|
66
|
+
let runningOffset = 0;
|
|
67
|
+
for (const width of widths) {
|
|
68
|
+
leftOffsets.push(runningOffset);
|
|
69
|
+
runningOffset += width + GAP;
|
|
70
|
+
}
|
|
71
|
+
const reservedWidth = Math.max(0, widths.reduce((sum, width) => sum + width + GAP, 0) - GAP);
|
|
72
|
+
// Nearest first on each side, an always-shown column included: movePanel swaps past one like any neighbour.
|
|
73
|
+
const neighborWidthsAt = (index) => {
|
|
74
|
+
const side = (step) => {
|
|
75
|
+
const out = [];
|
|
76
|
+
for (let i = index + step; i >= 0 && i < present.length; i += step)
|
|
77
|
+
out.push(widths[i]);
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
return { left: side(-1), right: side(1) };
|
|
81
|
+
};
|
|
82
|
+
// Per panel, and searched in `present`: each slot's own `index` below counts in that same array.
|
|
83
|
+
const anchorIndexOf = (p) => present.findIndex((x) => x.id === anchorTargetOf(p, rootId));
|
|
84
|
+
return (
|
|
85
|
+
// Always mounted: an exiting entry needs AnimatePresence to animate inside, and `initial={false}` must see
|
|
86
|
+
// what is present at app start to skip animating a restored session. `display: none` (not width 0) so an
|
|
87
|
+
// empty row consumes no flex `gap` from its siblings.
|
|
88
|
+
_jsx(Box, { sx: {
|
|
89
|
+
position: 'relative',
|
|
90
|
+
flexShrink: 0,
|
|
91
|
+
height: phoneHeight,
|
|
92
|
+
display: present.length > 0 ? 'block' : 'none',
|
|
93
|
+
width: reservedWidth,
|
|
94
|
+
}, children: _jsx(AnimatePresence, { initial: false, children: present.map((p, index) => {
|
|
95
|
+
const Component = resolveView(p.view);
|
|
96
|
+
return (_jsx(PanelSlotContext.Provider, { value: { collapsible: p.collapsible, neighborWidths: neighborWidthsAt(index), pinned: anchorTargetOf(p, rootId) === rootId && p.anchor?.position === 'left' }, children: _jsx(PanelRowSlot, { leftOffset: leftOffsets[index], width: widths[index], phoneHeight: phoneHeight, enterDirection: enterSideById.get(p.id) ?? ((p.anchoredEnter ?? false) || enteringViaAnchorIds.has(p.id) ? resolveEnterDirection(anchorIndexOf(p), index) : 'right'), exitDirection: closingIds.has(p.id) ? 'down' : exitRightIds.has(p.id) ? 'right' : 'left', zIndex: (p.layer ?? 'base') === 'raised' ? RAISED_LAYER_Z_INDEX : BASE_LAYER_Z_INDEX, children: _jsx(useViewState.Provider, { value: p.id, children: _jsx(Component, { panel: renderedSummary(p, widths[index]) }) }) }) }, p.id));
|
|
97
|
+
}) }) }));
|
|
98
|
+
}
|