react-zeugma 6.9.5 → 6.9.6

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/README.md CHANGED
@@ -96,29 +96,28 @@ import { Zeugma } from 'react-zeugma'
96
96
 
97
97
  ##### Props
98
98
 
99
- | Property | Description | Type | Default |
100
- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
101
- | `controller` | The layout state controller returned by `useZeugma(options)`. | `ZeugmaController` | - |
102
- | `children` | Children components rendered inside the context provider. | `ReactNode` | - |
103
- | `renderPane` | Callback function to map active pane IDs to custom pane structures. Required in standalone mode (without children) and must not be passed in provider mode. | `(paneId: string) => ReactNode` | - |
104
- | `renderDragOverlay` | Custom overlay renderer function for the drag-under-cursor preview. | `(active: DragOverlayActiveItem) => ReactNode` | - |
105
- | `classNames` | CSS class name mapping overrides for custom dashboard and overlay styling. | `ZeugmaClassNames` | - |
106
- | `resizerSize` | Thickness of the split resizer bars in pixels. | `number` | `4` |
107
- | `dragActivationDistance` | Minimum pointer drag distance (in pixels) required to activate dragging. | `number` | `8` |
108
- | `snapThreshold` | Threshold in pixels to snap layout resizers to adjacent edges. | `number` | `8` |
109
- | `minSplitPercentage` | Minimum split limit percentage allowed for resized panes. | `number` | `5` |
110
- | `maxSplitPercentage` | Maximum split limit percentage allowed for resized panes. | `number` | `95` |
111
- | `enableDragToDismiss` | Enables drag-out-to-dismiss gesture for widgets. | `boolean` | `false` |
112
- | `dismissThreshold` | Distance in pixels outside container bounds required to trigger dismissal. | `number` | `60` |
113
- | `onRemove` | Callback triggered when a pane is removed. | `(paneId: string) => void` | - |
114
- | `onDragStart` | Callback triggered when a drag gesture begins. | `(activeId: string) => void` | - |
115
- | `onDragEnd` | Callback triggered when a drag gesture ends, containing active pane, target pane, and action metadata. | `(activeId: string, overId: string \| null, dropAction: { type: 'split' \| 'move'; direction?: SplitDirection; position?: 'top' \| 'bottom' \| 'left' \| 'right' \| 'center' } \| null) => void` | - |
116
- | `onResizeStart` | Callback triggered when resizing begins. | `(currentNode: SplitNode) => void` | - |
117
- | `onResize` | Callback triggered during pane resizing. | `(currentNode: SplitNode, percentage: number) => void` | - |
118
- | `onResizeEnd` | Callback triggered when pane resizing completes. | `(currentNode: SplitNode, percentage: number) => void` | - |
119
- | `onDismissIntentChange` | Callback triggered when drag-out dismiss intent changes. | `(paneId: string \| null) => void` | - |
120
- | `persist` | Layout persistence configuration in localStorage. If true, uses default options. | `boolean \| ZeugmaPersistOptions` | `false` |
121
- | `renderPopoutWrapper` | **[Experimental]** Custom wrapper to inject context or style managers (e.g. `styled-components`' `StyleSheetManager` or `antd`'s `StyleProvider`/`ConfigProvider`) into popout windows. | `(props: { tabId: string; document: Document; window: Window; children: React.ReactNode }) => React.ReactNode` | - |
99
+ | Property | Description | Type | Default |
100
+ | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- |
101
+ | `controller` | The layout state controller returned by `useZeugma(options)`. | `ZeugmaController` | - |
102
+ | `children` | Children components rendered inside the context provider. | `ReactNode` | - |
103
+ | `renderPane` | Callback function to map active pane IDs to custom pane structures. Required in standalone mode (without children) and must not be passed in provider mode. | `(paneId: string) => ReactNode` | - |
104
+ | `renderDragOverlay` | Custom overlay renderer function for the drag-under-cursor preview. | `(active: DragOverlayActiveItem) => ReactNode` | - |
105
+ | `classNames` | CSS class name mapping overrides for custom dashboard and overlay styling. | `ZeugmaClassNames` | - |
106
+ | `resizerSize` | Thickness of the split resizer bars in pixels. | `number` | `4` |
107
+ | `dragActivationDistance` | Minimum pointer drag distance (in pixels) required to activate dragging. | `number` | `8` |
108
+ | `snapThreshold` | Threshold in pixels to snap layout resizers to adjacent edges. | `number` | `8` |
109
+ | `minSplitPercentage` | Minimum split limit percentage allowed for resized panes. | `number` | `5` |
110
+ | `maxSplitPercentage` | Maximum split limit percentage allowed for resized panes. | `number` | `95` |
111
+ | `enableDragToDismiss` | Enables drag-out-to-dismiss gesture for widgets. | `boolean` | `false` |
112
+ | `dismissThreshold` | Distance in pixels outside container bounds required to trigger dismissal. | `number` | `60` |
113
+ | `onRemove` | Callback triggered when a pane is removed. | `(paneId: string) => void` | - |
114
+ | `onDragStart` | Callback triggered when a drag gesture begins. | `(activeId: string) => void` | - |
115
+ | `onDragEnd` | Callback triggered when a drag gesture ends, containing active pane, target pane, and action metadata. | `(activeId: string, overId: string \| null, dropAction: { type: 'split' \| 'move'; direction?: SplitDirection; position?: 'top' \| 'bottom' \| 'left' \| 'right' \| 'center' } \| null) => void` | - |
116
+ | `onResizeStart` | Callback triggered when resizing begins. | `(currentNode: SplitNode) => void` | - |
117
+ | `onResize` | Callback triggered during pane resizing. | `(currentNode: SplitNode, percentage: number) => void` | - |
118
+ | `onResizeEnd` | Callback triggered when pane resizing completes. | `(currentNode: SplitNode, percentage: number) => void` | - |
119
+ | `onDismissIntentChange` | Callback triggered when drag-out dismiss intent changes. | `(paneId: string \| null) => void` | - |
120
+ | `persist` | Layout persistence configuration in localStorage. If true, uses default options. | `boolean \| ZeugmaPersistOptions` | `false` |
122
121
 
