@kushagradhawan/kookie-ui 0.1.69 → 0.1.71

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 (87) hide show
  1. package/dist/cjs/components/_internal/shell-bottom.d.ts +2 -21
  2. package/dist/cjs/components/_internal/shell-bottom.d.ts.map +1 -1
  3. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  4. package/dist/cjs/components/_internal/shell-bottom.js.map +3 -3
  5. package/dist/cjs/components/_internal/shell-inspector.d.ts +10 -21
  6. package/dist/cjs/components/_internal/shell-inspector.d.ts.map +1 -1
  7. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  8. package/dist/cjs/components/_internal/shell-inspector.js.map +3 -3
  9. package/dist/cjs/components/_internal/shell-prop-helpers.d.ts +7 -0
  10. package/dist/cjs/components/_internal/shell-prop-helpers.d.ts.map +1 -0
  11. package/dist/cjs/components/_internal/shell-prop-helpers.js +2 -0
  12. package/dist/cjs/components/_internal/shell-prop-helpers.js.map +7 -0
  13. package/dist/cjs/components/_internal/shell-sidebar.d.ts +4 -21
  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 +12 -3
  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/schemas/shell.schema.d.ts +70 -70
  22. package/dist/cjs/components/shell.context.d.ts +1 -0
  23. package/dist/cjs/components/shell.context.d.ts.map +1 -1
  24. package/dist/cjs/components/shell.context.js.map +2 -2
  25. package/dist/cjs/components/shell.d.ts +6 -26
  26. package/dist/cjs/components/shell.d.ts.map +1 -1
  27. package/dist/cjs/components/shell.hooks.d.ts +19 -2
  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 +21 -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 +2 -21
  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-inspector.d.ts +10 -21
  42. package/dist/esm/components/_internal/shell-inspector.d.ts.map +1 -1
  43. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  44. package/dist/esm/components/_internal/shell-inspector.js.map +3 -3
  45. package/dist/esm/components/_internal/shell-prop-helpers.d.ts +7 -0
  46. package/dist/esm/components/_internal/shell-prop-helpers.d.ts.map +1 -0
  47. package/dist/esm/components/_internal/shell-prop-helpers.js +2 -0
  48. package/dist/esm/components/_internal/shell-prop-helpers.js.map +7 -0
  49. package/dist/esm/components/_internal/shell-sidebar.d.ts +4 -21
  50. package/dist/esm/components/_internal/shell-sidebar.d.ts.map +1 -1
  51. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  52. package/dist/esm/components/_internal/shell-sidebar.js.map +3 -3
  53. package/dist/esm/components/chatbar.d.ts +12 -3
  54. package/dist/esm/components/chatbar.d.ts.map +1 -1
  55. package/dist/esm/components/chatbar.js +1 -1
  56. package/dist/esm/components/chatbar.js.map +3 -3
  57. package/dist/esm/components/schemas/shell.schema.d.ts +70 -70
  58. package/dist/esm/components/shell.context.d.ts +1 -0
  59. package/dist/esm/components/shell.context.d.ts.map +1 -1
  60. package/dist/esm/components/shell.context.js.map +2 -2
  61. package/dist/esm/components/shell.d.ts +6 -26
  62. package/dist/esm/components/shell.d.ts.map +1 -1
  63. package/dist/esm/components/shell.hooks.d.ts +19 -2
  64. package/dist/esm/components/shell.hooks.d.ts.map +1 -1
  65. package/dist/esm/components/shell.hooks.js +1 -1
  66. package/dist/esm/components/shell.hooks.js.map +3 -3
  67. package/dist/esm/components/shell.js +1 -1
  68. package/dist/esm/components/shell.js.map +3 -3
  69. package/dist/esm/components/shell.types.d.ts +21 -0
  70. package/dist/esm/components/shell.types.d.ts.map +1 -1
  71. package/dist/esm/components/shell.types.js.map +2 -2
  72. package/package.json +1 -1
  73. package/schemas/base-button.json +1 -1
  74. package/schemas/button.json +1 -1
  75. package/schemas/icon-button.json +1 -1
  76. package/schemas/index.json +6 -6
  77. package/schemas/toggle-button.json +1 -1
  78. package/schemas/toggle-icon-button.json +1 -1
  79. package/src/components/_internal/shell-bottom.tsx +305 -321
  80. package/src/components/_internal/shell-inspector.tsx +310 -320
  81. package/src/components/_internal/shell-prop-helpers.ts +53 -0
  82. package/src/components/_internal/shell-sidebar.tsx +370 -384
  83. package/src/components/chatbar.tsx +22 -6
  84. package/src/components/shell.context.tsx +1 -0
  85. package/src/components/shell.hooks.ts +67 -2
  86. package/src/components/shell.tsx +186 -200
  87. package/src/components/shell.types.ts +23 -0
