react-zeugma 6.9.6 → 6.9.8

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,28 +96,29 @@ 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` |
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` | - |
121
122
 
122
123
  ##### `ZeugmaPersistOptions`
123
124
 
@@ -507,9 +508,7 @@ const parentPane = findPaneContainingTab(currentTree, 'new-file.js')
507
508
 
508
509
  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.
509
510
 
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.
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.
513
512
 
514
513
  ##### Example (`styled-components` & `antd`):
515
514
 
@@ -519,8 +518,8 @@ import { StyleSheetManager } from 'styled-components'
519
518
  import { StyleProvider, createCache } from '@ant-design/cssinjs'
520
519
  import { ConfigProvider } from 'antd'
521
520
 
522
- // 1. Create a wrapper component to safely instantiate and reuse a style cache
523
- const StableStyleManager = ({ children }) => {
521
+ // Using a wrapper component is recommended to safely instantiate a style cache per popout window
522
+ const PopoutStyleManager = ({ document, children }) => {
524
523
  const cache = useMemo(() => createCache(), [])
525
524
 
526
525
  return (
@@ -532,18 +531,14 @@ const StableStyleManager = ({ children }) => {
532
531
  )
533
532
  }
534
533
 
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
- )
534
+ ;<Zeugma
535
+ controller={controller}
536
+ renderPopoutWrapper={({ document, children }) => (
537
+ <PopoutStyleManager document={document}>{children}</PopoutStyleManager>
538
+ )}
539
+ >
540
+ {/* Dashboard grid structure */}
541
+ </Zeugma>
547
542
  ```
548
543
 
549
544
  ---
@@ -578,5 +573,5 @@ function MyDashboardWidget({ id }: { id: string }) {
578
573
  ### Components & Hooks
579
574
 
580
575
  - `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.
576
+ - `<RenderCounterBadge id={id} position="top-right" className="..." />`: Unstyled overlay badge for any panel or component.
577
+ - `<RenderCounterFooter id={id} label="Widget Name" className="..." footerClassName="...">`: Unstyled wrapper component rendering a bottom status footer for tabbed widgets.
package/dist/devtools.cjs CHANGED
@@ -1,2 +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
1
+ 'use strict';var react=require('react'),jsxRuntime=require('react/jsx-runtime');var a=new Map;function i(r){let e=a.get(r);return e||(e={mounts:0,renders:0,listeners:new Set},a.set(r,e)),e}function N(r,e){let n=i(r);return n.listeners.add(e),()=>{n.listeners.delete(e);}}function g(r){let e=a.get(r);e&&e.listeners.forEach(n=>n());}function v(r){let e=a.get(r);e&&(e.mounts=0,e.renders=0,g(r));}function S(){a.clear();}function p(r,e){let n=react.useId(),c=typeof r=="string"?{id:r,...e}:r||{},{id:t=n,logToConsole:u=false,disabled:o=false}=c,s=react.useRef(0),l=react.useRef(false),[,R]=react.useState(0);if(o||(s.current+=1),react.useEffect(()=>{if(o)return;let C=N(t,()=>{R(P=>P+1);}),b=i(t);return b.mounts+=1,l.current=true,u&&console.log(`[DevTools:${t}] Mounted: ${b.mounts} | Rendered: ${s.current}`),g(t),()=>{l.current=false,C();}},[t,o,u]),!o){let C=i(t);C.renders=Math.max(C.renders,s.current);}let m=react.useCallback(()=>{s.current=0,v(t);},[t]);return o?{mounts:0,renders:0,reset:m}:{mounts:i(t).mounts,renders:s.current,reset:m}}function G({id:r,className:e,style:n,logToConsole:c=false,disabled:t=false}){let{mounts:u,renders:o}=p(r,{logToConsole:c,disabled:t});return t?null:jsxRuntime.jsxs("div",{className:e,style:n,children:[jsxRuntime.jsxs("span",{children:["M: ",jsxRuntime.jsx("strong",{children:u})]}),jsxRuntime.jsx("span",{children:"|"}),jsxRuntime.jsxs("span",{children:["R: ",jsxRuntime.jsx("strong",{children:o})]})]})}function I({id:r,label:e,children:n,className:c,footerClassName:t,style:u,logToConsole:o=false,disabled:s=false}){let l=e||r||"widget",{mounts:R,renders:m}=p(r,{logToConsole:o,disabled:s});return s?jsxRuntime.jsx(jsxRuntime.Fragment,{children:n}):jsxRuntime.jsxs("div",{className:c,style:u,children:[n&&jsxRuntime.jsx("div",{className:"flex-1 overflow-hidden relative min-h-0",children:n}),jsxRuntime.jsxs("div",{className:t,children:[jsxRuntime.jsx("span",{className:"truncate",children:l}),jsxRuntime.jsxs("div",{className:"flex items-center gap-3",children:[jsxRuntime.jsxs("span",{children:["Mounts: ",jsxRuntime.jsx("strong",{className:"counter-mounts",children:R})]}),jsxRuntime.jsx("span",{children:"|"}),jsxRuntime.jsxs("span",{children:["Renders: ",jsxRuntime.jsx("strong",{className:"counter-renders",children:m})]})]})]})]})}exports.RenderCounterBadge=G;exports.RenderCounterFooter=I;exports.clearAllCounters=S;exports.resetCounterRecord=v;exports.useRenderCounter=p;//# sourceMappingURL=devtools.cjs.map
2
2
  //# sourceMappingURL=devtools.cjs.map
@@ -1 +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"]}
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","RenderCounterBadge","className","style","mounts","renders","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\n/**\n * A badge displaying mount and render counters for debugging component lifecycles.\n */\nexport function RenderCounterBadge({\n id,\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 return (\n <div className={className} style={style}>\n <span>\n M: <strong>{mounts}</strong>\n </span>\n <span>|</span>\n <span>\n R: <strong>{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 className={className} style={style}>\n {children && <div className=\"flex-1 overflow-hidden relative min-h-0\">{children}</div>}\n <div className={footerClassName}>\n <span className=\"truncate\">{displayId}</span>\n <div className=\"flex items-center gap-3\">\n <span>\n Mounts: <strong className=\"counter-mounts\">{mounts}</strong>\n </span>\n <span>|</span>\n <span>\n Renders: <strong className=\"counter-renders\">{renders}</strong>\n </span>\n </div>\n </div>\n </div>\n )\n}\n"]}
@@ -1,4 +1,4 @@
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';
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-rAKCzuRX.cjs';
2
2
  import 'react';
3
3
 
4
4
  declare function resetCounterRecord(id: string): void;
@@ -1,4 +1,4 @@
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';
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-rAKCzuRX.js';
2
2
  import 'react';
3
3
 
4
4
  declare function resetCounterRecord(id: string): void;
package/dist/devtools.js CHANGED
@@ -1,2 +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
1
+ import {useId,useRef,useState,useEffect,useCallback}from'react';import {jsxs,jsx,Fragment}from'react/jsx-runtime';var a=new Map;function i(r){let e=a.get(r);return e||(e={mounts:0,renders:0,listeners:new Set},a.set(r,e)),e}function N(r,e){let n=i(r);return n.listeners.add(e),()=>{n.listeners.delete(e);}}function g(r){let e=a.get(r);e&&e.listeners.forEach(n=>n());}function v(r){let e=a.get(r);e&&(e.mounts=0,e.renders=0,g(r));}function S(){a.clear();}function p(r,e){let n=useId(),c=typeof r=="string"?{id:r,...e}:r||{},{id:t=n,logToConsole:u=false,disabled:o=false}=c,s=useRef(0),l=useRef(false),[,R]=useState(0);if(o||(s.current+=1),useEffect(()=>{if(o)return;let C=N(t,()=>{R(P=>P+1);}),b=i(t);return b.mounts+=1,l.current=true,u&&console.log(`[DevTools:${t}] Mounted: ${b.mounts} | Rendered: ${s.current}`),g(t),()=>{l.current=false,C();}},[t,o,u]),!o){let C=i(t);C.renders=Math.max(C.renders,s.current);}let m=useCallback(()=>{s.current=0,v(t);},[t]);return o?{mounts:0,renders:0,reset:m}:{mounts:i(t).mounts,renders:s.current,reset:m}}function G({id:r,className:e,style:n,logToConsole:c=false,disabled:t=false}){let{mounts:u,renders:o}=p(r,{logToConsole:c,disabled:t});return t?null:jsxs("div",{className:e,style:n,children:[jsxs("span",{children:["M: ",jsx("strong",{children:u})]}),jsx("span",{children:"|"}),jsxs("span",{children:["R: ",jsx("strong",{children:o})]})]})}function I({id:r,label:e,children:n,className:c,footerClassName:t,style:u,logToConsole:o=false,disabled:s=false}){let l=e||r||"widget",{mounts:R,renders:m}=p(r,{logToConsole:o,disabled:s});return s?jsx(Fragment,{children:n}):jsxs("div",{className:c,style:u,children:[n&&jsx("div",{className:"flex-1 overflow-hidden relative min-h-0",children:n}),jsxs("div",{className:t,children:[jsx("span",{className:"truncate",children:l}),jsxs("div",{className:"flex items-center gap-3",children:[jsxs("span",{children:["Mounts: ",jsx("strong",{className:"counter-mounts",children:R})]}),jsx("span",{children:"|"}),jsxs("span",{children:["Renders: ",jsx("strong",{className:"counter-renders",children:m})]})]})]})]})}export{G as RenderCounterBadge,I as RenderCounterFooter,S as clearAllCounters,v as resetCounterRecord,p as useRenderCounter};//# sourceMappingURL=devtools.js.map
2
2
  //# sourceMappingURL=devtools.js.map
@@ -1 +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"]}
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","RenderCounterBadge","className","style","mounts","renders","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\n/**\n * A badge displaying mount and render counters for debugging component lifecycles.\n */\nexport function RenderCounterBadge({\n id,\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 return (\n <div className={className} style={style}>\n <span>\n M: <strong>{mounts}</strong>\n </span>\n <span>|</span>\n <span>\n R: <strong>{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 className={className} style={style}>\n {children && <div className=\"flex-1 overflow-hidden relative min-h-0\">{children}</div>}\n <div className={footerClassName}>\n <span className=\"truncate\">{displayId}</span>\n <div className=\"flex items-center gap-3\">\n <span>\n Mounts: <strong className=\"counter-mounts\">{mounts}</strong>\n </span>\n <span>|</span>\n <span>\n Renders: <strong className=\"counter-renders\">{renders}</strong>\n </span>\n </div>\n </div>\n </div>\n )\n}\n"]}