123
122
  ##### `ZeugmaPersistOptions`
124
123
 
@@ -508,7 +507,9 @@ const parentPane = findPaneContainingTab(currentTree, 'new-file.js')
508
507
 
509
508
  When using CSS-in-JS libraries like `styled-components` or `antd` (Ant Design) inside your widgets, dynamic styles are injected into the main document's `<head>` by default. In popped-out tabs, these dynamic styles will not apply because they render inside a separate window. Additionally, shared JS context caches will prevent styles from being re-emitted unless a separate cache instance is created.
510
509
 
511
- You can use the `renderPopoutWrapper` callback prop on the `<Zeugma>` component to wrap popped-out widgets with the required style managers and providers targeting the child window's DOM.
510
+ To support this cleanly without causing widget remounts when popping windows out (which would reset the widget's internal state), you should wrap your widgets with the required style/theme providers **always** (both when docked and when popped out) directly inside the child render function of `<Pane.Content>`.
511
+
512
+ Because React Zeugma globally intercepts and patches `document` references (like `document.head` and `document.body`) during the portal rendering phase, you can pass the global `document` reference directly to the providers. It will automatically resolve to the correct active window's document.
512
513
 
513
514
  ##### Example (`styled-components` & `antd`):
514
515
 
@@ -518,8 +519,8 @@ import { StyleSheetManager } from 'styled-components'
518
519
  import { StyleProvider, createCache } from '@ant-design/cssinjs'
519
520
  import { ConfigProvider } from 'antd'
520
521
 
521
- // Using a wrapper component is recommended to safely instantiate a style cache per popout window
522
- const PopoutStyleManager = ({ document, children }) => {
522
+ // 1. Create a wrapper component to safely instantiate and reuse a style cache
523
+ const StableStyleManager = ({ children }) => {
523
524
  const cache = useMemo(() => createCache(), [])
524
525
 
525
526
  return (
@@ -531,12 +532,51 @@ const PopoutStyleManager = ({ document, children }) => {
531
532
  )
532
533
  }
533
534
 
534
- ;<Zeugma
535
- controller={controller}
536
- renderPopoutWrapper={({ document, children }) => (
537
- <PopoutStyleManager document={document}>{children}</PopoutStyleManager>
538
- )}
539
- >
540
- {/* Dashboard grid structure */}
541
- </Zeugma>
535
+ // 2. Wrap your widget directly inside Pane.Content
536
+ const renderPane = (paneId: string) => (
537
+ <Pane id={paneId}>
538
+ <Pane.Content>
539
+ {(tab) => (
540
+ <StableStyleManager>
541
+ <Widget tabId={tab.id} />
542
+ </StableStyleManager>
543
+ )}
544
+ </Pane.Content>
545
+ </Pane>
546
+ )
542
547
  ```
548
+
549
+ ---
550
+
551
+ ## DevTools & Performance Profiling (`react-zeugma/devtools`)
552
+
553
+ `react-zeugma` includes built-in DevTools utilities to audit component mount lifecycles and render frequencies across complex dashboard layouts.
554
+
555
+ ### Usage
556
+
557
+ ```tsx
558
+ import { useRenderCounter, RenderCounterBadge, RenderCounterFooter } from 'react-zeugma/devtools'
559
+ // Or directly from 'react-zeugma'
560
+ // import { useRenderCounter, RenderCounterBadge, RenderCounterFooter } from 'react-zeugma'
561
+
562
+ function MyDashboardWidget({ id }: { id: string }) {
563
+ // Hook usage
564
+ const { mounts, renders, reset } = useRenderCounter(id, { logToConsole: true })
565
+
566
+ return (
567
+ <RenderCounterFooter label={id} id={id}>
568
+ <div className="p-4">
569
+ <p>Mount count: {mounts}</p>
570
+ <p>Render count: {renders}</p>
571
+ <button onClick={reset}>Reset Counters</button>
572
+ </div>
573
+ </RenderCounterFooter>
574
+ )
575
+ }
576
+ ```
577
+
578
+ ### Components & Hooks
579
+
580
+ - `useRenderCounter(idOrOptions?, options?)`: React 18/19 StrictMode-safe hook returning `{ mounts, renders, reset }`.
581
+ - `<RenderCounterBadge id={id} position="top-right" />`: Floating overlay badge for any panel or component.
582
+ - `<RenderCounterFooter id={id} label="Widget Name">`: Wrapper component rendering a bottom status footer for tabbed widgets.
@@ -0,0 +1,2 @@
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var l=new Map;function a(t){let e=l.get(t);return e||(e={mounts:0,renders:0,listeners:new Set},l.set(t,e)),e}function N(t,e){let o=a(t);return o.listeners.add(e),()=>{o.listeners.delete(e);}}function R(t){let e=l.get(t);e&&e.listeners.forEach(o=>o());}function x(t){let e=l.get(t);e&&(e.mounts=0,e.renders=0,R(t));}function z(){l.clear();}function p(t,e){let o=react.useId(),c=typeof t=="string"?{id:t,...e}:t||{},{id:r=o,logToConsole:u=false,disabled:s=false}=c,n=react.useRef(0),d=react.useRef(false),[,C]=react.useState(0);if(s||(n.current+=1),react.useEffect(()=>{if(s)return;let g=N(r,()=>{C(S=>S+1);}),v=a(r);return v.mounts+=1,d.current=true,u&&console.log(`[DevTools:${r}] Mounted: ${v.mounts} | Rendered: ${n.current}`),R(r),()=>{d.current=false,g();}},[r,s,u]),!s){let g=a(r);g.renders=Math.max(g.renders,n.current);}let m=react.useCallback(()=>{n.current=0,x(r);},[r]);return s?{mounts:0,renders:0,reset:m}:{mounts:a(r).mounts,renders:n.current,reset:m}}var w={"top-right":"top-2 right-2","top-left":"top-2 left-2","bottom-right":"bottom-2 right-2","bottom-left":"bottom-2 left-2"};function F({id:t,position:e="top-right",className:o="",style:c,logToConsole:r=false,disabled:u=false}){let{mounts:s,renders:n}=p(t,{logToConsole:r,disabled:u});if(u)return null;let d=w[e]||w["top-right"];return jsxRuntime.jsxs("div",{className:`absolute ${d} z-50 pointer-events-none flex items-center gap-2 rounded px-2 py-1 text-[11px] font-mono bg-zinc-900/90 text-zinc-200 border border-zinc-700/80 shadow-md backdrop-blur-xs select-none ${o}`,style:c,children:[jsxRuntime.jsxs("span",{children:["M: ",jsxRuntime.jsx("strong",{className:"text-emerald-400 font-semibold",children:s})]}),jsxRuntime.jsx("span",{className:"opacity-30",children:"|"}),jsxRuntime.jsxs("span",{children:["R: ",jsxRuntime.jsx("strong",{className:"text-indigo-400 font-semibold",children:n})]})]})}function I({id:t,label:e,children:o,className:c="",footerClassName:r="",style:u,logToConsole:s=false,disabled:n=false}){let d=e||t||"widget",{mounts:C,renders:m}=p(t,{logToConsole:s,disabled:n});return n?jsxRuntime.jsx(jsxRuntime.Fragment,{children:o}):jsxRuntime.jsxs("div",{className:`h-full w-full flex flex-col overflow-hidden min-h-0 ${c}`,style:u,children:[o&&jsxRuntime.jsx("div",{className:"flex-1 overflow-hidden relative min-h-0",children:o}),jsxRuntime.jsxs("div",{className:`bg-zinc-900/90 border-t border-zinc-800 px-3 py-1.5 flex items-center justify-between text-[11px] text-zinc-400 font-mono shrink-0 select-none ${r}`,children:[jsxRuntime.jsx("span",{className:"truncate max-w-[140px] text-zinc-300 font-semibold",children:d}),jsxRuntime.jsxs("div",{className:"flex items-center gap-3",children:[jsxRuntime.jsxs("span",{children:["Mounts: ",jsxRuntime.jsx("strong",{className:"text-emerald-400 font-semibold",children:C})]}),jsxRuntime.jsx("span",{className:"opacity-20",children:"|"}),jsxRuntime.jsxs("span",{children:["Renders: ",jsxRuntime.jsx("strong",{className:"text-indigo-400 font-semibold",children:m})]})]})]})]})}exports.RenderCounterBadge=F;exports.RenderCounterFooter=I;exports.clearAllCounters=z;exports.resetCounterRecord=x;exports.useRenderCounter=p;//# sourceMappingURL=devtools.cjs.map
2
+ //# sourceMappingURL=devtools.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/devtools/store.ts","../src/devtools/use-render-counter.ts","../src/devtools/render-counter-badge.tsx","../src/devtools/render-counter-footer.tsx"],"names":["store","getCounterRecord","id","record","subscribeCounter","listener","notifyCounter","fn","resetCounterRecord","clearAllCounters","useRenderCounter","idOrOptions","maybeOptions","defaultAutoId","useId","options","logToConsole","disabled","renderCountRef","useRef","isMountedRef","forceUpdate","useState","useEffect","unsubscribe","c","reset","useCallback","POSITION_STYLES","RenderCounterBadge","position","className","style","mounts","renders","posClass","jsxs","jsx","RenderCounterFooter","label","children","footerClassName","displayId","Fragment"],"mappings":"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","file":"devtools.cjs","sourcesContent":["type Listener = () => void\n\ninterface GlobalCounterRecord {\n mounts: number\n renders: number\n listeners: Set<Listener>\n}\n\nconst store = new Map<string, GlobalCounterRecord>()\n\nexport function getCounterRecord(id: string): GlobalCounterRecord {\n let record = store.get(id)\n if (!record) {\n record = { mounts: 0, renders: 0, listeners: new Set() }\n store.set(id, record)\n }\n return record\n}\n\nexport function subscribeCounter(id: string, listener: Listener): () => void {\n const record = getCounterRecord(id)\n record.listeners.add(listener)\n return () => {\n record.listeners.delete(listener)\n }\n}\n\nexport function notifyCounter(id: string) {\n const record = store.get(id)\n if (record) {\n record.listeners.forEach((fn) => fn())\n }\n}\n\nexport function resetCounterRecord(id: string) {\n const record = store.get(id)\n if (record) {\n record.mounts = 0\n record.renders = 0\n notifyCounter(id)\n }\n}\n\nexport function clearAllCounters() {\n store.clear()\n}\n","'use client'\n\nimport { useRef, useEffect, useState, useCallback, useId } from 'react'\nimport type { RenderCounterOptions, RenderCounterState } from './types'\nimport { getCounterRecord, subscribeCounter, notifyCounter, resetCounterRecord } from './store'\n\n/**\n * A React hook that tracks component mount and render counts in a React 18/19 StrictMode safe manner.\n * Supports optional global ID sharing, console logging, and manual reset.\n */\nexport function useRenderCounter(\n idOrOptions?: string | RenderCounterOptions,\n maybeOptions?: RenderCounterOptions,\n): RenderCounterState {\n const defaultAutoId = useId()\n\n const options: RenderCounterOptions =\n typeof idOrOptions === 'string' ? { id: idOrOptions, ...maybeOptions } : idOrOptions || {}\n\n const { id = defaultAutoId, logToConsole = false, disabled = false } = options\n\n const renderCountRef = useRef<number>(0)\n const isMountedRef = useRef<boolean>(false)\n const [, forceUpdate] = useState<number>(0)\n\n if (!disabled) {\n renderCountRef.current += 1\n }\n\n useEffect(() => {\n if (disabled) return\n\n const unsubscribe = subscribeCounter(id, () => {\n forceUpdate((c) => c + 1)\n })\n\n const record = getCounterRecord(id)\n record.mounts += 1\n isMountedRef.current = true\n\n if (logToConsole) {\n console.log(\n `[DevTools:${id}] Mounted: ${record.mounts} | Rendered: ${renderCountRef.current}`,\n )\n }\n\n notifyCounter(id)\n\n return () => {\n isMountedRef.current = false\n unsubscribe()\n }\n }, [id, disabled, logToConsole])\n\n if (!disabled) {\n const record = getCounterRecord(id)\n record.renders = Math.max(record.renders, renderCountRef.current)\n }\n\n const reset = useCallback(() => {\n renderCountRef.current = 0\n resetCounterRecord(id)\n }, [id])\n\n if (disabled) {\n return { mounts: 0, renders: 0, reset }\n }\n\n const record = getCounterRecord(id)\n\n return {\n mounts: record.mounts,\n renders: renderCountRef.current,\n reset,\n }\n}\n","'use client'\n\nimport type { RenderCounterBadgeProps } from './types'\nimport { useRenderCounter } from './use-render-counter'\n\nconst POSITION_STYLES: Record<NonNullable<RenderCounterBadgeProps['position']>, string> = {\n 'top-right': 'top-2 right-2',\n 'top-left': 'top-2 left-2',\n 'bottom-right': 'bottom-2 right-2',\n 'bottom-left': 'bottom-2 left-2',\n}\n\n/**\n * A floating overlay badge displaying mount and render counters for debugging component lifecycles.\n */\nexport function RenderCounterBadge({\n id,\n position = 'top-right',\n className = '',\n style,\n logToConsole = false,\n disabled = false,\n}: RenderCounterBadgeProps) {\n const { mounts, renders } = useRenderCounter(id, { logToConsole, disabled })\n\n if (disabled) {\n return null\n }\n\n const posClass = POSITION_STYLES[position] || POSITION_STYLES['top-right']\n\n return (\n <div\n className={`absolute ${posClass} z-50 pointer-events-none flex items-center gap-2 rounded px-2 py-1 text-[11px] font-mono bg-zinc-900/90 text-zinc-200 border border-zinc-700/80 shadow-md backdrop-blur-xs select-none ${className}`}\n style={style}\n >\n <span>\n M: <strong className=\"text-emerald-400 font-semibold\">{mounts}</strong>\n </span>\n <span className=\"opacity-30\">|</span>\n <span>\n R: <strong className=\"text-indigo-400 font-semibold\">{renders}</strong>\n </span>\n </div>\n )\n}\n","'use client'\n\nimport type { RenderCounterFooterProps } from './types'\nimport { useRenderCounter } from './use-render-counter'\n\n/**\n * A container footer component for displaying mount and render counters at the bottom of widgets or panels.\n */\nexport function RenderCounterFooter({\n id,\n label,\n children,\n className = '',\n footerClassName = '',\n style,\n logToConsole = false,\n disabled = false,\n}: RenderCounterFooterProps) {\n const displayId = label || id || 'widget'\n const { mounts, renders } = useRenderCounter(id, { logToConsole, disabled })\n\n if (disabled) {\n return <>{children}</>\n }\n\n return (\n <div\n className={`h-full w-full flex flex-col overflow-hidden min-h-0 ${className}`}\n style={style}\n >\n {children && <div className=\"flex-1 overflow-hidden relative min-h-0\">{children}</div>}\n <div\n className={`bg-zinc-900/90 border-t border-zinc-800 px-3 py-1.5 flex items-center justify-between text-[11px] text-zinc-400 font-mono shrink-0 select-none ${footerClassName}`}\n >\n <span className=\"truncate max-w-[140px] text-zinc-300 font-semibold\">{displayId}</span>\n <div className=\"flex items-center gap-3\">\n <span>\n Mounts: <strong className=\"text-emerald-400 font-semibold\">{mounts}</strong>\n </span>\n <span className=\"opacity-20\">|</span>\n <span>\n Renders: <strong className=\"text-indigo-400 font-semibold\">{renders}</strong>\n </span>\n </div>\n </div>\n </div>\n )\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export { R as RenderCounterBadge, a as RenderCounterBadgeProps, b as RenderCounterFooter, c as RenderCounterFooterProps, d as RenderCounterOptions, e as RenderCounterState, u as useRenderCounter } from './render-counter-footer-CYe6-Atc.cjs';
2
+ import 'react';
3
+
4
+ declare function resetCounterRecord(id: string): void;
5
+ declare function clearAllCounters(): void;
6
+
7
+ export { clearAllCounters, resetCounterRecord };
@@ -0,0 +1,7 @@
1
+ export { R as RenderCounterBadge, a as RenderCounterBadgeProps, b as RenderCounterFooter, c as RenderCounterFooterProps, d as RenderCounterOptions, e as RenderCounterState, u as useRenderCounter } from './render-counter-footer-CYe6-Atc.js';
2
+ import 'react';
3
+
4
+ declare function resetCounterRecord(id: string): void;
5
+ declare function clearAllCounters(): void;
6
+
7
+ export { clearAllCounters, resetCounterRecord };
@@ -0,0 +1,2 @@
1
+ import {useId,useRef,useState,useEffect,useCallback}from'react';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var l=new Map;function a(t){let e=l.get(t);return e||(e={mounts:0,renders:0,listeners:new Set},l.set(t,e)),e}function N(t,e){let o=a(t);return o.listeners.add(e),()=>{o.listeners.delete(e);}}function R(t){let e=l.get(t);e&&e.listeners.forEach(o=>o());}function x(t){let e=l.get(t);e&&(e.mounts=0,e.renders=0,R(t));}function z(){l.clear();}function p(t,e){let o=useId(),c=typeof t=="string"?{id:t,...e}:t||{},{id:r=o,logToConsole:u=false,disabled:s=false}=c,n=useRef(0),d=useRef(false),[,C]=useState(0);if(s||(n.current+=1),useEffect(()=>{if(s)return;let g=N(r,()=>{C(S=>S+1);}),v=a(r);return v.mounts+=1,d.current=true,u&&console.log(`[DevTools:${r}] Mounted: ${v.mounts} | Rendered: ${n.current}`),R(r),()=>{d.current=false,g();}},[r,s,u]),!s){let g=a(r);g.renders=Math.max(g.renders,n.current);}let m=useCallback(()=>{n.current=0,x(r);},[r]);return s?{mounts:0,renders:0,reset:m}:{mounts:a(r).mounts,renders:n.current,reset:m}}var w={"top-right":"top-2 right-2","top-left":"top-2 left-2","bottom-right":"bottom-2 right-2","bottom-left":"bottom-2 left-2"};function F({id:t,position:e="top-right",className:o="",style:c,logToConsole:r=false,disabled:u=false}){let{mounts:s,renders:n}=p(t,{logToConsole:r,disabled:u});if(u)return null;let d=w[e]||w["top-right"];return jsxs("div",{className:`absolute ${d} z-50 pointer-events-none flex items-center gap-2 rounded px-2 py-1 text-[11px] font-mono bg-zinc-900/90 text-zinc-200 border border-zinc-700/80 shadow-md backdrop-blur-xs select-none ${o}`,style:c,children:[jsxs("span",{children:["M: ",jsx("strong",{className:"text-emerald-400 font-semibold",children:s})]}),jsx("span",{className:"opacity-30",children:"|"}),jsxs("span",{children:["R: ",jsx("strong",{className:"text-indigo-400 font-semibold",children:n})]})]})}function I({id:t,label:e,children:o,className:c="",footerClassName:r="",style:u,logToConsole:s=false,disabled:n=false}){let d=e||t||"widget",{mounts:C,renders:m}=p(t,{logToConsole:s,disabled:n});return n?jsx(Fragment,{children:o}):jsxs("div",{className:`h-full w-full flex flex-col overflow-hidden min-h-0 ${c}`,style:u,children:[o&&jsx("div",{className:"flex-1 overflow-hidden relative min-h-0",children:o}),jsxs("div",{className:`bg-zinc-900/90 border-t border-zinc-800 px-3 py-1.5 flex items-center justify-between text-[11px] text-zinc-400 font-mono shrink-0 select-none ${r}`,children:[jsx("span",{className:"truncate max-w-[140px] text-zinc-300 font-semibold",children:d}),jsxs("div",{className:"flex items-center gap-3",children:[jsxs("span",{children:["Mounts: ",jsx("strong",{className:"text-emerald-400 font-semibold",children:C})]}),jsx("span",{className:"opacity-20",children:"|"}),jsxs("span",{children:["Renders: ",jsx("strong",{className:"text-indigo-400 font-semibold",children:m})]})]})]})]})}export{F as RenderCounterBadge,I as RenderCounterFooter,z as clearAllCounters,x as resetCounterRecord,p as useRenderCounter};//# sourceMappingURL=devtools.js.map
2
+ //# sourceMappingURL=devtools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/devtools/store.ts","../src/devtools/use-render-counter.ts","../src/devtools/render-counter-badge.tsx","../src/devtools/render-counter-footer.tsx"],"names":["store","getCounterRecord","id","record","subscribeCounter","listener","notifyCounter","fn","resetCounterRecord","clearAllCounters","useRenderCounter","idOrOptions","maybeOptions","defaultAutoId","useId","options","logToConsole","disabled","renderCountRef","useRef","isMountedRef","forceUpdate","useState","useEffect","unsubscribe","c","reset","useCallback","POSITION_STYLES","RenderCounterBadge","position","className","style","mounts","renders","posClass","jsxs","jsx","RenderCounterFooter","label","children","footerClassName","displayId","Fragment"],"mappings":"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","file":"devtools.js","sourcesContent":["type Listener = () => void\n\ninterface GlobalCounterRecord {\n mounts: number\n renders: number\n listeners: Set<Listener>\n}\n\nconst store = new Map<string, GlobalCounterRecord>()\n\nexport function getCounterRecord(id: string): GlobalCounterRecord {\n let record = store.get(id)\n if (!record) {\n record = { mounts: 0, renders: 0, listeners: new Set() }\n store.set(id, record)\n }\n return record\n}\n\nexport function subscribeCounter(id: string, listener: Listener): () => void {\n const record = getCounterRecord(id)\n record.listeners.add(listener)\n return () => {\n record.listeners.delete(listener)\n }\n}\n\nexport function notifyCounter(id: string) {\n const record = store.get(id)\n if (record) {\n record.listeners.forEach((fn) => fn())\n }\n}\n\nexport function resetCounterRecord(id: string) {\n const record = store.get(id)\n if (record) {\n record.mounts = 0\n record.renders = 0\n notifyCounter(id)\n }\n}\n\nexport function clearAllCounters() {\n store.clear()\n}\n","'use client'\n\nimport { useRef, useEffect, useState, useCallback, useId } from 'react'\nimport type { RenderCounterOptions, RenderCounterState } from './types'\nimport { getCounterRecord, subscribeCounter, notifyCounter, resetCounterRecord } from './store'\n\n/**\n * A React hook that tracks component mount and render counts in a React 18/19 StrictMode safe manner.\n * Supports optional global ID sharing, console logging, and manual reset.\n */\nexport function useRenderCounter(\n idOrOptions?: string | RenderCounterOptions,\n maybeOptions?: RenderCounterOptions,\n): RenderCounterState {\n const defaultAutoId = useId()\n\n const options: RenderCounterOptions =\n typeof idOrOptions === 'string' ? { id: idOrOptions, ...maybeOptions } : idOrOptions || {}\n\n const { id = defaultAutoId, logToConsole = false, disabled = false } = options\n\n const renderCountRef = useRef<number>(0)\n const isMountedRef = useRef<boolean>(false)\n const [, forceUpdate] = useState<number>(0)\n\n if (!disabled) {\n renderCountRef.current += 1\n }\n\n useEffect(() => {\n if (disabled) return\n\n const unsubscribe = subscribeCounter(id, () => {\n forceUpdate((c) => c + 1)\n })\n\n const record = getCounterRecord(id)\n record.mounts += 1\n isMountedRef.current = true\n\n if (logToConsole) {\n console.log(\n `[DevTools:${id}] Mounted: ${record.mounts} | Rendered: ${renderCountRef.current}`,\n )\n }\n\n notifyCounter(id)\n\n return () => {\n isMountedRef.current = false\n unsubscribe()\n }\n }, [id, disabled, logToConsole])\n\n if (!disabled) {\n const record = getCounterRecord(id)\n record.renders = Math.max(record.renders, renderCountRef.current)\n }\n\n const reset = useCallback(() => {\n renderCountRef.current = 0\n resetCounterRecord(id)\n }, [id])\n\n if (disabled) {\n return { mounts: 0, renders: 0, reset }\n }\n\n const record = getCounterRecord(id)\n\n return {\n mounts: record.mounts,\n renders: renderCountRef.current,\n reset,\n }\n}\n","'use client'\n\nimport type { RenderCounterBadgeProps } from './types'\nimport { useRenderCounter } from './use-render-counter'\n\nconst POSITION_STYLES: Record<NonNullable<RenderCounterBadgeProps['position']>, string> = {\n 'top-right': 'top-2 right-2',\n 'top-left': 'top-2 left-2',\n 'bottom-right': 'bottom-2 right-2',\n 'bottom-left': 'bottom-2 left-2',\n}\n\n/**\n * A floating overlay badge displaying mount and render counters for debugging component lifecycles.\n */\nexport function RenderCounterBadge({\n id,\n position = 'top-right',\n className = '',\n style,\n logToConsole = false,\n disabled = false,\n}: RenderCounterBadgeProps) {\n const { mounts, renders } = useRenderCounter(id, { logToConsole, disabled })\n\n if (disabled) {\n return null\n }\n\n const posClass = POSITION_STYLES[position] || POSITION_STYLES['top-right']\n\n return (\n <div\n className={`absolute ${posClass} z-50 pointer-events-none flex items-center gap-2 rounded px-2 py-1 text-[11px] font-mono bg-zinc-900/90 text-zinc-200 border border-zinc-700/80 shadow-md backdrop-blur-xs select-none ${className}`}\n style={style}\n >\n <span>\n M: <strong className=\"text-emerald-400 font-semibold\">{mounts}</strong>\n </span>\n <span className=\"opacity-30\">|</span>\n <span>\n R: <strong className=\"text-indigo-400 font-semibold\">{renders}</strong>\n </span>\n </div>\n )\n}\n","'use client'\n\nimport type { RenderCounterFooterProps } from './types'\nimport { useRenderCounter } from './use-render-counter'\n\n/**\n * A container footer component for displaying mount and render counters at the bottom of widgets or panels.\n */\nexport function RenderCounterFooter({\n id,\n label,\n children,\n className = '',\n footerClassName = '',\n style,\n logToConsole = false,\n disabled = false,\n}: RenderCounterFooterProps) {\n const displayId = label || id || 'widget'\n const { mounts, renders } = useRenderCounter(id, { logToConsole, disabled })\n\n if (disabled) {\n return <>{children}</>\n }\n\n return (\n <div\n className={`h-full w-full flex flex-col overflow-hidden min-h-0 ${className}`}\n style={style}\n >\n {children && <div className=\"flex-1 overflow-hidden relative min-h-0\">{children}</div>}\n <div\n className={`bg-zinc-900/90 border-t border-zinc-800 px-3 py-1.5 flex items-center justify-between text-[11px] text-zinc-400 font-mono shrink-0 select-none ${footerClassName}`}\n >\n <span className=\"truncate max-w-[140px] text-zinc-300 font-semibold\">{displayId}</span>\n <div className=\"flex items-center gap-3\">\n <span>\n Mounts: <strong className=\"text-emerald-400 font-semibold\">{mounts}</strong>\n </span>\n <span className=\"opacity-20\">|</span>\n <span>\n Renders: <strong className=\"text-indigo-400 font-semibold\">{renders}</strong>\n </span>\n </div>\n </div>\n </div>\n )\n}\n"]}