@kushagradhawan/kookie-ui 0.1.50 → 0.1.52

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 (94) hide show
  1. package/components.css +582 -116
  2. package/dist/cjs/components/_internal/shell-bottom.d.ts +31 -5
  3. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  4. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  5. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  6. package/dist/cjs/components/_internal/shell-handles.d.ts.map +1 -1
  7. package/dist/cjs/components/_internal/shell-handles.js +1 -1
  8. package/dist/cjs/components/_internal/shell-handles.js.map +3 -3
  9. package/dist/cjs/components/_internal/shell-inspector.d.ts +23 -5
  10. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  11. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  12. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  13. package/dist/cjs/components/_internal/shell-sidebar.d.ts +24 -6
  14. package/dist/cjs/components/_internal/shell-sidebar.d.ts.map +1 -1
  15. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  16. package/dist/cjs/components/_internal/shell-sidebar.js.map +3 -3
  17. package/dist/cjs/components/chatbar.d.ts +21 -2
  18. package/dist/cjs/components/chatbar.d.ts.map +1 -1
  19. package/dist/cjs/components/chatbar.js +1 -1
  20. package/dist/cjs/components/chatbar.js.map +3 -3
  21. package/dist/cjs/components/shell.context.d.ts +88 -1
  22. package/dist/cjs/components/shell.context.d.ts.map +1 -1
  23. package/dist/cjs/components/shell.context.js +1 -1
  24. package/dist/cjs/components/shell.context.js.map +3 -3
  25. package/dist/cjs/components/shell.d.ts +51 -13
  26. package/dist/cjs/components/shell.d.ts.map +1 -1
  27. package/dist/cjs/components/shell.hooks.d.ts +7 -1
  28. package/dist/cjs/components/shell.hooks.d.ts.map +1 -1
  29. package/dist/cjs/components/shell.hooks.js +1 -1
  30. package/dist/cjs/components/shell.hooks.js.map +3 -3
  31. package/dist/cjs/components/shell.js +1 -1
  32. package/dist/cjs/components/shell.js.map +3 -3
  33. package/dist/cjs/components/shell.types.d.ts +1 -0
  34. package/dist/cjs/components/shell.types.d.ts.map +1 -1
  35. package/dist/cjs/components/shell.types.js +1 -1
  36. package/dist/cjs/components/shell.types.js.map +2 -2
  37. package/dist/esm/components/_internal/shell-bottom.d.ts +31 -5
  38. package/dist/esm/components/_internal/shell-bottom.d.ts.map +1 -1
  39. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  40. package/dist/esm/components/_internal/shell-bottom.js.map +3 -3
  41. package/dist/esm/components/_internal/shell-handles.d.ts.map +1 -1
  42. package/dist/esm/components/_internal/shell-handles.js +1 -1
  43. package/dist/esm/components/_internal/shell-handles.js.map +3 -3
  44. package/dist/esm/components/_internal/shell-inspector.d.ts +23 -5
  45. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  46. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  47. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  48. package/dist/esm/components/_internal/shell-sidebar.d.ts +24 -6
  49. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  50. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  51. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  52. package/dist/esm/components/chatbar.d.ts +21 -2
  53. package/dist/esm/components/chatbar.d.ts.map +1 -1
  54. package/dist/esm/components/chatbar.js +1 -1
  55. package/dist/esm/components/chatbar.js.map +3 -3
  56. package/dist/esm/components/shell.context.d.ts +88 -1
  57. package/dist/esm/components/shell.context.d.ts.map +1 -1
  58. package/dist/esm/components/shell.context.js +1 -1
  59. package/dist/esm/components/shell.context.js.map +3 -3
  60. package/dist/esm/components/shell.d.ts +51 -13
  61. package/dist/esm/components/shell.d.ts.map +1 -1
  62. package/dist/esm/components/shell.hooks.d.ts +7 -1
  63. package/dist/esm/components/shell.hooks.d.ts.map +1 -1
  64. package/dist/esm/components/shell.hooks.js +1 -1
  65. package/dist/esm/components/shell.hooks.js.map +3 -3
  66. package/dist/esm/components/shell.js +1 -1
  67. package/dist/esm/components/shell.js.map +3 -3
  68. package/dist/esm/components/shell.types.d.ts +1 -0
  69. package/dist/esm/components/shell.types.d.ts.map +1 -1
  70. package/dist/esm/components/shell.types.js.map +2 -2
  71. package/package.json +14 -3
  72. package/schemas/base-button.json +1 -1
  73. package/schemas/button.json +1 -1
  74. package/schemas/icon-button.json +1 -1
  75. package/schemas/index.json +6 -6
  76. package/schemas/toggle-button.json +1 -1
  77. package/schemas/toggle-icon-button.json +1 -1
  78. package/src/components/_internal/base-menu.css +16 -16
  79. package/src/components/_internal/base-sidebar-menu.css +23 -20
  80. package/src/components/_internal/base-sidebar.css +13 -0
  81. package/src/components/_internal/shell-bottom.tsx +176 -49
  82. package/src/components/_internal/shell-handles.tsx +29 -4
  83. package/src/components/_internal/shell-inspector.tsx +175 -43
  84. package/src/components/_internal/shell-sidebar.tsx +177 -93
  85. package/src/components/chatbar.css +240 -21
  86. package/src/components/chatbar.tsx +280 -291
  87. package/src/components/sheet.css +8 -16
  88. package/src/components/shell.context.tsx +79 -3
  89. package/src/components/shell.css +0 -1
  90. package/src/components/shell.hooks.ts +35 -0
  91. package/src/components/shell.tsx +574 -235
  92. package/src/components/shell.types.ts +2 -0
  93. package/src/components/sidebar.css +2 -2
  94. package/styles.css +582 -116