@@ -3,39 +3,22 @@ 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, useResponsiveValue } from '../shell.hooks.js';
6
+ import { useResponsivePresentation, useResponsiveInitialState } 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';
10
- import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation } from '../shell.types.js';
11
-
12
- interface PaneProps extends React.ComponentPropsWithoutRef<'div'> {
13
- presentation?: ResponsivePresentation;
14
- // legacy mode removed
15
- expandedSize?: number;
16
- minSize?: number;
17
- maxSize?: number;
18
- resizable?: boolean;
19
- collapsible?: boolean;
20
- onExpand?: () => void;
21
- onCollapse?: () => void;
22
- onResize?: (size: number) => void;
23
- resizer?: React.ReactNode;
24
- onResizeStart?: (size: number) => void;
25
- onResizeEnd?: (size: number) => void;
26
- snapPoints?: number[];
27
- snapTolerance?: number;
28
- collapseThreshold?: number;
29
- paneId?: string;
30
- persistence?: PaneSizePersistence;
31
- }
10
+ import type { Breakpoint, PaneMode, PaneSizePersistence, ResponsivePresentation, PaneBaseProps } from '../shell.types.js';
11
+ import { extractPaneDomProps, mapResponsiveBooleanToPaneMode } from './shell-prop-helpers.js';
32
12
 
33
13
  type InspectorOpenChangeMeta = { reason: 'init' | 'toggle' | 'responsive' };
34
14
  type InspectorControlledProps = { open: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: InspectorOpenChangeMeta) => void; defaultOpen?: never };
35
15
  type InspectorUncontrolledProps = { defaultOpen?: boolean | Partial<Record<Breakpoint, boolean>>; onOpenChange?: (open: boolean, meta: InspectorOpenChangeMeta) => void; open?: never };
36
16
  type InspectorSizeChangeMeta = { reason: 'init' | 'resize' | 'controlled' };
