@kushagradhawan/kookie-ui 0.1.49 → 0.1.51
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/components.css +880 -243
- package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js +1 -1
- package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
- package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-handles.js +1 -1
- package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
- package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js +1 -1
- package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
- package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
- package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/cjs/components/chatbar.d.ts +9 -2
- package/dist/cjs/components/chatbar.d.ts.map +1 -1
- package/dist/cjs/components/chatbar.js +1 -1
- package/dist/cjs/components/chatbar.js.map +3 -3
- package/dist/cjs/components/shell.context.d.ts +88 -0
- package/dist/cjs/components/shell.context.d.ts.map +1 -1
- package/dist/cjs/components/shell.context.js +1 -1
- package/dist/cjs/components/shell.context.js.map +3 -3
- package/dist/cjs/components/shell.d.ts +51 -13
- package/dist/cjs/components/shell.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.d.ts +7 -1
- package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
- package/dist/cjs/components/shell.hooks.js +1 -1
- package/dist/cjs/components/shell.hooks.js.map +3 -3
- package/dist/cjs/components/shell.js +1 -1
- package/dist/cjs/components/shell.js.map +3 -3
- package/dist/cjs/components/shell.types.d.ts +1 -0
- package/dist/cjs/components/shell.types.d.ts.map +1 -1
- package/dist/cjs/components/shell.types.js +1 -1
- package/dist/cjs/components/shell.types.js.map +2 -2
- package/dist/cjs/components/sidebar.d.ts +7 -1
- package/dist/cjs/components/sidebar.d.ts.map +1 -1
- package/dist/cjs/components/sidebar.js +1 -1
- package/dist/cjs/components/sidebar.js.map +3 -3
- package/dist/esm/components/_internal/shell-bottom.d.ts +31 -5
- package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-bottom.js +1 -1
- package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
- package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-handles.js +1 -1
- package/dist/esm/components/_internal/shell-handles.js.map +3 -3
- package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
- package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-inspector.js +1 -1
- package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
- package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
- package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js +1 -1
- package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
- package/dist/esm/components/chatbar.d.ts +9 -2
- package/dist/esm/components/chatbar.d.ts.map +1 -1
- package/dist/esm/components/chatbar.js +1 -1
- package/dist/esm/components/chatbar.js.map +3 -3
- package/dist/esm/components/shell.context.d.ts +88 -0
- package/dist/esm/components/shell.context.d.ts.map +1 -1
- package/dist/esm/components/shell.context.js +1 -1
- package/dist/esm/components/shell.context.js.map +3 -3
- package/dist/esm/components/shell.d.ts +51 -13
- package/dist/esm/components/shell.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.d.ts +7 -1
- package/dist/esm/components/shell.hooks.d.ts.map +1 -1
- package/dist/esm/components/shell.hooks.js +1 -1
- package/dist/esm/components/shell.hooks.js.map +3 -3
- package/dist/esm/components/shell.js +1 -1
- package/dist/esm/components/shell.js.map +3 -3
- package/dist/esm/components/shell.types.d.ts +1 -0
- package/dist/esm/components/shell.types.d.ts.map +1 -1
- package/dist/esm/components/shell.types.js.map +2 -2
- package/dist/esm/components/sidebar.d.ts +7 -1
- package/dist/esm/components/sidebar.d.ts.map +1 -1
- package/dist/esm/components/sidebar.js +1 -1
- package/dist/esm/components/sidebar.js.map +3 -3
- package/package.json +14 -3
- package/schemas/base-button.json +1 -1
- package/schemas/button.json +1 -1
- package/schemas/icon-button.json +1 -1
- package/schemas/index.json +6 -6
- package/schemas/toggle-button.json +1 -1
- package/schemas/toggle-icon-button.json +1 -1
- package/src/components/_internal/base-menu.css +17 -18
- package/src/components/_internal/base-sidebar-menu.css +23 -21
- package/src/components/_internal/base-sidebar.css +20 -0
- package/src/components/_internal/shell-bottom.tsx +176 -49
- package/src/components/_internal/shell-handles.tsx +29 -4
- package/src/components/_internal/shell-inspector.tsx +175 -43
- package/src/components/_internal/shell-sidebar.tsx +176 -69
- package/src/components/chatbar.css +240 -21
- package/src/components/chatbar.tsx +246 -290
- package/src/components/sheet.css +8 -16
- package/src/components/shell.context.tsx +79 -0
- package/src/components/shell.css +28 -2
- package/src/components/shell.hooks.ts +35 -0
- package/src/components/shell.tsx +574 -214
- package/src/components/shell.types.ts +2 -0
- package/src/components/sidebar.css +233 -33
- package/src/components/sidebar.tsx +247 -213
- package/styles.css +841 -204
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
flex: 1;
|
|
11
11
|
display: flex;
|
|
12
12
|
flex-direction: column;
|
|
13
|
-
padding-bottom: var(--base-menu-content-padding);
|
|
14
13
|
box-sizing: border-box;
|
|
15
14
|
list-style: none;
|
|
16
15
|
margin: 0;
|
|
17
16
|
min-height: 0;
|
|
18
17
|
|
|
19
18
|
:where(.rt-SidebarContent:has(.rt-ScrollAreaScrollbar[data-orientation='vertical'])) & {
|
|
20
|
-
padding-
|
|
19
|
+
padding-inline-end: var(--space-3);
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
@@ -34,8 +33,8 @@
|
|
|
34
33
|
min-height: var(--base-menu-item-height);
|
|
35
34
|
padding-top: var(--base-menu-item-padding-y);
|
|
36
35
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
37
|
-
padding-
|
|
38
|
-
padding-
|
|
36
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
37
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
39
38
|
box-sizing: border-box;
|
|
40
39
|
position: relative;
|
|
41
40
|
outline: none;
|
|
@@ -43,11 +42,14 @@
|
|
|
43
42
|
background: none;
|
|
44
43
|
border: none;
|
|
45
44
|
width: 100%;
|
|
46
|
-
text-align:
|
|
45
|
+
text-align: start;
|
|
47
46
|
/* No default border radius - inherited from size-specific rules */
|
|
48
47
|
|
|
49
|
-
/*
|
|
50
|
-
transition:
|
|
48
|
+
/* Step 3: restrict to paint-only transitions to avoid Chrome compositing nudge */
|
|
49
|
+
transition:
|
|
50
|
+
background-color var(--motion-duration-micro) var(--motion-ease-standard),
|
|
51
|
+
color var(--motion-duration-small) var(--motion-ease-standard),
|
|
52
|
+
font-weight var(--motion-duration-small) var(--motion-ease-standard);
|
|
51
53
|
|
|
52
54
|
/* Fix sticky text highlighting after selection in Firefox */
|
|
53
55
|
user-select: none;
|
|
@@ -147,14 +149,14 @@
|
|
|
147
149
|
}
|
|
148
150
|
|
|
149
151
|
.rt-SidebarMenuSubList {
|
|
150
|
-
padding-
|
|
151
|
-
border-
|
|
152
|
-
margin-
|
|
152
|
+
padding-inline-start: var(--space-4);
|
|
153
|
+
border-inline-start: 1px solid var(--gray-a5);
|
|
154
|
+
margin-inline-start: var(--space-3);
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
/* Sub-menu items have consistent heights based on size - match dropdown menu exactly */
|
|
156
158
|
:where(.rt-SidebarContent.rt-r-size-1) :where(.rt-SidebarMenuSubList) .rt-SidebarMenuButton {
|
|
157
|
-
padding-
|
|
159
|
+
padding-inline-start: var(--space-3);
|
|
158
160
|
padding-top: calc(var(--space-1) * 0.75);
|
|
159
161
|
padding-bottom: calc(var(--space-1) * 0.75);
|
|
160
162
|
min-height: var(--space-5); /* 20px */
|
|
@@ -162,10 +164,10 @@
|
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
:where(.rt-SidebarContent.rt-r-size-2) :where(.rt-SidebarMenuSubList) .rt-SidebarMenuButton {
|
|
165
|
-
padding-
|
|
167
|
+
padding-inline-start: var(--space-3);
|
|
166
168
|
padding-top: var(--space-1);
|
|
167
169
|
padding-bottom: var(--space-1);
|
|
168
|
-
min-height: var(--space-6); /*
|
|
170
|
+
min-height: var(--space-6); /* 32px */
|
|
169
171
|
font-size: var(--font-size-2);
|
|
170
172
|
}
|
|
171
173
|
|
|
@@ -176,8 +178,8 @@
|
|
|
176
178
|
min-height: var(--base-menu-item-height);
|
|
177
179
|
padding-top: var(--base-menu-item-padding-y);
|
|
178
180
|
padding-bottom: var(--base-menu-item-padding-y);
|
|
179
|
-
padding-
|
|
180
|
-
padding-
|
|
181
|
+
padding-inline-start: var(--base-menu-item-padding-left);
|
|
182
|
+
padding-inline-end: var(--base-menu-item-padding-right);
|
|
181
183
|
box-sizing: border-box;
|
|
182
184
|
color: var(--gray-a10);
|
|
183
185
|
user-select: none;
|
|
@@ -193,27 +195,27 @@
|
|
|
193
195
|
.rt-SidebarMenuButton:where(:has(.rt-SidebarMenuShortcut, .rt-SidebarMenuBadge)) {
|
|
194
196
|
/* Use base menu padding tokens */
|
|
195
197
|
:where(.rt-SidebarContent.rt-r-size-1) & {
|
|
196
|
-
padding-
|
|
198
|
+
padding-inline-end: var(--base-menu-item-padding-y); /* Matches top/bottom padding */
|
|
197
199
|
}
|
|
198
200
|
|
|
199
201
|
:where(.rt-SidebarContent.rt-r-size-2) & {
|
|
200
|
-
padding-
|
|
202
|
+
padding-inline-end: var(--base-menu-item-padding-y); /* Matches top/bottom padding */
|
|
201
203
|
}
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
.rt-SidebarMenuShortcut {
|
|
205
207
|
display: flex;
|
|
206
208
|
align-items: center;
|
|
207
|
-
margin-
|
|
208
|
-
padding-
|
|
209
|
+
margin-inline-start: auto;
|
|
210
|
+
padding-inline-start: var(--space-4);
|
|
209
211
|
color: var(--gray-a11);
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
.rt-SidebarMenuBadge {
|
|
213
215
|
display: flex;
|
|
214
216
|
align-items: center;
|
|
215
|
-
margin-
|
|
216
|
-
padding-
|
|
217
|
+
margin-inline-start: auto;
|
|
218
|
+
padding-inline-start: var(--space-2);
|
|
217
219
|
}
|
|
218
220
|
|
|
219
221
|
/* Add balanced spacing for sidebar menu items while preserving base menu border radius */
|
|
@@ -78,6 +78,17 @@
|
|
|
78
78
|
box-shadow: none !important;
|
|
79
79
|
border-radius: 0 !important;
|
|
80
80
|
|
|
81
|
+
/* Step 2: stabilize text rasterization to avoid Chrome AA flicker */
|
|
82
|
+
-webkit-font-smoothing: antialiased;
|
|
83
|
+
text-rendering: optimizeLegibility;
|
|
84
|
+
|
|
85
|
+
/* Add gap between groups using flex */
|
|
86
|
+
/* stylelint-disable-next-line selector-max-specificity */
|
|
87
|
+
& .rt-BaseMenuViewport {
|
|
88
|
+
/* gap: var(--base-menu-content-padding); */
|
|
89
|
+
gap: var(--space-5);
|
|
90
|
+
}
|
|
91
|
+
|
|
81
92
|
/* Ensure ScrollArea takes full height within SidebarContent */
|
|
82
93
|
& :where(.rt-ScrollAreaRoot) {
|
|
83
94
|
flex: 1;
|
|
@@ -92,6 +103,15 @@
|
|
|
92
103
|
flex-direction: column;
|
|
93
104
|
min-height: 0;
|
|
94
105
|
}
|
|
106
|
+
|
|
107
|
+
/* Step 1: promote menu viewport to its own layer (Chrome jitter test) */
|
|
108
|
+
& :where(.rt-BaseMenuViewport) {
|
|
109
|
+
transform: translateZ(0);
|
|
110
|
+
backface-visibility: hidden;
|
|
111
|
+
will-change: transform;
|
|
112
|
+
/* Step 4: isolate painting to prevent cross-layer nudges */
|
|
113
|
+
contain: paint;
|
|
114
|
+
}
|
|
95
115
|
}
|
|
96
116
|
|
|
97
117
|
/* Sidebar Footer */
|
|
@@ -3,7 +3,7 @@ import classNames from 'classnames';
|
|
|
3
3
|
import * as Sheet from '../sheet.js';
|
|
4
4
|
import { VisuallyHidden } from '../visually-hidden.js';
|
|
5
5
|
import { useShell } from '../shell.context.js';
|
|
6
|
-
import { useResponsivePresentation } from '../shell.hooks.js';
|
|
6
|
+
import { useResponsivePresentation, useResponsiveValue } from '../shell.hooks.js';
|
|
7
7
|
import { PaneResizeContext } from './shell-resize.js';
|
|
8
8
|
import { BottomHandle, PaneHandle } from './shell-handles.js';
|
|
9
9
|
import { BREAKPOINTS } from '../shell.types.js';
|
|
@@ -11,9 +11,7 @@ import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation
|
|
|
11
11
|
|
|
12
12
|
interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
13
13
|
presentation?: ResponsivePresentation;
|
|
14
|
-
mode
|
|
15
|
-
defaultMode?: any;
|
|
16
|
-
onModeChange?: (mode: PaneMode) => void;
|
|
14
|
+
// legacy mode removed
|
|
17
15
|
expandedSize?: number;
|
|
18
16
|
minSize?: number;
|
|
19
17
|
maxSize?: number;
|
|
@@ -32,16 +30,32 @@ interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
|
|
|
32
30
|
persistence?: PaneSizePersistence;
|
|
33
31
|
}
|
|
34
32
|
|
|
35
|
-
type
|
|
33
|
+
type BottomOpenChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
|
|
34
|
+
type BottomControlledProps = { open: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: BottomOpenChangeMeta) => void; defaultOpen?: never };
|
|
35
|
+
type BottomUncontrolledProps = { defaultOpen?: boolean; onOpenChange?: (open: boolean, meta: BottomOpenChangeMeta) => void; open?: never };
|
|
36
|
+
type BottomSizeControlledProps = { size: number | string; defaultSize?: never };
|
|
37
|
+
type BottomSizeUncontrolledProps = { defaultSize?: number | string; size?: never };
|
|
38
|
+
type BottomSizeChangeMeta = { reason: 'init' | 'resize' | 'controlled' };
|
|
39
|
+
type BottomPublicProps = PaneProps &
|
|
40
|
+
(BottomControlledProps | BottomUncontrolledProps) &
|
|
41
|
+
(BottomSizeControlledProps | BottomSizeUncontrolledProps) & {
|
|
42
|
+
onSizeChange?: (size: number, meta: BottomSizeChangeMeta) => void;
|
|
43
|
+
sizeUpdate?: 'throttle' | 'debounce';
|
|
44
|
+
sizeUpdateMs?: number;
|
|
45
|
+
};
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
type BottomComponent = React.ForwardRefExoticComponent<BottomPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof BottomHandle };
|
|
48
|
+
|
|
49
|
+
export const Bottom = React.forwardRef<HTMLDivElement, BottomPublicProps>(
|
|
38
50
|
(
|
|
39
51
|
{
|
|
40
52
|
className,
|
|
41
53
|
presentation = 'fixed',
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
54
|
+
// removed legacy props
|
|
55
|
+
// new API
|
|
56
|
+
defaultOpen,
|
|
57
|
+
open,
|
|
58
|
+
onOpenChange,
|
|
45
59
|
expandedSize = 200,
|
|
46
60
|
minSize = 100,
|
|
47
61
|
maxSize = 400,
|
|
@@ -80,58 +94,107 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
80
94
|
const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === BottomHandle);
|
|
81
95
|
const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === BottomHandle));
|
|
82
96
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
// Throttled/debounced emitter for onSizeChange
|
|
98
|
+
const emitSizeChange = React.useMemo(() => {
|
|
99
|
+
const cb = (props as any).onSizeChange as undefined | ((s: number, meta: BottomSizeChangeMeta) => void);
|
|
100
|
+
const strategy = (props as any).sizeUpdate as undefined | 'throttle' | 'debounce';
|
|
101
|
+
const ms = (props as any).sizeUpdateMs ?? 50;
|
|
102
|
+
if (!cb) return () => {};
|
|
103
|
+
if (strategy === 'debounce') {
|
|
104
|
+
let t: any = null;
|
|
105
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
106
|
+
if (t) clearTimeout(t);
|
|
107
|
+
t = setTimeout(() => {
|
|
108
|
+
cb(s, meta);
|
|
109
|
+
}, ms);
|
|
110
|
+
};
|
|
88
111
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
if (strategy === 'throttle') {
|
|
113
|
+
let last = 0;
|
|
114
|
+
return (s: number, meta: BottomSizeChangeMeta) => {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
if (now - last >= ms) {
|
|
117
|
+
last = now;
|
|
118
|
+
cb(s, meta);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
97
121
|
}
|
|
98
|
-
return
|
|
99
|
-
}, [
|
|
122
|
+
return (s: number, meta: BottomSizeChangeMeta) => cb(s, meta);
|
|
123
|
+
}, [(props as any).onSizeChange, (props as any).sizeUpdate, (props as any).sizeUpdateMs]);
|
|
100
124
|
|
|
101
125
|
const didInitRef = React.useRef(false);
|
|
126
|
+
const didInitFromDefaultOpenRef = React.useRef(false);
|
|
127
|
+
const resolvedDefaultOpen = useResponsiveValue(defaultOpen as any);
|
|
102
128
|
React.useEffect(() => {
|
|
103
129
|
if (didInitRef.current) return;
|
|
130
|
+
if (!shell.currentBreakpointReady) return;
|
|
104
131
|
didInitRef.current = true;
|
|
105
|
-
if (
|
|
106
|
-
const initial =
|
|
107
|
-
|
|
132
|
+
if (typeof open === 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
133
|
+
const initial = Boolean(resolvedDefaultOpen);
|
|
134
|
+
shell.setBottomMode(initial ? 'expanded' : 'collapsed');
|
|
135
|
+
didInitFromDefaultOpenRef.current = true;
|
|
108
136
|
}
|
|
109
|
-
}, []);
|
|
137
|
+
}, [shell.currentBreakpointReady, open, defaultOpen, resolvedDefaultOpen]);
|
|
110
138
|
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
139
|
+
// Dev guards
|
|
140
|
+
const wasControlledRef = React.useRef<boolean | null>(null);
|
|
141
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
142
|
+
if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.error('Shell.Bottom: Do not pass both `open` and `defaultOpen`. Choose one.');
|
|
145
|
+
}
|
|
146
|
+
if (typeof (props as any).size !== 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
|
|
147
|
+
// eslint-disable-next-line no-console
|
|
148
|
+
console.error('Shell.Bottom: Do not pass both `size` and `defaultSize`. Choose one.');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
123
151
|
|
|
124
152
|
React.useEffect(() => {
|
|
125
|
-
|
|
126
|
-
|
|
153
|
+
const isControlled = typeof open !== 'undefined';
|
|
154
|
+
if (wasControlledRef.current === null) {
|
|
155
|
+
wasControlledRef.current = isControlled;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (wasControlledRef.current !== isControlled) {
|
|
159
|
+
// eslint-disable-next-line no-console
|
|
160
|
+
console.warn('Shell.Bottom: Switching between controlled and uncontrolled `open` is not supported.');
|
|
161
|
+
wasControlledRef.current = isControlled;
|
|
127
162
|
}
|
|
128
|
-
}, [
|
|
163
|
+
}, [open]);
|
|
164
|
+
|
|
165
|
+
// Controlled sync (responsive handled below)
|
|
166
|
+
React.useEffect(() => {
|
|
167
|
+
if (typeof open === 'undefined') return;
|
|
168
|
+
shell.setBottomMode(open ? 'expanded' : 'collapsed');
|
|
169
|
+
}, [open]);
|
|
170
|
+
|
|
171
|
+
const responsiveNotifiedRef = React.useRef(false);
|
|
172
|
+
|
|
173
|
+
// Controlled responsive open
|
|
174
|
+
const resolvedOpen = useResponsiveValue(open);
|
|
175
|
+
React.useEffect(() => {
|
|
176
|
+
if (typeof resolvedOpen === 'undefined') return;
|
|
177
|
+
const shouldExpand = Boolean(resolvedOpen);
|
|
178
|
+
shell.setBottomMode(shouldExpand ? 'expanded' : 'collapsed');
|
|
179
|
+
}, [resolvedOpen]);
|
|
129
180
|
|
|
181
|
+
const initNotifiedRef = React.useRef(false);
|
|
182
|
+
const lastBottomModeRef = React.useRef<PaneMode | null>(null);
|
|
130
183
|
React.useEffect(() => {
|
|
131
|
-
if (
|
|
132
|
-
|
|
184
|
+
if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.bottomMode === 'expanded') {
|
|
185
|
+
onOpenChange?.(true, { reason: 'init' });
|
|
186
|
+
initNotifiedRef.current = true;
|
|
133
187
|
}
|
|
134
|
-
|
|
188
|
+
if (typeof open === 'undefined') {
|
|
189
|
+
if (lastBottomModeRef.current !== null && lastBottomModeRef.current !== shell.bottomMode) {
|
|
190
|
+
if (!responsiveNotifiedRef.current) {
|
|
191
|
+
onOpenChange?.(shell.bottomMode === 'expanded', { reason: 'toggle' });
|
|
192
|
+
}
|
|
193
|
+
responsiveNotifiedRef.current = false;
|
|
194
|
+
}
|
|
195
|
+
lastBottomModeRef.current = shell.bottomMode;
|
|
196
|
+
}
|
|
197
|
+
}, [shell.bottomMode, open, defaultOpen, onOpenChange]);
|
|
135
198
|
|
|
136
199
|
React.useEffect(() => {
|
|
137
200
|
if (shell.bottomMode === 'expanded') {
|
|
@@ -194,6 +257,7 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
194
257
|
onResizeStart,
|
|
195
258
|
onResizeEnd: (size) => {
|
|
196
259
|
onResizeEnd?.(size);
|
|
260
|
+
emitSizeChange(size, { reason: 'resize' });
|
|
197
261
|
persistenceAdapter?.save?.(size);
|
|
198
262
|
},
|
|
199
263
|
target: 'bottom',
|
|
@@ -209,6 +273,69 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
209
273
|
</PaneResizeContext.Provider>
|
|
210
274
|
) : null;
|
|
211
275
|
|
|
276
|
+
// Strip control/size props from DOM spread (moved above overlay return to keep hook order consistent)
|
|
277
|
+
const {
|
|
278
|
+
defaultOpen: _bottomDefaultOpenIgnored,
|
|
279
|
+
open: _bottomOpenIgnored,
|
|
280
|
+
onOpenChange: _bottomOnOpenChangeIgnored,
|
|
281
|
+
size: _bottomSizeIgnored,
|
|
282
|
+
defaultSize: _bottomDefaultSizeIgnored,
|
|
283
|
+
onSizeChange: _bottomOnSizeChangeIgnored,
|
|
284
|
+
sizeUpdate: _szu,
|
|
285
|
+
sizeUpdateMs: _szums,
|
|
286
|
+
...bottomDomProps
|
|
287
|
+
} = props as any;
|
|
288
|
+
|
|
289
|
+
// Normalize CSS lengths to px (moved above overlay return to keep hook order consistent)
|
|
290
|
+
const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
|
|
291
|
+
if (value == null) return undefined;
|
|
292
|
+
if (typeof value === 'number' && Number.isFinite(value)) return value;
|
|
293
|
+
const str = String(value).trim();
|
|
294
|
+
if (!str) return undefined;
|
|
295
|
+
if (str.endsWith('px')) return Number.parseFloat(str);
|
|
296
|
+
if (str.endsWith('rem')) {
|
|
297
|
+
const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
|
|
298
|
+
return Number.parseFloat(str) * rem;
|
|
299
|
+
}
|
|
300
|
+
if (str.endsWith('%')) {
|
|
301
|
+
const pct = Number.parseFloat(str);
|
|
302
|
+
const base = document.documentElement.clientHeight || window.innerHeight || 0;
|
|
303
|
+
return (pct / 100) * base;
|
|
304
|
+
}
|
|
305
|
+
const n = Number.parseFloat(str);
|
|
306
|
+
return Number.isFinite(n) ? n : undefined;
|
|
307
|
+
}, []);
|
|
308
|
+
|
|
309
|
+
// Apply defaultSize on mount when uncontrolled (moved above overlay return)
|
|
310
|
+
React.useEffect(() => {
|
|
311
|
+
if (!localRef.current) return;
|
|
312
|
+
if (typeof (props as any).size === 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
|
|
313
|
+
const px = normalizeToPx((props as any).defaultSize);
|
|
314
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
315
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
316
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
317
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
318
|
+
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
319
|
+
emitSizeChange(clamped, { reason: 'init' });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
323
|
+
}, []);
|
|
324
|
+
|
|
325
|
+
// Controlled size sync (moved above overlay return)
|
|
326
|
+
React.useEffect(() => {
|
|
327
|
+
if (!localRef.current) return;
|
|
328
|
+
if (typeof (props as any).size === 'undefined') return;
|
|
329
|
+
const px = normalizeToPx((props as any).size);
|
|
330
|
+
if (typeof px === 'number' && Number.isFinite(px)) {
|
|
331
|
+
const minPx = typeof minSize === 'number' ? minSize : undefined;
|
|
332
|
+
const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
|
|
333
|
+
const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
|
|
334
|
+
localRef.current.style.setProperty('--bottom-size', `${clamped}px`);
|
|
335
|
+
emitSizeChange(clamped, { reason: 'controlled' });
|
|
336
|
+
}
|
|
337
|
+
}, [(props as any).size, minSize, maxSize, normalizeToPx, emitSizeChange]);
|
|
338
|
+
|
|
212
339
|
if (isOverlay) {
|
|
213
340
|
const open = shell.bottomMode === 'expanded';
|
|
214
341
|
return (
|
|
@@ -225,13 +352,13 @@ export const Bottom = React.forwardRef<HTMLDivElement, PaneProps>(
|
|
|
225
352
|
|
|
226
353
|
return (
|
|
227
354
|
<div
|
|
228
|
-
{...
|
|
355
|
+
{...bottomDomProps}
|
|
229
356
|
ref={setRef}
|
|
230
357
|
className={classNames('rt-ShellBottom', className)}
|
|
231
358
|
data-mode={shell.bottomMode}
|
|
232
359
|
data-peek={shell.peekTarget === 'bottom' || undefined}
|
|
233
|
-
data-presentation={resolvedPresentation}
|
|
234
|
-
data-open={(isStacked && isExpanded) || undefined}
|
|
360
|
+
data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
|
|
361
|
+
data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
|
|
235
362
|
style={{
|
|
236
363
|
...style,
|
|
237
364
|
['--bottom-size' as any]: `${expandedSize}px`,
|
|
@@ -84,8 +84,16 @@ export const PaneHandle = React.forwardRef<HTMLDivElement, React.ComponentPropsW
|
|
|
84
84
|
handleEl.releasePointerCapture(pointerId);
|
|
85
85
|
} catch {}
|
|
86
86
|
window.removeEventListener('pointermove', handleMove as any);
|
|
87
|
+
document.removeEventListener('pointermove', handleMove as any);
|
|
88
|
+
window.removeEventListener('mousemove', handleMove as any);
|
|
89
|
+
document.removeEventListener('mousemove', handleMove as any);
|
|
90
|
+
handleEl.removeEventListener('pointermove', handleMove as any);
|
|
87
91
|
window.removeEventListener('pointerup', handleUp as any);
|
|
92
|
+
document.removeEventListener('pointerup', handleUp as any);
|
|
93
|
+
window.removeEventListener('mouseup', handleUp as any);
|
|
94
|
+
document.removeEventListener('mouseup', handleUp as any);
|
|
88
95
|
window.removeEventListener('pointercancel', handleUp as any);
|
|
96
|
+
document.removeEventListener('pointercancel', handleUp as any);
|
|
89
97
|
window.removeEventListener('keydown', handleKey as any);
|
|
90
98
|
handleEl.removeEventListener('lostpointercapture', handleUp as any);
|
|
91
99
|
container.removeAttribute('data-resizing');
|
|
@@ -120,8 +128,17 @@ export const PaneHandle = React.forwardRef<HTMLDivElement, React.ComponentPropsW
|
|
|
120
128
|
}
|
|
121
129
|
};
|
|
122
130
|
window.addEventListener('pointermove', handleMove as any);
|
|
131
|
+
document.addEventListener('pointermove', handleMove as any);
|
|
132
|
+
// Fallbacks for environments that don't fully support PointerEvent on window
|
|
133
|
+
window.addEventListener('mousemove', handleMove as any);
|
|
134
|
+
document.addEventListener('mousemove', handleMove as any);
|
|
135
|
+
handleEl.addEventListener('pointermove', handleMove as any);
|
|
123
136
|
window.addEventListener('pointerup', handleUp as any);
|
|
137
|
+
document.addEventListener('pointerup', handleUp as any);
|
|
138
|
+
window.addEventListener('mouseup', handleUp as any);
|
|
139
|
+
document.addEventListener('mouseup', handleUp as any);
|
|
124
140
|
window.addEventListener('pointercancel', handleUp as any);
|
|
141
|
+
document.addEventListener('pointercancel', handleUp as any);
|
|
125
142
|
window.addEventListener('keydown', handleKey as any);
|
|
126
143
|
handleEl.addEventListener('lostpointercapture', handleUp as any);
|
|
127
144
|
activeCleanupRef.current = cleanup;
|
|
@@ -132,13 +149,20 @@ export const PaneHandle = React.forwardRef<HTMLDivElement, React.ComponentPropsW
|
|
|
132
149
|
onKeyDown={(e) => {
|
|
133
150
|
if (!containerRef.current) return;
|
|
134
151
|
const container = containerRef.current;
|
|
135
|
-
const
|
|
152
|
+
const rawCurrent = getComputedStyle(container).getPropertyValue(cssVarName);
|
|
153
|
+
const parsedCurrent = Number.parseFloat(rawCurrent.trim());
|
|
154
|
+
const current = Number.isFinite(parsedCurrent) ? parsedCurrent : defaultSize;
|
|
136
155
|
const clamp = (v: number) => Math.min(Math.max(v, minSize), maxSize);
|
|
137
156
|
const step = e.shiftKey ? 32 : 8;
|
|
138
157
|
let delta = 0;
|
|
139
158
|
if (orientation === 'vertical') {
|
|
140
|
-
|
|
141
|
-
|
|
159
|
+
const docDir = typeof document !== 'undefined' ? document.dir : undefined;
|
|
160
|
+
const cssDir = getComputedStyle(container).direction;
|
|
161
|
+
const hasRtlAncestor = !!(container.closest && container.closest('[dir="rtl"]'));
|
|
162
|
+
const isRtl = docDir === 'rtl' || cssDir === 'rtl' || hasRtlAncestor;
|
|
163
|
+
if (e.key === 'ArrowRight')
|
|
164
|
+
delta = isRtl ? -step : step; // inline-end
|
|
165
|
+
else if (e.key === 'ArrowLeft') delta = isRtl ? step : -step; // inline-start
|
|
142
166
|
} else {
|
|
143
167
|
if (e.key === 'ArrowDown') delta = step;
|
|
144
168
|
else if (e.key === 'ArrowUp') delta = -step;
|
|
@@ -166,7 +190,8 @@ export const PaneHandle = React.forwardRef<HTMLDivElement, React.ComponentPropsW
|
|
|
166
190
|
if (delta !== 0) {
|
|
167
191
|
e.preventDefault();
|
|
168
192
|
onResizeStart?.(current);
|
|
169
|
-
const
|
|
193
|
+
const signedDelta = orientation === 'vertical' ? (edge === 'start' ? -delta : delta) : delta;
|
|
194
|
+
const next = clamp(current + signedDelta);
|
|
170
195
|
container.style.setProperty(cssVarName, `${next}px`);
|
|
171
196
|
(e.currentTarget as HTMLElement).setAttribute('aria-valuenow', String(next));
|
|
172
197
|
onResize?.(next);
|