@@ -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 current = parseFloat(getComputedStyle(container).getPropertyValue(cssVarName) || `${defaultSize}`);
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
- if (e.key === 'ArrowRight') delta = step;
141
- else if (e.key === 'ArrowLeft') delta = -step;
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 next = clamp(current + (edge === 'start' && orientation === 'vertical' ? -delta : delta));
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);
@@ -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 { InspectorHandle, 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?: PaneMode;
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,29 @@ interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
32
30
  persistence?: PaneSizePersistence;
33
31
  }
34
32
 
35
- type InspectorComponent = React.ForwardRefExoticComponent<PaneProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof InspectorHandle };
33
+ type InspectorOpenChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
34
+ type InspectorControlledProps = { open: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: InspectorOpenChangeMeta) => void; defaultOpen?: never };
35
+ type InspectorUncontrolledProps = { defaultOpen?: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: InspectorOpenChangeMeta) => void; open?: never };
36
+ type InspectorSizeChangeMeta = { reason: 'init' | 'resize' | 'controlled' };
37
+ type InspectorPublicProps = PaneProps &
38
+ (InspectorControlledProps | InspectorUncontrolledProps) & {
39
+ onSizeChange?: (size: number, meta: InspectorSizeChangeMeta) => void;
40
+ sizeUpdate?: 'throttle' | 'debounce';
41
+ sizeUpdateMs?: number;
42
+ };
36
43
 