37
- type InspectorPublicProps = PaneProps &
38
- (InspectorControlledProps | InspectorUncontrolledProps) & {
17
+ type InspectorSizeControlledProps = { size: number | string; defaultSize?: never };
18
+ type InspectorSizeUncontrolledProps = { defaultSize?: number | string; size?: never };
19
+ type InspectorPublicProps = PaneBaseProps &
20
+ (InspectorControlledProps | InspectorUncontrolledProps) &
21
+ (InspectorSizeControlledProps | InspectorSizeUncontrolledProps) & {
39
22
  onSizeChange?: (size: number, meta: InspectorSizeChangeMeta) => void;
40
23
  sizeUpdate?: 'throttle' | 'debounce';
41
24
  sizeUpdateMs?: number;
@@ -43,334 +26,341 @@ type InspectorPublicProps = PaneProps &
43
26
 
44
27
  type InspectorComponent = React.ForwardRefExoticComponent<InspectorPublicProps & React.RefAttributes<HTMLDivElement>> & { Handle: typeof InspectorHandle };
45
28
 
46
- export const Inspector = React.forwardRef<HTMLDivElement, InspectorPublicProps>(
47
- (
48
- {
49
- className,
50
- presentation = { initial: 'overlay', lg: 'fixed' },
51
- // removed legacy props
52
- // new API
53
- defaultOpen,
54
- open,
55
- onOpenChange,
56
- expandedSize = 320,
57
- minSize = 200,
58
- maxSize = 500,
59
- resizable = false,
60
- collapsible = true,
61
- onExpand,
62
- onCollapse,
63
- onResize,
64
- onResizeStart,
65
- onResizeEnd,
66
- snapPoints,
67
- snapTolerance,
68
- collapseThreshold,
69
- paneId,
70
- persistence,
71
- children,
72
- style,
73
- ...props
29
+ const INSPECTOR_DOM_PROP_KEYS = [
30
+ 'className',
31
+ 'children',
32
+ 'defaultOpen',
33
+ 'open',
34
+ 'onOpenChange',
35
+ 'size',
36
+ 'defaultSize',
37
+ 'onSizeChange',
38
+ 'sizeUpdate',
39
+ 'sizeUpdateMs',
40
+ 'style',
41
+ ] as const satisfies readonly (keyof InspectorPublicProps)[];
42
+
43
+ export const Inspector = React.forwardRef<HTMLDivElement, InspectorPublicProps>((initialProps, ref) => {
44
+ const {
45
+ className,
46
+ presentation = { initial: 'overlay', lg: 'fixed' },
47
+ defaultOpen,
48
+ open,
49
+ onOpenChange,
50
+ expandedSize = 320,
51
+ minSize = 200,
52
+ maxSize = 500,
53
+ resizable = false,
54
+ collapsible = true,
55
+ onExpand,
56
+ onCollapse,
57
+ onResize,
58
+ onResizeStart,
59
+ onResizeEnd,
60
+ snapPoints,
61
+ snapTolerance,
62
+ collapseThreshold,
63
+ paneId,
64
+ persistence,
65
+ children,
66
+ style,
67
+ onSizeChange,
68
+ sizeUpdate,
69
+ sizeUpdateMs = 50,
70
+ size,
71
+ defaultSize,
72
+ } = initialProps;
73
+ const inspectorDomProps = extractPaneDomProps(initialProps, INSPECTOR_DOM_PROP_KEYS);
74
+ const shell = useShell();
75
+ const resolvedPresentation = useResponsivePresentation(presentation);
76
+ const isOverlay = resolvedPresentation === 'overlay';
77
+ const isStacked = resolvedPresentation === 'stacked';
78
+ const localRef = React.useRef<HTMLDivElement | null>(null);
79
+ const setRef = React.useCallback(
80
+ (node: HTMLDivElement | null) => {
81
+ localRef.current = node;
82
+ if (typeof ref === 'function') ref(node);
83
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
74
84
  },
75
- ref,
76
- ) => {
77
- const shell = useShell();
78
- const resolvedPresentation = useResponsivePresentation(presentation);
79
- const isOverlay = resolvedPresentation === 'overlay';
80
- const isStacked = resolvedPresentation === 'stacked';
81
- const localRef = React.useRef<HTMLDivElement | null>(null);
82
- const setRef = React.useCallback(
83
- (node: HTMLDivElement | null) => {
84
- localRef.current = node;
85
- if (typeof ref === 'function') ref(node);
86
- else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
87
- },
88
- [ref],
89
- );
90
- const childArray = React.Children.toArray(children) as React.ReactElement[];
91
- const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === InspectorHandle);
92
- const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === InspectorHandle));
85
+ [ref],
86
+ );
87
+ const childArray = React.Children.toArray(children) as React.ReactElement[];
88
+ const handleChildren = childArray.filter((el: React.ReactElement) => React.isValidElement(el) && el.type === InspectorHandle);
89
+ const contentChildren = childArray.filter((el: React.ReactElement) => !(React.isValidElement(el) && el.type === InspectorHandle));
93
90
 
94
- // Throttled/debounced emitter for onSizeChange
95
- const onSizeChange = (props as any).onSizeChange;
96
- const sizeUpdate = (props as any).sizeUpdate;
97
- const sizeUpdateMs = (props as any).sizeUpdateMs;
98
- const emitSizeChange = React.useMemo(() => {
99
- const cb = onSizeChange as undefined | ((s: number, meta: InspectorSizeChangeMeta) => void);
100
- const strategy = sizeUpdate as undefined | 'throttle' | 'debounce';
101
- const ms = sizeUpdateMs ?? 50;
102
- if (!cb) return () => {};
103
- if (strategy === 'debounce') {
104
- let t: any = null;
105
- return (s: number, meta: InspectorSizeChangeMeta) => {
106
- if (t) clearTimeout(t);
107
- t = setTimeout(() => {
108
- cb(s, meta);
109
- }, ms);
110
- };
111
- }
112
- if (strategy === 'throttle') {
113
- let last = 0;
114
- return (s: number, meta: InspectorSizeChangeMeta) => {
115
- const now = Date.now();
116
- if (now - last >= ms) {
117
- last = now;
118
- cb(s, meta);
119
- }
120
- };
91
+ // Throttled/debounced emitter for onSizeChange
92
+ const normalizedControlledOpen = React.useMemo(() => mapResponsiveBooleanToPaneMode(open), [open]);
93
+ const normalizedDefaultOpen = React.useMemo(() => mapResponsiveBooleanToPaneMode(defaultOpen), [defaultOpen]);
94
+ const openIsResponsive = typeof open === 'object' && open !== null;
95
+ useResponsiveInitialState<PaneMode>({
96
+ controlledValue: normalizedControlledOpen,
97
+ defaultValue: normalizedDefaultOpen,
98
+ currentValue: shell.inspectorMode,
99
+ setValue: shell.setInspectorMode,
100
+ breakpointReady: shell.currentBreakpointReady,
101
+ controlledIsResponsive: openIsResponsive,
102
+ onResponsiveChange: (next) => onOpenChange?.(next === 'expanded', { reason: 'responsive' }),
103
+ onInit: (initial) => {
104
+ if (typeof open === 'undefined') {
105
+ onOpenChange?.(initial === 'expanded', { reason: 'init' });
121
106
  }
122
- return (s: number, meta: InspectorSizeChangeMeta) => cb(s, meta);
123
- }, [onSizeChange, sizeUpdate, sizeUpdateMs]);
107
+ },
108
+ });
124
109
 
125
- // Dev guards
126
- const wasControlledRef = React.useRef<boolean | null>(null);
127
- if (process.env.NODE_ENV !== 'production') {
128
- if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
129
- console.error('Shell.Inspector: Do not pass both `open` and `defaultOpen`. Choose one.');
130
- }
110
+ const emitSizeChange = React.useMemo(() => {
111
+ const cb = onSizeChange as undefined | ((s: number, meta: InspectorSizeChangeMeta) => void);
112
+ const strategy = sizeUpdate as undefined | 'throttle' | 'debounce';
113
+ const ms = sizeUpdateMs ?? 50;
114
+ if (!cb) return () => {};
115
+ if (strategy === 'debounce') {
116
+ let t: any = null;
117
+ return (s: number, meta: InspectorSizeChangeMeta) => {
118
+ if (t) clearTimeout(t);
119
+ t = setTimeout(() => {
120
+ cb(s, meta);
121
+ }, ms);
122
+ };
131
123
  }
124
+ if (strategy === 'throttle') {
125
+ let last = 0;
126
+ return (s: number, meta: InspectorSizeChangeMeta) => {
127
+ const now = Date.now();
128
+ if (now - last >= ms) {
129
+ last = now;
130
+ cb(s, meta);
131
+ }
132
+ };
133
+ }
134
+ return (s: number, meta: InspectorSizeChangeMeta) => cb(s, meta);
135
+ }, [onSizeChange, sizeUpdate, sizeUpdateMs]);
132
136
 
133
- // Warn on controlled/uncontrolled mode switch
134
- React.useEffect(() => {
135
- const isControlled = typeof open !== 'undefined';
136
- if (wasControlledRef.current === null) {
137
- wasControlledRef.current = isControlled;
138
- return;
139
- }
140
- if (wasControlledRef.current !== isControlled) {
141
- console.warn('Shell.Inspector: Switching between controlled and uncontrolled `open` is not supported.');
142
- wasControlledRef.current = isControlled;
143
- }
144
- }, [open]);
145
-
146
- const responsiveNotifiedRef = React.useRef(false);
147
- const didInitFromDefaultOpenRef = React.useRef(false);
148
- const resolvedDefaultOpen = useResponsiveValue(defaultOpen);
149
- React.useEffect(() => {
150
- if (!shell.currentBreakpointReady) return;
151
- if (didInitFromDefaultOpenRef.current) return;
152
- if (typeof open !== 'undefined') return; // controlled ignores default
153
- if (typeof defaultOpen === 'undefined') return;
154
- const initialOpen = Boolean(resolvedDefaultOpen);
155
- shell.setInspectorMode(initialOpen ? 'expanded' : 'collapsed');
156
- if (initialOpen) onOpenChange?.(true, { reason: 'init' });
157
- didInitFromDefaultOpenRef.current = true;
158
- }, [shell, resolvedDefaultOpen, defaultOpen, open, onOpenChange]);
159
-
160
- // Controlled responsive open
161
- const resolvedOpen = useResponsiveValue(open);
162
- React.useEffect(() => {
163
- if (typeof resolvedOpen === 'undefined') return;
164
- const shouldExpand = Boolean(resolvedOpen);
165
- if (shouldExpand && shell.inspectorMode !== 'expanded') shell.setInspectorMode('expanded');
166
- if (!shouldExpand && shell.inspectorMode !== 'collapsed') shell.setInspectorMode('collapsed');
167
- }, [shell, resolvedOpen]);
168
-
169
- // Removed boolean-only mount init; handled in responsive init effect above
137
+ // Dev guards
138
+ const wasControlledRef = React.useRef<boolean | null>(null);
139
+ if (process.env.NODE_ENV !== 'production') {
140
+ if (typeof open !== 'undefined' && typeof defaultOpen !== 'undefined') {
141
+ console.error('Shell.Inspector: Do not pass both `open` and `defaultOpen`. Choose one.');
142
+ }
143
+ }
170
144
 
171
- // Removed: boolean-only controlled sync. Use responsive-resolved effect below instead.
145
+ // Warn on controlled/uncontrolled mode switch
146
+ React.useEffect(() => {
147
+ const isControlled = typeof open !== 'undefined';
148
+ if (wasControlledRef.current === null) {
149
+ wasControlledRef.current = isControlled;
150
+ return;
151
+ }
152
+ if (wasControlledRef.current !== isControlled) {
153
+ console.warn('Shell.Inspector: Switching between controlled and uncontrolled `open` is not supported.');
154
+ wasControlledRef.current = isControlled;
155
+ }
156
+ }, [open]);
172
157
 
173
- const initNotifiedRef = React.useRef(false);
174
- const lastInspectorModeRef = React.useRef<PaneMode | null>(null);
175
- React.useEffect(() => {
176
- // Notify init open
177
- if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.inspectorMode === 'expanded') {
178
- onOpenChange?.(true, { reason: 'init' });
179
- initNotifiedRef.current = true;
180
- }
158
+ const initNotifiedRef = React.useRef(false);
159
+ const lastInspectorModeRef = React.useRef<PaneMode | null>(null);
160
+ React.useEffect(() => {
161
+ // Notify init open
162
+ if (!initNotifiedRef.current && typeof open === 'undefined' && defaultOpen && shell.inspectorMode === 'expanded') {
163
+ onOpenChange?.(true, { reason: 'init' });
164
+ initNotifiedRef.current = true;
165
+ }
181
166
 
182
- // Notify toggles when uncontrolled (avoid double-notify after responsive change)
183
- if (typeof open === 'undefined') {
184
- if (lastInspectorModeRef.current !== null && lastInspectorModeRef.current !== shell.inspectorMode) {
185
- if (!responsiveNotifiedRef.current) {
186
- onOpenChange?.(shell.inspectorMode === 'expanded', { reason: 'toggle' });
187
- }
188
- responsiveNotifiedRef.current = false;
189
- }
190
- lastInspectorModeRef.current = shell.inspectorMode;
167
+ // Notify toggles when uncontrolled (avoid double-notify after responsive change)
168
+ if (typeof open === 'undefined') {
169
+ if (lastInspectorModeRef.current !== null && lastInspectorModeRef.current !== shell.inspectorMode) {
170
+ onOpenChange?.(shell.inspectorMode === 'expanded', { reason: 'toggle' });
191
171
  }
192
- }, [shell.inspectorMode, open, defaultOpen, onOpenChange]);
172
+ lastInspectorModeRef.current = shell.inspectorMode;
173
+ }
174
+ }, [shell.inspectorMode, open, defaultOpen, onOpenChange]);
193
175
 
194
- React.useEffect(() => {
195
- if (shell.inspectorMode === 'expanded') {
196
- onExpand?.();
197
- } else {
198
- onCollapse?.();
199
- }
200
- }, [shell.inspectorMode, onExpand, onCollapse]);
176
+ React.useEffect(() => {
177
+ if (shell.inspectorMode === 'expanded') {
178
+ onExpand?.();
179
+ } else {
180
+ onCollapse?.();
181
+ }
182
+ }, [shell.inspectorMode, onExpand, onCollapse]);
201
183
 
202
- const isExpanded = shell.inspectorMode === 'expanded';
184
+ const isExpanded = shell.inspectorMode === 'expanded';
203
185
 
204
- const persistenceAdapter = React.useMemo(() => {
205
- if (!paneId || persistence) return persistence;
206
- const key = `kookie-ui:shell:inspector:${paneId}`;
207
- const adapter: PaneSizePersistence = {
208
- load: () => {
209
- if (typeof window === 'undefined') return undefined;
186
+ const persistenceAdapter = React.useMemo(() => {
187
+ if (!paneId || persistence) return persistence;
188
+ const key = `kookie-ui:shell:inspector:${paneId}`;
189
+ const adapter: PaneSizePersistence = {
190
+ load: () => {
191
+ if (typeof window === 'undefined') return undefined;
192
+ try {
210
193
  const v = window.localStorage.getItem(key);
211
194
  return v ? Number(v) : undefined;
212
- },
213
- save: (size: number) => {
214
- if (typeof window === 'undefined') return;
195
+ } catch (err) {
196
+ if (process.env.NODE_ENV !== 'production') {
197
+ console.warn('Shell.Inspector: failed to load persisted size', err);
198
+ }
199
+ return undefined;
200
+ }
201
+ },
202
+ save: (size: number) => {
203
+ if (typeof window === 'undefined') return;
204
+ try {
215
205
  window.localStorage.setItem(key, String(size));
216
- },
217
- };
218
- return adapter;
219
- }, [paneId, persistence]);
220
-
221
- React.useEffect(() => {
222
- let mounted = true;
223
- (async () => {
224
- if (!resizable || !persistenceAdapter?.load || isOverlay) return;
225
- const loaded = await persistenceAdapter.load();
226
- if (mounted && typeof loaded === 'number' && localRef.current) {
227
- localRef.current.style.setProperty('--inspector-size', `${loaded}px`);
228
- onResize?.(loaded);
206
+ } catch (err) {
207
+ if (process.env.NODE_ENV !== 'production') {
208
+ console.warn('Shell.Inspector: failed to save persisted size', err);
209
+ }
229
210
  }
230
- })();
231
- return () => {
232
- mounted = false;
233
- };
234
- }, [resizable, persistenceAdapter, onResize, isOverlay]);
211
+ },
212
+ };
213
+ return adapter;
214
+ }, [paneId, persistence]);
235
215
 
236
- const handleEl =
237
- resizable && !isOverlay && isExpanded ? (
238
- <PaneResizeContext.Provider
239
- value={{
240
- containerRef: localRef,
241
- cssVarName: '--inspector-size',
242
- minSize,
243
- maxSize,
244
- defaultSize: expandedSize,
245
- orientation: 'vertical',
246
- edge: 'start',
247
- computeNext: (client, startClient, startSize) => {
248
- const isRtl = getComputedStyle(localRef.current!).direction === 'rtl';
249
- const delta = client - startClient;
250
- return startSize + (isRtl ? delta : -delta);
251
- },
252
- onResize,
253
- onResizeStart,
254
- onResizeEnd: (size) => {
255
- onResizeEnd?.(size);
256
- emitSizeChange(size, { reason: 'resize' });
257
- persistenceAdapter?.save?.(size);
258
- },
259
- target: 'inspector',
260
- collapsible,
261
- snapPoints,
262
- snapTolerance: snapTolerance ?? 8,
263
- collapseThreshold,
264
- requestCollapse: () => shell.setInspectorMode('collapsed'),
265
- requestToggle: () => shell.togglePane('inspector'),
266
- }}
267
- >
268
- {handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
269
- </PaneResizeContext.Provider>
270
- ) : null;
216
+ React.useEffect(() => {
217
+ let mounted = true;
218
+ if (!resizable || !persistenceAdapter?.load || isOverlay) return undefined;
219
+ const loaded = persistenceAdapter.load();
220
+ const applyLoaded = (value?: number) => {
221
+ if (!mounted || typeof value !== 'number' || !localRef.current) return;
222
+ localRef.current.style.setProperty('--inspector-size', `${value}px`);
223
+ onResize?.(value);
224
+ };
225
+ if (loaded instanceof Promise) {
226
+ loaded.then(applyLoaded).catch((err) => {
227
+ if (process.env.NODE_ENV !== 'production') {
228
+ console.warn('Shell.Inspector: failed to load persisted size', err);
229
+ }
230
+ });
231
+ } else {
232
+ applyLoaded(loaded);
233
+ }
234
+ return () => {
235
+ mounted = false;
236
+ };
237
+ }, [resizable, persistenceAdapter, onResize, isOverlay]);
271
238
 
272
- // Normalize CSS lengths to px
273
- const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
274
- if (value == null) return undefined;
275
- if (typeof value === 'number' && Number.isFinite(value)) return value;
276
- const str = String(value).trim();
277
- if (!str) return undefined;
278
- if (str.endsWith('px')) return Number.parseFloat(str);
279
- if (str.endsWith('rem')) {
280
- const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
281
- return Number.parseFloat(str) * rem;
282
- }
283
- if (str.endsWith('%')) {
284
- const pct = Number.parseFloat(str);
285
- const base = document.documentElement.clientWidth || window.innerWidth || 0;
286
- return (pct / 100) * base;
287
- }
288
- const n = Number.parseFloat(str);
289
- return Number.isFinite(n) ? n : undefined;
290
- }, []);
239
+ const handleEl =
240
+ resizable && !isOverlay && isExpanded ? (
241
+ <PaneResizeContext.Provider
242
+ value={{
243
+ containerRef: localRef,
244
+ cssVarName: '--inspector-size',
245
+ minSize,
246
+ maxSize,
247
+ defaultSize: expandedSize,
248
+ orientation: 'vertical',
249
+ edge: 'start',
250
+ computeNext: (client, startClient, startSize) => {
251
+ const isRtl = getComputedStyle(localRef.current!).direction === 'rtl';
252
+ const delta = client - startClient;
253
+ return startSize + (isRtl ? delta : -delta);
254
+ },
255
+ onResize,
256
+ onResizeStart,
257
+ onResizeEnd: (size) => {
258
+ onResizeEnd?.(size);
259
+ emitSizeChange(size, { reason: 'resize' });
260
+ persistenceAdapter?.save?.(size);
261
+ },
262
+ target: 'inspector',
263
+ collapsible,
264
+ snapPoints,
265
+ snapTolerance: snapTolerance ?? 8,
266
+ collapseThreshold,
267
+ requestCollapse: () => shell.setInspectorMode('collapsed'),
268
+ requestToggle: () => shell.togglePane('inspector'),
269
+ }}
270
+ >
271
+ {handleChildren.length > 0 ? handleChildren.map((el, i) => React.cloneElement(el, { key: el.key ?? i })) : <PaneHandle />}
272
+ </PaneResizeContext.Provider>
273
+ ) : null;
291
274
 
292
- // Apply defaultSize on mount when uncontrolled
293
- React.useEffect(() => {
294
- if (!localRef.current) return;
295
- if (typeof (props as any).size === 'undefined' && typeof (props as any).defaultSize !== 'undefined') {
296
- const px = normalizeToPx((props as any).defaultSize);
297
- if (typeof px === 'number' && Number.isFinite(px)) {
298
- const minPx = typeof minSize === 'number' ? minSize : undefined;
299
- const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
300
- const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
301
- localRef.current.style.setProperty('--inspector-size', `${clamped}px`);
302
- emitSizeChange(clamped, { reason: 'init' });
303
- }
304
- }
305
- // eslint-disable-next-line react-hooks/exhaustive-deps
306
- }, []);
275
+ // Normalize CSS lengths to px
276
+ const normalizeToPx = React.useCallback((value: number | string | undefined): number | undefined => {
277
+ if (value == null) return undefined;
278
+ if (typeof value === 'number' && Number.isFinite(value)) return value;
279
+ const str = String(value).trim();
280
+ if (!str) return undefined;
281
+ if (str.endsWith('px')) return Number.parseFloat(str);
282
+ if (str.endsWith('rem')) {
283
+ const rem = Number.parseFloat(getComputedStyle(document.documentElement).fontSize || '16') || 16;
284
+ return Number.parseFloat(str) * rem;
285
+ }
286
+ if (str.endsWith('%')) {
287
+ const pct = Number.parseFloat(str);
288
+ const base = document.documentElement.clientWidth || window.innerWidth || 0;
289
+ return (pct / 100) * base;
290
+ }
291
+ const n = Number.parseFloat(str);
292
+ return Number.isFinite(n) ? n : undefined;
293
+ }, []);
307
294
 
308
- // Controlled size sync
309
- const controlledSize = (props as any).size;
310
- React.useEffect(() => {
311
- if (!localRef.current) return;
312
- if (typeof controlledSize === 'undefined') return;
313
- const px = normalizeToPx(controlledSize);
295
+ // Apply defaultSize on mount when uncontrolled
296
+ React.useEffect(() => {
297
+ if (!localRef.current) return;
298
+ if (typeof size === 'undefined' && typeof defaultSize !== 'undefined') {
299
+ const px = normalizeToPx(defaultSize);
314
300
  if (typeof px === 'number' && Number.isFinite(px)) {
315
301
  const minPx = typeof minSize === 'number' ? minSize : undefined;
316
302
  const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
317
303
  const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
318
304
  localRef.current.style.setProperty('--inspector-size', `${clamped}px`);
319
- emitSizeChange(clamped, { reason: 'controlled' });
305
+ emitSizeChange(clamped, { reason: 'init' });
320
306
  }
321
- }, [controlledSize, minSize, maxSize, normalizeToPx, emitSizeChange]);
322
-
323
- if (isOverlay) {
324
- const open = shell.inspectorMode === 'expanded';
325
- return (
326
- <Sheet.Root open={open} onOpenChange={(o) => shell.setInspectorMode(o ? 'expanded' : 'collapsed')}>
327
- <Sheet.Content side="end" style={{ padding: 0 }} width={{ initial: `${expandedSize}px` }}>
328
- <VisuallyHidden>
329
- <Sheet.Title>Inspector</Sheet.Title>
330
- </VisuallyHidden>
331
- {contentChildren}
332
- </Sheet.Content>
333
- </Sheet.Root>
334
- );
335
307
  }
308
+ // eslint-disable-next-line react-hooks/exhaustive-deps
309
+ }, []);
336
310
 
337
- // Strip control/size props from DOM spread
338
- const {
339
- defaultOpen: _inspectorDefaultOpenIgnored,
340
- open: _inspectorOpenIgnored,
341
- onOpenChange: _inspectorOnOpenChangeIgnored,
342
- size: _sz,
343
- defaultSize: _dsz,
344
- onSizeChange: _osc,
345
- sizeUpdate: _szu,
346
- sizeUpdateMs: _szums,
347
- ...inspectorDomProps
348
- } = props as any;
311
+ // Controlled size sync
312
+ const controlledSize = size;
313
+ React.useEffect(() => {
314
+ if (!localRef.current) return;
315
+ if (typeof controlledSize === 'undefined') return;
316
+ const px = normalizeToPx(controlledSize);
317
+ if (typeof px === 'number' && Number.isFinite(px)) {
318
+ const minPx = typeof minSize === 'number' ? minSize : undefined;
319
+ const maxPx = typeof maxSize === 'number' ? maxSize : undefined;
320
+ const clamped = Math.min(maxPx ?? px, Math.max(minPx ?? px, px));
321
+ localRef.current.style.setProperty('--inspector-size', `${clamped}px`);
322
+ emitSizeChange(clamped, { reason: 'controlled' });
323
+ }
324
+ }, [controlledSize, minSize, maxSize, normalizeToPx, emitSizeChange]);
349
325
 
326
+ if (isOverlay) {
327
+ const open = shell.inspectorMode === 'expanded';
350
328
  return (
351
- <div
352
- {...inspectorDomProps}
353
- ref={setRef}
354
- className={classNames('rt-ShellInspector', className)}
355
- data-mode={shell.inspectorMode}
356
- data-peek={shell.peekTarget === 'inspector' || undefined}
357
- data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
358
- data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
359
- style={{
360
- ...style,
361
- ['--inspector-size' as any]: `${expandedSize}px`,
362
- ['--inspector-min-size' as any]: `${minSize}px`,
363
- ['--inspector-max-size' as any]: `${maxSize}px`,
364
- }}
365
- >
366
- <div className="rt-ShellInspectorContent" data-visible={isExpanded || undefined}>
329
+ <Sheet.Root open={open} onOpenChange={(o) => shell.setInspectorMode(o ? 'expanded' : 'collapsed')}>
330
+ <Sheet.Content side="end" style={{ padding: 0 }} width={{ initial: `${expandedSize}px` }}>
331
+ <VisuallyHidden>
332
+ <Sheet.Title>Inspector</Sheet.Title>
333
+ </VisuallyHidden>
367
334
  {contentChildren}
368
- </div>
369
- {handleEl}
370
- </div>
335
+ </Sheet.Content>
336
+ </Sheet.Root>
371
337
  );
372
- },
373
- ) as InspectorComponent;
338
+ }
339
+
340
+ // Strip control/size props from DOM spread
341
+ return (
342
+ <div
343
+ {...inspectorDomProps}
344
+ ref={setRef}
345
+ className={classNames('rt-ShellInspector', className)}
346
+ data-mode={shell.inspectorMode}
347
+ data-peek={shell.peekTarget === 'inspector' || undefined}
348
+ data-presentation={shell.currentBreakpointReady ? resolvedPresentation : undefined}
349
+ data-open={(shell.currentBreakpointReady && isStacked && isExpanded) || undefined}
350
+ style={{
351
+ ...style,
352
+ ['--inspector-size' as any]: `${expandedSize}px`,
353
+ ['--inspector-min-size' as any]: `${minSize}px`,
354
+ ['--inspector-max-size' as any]: `${maxSize}px`,
355
+ }}
356
+ >
357
+ <div className="rt-ShellInspectorContent" data-visible={isExpanded || undefined}>
358
+ {contentChildren}
359
+ </div>
360
+ {handleEl}
361
+ </div>
362
+ );
363
+ }) as InspectorComponent;
374
364
 
375
365
  Inspector.displayName = 'Shell.Inspector';
376
366
  Inspector.Handle = InspectorHandle;