37
- export const Inspector = React.forwardRef<HTMLDivElement, PaneProps>(
44
+ type InspectorComponent = React.ForwardRefExoticComponent<InspectorPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof InspectorHandle };
45
+
46
+ export const Inspector = React.forwardRef<HTMLDivElement, InspectorPublicProps>(
38
47
  (
39
48
  {
40
49
  className,
41
50
  presentation = { initial: 'overlay', lg: 'fixed' },
42
- mode,
43
- defaultMode = 'collapsed',
44
- onModeChange,
51
+ // removed legacy props
52
+ // new API
53
+ defaultOpen,
54
+ open,
55
+ onOpenChange,
45
56
  expandedSize = 320,
46
57
  minSize = 200,
47
58
  maxSize = 500,
@@ -80,47 +91,104 @@ export const Inspector = React.forwardRef<HTMLDivElement, PaneProps>(
80
91
  const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === InspectorHandle);
81
92
  const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === InspectorHandle));
82
93
 
83
- const resolveResponsiveMode = React.useCallback((): PaneMode => {
84
- if (typeof defaultMode === 'string') return defaultMode as PaneMode;
85
- const dm = defaultMode as Partial<Record<Breakpoint, PaneMode>> | undefined;
86
- if (dm && dm[shell.currentBreakpoint as Breakpoint]) {
87
- return dm[shell.currentBreakpoint as Breakpoint] as PaneMode;
94
+ // Throttled/debounced emitter for onSizeChange
95
+ const emitSizeChange = React.useMemo(() => {
96
+ const cb = (props as any).onSizeChange as undefined | ((s: number, meta: InspectorSizeChangeMeta) => void);
97
+ const strategy = (props as any).sizeUpdate as undefined | 'throttle' | 'debounce';
98
+ const ms = (props as any).sizeUpdateMs ?? 50;
99
+ if (!cb) return () => {};
100
+ if (strategy === 'debounce') {
101
+ let t: any = null;
102
+ return (s: number, meta: InspectorSizeChangeMeta) => {
103
+ if (t) clearTimeout(t);
104
+ t = setTimeout(() => {
105
+ cb(s, meta);
106
+ }, ms);
107
+ };
88
108
  }
89
- const bpKeys = Object.keys(BREAKPOINTS) as Array<keyof typeof BREAKPOINTS>;
90
- const order: Breakpoint[] = ([...bpKeys].reverse() as Breakpoint[]).concat('initial' as Breakpoint);
91
- const startIdx = order.indexOf(shell.currentBreakpoint as Breakpoint);
92
- for (let i = startIdx + 1; i < order.length; i++) {
93
- const bp = order[i];
94
- if (dm && dm[bp]) {
95
- return dm[bp] as PaneMode;
96
- }
109
+ if (strategy === 'throttle') {
110
+ let last = 0;
111
+ return (s: number, meta: InspectorSizeChangeMeta) => {
112
+ const now = Date.now();
113
+ if (now - last >= ms) {
114
+ last = now;
115
+ cb(s, meta);
116
+ }
117
+ };
97
118
  }
98
- return 'collapsed';
99
- }, [defaultMode, shell.currentBreakpoint]);
119
+ return (s: number, meta: InspectorSizeChangeMeta) => cb(s, meta);
120
+ }, [(props as any).onSizeChange, (props as any).sizeUpdate, (props as any).sizeUpdateMs]);
100
121
 
101
- const lastInspectorBpRef = React.useRef<Breakpoint | null>(null);
102
- React.useEffect(() => {
103
- if (mode !== undefined) return;
104
- if (!shell.currentBreakpointReady) return;
105
- if (lastInspectorBpRef.current === shell.currentBreakpoint) return;
106
- lastInspectorBpRef.current = shell.currentBreakpoint as Breakpoint;
107
- const next = resolveResponsiveMode();
108
- if (next !== shell.inspectorMode) {
109
- shell.setInspectorMode(next);
122
+ // Dev guards
123
+ const wasControlledRef = React.useRef<boolean | null>(null);
124
+ if (process.env.NODE_ENV !== 'production') {
125
+ if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
126
+ // eslint-disable-next-line no-console
127
+ console.error('Shell.Inspector: Do not pass both `open` and `defaultOpen`. Choose one.');
110
128
  }
111
- }, [mode, shell.currentBreakpoint, shell.currentBreakpointReady, resolveResponsiveMode, shell.inspectorMode, shell.setInspectorMode]);
129
+ }
112
130
 
131
+ // Warn on controlled/uncontrolled mode switch
113
132
  React.useEffect(() => {
114
- if (mode !== undefined && shell.inspectorMode !== mode) {
115
- shell.setInspectorMode(mode);
133
+ const isControlled = typeof open !== 'undefined';
134
+ if (wasControlledRef.current === null) {
135
+ wasControlledRef.current = isControlled;
136
+ return;
137
+ }
138
+ if (wasControlledRef.current !== isControlled) {
139
+ // eslint-disable-next-line no-console
140
+ console.warn('Shell.Inspector: Switching between controlled and uncontrolled `open` is not supported.');
141
+ wasControlledRef.current = isControlled;
116
142
  }
117
- }, [mode, shell]);
143
+ }, [open]);
144
+
145
+ const responsiveNotifiedRef = React.useRef(false);
146
+ const didInitFromDefaultOpenRef = React.useRef(false);
147
+ const resolvedDefaultOpen = useResponsiveValue(defaultOpen);
148
+ React.useEffect(() => {
149
+ if (!shell.currentBreakpointReady) return;
150
+ if (didInitFromDefaultOpenRef.current) return;
151
+ if (typeof open !== 'undefined') return; // controlled ignores default
152
+ if (typeof defaultOpen === 'undefined') return;
153
+ const initialOpen = Boolean(resolvedDefaultOpen);
154
+ shell.setInspectorMode(initialOpen ? 'expanded' : 'collapsed');
155
+ if (initialOpen) onOpenChange?.(true, { reason: 'init' });
156
+ didInitFromDefaultOpenRef.current = true;
157
+ }, [shell.currentBreakpointReady, resolvedDefaultOpen, defaultOpen, open, onOpenChange]);
118
158
 
159
+ // Controlled responsive open
160
+ const resolvedOpen = useResponsiveValue(open);
119
161
  React.useEffect(() => {
120
- if (mode === undefined) {
121
- onModeChange?.(shell.inspectorMode);
162
+ if (typeof resolvedOpen === 'undefined') return;
163
+ const shouldExpand = Boolean(resolvedOpen);
164
+ if (shouldExpand && shell.inspectorMode !== 'expanded') shell.setInspectorMode('expanded');
165
+ if (!shouldExpand && shell.inspectorMode !== 'collapsed') shell.setInspectorMode('collapsed');
166
+ }, [resolvedOpen, shell.inspectorMode]);
167
+
168
+ // Removed boolean-only mount init; handled in responsive init effect above
169
+
170
+ // Removed: boolean-only controlled sync. Use responsive-resolved effect below instead.
171
+
172
+ const initNotifiedRef = React.useRef(false);
173
+ const lastInspectorModeRef = React.useRef<PaneMode | null>(null);
174
+ React.useEffect(() => {
175
+ // Notify init open
176
+ if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.inspectorMode === 'expanded') {
177
+ onOpenChange?.(true, { reason: 'init' });
178
+ initNotifiedRef.current = true;
122
179
  }
123
- }, [shell.inspectorMode, mode, onModeChange]);
180
+
181
+ // Notify toggles when uncontrolled (avoid double-notify after responsive change)
182
+ if (typeof open === 'undefined') {
183
+ if (lastInspectorModeRef.current !== null && lastInspectorModeRef.current !== shell.inspectorMode) {
184
+ if (!responsiveNotifiedRef.current) {
185
+ onOpenChange?.(shell.inspectorMode === 'expanded', { reason: 'toggle' });
186
+ }
187
+ responsiveNotifiedRef.current = false;
188
+ }
189
+ lastInspectorModeRef.current = shell.inspectorMode;
190
+ }
191
+ }, [shell.inspectorMode, open, defaultOpen, onOpenChange]);
124
192
 
125
193
  React.useEffect(() => {
126
194
  if (shell.inspectorMode === 'expanded') {
@@ -184,6 +252,7 @@ export const Inspector = React.forwardRef<HTMLDivElement, PaneProps>(
184
252
  onResizeStart,
185
253
  onResizeEnd: (size) => {
186
254
  onResizeEnd?.(size);
255
+ emitSizeChange(size, { reason: 'resize' });
187
256
  persistenceAdapter?.save?.(size);
188
257
  },
189
258
  target: 'inspector',
@@ -199,6 +268,56 @@ export const Inspector = React.forwardRef<HTMLDivElement, PaneProps>(
199
268
  </PaneResizeContext.Provider>
200
269
  ) : null;
201
270
 
271
+ // Normalize CSS lengths to px
272
+ const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
273
+ if (value == null) return undefined;
274
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
275
+ const str = String(value).trim();
276
+ if (!str) return undefined;
277
+ if (str.endsWith('px')) return Number.parseFloat(str);
278
+ if (str.endsWith('rem')) {
279
+ const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
280
+ return Number.parseFloat(str) * rem;
281
+ }
282
+ if (str.endsWith('%')) {
283
+ const pct = Number.parseFloat(str);
284
+ const base = document.documentElement.clientWidth || window.innerWidth || 0;
285
+ return (pct / 100) * base;
286
+ }
287
+ const n = Number.parseFloat(str);
288
+ return Number.isFinite(n) ? n : undefined;
289
+ }, []);
290
+
291
+ // Apply defaultSize on mount when uncontrolled
292
+ React.useEffect(() => {
293
+ if (!localRef.current) return;
294
+ if (typeof (props as any).size === 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
295
+ const px = normalizeToPx((props as any).defaultSize);
296
+ if (typeof px === 'number' && Number.isFinite(px)) {
297
+ const minPx = typeof minSize === 'number' ? minSize : undefined;
298
+ const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
299
+ const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
300
+ localRef.current.style.setProperty('--inspector-size', `${clamped}px`);
301
+ emitSizeChange(clamped, { reason: 'init' });
302
+ }
303
+ }
304
+ // eslint-disable-next-line react-hooks/exhaustive-deps
305
+ }, []);
306
+
307
+ // Controlled size sync
308
+ React.useEffect(() => {
309
+ if (!localRef.current) return;
310
+ if (typeof (props as any).size === 'undefined') return;
311
+ const px = normalizeToPx((props as any).size);
312
+ if (typeof px === 'number' && Number.isFinite(px)) {
313
+ const minPx = typeof minSize === 'number' ? minSize : undefined;
314
+ const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
315
+ const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
316
+ localRef.current.style.setProperty('--inspector-size', `${clamped}px`);
317
+ emitSizeChange(clamped, { reason: 'controlled' });
318
+ }
319
+ }, [(props as any).size, minSize, maxSize, normalizeToPx, emitSizeChange]);
320
+
202
321
  if (isOverlay) {
203
322
  const open = shell.inspectorMode === 'expanded';
204
323
  return (
@@ -213,15 +332,28 @@ export const Inspector = React.forwardRef<HTMLDivElement, PaneProps>(
213
332
  );
214
333
  }
215
334
 
335
+ // Strip control/size props from DOM spread
336
+ const {
337
+ defaultOpen: _inspectorDefaultOpenIgnored,
338
+ open: _inspectorOpenIgnored,
339
+ onOpenChange: _inspectorOnOpenChangeIgnored,
340
+ size: _sz,
341
+ defaultSize: _dsz,
342
+ onSizeChange: _osc,
343
+ sizeUpdate: _szu,
344
+ sizeUpdateMs: _szums,
345
+ ...inspectorDomProps
346
+ } = props as any;
347
+
216
348
  return (
217
349
  <div
218
- {...props}
350
+ {...inspectorDomProps}
219
351
  ref={setRef}
220
352
  className={classNames('rt-ShellInspector', className)}
221
353
  data-mode={shell.inspectorMode}
222
354
  data-peek={shell.peekTarget === 'inspector' || undefined}
223
- data-presentation={resolvedPresentation}
224
- data-open={(isStacked && isExpanded) || undefined}
355
+ data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
356
+ data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
225
357
  style={{
226
358
  ...style,
227
359
  ['--inspector-size' as any]: `${expandedSize}px`,