@unhingged/vizu-react 0.1.23 → 0.2.0

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
@@ -15,10 +15,9 @@ import { VizuProvider, useVizu, useComments, useVizuUser, useVizuEvent, useVizuA
15
15
  export default function App() {
16
16
  return (
17
17
  <VizuProvider options={{
18
- namespace: 'my-site',
18
+ workspace: 'my-team', // required — created in the Vizu dashboard
19
19
  pageVersion: 'v1',
20
20
  shortcut: 'mod+shift+e',
21
- user: { name: 'Anonymous' },
22
21
  startEnabled: true,
23
22
  }}>
24
23
  <YourPage />
@@ -27,39 +26,30 @@ export default function App() {
27
26
  }
28
27
 
29
28
  function YourPage() {
30
- // 1. Read current user (re-renders on setUser)
31
- const [user, setUser] = useVizuUser();
29
+ // 1. Read current user (set automatically after workspace sign-in)
30
+ const [user] = useVizuUser();
32
31
 
33
- // 2. Hydrate identity from your auth/session
34
- useEffect(() => {
35
- setUser({ id: session.userId, name: session.name, avatarUrl: session.avatar });
36
- }, [session]);
37
-
38
- // 3. Read live comments (re-renders on add/remove/clear/set)
32
+ // 2. Read live comments (re-renders on add/remove/clear/set)
39
33
  const comments = useComments();
40
34
 
41
- // 4. Listen to events — persist to your backend
35
+ // 3. Listen to events — mirror activity into your own systems
42
36
  useVizuEvent('comment:added', ({ comment }) => {
43
- fetch('/api/comments', { method: 'POST', body: JSON.stringify(comment) });
37
+ analytics.track('vizu_comment', { id: comment.id });
44
38
  });
45
39
 
46
- // 5. Register a pill action
40
+ // 4. Register a pill action
47
41
  useVizuAction({
48
- id: 'send-to-api',
49
- label: 'Send to API',
42
+ id: 'copy-prompt',
43
+ label: 'Copy as prompt',
50
44
  variant: 'primary',
51
- onClick: (ctx) => fetch('/api/iterate', {
52
- method: 'POST',
53
- body: JSON.stringify({ comments: ctx.comments, pageHtml: ctx.pageHtml }),
54
- }),
45
+ onClick: (ctx) => ctx.copyToClipboard(buildPrompt(ctx.comments)),
55
46
  visibleWhen: ({ commentsCount }) => commentsCount > 0,
56
47
  });
57
48
 
58
- // 6. Imperative access
49
+ // 5. Imperative access
59
50
  const vizu = useVizu();
60
51
  useEffect(() => {
61
- // Pre-load comments from your backend
62
- fetch(`/api/comments?ns=my-site`).then(r => r.json()).then(c => vizu.setComments(c, { persist: false }));
52
+ vizu.enable(); // or leave it to the shortcut / <VizuLauncher />
63
53
  }, [vizu]);
64
54
 
65
55
  return <div>Your page content…</div>;
@@ -68,12 +58,12 @@ function YourPage() {
68
58
 
69
59
  ## Floating launcher pill
70
60
 
71
- Not everyone knows the keyboard shortcut. Drop `<VizuLauncher />` anywhere inside the provider and visitors get a floating pill that turns Vizu on with one click (and kicks the sign-in popup in cloud mode, same as the shortcut):
61
+ Not everyone knows the keyboard shortcut. Drop `<VizuLauncher />` anywhere inside the provider and visitors get a floating pill that turns Vizu on with one click (kicking the workspace sign-in popup, same as the shortcut):
72
62
 
73
63
  ```tsx
74
64
  import { VizuProvider, VizuLauncher } from '@vizu/react';
75
65
 
76
- <VizuProvider options={{ namespace: 'my-site' }}>
66
+ <VizuProvider options={{ workspace: 'my-team' }}>
77
67
  <YourPage />
78
68
  <VizuLauncher />
79
69
  </VizuProvider>
@@ -109,16 +99,16 @@ Or replace the content entirely while keeping the pill shell and behavior:
109
99
  - `useVizuEvent('event', handler)` — subscribe to any Vizu event for the component lifetime
110
100
  - `useVizuAction({...})` — register an action while mounted; auto-removed on unmount
111
101
 
112
- ## Storage defaults
102
+ ## Cloud workspace
113
103
 
114
- Programmatic mode defaults to **in-memory** storage. Provide `options.storage` if you want persistence:
104
+ Vizu is cloud-only: every provider connects to a workspace created in the [Vizu dashboard](https://vizu.unhingged.com), and `options.workspace` is required. Comments are private to the workspace — the UI mounts after a member signs in (a popup handled by Vizu; kick it early with `<VizuLauncher />`, the keyboard shortcut, or `vizu.requestAuth()`).
115
105
 
116
106
  ```tsx
117
- <VizuProvider options={{ storage: 'local' }}>...
118
- <VizuProvider options={{ storage: myCustomAdapter }}>...
107
+ <VizuProvider options={{ workspace: 'my-team' }}>...
108
+ <VizuProvider options={{ workspace: 'my-team', autoSignIn: false }}>... // never auto-open the popup
119
109
  ```
120
110
 
121
- Or skip storage entirely and own it in your effects listen to `comment:added` and POST to your API.
111
+ Identity comes from the workspace sign-in — you no longer set it yourself.
122
112
 
123
113
  ## Next.js (App Router)
124
114
 
@@ -129,7 +119,7 @@ Or skip storage entirely and own it in your effects — listen to `comment:added
129
119
  'use client';
130
120
  import { VizuProvider } from '@vizu/react';
131
121
  export default function Layout({ children }) {
132
- return <VizuProvider options={{ namespace: 'my-app' }}>{children}</VizuProvider>;
122
+ return <VizuProvider options={{ workspace: 'my-team' }}>{children}</VizuProvider>;
133
123
  }
134
124
  ```
135
125
 
package/dist/index.cjs CHANGED
@@ -167,26 +167,32 @@ function VizuLauncher({
167
167
  // src/index.tsx
168
168
  var import_jsx_runtime2 = require("react/jsx-runtime");
169
169
  var VizuContext = (0, import_react2.createContext)(null);
170
- function VizuProvider({ options, children }) {
170
+ function VizuProvider({ options, instance, children }) {
171
+ if (!instance && !options) {
172
+ throw new Error(
173
+ "[vizu] VizuProvider requires `options` with a workspace \u2014 <VizuProvider options={{ workspace: 'your-slug' }}> \u2014 or a caller-owned `instance`."
174
+ );
175
+ }
171
176
  const optionsRef = (0, import_react2.useRef)(options);
172
177
  optionsRef.current = options;
173
178
  const ref = (0, import_react2.useRef)(null);
174
- if (ref.current === null) {
175
- ref.current = new import_vizu_core.Vizu(options ?? {});
179
+ if (!instance && ref.current === null) {
180
+ ref.current = new import_vizu_core.Vizu(options);
176
181
  }
177
- const [vizu, setVizu] = (0, import_react2.useState)(ref.current);
182
+ const [selfVizu, setSelfVizu] = (0, import_react2.useState)(ref.current);
178
183
  (0, import_react2.useEffect)(() => {
184
+ if (instance) return;
179
185
  if (ref.current === null) {
180
- ref.current = new import_vizu_core.Vizu(optionsRef.current ?? {});
181
- setVizu(ref.current);
186
+ ref.current = new import_vizu_core.Vizu(optionsRef.current);
187
+ setSelfVizu(ref.current);
182
188
  }
183
- const instance = ref.current;
189
+ const self = ref.current;
184
190
  return () => {
185
- instance.destroy();
191
+ self.destroy();
186
192
  ref.current = null;
187
193
  };
188
- }, []);
189
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VizuContext.Provider, { value: vizu, children });
194
+ }, [instance]);
195
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VizuContext.Provider, { value: instance ?? selfVizu, children });
190
196
  }
191
197
  function useVizu() {
192
198
  const v = (0, import_react2.useContext)(VizuContext);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/launcher.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n Vizu,\n type VizuOptions,\n type VizuComment,\n type VizuUser,\n type VizuAction,\n type VizuEventName,\n type VizuEventHandler,\n} from '@unhingged/vizu-core';\n\nexport type {\n Vizu,\n VizuOptions,\n VizuComment,\n VizuUser,\n VizuAction,\n VizuEventName,\n VizuEventHandler,\n};\n\nexport { VizuLauncher } from './launcher';\nexport type { VizuLauncherProps, VizuLauncherPosition } from './launcher';\n\nconst VizuContext = createContext<Vizu | null>(null);\n\nexport interface VizuProviderProps {\n options?: VizuOptions;\n children?: ReactNode;\n}\n\n/**\n * Mounts a single Vizu instance for the subtree. The instance is created lazily\n * once and persists across re-renders. Pass options once at construction;\n * imperative changes go through `useVizu()`.\n */\nexport function VizuProvider({ options, children }: VizuProviderProps) {\n // Latest options, so a StrictMode recreation (below) doesn't construct\n // from a stale first-render snapshot.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // The instance must exist during the first render (children call useVizu\n // immediately), so construct lazily here rather than in the effect.\n const ref = useRef<Vizu | null>(null);\n if (ref.current === null) {\n ref.current = new Vizu(options ?? {});\n }\n // Context value lives in state so a recreated instance propagates to\n // consumers; re-renders don't construct new ones.\n const [vizu, setVizu] = useState<Vizu>(ref.current);\n\n useEffect(() => {\n // StrictMode's mount→cleanup→remount cycle destroys the render-time\n // instance in the first cleanup without re-running the render-phase\n // construction. Recreate here and push it through state so every\n // consumer re-subscribes to the live instance.\n if (ref.current === null) {\n ref.current = new Vizu(optionsRef.current ?? {});\n setVizu(ref.current);\n }\n const instance = ref.current;\n return () => {\n instance.destroy();\n ref.current = null;\n };\n }, []);\n\n return <VizuContext.Provider value={vizu}>{children}</VizuContext.Provider>;\n}\n\n/** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */\nexport function useVizu(): Vizu {\n const v = useContext(VizuContext);\n if (!v) {\n throw new Error('useVizu must be called inside a <VizuProvider>.');\n }\n return v;\n}\n\n/** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */\nexport function useVizuEvent<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void {\n const vizu = useVizu();\n // Stable handler ref — re-subscribe only when the event name changes.\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n const off = vizu.on(event, (payload) => handlerRef.current(payload));\n return off;\n }, [vizu, event]);\n}\n\n/** Returns the current comments array and re-renders when it changes. */\nexport function useComments(): VizuComment[] {\n const vizu = useVizu();\n // Stable subscribe + snapshot for useSyncExternalStore.\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n // getSnapshot must return a referentially stable result when the data hasn't changed.\n // We re-derive only when the subscribe fires; meanwhile memoize the last array.\n const cacheRef = useRef<VizuComment[]>(vizu.getComments());\n const getSnapshot = () => {\n const next = vizu.getComments();\n const last = cacheRef.current;\n if (last.length === next.length && last.every((c, i) => c === next[i] || (c.id === next[i].id && c.text === next[i].text))) {\n return last;\n }\n cacheRef.current = next;\n return next;\n };\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Returns the current user and re-renders when it changes. Also exposes a setter. */\nexport function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void] {\n const vizu = useVizu();\n const subscribe = useMemo(\n () => (cb: () => void) => vizu.on('user:changed', cb),\n [vizu],\n );\n const getSnapshot = () => vizu.getUser();\n const user = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n return [user, (u) => vizu.setUser(u)];\n}\n\n/**\n * Register an action while the component is mounted. The action is removed\n * automatically on unmount. Re-renders DON'T re-register unless `action.id` changes.\n */\nexport function useVizuAction(action: VizuAction): void {\n const vizu = useVizu();\n const actionRef = useRef(action);\n actionRef.current = action;\n useEffect(() => {\n // Wrap so we always call the latest action.onClick / visibleWhen\n vizu.addAction({\n ...action,\n onClick: (ctx) => actionRef.current.onClick(ctx),\n visibleWhen: action.visibleWhen\n ? ({ commentsCount }) => actionRef.current.visibleWhen?.({ commentsCount }) ?? true\n : undefined,\n });\n return () => vizu.removeAction(action.id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [vizu, action.id]);\n}\n","'use client';\n\nimport {\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport type { Vizu } from '@unhingged/vizu-core';\nimport { useVizu } from './index';\n\nexport type VizuLauncherPosition =\n | 'bottom-right'\n | 'bottom-left'\n | 'top-right'\n | 'top-left';\n\nexport interface VizuLauncherProps {\n /** Text inside the pill. Default: \"Feedback\". */\n label?: string;\n /** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */\n position?: VizuLauncherPosition;\n /** Distance from the two nearest viewport edges, in px. Default: 20. */\n offset?: number;\n /** Show the saved-comment count as a badge. Default: true. */\n showCount?: boolean;\n /**\n * Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar\n * has a disable button, so a second toggle would be redundant. Pass false\n * to keep the pill visible as an on/off toggle instead.\n */\n hideWhenActive?: boolean;\n /** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */\n accent?: string;\n /** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */\n zIndex?: number;\n className?: string;\n /** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */\n style?: CSSProperties;\n /** Replaces the default dot + label + count content. Styling of the pill shell still applies. */\n children?: ReactNode;\n /** Called after the pill toggles Vizu, with the new enabled state. */\n onToggle?: (enabled: boolean) => void;\n}\n\nfunction useVizuEnabled(vizu: Vizu): boolean {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [vizu.on('enabled', cb), vizu.on('disabled', cb)];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.isEnabled();\n return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n\nfunction useCommentCount(vizu: Vizu): number {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.getComments().length;\n return useSyncExternalStore(subscribe, getSnapshot, () => 0);\n}\n\nconst ACCENT_DEFAULT = '#FF6647';\n\n/**\n * A floating pill the host app renders anywhere inside <VizuProvider>.\n * One click enables Vizu — no keyboard shortcut needed. In cloud mode the\n * click also kicks the sign-in popup (same as the shortcut path), so a\n * signed-out reviewer lands straight in the auth flow.\n *\n * By default the pill hides itself while Vizu is active (Vizu's own toolbar\n * takes over) and reappears when Vizu is disabled.\n */\nexport function VizuLauncher({\n label = 'Feedback',\n position = 'bottom-right',\n offset = 20,\n showCount = true,\n hideWhenActive = true,\n accent = ACCENT_DEFAULT,\n zIndex = 2147483646,\n className,\n style,\n children,\n onToggle,\n}: VizuLauncherProps) {\n const vizu = useVizu();\n const enabled = useVizuEnabled(vizu);\n const count = useCommentCount(vizu);\n const [hovered, setHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n if (enabled && hideWhenActive) return null;\n\n const [vSide, hSide] = position.split('-') as ['bottom' | 'top', 'left' | 'right'];\n\n const base: CSSProperties = {\n position: 'fixed',\n [vSide]: offset,\n [hSide]: offset,\n zIndex,\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '9px 16px',\n borderRadius: 999,\n border: `1px solid ${enabled ? accent : hovered ? '#3A3A3A' : '#2A2A2A'}`,\n background: hovered ? '#1A1A1A' : '#0A0A0A',\n color: '#fafafa',\n font: '500 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif',\n cursor: 'pointer',\n boxShadow: '0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)',\n userSelect: 'none',\n transition: 'background 120ms, border-color 120ms, transform 120ms',\n transform: hovered ? 'translateY(-1px)' : 'none',\n };\n\n const handleClick = () => {\n vizu.toggle();\n // Enabling from a click is explicit user intent — kick cloud sign-in\n // now (no-op outside cloud mode / when signed in), mirroring the\n // keyboard shortcut's behavior.\n if (vizu.isEnabled()) vizu.requestAuth();\n onToggle?.(vizu.isEnabled());\n buttonRef.current?.blur();\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n // Keeps Vizu's highlighter from treating the launcher as a commentable\n // element when it stays visible while Vizu is active (hideWhenActive={false}).\n data-vizu-ignore=\"\"\n className={className}\n style={{ ...base, ...style }}\n onClick={handleClick}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n aria-pressed={enabled}\n title={enabled ? 'Turn off commenting' : 'Comment on this page'}\n >\n {children ?? (\n <>\n <span\n aria-hidden=\"true\"\n style={{\n width: 7,\n height: 7,\n borderRadius: '50%',\n background: accent,\n boxShadow: `0 0 8px ${accent}`,\n flexShrink: 0,\n }}\n />\n <span>{label}</span>\n {showCount && count > 0 && (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: '#2A2A2A',\n color: '#fafafa',\n fontSize: 11,\n fontWeight: 600,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n {count}\n </span>\n )}\n </>\n )}\n </button>\n );\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/launcher.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n Vizu,\n type VizuOptions,\n type VizuComment,\n type VizuUser,\n type VizuAction,\n type VizuEventName,\n type VizuEventHandler,\n} from '@unhingged/vizu-core';\n\nexport type {\n Vizu,\n VizuOptions,\n VizuComment,\n VizuUser,\n VizuAction,\n VizuEventName,\n VizuEventHandler,\n};\n\nexport { VizuLauncher } from './launcher';\nexport type { VizuLauncherProps, VizuLauncherPosition } from './launcher';\n\nconst VizuContext = createContext<Vizu | null>(null);\n\nexport interface VizuProviderProps {\n /**\n * Construction options — `workspace` is required (Vizu is cloud-only).\n * The provider owns the instance lifecycle: constructed once, destroyed\n * on unmount. Read once at construction; imperative changes go through\n * `useVizu()`.\n */\n options?: VizuOptions;\n /**\n * Advanced: a caller-constructed Vizu instance. The provider does NOT\n * construct or destroy it — the caller owns its lifecycle. Mutually\n * exclusive with `options` (used by tests/storybook to inject offline\n * instances).\n */\n instance?: Vizu;\n children?: ReactNode;\n}\n\n/**\n * Mounts a single Vizu instance for the subtree. The instance is created lazily\n * once and persists across re-renders. Pass options once at construction;\n * imperative changes go through `useVizu()`.\n */\nexport function VizuProvider({ options, instance, children }: VizuProviderProps) {\n if (!instance && !options) {\n throw new Error(\n \"[vizu] VizuProvider requires `options` with a workspace — <VizuProvider options={{ workspace: 'your-slug' }}> — or a caller-owned `instance`.\",\n );\n }\n // Latest options, so a StrictMode recreation (below) doesn't construct\n // from a stale first-render snapshot.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // The instance must exist during the first render (children call useVizu\n // immediately), so construct lazily here rather than in the effect.\n const ref = useRef<Vizu | null>(null);\n if (!instance && ref.current === null) {\n ref.current = new Vizu(options!);\n }\n // Context value lives in state so a recreated instance propagates to\n // consumers; re-renders don't construct new ones.\n const [selfVizu, setSelfVizu] = useState<Vizu | null>(ref.current);\n\n useEffect(() => {\n // Caller-owned instance: nothing to construct, nothing to destroy.\n if (instance) return;\n // StrictMode's mount→cleanup→remount cycle destroys the render-time\n // instance in the first cleanup without re-running the render-phase\n // construction. Recreate here and push it through state so every\n // consumer re-subscribes to the live instance.\n if (ref.current === null) {\n ref.current = new Vizu(optionsRef.current!);\n setSelfVizu(ref.current);\n }\n const self = ref.current;\n return () => {\n self.destroy();\n ref.current = null;\n };\n }, [instance]);\n\n return <VizuContext.Provider value={instance ?? selfVizu}>{children}</VizuContext.Provider>;\n}\n\n/** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */\nexport function useVizu(): Vizu {\n const v = useContext(VizuContext);\n if (!v) {\n throw new Error('useVizu must be called inside a <VizuProvider>.');\n }\n return v;\n}\n\n/** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */\nexport function useVizuEvent<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void {\n const vizu = useVizu();\n // Stable handler ref — re-subscribe only when the event name changes.\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n const off = vizu.on(event, (payload) => handlerRef.current(payload));\n return off;\n }, [vizu, event]);\n}\n\n/** Returns the current comments array and re-renders when it changes. */\nexport function useComments(): VizuComment[] {\n const vizu = useVizu();\n // Stable subscribe + snapshot for useSyncExternalStore.\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n // getSnapshot must return a referentially stable result when the data hasn't changed.\n // We re-derive only when the subscribe fires; meanwhile memoize the last array.\n const cacheRef = useRef<VizuComment[]>(vizu.getComments());\n const getSnapshot = () => {\n const next = vizu.getComments();\n const last = cacheRef.current;\n if (last.length === next.length && last.every((c, i) => c === next[i] || (c.id === next[i].id && c.text === next[i].text))) {\n return last;\n }\n cacheRef.current = next;\n return next;\n };\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Returns the current user and re-renders when it changes. Also exposes a setter. */\nexport function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void] {\n const vizu = useVizu();\n const subscribe = useMemo(\n () => (cb: () => void) => vizu.on('user:changed', cb),\n [vizu],\n );\n const getSnapshot = () => vizu.getUser();\n const user = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n return [user, (u) => vizu.setUser(u)];\n}\n\n/**\n * Register an action while the component is mounted. The action is removed\n * automatically on unmount. Re-renders DON'T re-register unless `action.id` changes.\n */\nexport function useVizuAction(action: VizuAction): void {\n const vizu = useVizu();\n const actionRef = useRef(action);\n actionRef.current = action;\n useEffect(() => {\n // Wrap so we always call the latest action.onClick / visibleWhen\n vizu.addAction({\n ...action,\n onClick: (ctx) => actionRef.current.onClick(ctx),\n visibleWhen: action.visibleWhen\n ? ({ commentsCount }) => actionRef.current.visibleWhen?.({ commentsCount }) ?? true\n : undefined,\n });\n return () => vizu.removeAction(action.id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [vizu, action.id]);\n}\n","'use client';\n\nimport {\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport type { Vizu } from '@unhingged/vizu-core';\nimport { useVizu } from './index';\n\nexport type VizuLauncherPosition =\n | 'bottom-right'\n | 'bottom-left'\n | 'top-right'\n | 'top-left';\n\nexport interface VizuLauncherProps {\n /** Text inside the pill. Default: \"Feedback\". */\n label?: string;\n /** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */\n position?: VizuLauncherPosition;\n /** Distance from the two nearest viewport edges, in px. Default: 20. */\n offset?: number;\n /** Show the saved-comment count as a badge. Default: true. */\n showCount?: boolean;\n /**\n * Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar\n * has a disable button, so a second toggle would be redundant. Pass false\n * to keep the pill visible as an on/off toggle instead.\n */\n hideWhenActive?: boolean;\n /** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */\n accent?: string;\n /** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */\n zIndex?: number;\n className?: string;\n /** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */\n style?: CSSProperties;\n /** Replaces the default dot + label + count content. Styling of the pill shell still applies. */\n children?: ReactNode;\n /** Called after the pill toggles Vizu, with the new enabled state. */\n onToggle?: (enabled: boolean) => void;\n}\n\nfunction useVizuEnabled(vizu: Vizu): boolean {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [vizu.on('enabled', cb), vizu.on('disabled', cb)];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.isEnabled();\n return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n\nfunction useCommentCount(vizu: Vizu): number {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.getComments().length;\n return useSyncExternalStore(subscribe, getSnapshot, () => 0);\n}\n\nconst ACCENT_DEFAULT = '#FF6647';\n\n/**\n * A floating pill the host app renders anywhere inside <VizuProvider>.\n * One click enables Vizu — no keyboard shortcut needed. In cloud mode the\n * click also kicks the sign-in popup (same as the shortcut path), so a\n * signed-out reviewer lands straight in the auth flow.\n *\n * By default the pill hides itself while Vizu is active (Vizu's own toolbar\n * takes over) and reappears when Vizu is disabled.\n */\nexport function VizuLauncher({\n label = 'Feedback',\n position = 'bottom-right',\n offset = 20,\n showCount = true,\n hideWhenActive = true,\n accent = ACCENT_DEFAULT,\n zIndex = 2147483646,\n className,\n style,\n children,\n onToggle,\n}: VizuLauncherProps) {\n const vizu = useVizu();\n const enabled = useVizuEnabled(vizu);\n const count = useCommentCount(vizu);\n const [hovered, setHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n if (enabled && hideWhenActive) return null;\n\n const [vSide, hSide] = position.split('-') as ['bottom' | 'top', 'left' | 'right'];\n\n const base: CSSProperties = {\n position: 'fixed',\n [vSide]: offset,\n [hSide]: offset,\n zIndex,\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '9px 16px',\n borderRadius: 999,\n border: `1px solid ${enabled ? accent : hovered ? '#3A3A3A' : '#2A2A2A'}`,\n background: hovered ? '#1A1A1A' : '#0A0A0A',\n color: '#fafafa',\n font: '500 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif',\n cursor: 'pointer',\n boxShadow: '0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)',\n userSelect: 'none',\n transition: 'background 120ms, border-color 120ms, transform 120ms',\n transform: hovered ? 'translateY(-1px)' : 'none',\n };\n\n const handleClick = () => {\n vizu.toggle();\n // Enabling from a click is explicit user intent — kick cloud sign-in\n // now (no-op outside cloud mode / when signed in), mirroring the\n // keyboard shortcut's behavior.\n if (vizu.isEnabled()) vizu.requestAuth();\n onToggle?.(vizu.isEnabled());\n buttonRef.current?.blur();\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n // Keeps Vizu's highlighter from treating the launcher as a commentable\n // element when it stays visible while Vizu is active (hideWhenActive={false}).\n data-vizu-ignore=\"\"\n className={className}\n style={{ ...base, ...style }}\n onClick={handleClick}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n aria-pressed={enabled}\n title={enabled ? 'Turn off commenting' : 'Comment on this page'}\n >\n {children ?? (\n <>\n <span\n aria-hidden=\"true\"\n style={{\n width: 7,\n height: 7,\n borderRadius: '50%',\n background: accent,\n boxShadow: `0 0 8px ${accent}`,\n flexShrink: 0,\n }}\n />\n <span>{label}</span>\n {showCount && count > 0 && (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: '#2A2A2A',\n color: '#fafafa',\n fontSize: 11,\n fontWeight: 600,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n {count}\n </span>\n )}\n </>\n )}\n </button>\n );\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ReactNode } from 'react';
3
- import { VizuOptions, VizuComment, Vizu, VizuAction, VizuEventName, VizuEventHandler, VizuUser } from '@unhingged/vizu-core';
3
+ import { VizuOptions, Vizu, VizuComment, VizuAction, VizuEventName, VizuEventHandler, VizuUser } from '@unhingged/vizu-core';
4
4
  export { Vizu, VizuAction, VizuComment, VizuEventHandler, VizuEventName, VizuOptions, VizuUser } from '@unhingged/vizu-core';
5
5
 
6
6
  type VizuLauncherPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
@@ -43,7 +43,20 @@ interface VizuLauncherProps {
43
43
  declare function VizuLauncher({ label, position, offset, showCount, hideWhenActive, accent, zIndex, className, style, children, onToggle, }: VizuLauncherProps): react.JSX.Element | null;
44
44
 
45
45
  interface VizuProviderProps {
46
+ /**
47
+ * Construction options — `workspace` is required (Vizu is cloud-only).
48
+ * The provider owns the instance lifecycle: constructed once, destroyed
49
+ * on unmount. Read once at construction; imperative changes go through
50
+ * `useVizu()`.
51
+ */
46
52
  options?: VizuOptions;
53
+ /**
54
+ * Advanced: a caller-constructed Vizu instance. The provider does NOT
55
+ * construct or destroy it — the caller owns its lifecycle. Mutually
56
+ * exclusive with `options` (used by tests/storybook to inject offline
57
+ * instances).
58
+ */
59
+ instance?: Vizu;
47
60
  children?: ReactNode;
48
61
  }
49
62
  /**
@@ -51,7 +64,7 @@ interface VizuProviderProps {
51
64
  * once and persists across re-renders. Pass options once at construction;
52
65
  * imperative changes go through `useVizu()`.
53
66
  */
54
- declare function VizuProvider({ options, children }: VizuProviderProps): react.JSX.Element;
67
+ declare function VizuProvider({ options, instance, children }: VizuProviderProps): react.JSX.Element;
55
68
  /** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */
56
69
  declare function useVizu(): Vizu;
57
70
  /** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties, ReactNode } from 'react';
3
- import { VizuOptions, VizuComment, Vizu, VizuAction, VizuEventName, VizuEventHandler, VizuUser } from '@unhingged/vizu-core';
3
+ import { VizuOptions, Vizu, VizuComment, VizuAction, VizuEventName, VizuEventHandler, VizuUser } from '@unhingged/vizu-core';
4
4
  export { Vizu, VizuAction, VizuComment, VizuEventHandler, VizuEventName, VizuOptions, VizuUser } from '@unhingged/vizu-core';
5
5
 
6
6
  type VizuLauncherPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
@@ -43,7 +43,20 @@ interface VizuLauncherProps {
43
43
  declare function VizuLauncher({ label, position, offset, showCount, hideWhenActive, accent, zIndex, className, style, children, onToggle, }: VizuLauncherProps): react.JSX.Element | null;
44
44
 
45
45
  interface VizuProviderProps {
46
+ /**
47
+ * Construction options — `workspace` is required (Vizu is cloud-only).
48
+ * The provider owns the instance lifecycle: constructed once, destroyed
49
+ * on unmount. Read once at construction; imperative changes go through
50
+ * `useVizu()`.
51
+ */
46
52
  options?: VizuOptions;
53
+ /**
54
+ * Advanced: a caller-constructed Vizu instance. The provider does NOT
55
+ * construct or destroy it — the caller owns its lifecycle. Mutually
56
+ * exclusive with `options` (used by tests/storybook to inject offline
57
+ * instances).
58
+ */
59
+ instance?: Vizu;
47
60
  children?: ReactNode;
48
61
  }
49
62
  /**
@@ -51,7 +64,7 @@ interface VizuProviderProps {
51
64
  * once and persists across re-renders. Pass options once at construction;
52
65
  * imperative changes go through `useVizu()`.
53
66
  */
54
- declare function VizuProvider({ options, children }: VizuProviderProps): react.JSX.Element;
67
+ declare function VizuProvider({ options, instance, children }: VizuProviderProps): react.JSX.Element;
55
68
  /** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */
56
69
  declare function useVizu(): Vizu;
57
70
  /** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */
package/dist/index.js CHANGED
@@ -153,26 +153,32 @@ function VizuLauncher({
153
153
  // src/index.tsx
154
154
  import { jsx as jsx2 } from "react/jsx-runtime";
155
155
  var VizuContext = createContext(null);
156
- function VizuProvider({ options, children }) {
156
+ function VizuProvider({ options, instance, children }) {
157
+ if (!instance && !options) {
158
+ throw new Error(
159
+ "[vizu] VizuProvider requires `options` with a workspace \u2014 <VizuProvider options={{ workspace: 'your-slug' }}> \u2014 or a caller-owned `instance`."
160
+ );
161
+ }
157
162
  const optionsRef = useRef2(options);
158
163
  optionsRef.current = options;
159
164
  const ref = useRef2(null);
160
- if (ref.current === null) {
161
- ref.current = new Vizu(options ?? {});
165
+ if (!instance && ref.current === null) {
166
+ ref.current = new Vizu(options);
162
167
  }
163
- const [vizu, setVizu] = useState2(ref.current);
168
+ const [selfVizu, setSelfVizu] = useState2(ref.current);
164
169
  useEffect(() => {
170
+ if (instance) return;
165
171
  if (ref.current === null) {
166
- ref.current = new Vizu(optionsRef.current ?? {});
167
- setVizu(ref.current);
172
+ ref.current = new Vizu(optionsRef.current);
173
+ setSelfVizu(ref.current);
168
174
  }
169
- const instance = ref.current;
175
+ const self = ref.current;
170
176
  return () => {
171
- instance.destroy();
177
+ self.destroy();
172
178
  ref.current = null;
173
179
  };
174
- }, []);
175
- return /* @__PURE__ */ jsx2(VizuContext.Provider, { value: vizu, children });
180
+ }, [instance]);
181
+ return /* @__PURE__ */ jsx2(VizuContext.Provider, { value: instance ?? selfVizu, children });
176
182
  }
177
183
  function useVizu() {
178
184
  const v = useContext(VizuContext);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/launcher.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n Vizu,\n type VizuOptions,\n type VizuComment,\n type VizuUser,\n type VizuAction,\n type VizuEventName,\n type VizuEventHandler,\n} from '@unhingged/vizu-core';\n\nexport type {\n Vizu,\n VizuOptions,\n VizuComment,\n VizuUser,\n VizuAction,\n VizuEventName,\n VizuEventHandler,\n};\n\nexport { VizuLauncher } from './launcher';\nexport type { VizuLauncherProps, VizuLauncherPosition } from './launcher';\n\nconst VizuContext = createContext<Vizu | null>(null);\n\nexport interface VizuProviderProps {\n options?: VizuOptions;\n children?: ReactNode;\n}\n\n/**\n * Mounts a single Vizu instance for the subtree. The instance is created lazily\n * once and persists across re-renders. Pass options once at construction;\n * imperative changes go through `useVizu()`.\n */\nexport function VizuProvider({ options, children }: VizuProviderProps) {\n // Latest options, so a StrictMode recreation (below) doesn't construct\n // from a stale first-render snapshot.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // The instance must exist during the first render (children call useVizu\n // immediately), so construct lazily here rather than in the effect.\n const ref = useRef<Vizu | null>(null);\n if (ref.current === null) {\n ref.current = new Vizu(options ?? {});\n }\n // Context value lives in state so a recreated instance propagates to\n // consumers; re-renders don't construct new ones.\n const [vizu, setVizu] = useState<Vizu>(ref.current);\n\n useEffect(() => {\n // StrictMode's mount→cleanup→remount cycle destroys the render-time\n // instance in the first cleanup without re-running the render-phase\n // construction. Recreate here and push it through state so every\n // consumer re-subscribes to the live instance.\n if (ref.current === null) {\n ref.current = new Vizu(optionsRef.current ?? {});\n setVizu(ref.current);\n }\n const instance = ref.current;\n return () => {\n instance.destroy();\n ref.current = null;\n };\n }, []);\n\n return <VizuContext.Provider value={vizu}>{children}</VizuContext.Provider>;\n}\n\n/** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */\nexport function useVizu(): Vizu {\n const v = useContext(VizuContext);\n if (!v) {\n throw new Error('useVizu must be called inside a <VizuProvider>.');\n }\n return v;\n}\n\n/** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */\nexport function useVizuEvent<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void {\n const vizu = useVizu();\n // Stable handler ref — re-subscribe only when the event name changes.\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n const off = vizu.on(event, (payload) => handlerRef.current(payload));\n return off;\n }, [vizu, event]);\n}\n\n/** Returns the current comments array and re-renders when it changes. */\nexport function useComments(): VizuComment[] {\n const vizu = useVizu();\n // Stable subscribe + snapshot for useSyncExternalStore.\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n // getSnapshot must return a referentially stable result when the data hasn't changed.\n // We re-derive only when the subscribe fires; meanwhile memoize the last array.\n const cacheRef = useRef<VizuComment[]>(vizu.getComments());\n const getSnapshot = () => {\n const next = vizu.getComments();\n const last = cacheRef.current;\n if (last.length === next.length && last.every((c, i) => c === next[i] || (c.id === next[i].id && c.text === next[i].text))) {\n return last;\n }\n cacheRef.current = next;\n return next;\n };\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Returns the current user and re-renders when it changes. Also exposes a setter. */\nexport function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void] {\n const vizu = useVizu();\n const subscribe = useMemo(\n () => (cb: () => void) => vizu.on('user:changed', cb),\n [vizu],\n );\n const getSnapshot = () => vizu.getUser();\n const user = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n return [user, (u) => vizu.setUser(u)];\n}\n\n/**\n * Register an action while the component is mounted. The action is removed\n * automatically on unmount. Re-renders DON'T re-register unless `action.id` changes.\n */\nexport function useVizuAction(action: VizuAction): void {\n const vizu = useVizu();\n const actionRef = useRef(action);\n actionRef.current = action;\n useEffect(() => {\n // Wrap so we always call the latest action.onClick / visibleWhen\n vizu.addAction({\n ...action,\n onClick: (ctx) => actionRef.current.onClick(ctx),\n visibleWhen: action.visibleWhen\n ? ({ commentsCount }) => actionRef.current.visibleWhen?.({ commentsCount }) ?? true\n : undefined,\n });\n return () => vizu.removeAction(action.id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [vizu, action.id]);\n}\n","'use client';\n\nimport {\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport type { Vizu } from '@unhingged/vizu-core';\nimport { useVizu } from './index';\n\nexport type VizuLauncherPosition =\n | 'bottom-right'\n | 'bottom-left'\n | 'top-right'\n | 'top-left';\n\nexport interface VizuLauncherProps {\n /** Text inside the pill. Default: \"Feedback\". */\n label?: string;\n /** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */\n position?: VizuLauncherPosition;\n /** Distance from the two nearest viewport edges, in px. Default: 20. */\n offset?: number;\n /** Show the saved-comment count as a badge. Default: true. */\n showCount?: boolean;\n /**\n * Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar\n * has a disable button, so a second toggle would be redundant. Pass false\n * to keep the pill visible as an on/off toggle instead.\n */\n hideWhenActive?: boolean;\n /** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */\n accent?: string;\n /** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */\n zIndex?: number;\n className?: string;\n /** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */\n style?: CSSProperties;\n /** Replaces the default dot + label + count content. Styling of the pill shell still applies. */\n children?: ReactNode;\n /** Called after the pill toggles Vizu, with the new enabled state. */\n onToggle?: (enabled: boolean) => void;\n}\n\nfunction useVizuEnabled(vizu: Vizu): boolean {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [vizu.on('enabled', cb), vizu.on('disabled', cb)];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.isEnabled();\n return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n\nfunction useCommentCount(vizu: Vizu): number {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.getComments().length;\n return useSyncExternalStore(subscribe, getSnapshot, () => 0);\n}\n\nconst ACCENT_DEFAULT = '#FF6647';\n\n/**\n * A floating pill the host app renders anywhere inside <VizuProvider>.\n * One click enables Vizu — no keyboard shortcut needed. In cloud mode the\n * click also kicks the sign-in popup (same as the shortcut path), so a\n * signed-out reviewer lands straight in the auth flow.\n *\n * By default the pill hides itself while Vizu is active (Vizu's own toolbar\n * takes over) and reappears when Vizu is disabled.\n */\nexport function VizuLauncher({\n label = 'Feedback',\n position = 'bottom-right',\n offset = 20,\n showCount = true,\n hideWhenActive = true,\n accent = ACCENT_DEFAULT,\n zIndex = 2147483646,\n className,\n style,\n children,\n onToggle,\n}: VizuLauncherProps) {\n const vizu = useVizu();\n const enabled = useVizuEnabled(vizu);\n const count = useCommentCount(vizu);\n const [hovered, setHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n if (enabled && hideWhenActive) return null;\n\n const [vSide, hSide] = position.split('-') as ['bottom' | 'top', 'left' | 'right'];\n\n const base: CSSProperties = {\n position: 'fixed',\n [vSide]: offset,\n [hSide]: offset,\n zIndex,\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '9px 16px',\n borderRadius: 999,\n border: `1px solid ${enabled ? accent : hovered ? '#3A3A3A' : '#2A2A2A'}`,\n background: hovered ? '#1A1A1A' : '#0A0A0A',\n color: '#fafafa',\n font: '500 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif',\n cursor: 'pointer',\n boxShadow: '0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)',\n userSelect: 'none',\n transition: 'background 120ms, border-color 120ms, transform 120ms',\n transform: hovered ? 'translateY(-1px)' : 'none',\n };\n\n const handleClick = () => {\n vizu.toggle();\n // Enabling from a click is explicit user intent — kick cloud sign-in\n // now (no-op outside cloud mode / when signed in), mirroring the\n // keyboard shortcut's behavior.\n if (vizu.isEnabled()) vizu.requestAuth();\n onToggle?.(vizu.isEnabled());\n buttonRef.current?.blur();\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n // Keeps Vizu's highlighter from treating the launcher as a commentable\n // element when it stays visible while Vizu is active (hideWhenActive={false}).\n data-vizu-ignore=\"\"\n className={className}\n style={{ ...base, ...style }}\n onClick={handleClick}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n aria-pressed={enabled}\n title={enabled ? 'Turn off commenting' : 'Comment on this page'}\n >\n {children ?? (\n <>\n <span\n aria-hidden=\"true\"\n style={{\n width: 7,\n height: 7,\n borderRadius: '50%',\n background: accent,\n boxShadow: `0 0 8px ${accent}`,\n flexShrink: 0,\n }}\n />\n <span>{label}</span>\n {showCount && count > 0 && (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: '#2A2A2A',\n color: '#fafafa',\n fontSize: 11,\n fontWeight: 600,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n {count}\n </span>\n )}\n </>\n )}\n </button>\n );\n}\n"],"mappings":"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","names":["useMemo","useRef","useState","useSyncExternalStore","jsx","useRef","useState","useMemo","useSyncExternalStore"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/launcher.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type ReactNode,\n} from 'react';\nimport {\n Vizu,\n type VizuOptions,\n type VizuComment,\n type VizuUser,\n type VizuAction,\n type VizuEventName,\n type VizuEventHandler,\n} from '@unhingged/vizu-core';\n\nexport type {\n Vizu,\n VizuOptions,\n VizuComment,\n VizuUser,\n VizuAction,\n VizuEventName,\n VizuEventHandler,\n};\n\nexport { VizuLauncher } from './launcher';\nexport type { VizuLauncherProps, VizuLauncherPosition } from './launcher';\n\nconst VizuContext = createContext<Vizu | null>(null);\n\nexport interface VizuProviderProps {\n /**\n * Construction options — `workspace` is required (Vizu is cloud-only).\n * The provider owns the instance lifecycle: constructed once, destroyed\n * on unmount. Read once at construction; imperative changes go through\n * `useVizu()`.\n */\n options?: VizuOptions;\n /**\n * Advanced: a caller-constructed Vizu instance. The provider does NOT\n * construct or destroy it — the caller owns its lifecycle. Mutually\n * exclusive with `options` (used by tests/storybook to inject offline\n * instances).\n */\n instance?: Vizu;\n children?: ReactNode;\n}\n\n/**\n * Mounts a single Vizu instance for the subtree. The instance is created lazily\n * once and persists across re-renders. Pass options once at construction;\n * imperative changes go through `useVizu()`.\n */\nexport function VizuProvider({ options, instance, children }: VizuProviderProps) {\n if (!instance && !options) {\n throw new Error(\n \"[vizu] VizuProvider requires `options` with a workspace — <VizuProvider options={{ workspace: 'your-slug' }}> — or a caller-owned `instance`.\",\n );\n }\n // Latest options, so a StrictMode recreation (below) doesn't construct\n // from a stale first-render snapshot.\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // The instance must exist during the first render (children call useVizu\n // immediately), so construct lazily here rather than in the effect.\n const ref = useRef<Vizu | null>(null);\n if (!instance && ref.current === null) {\n ref.current = new Vizu(options!);\n }\n // Context value lives in state so a recreated instance propagates to\n // consumers; re-renders don't construct new ones.\n const [selfVizu, setSelfVizu] = useState<Vizu | null>(ref.current);\n\n useEffect(() => {\n // Caller-owned instance: nothing to construct, nothing to destroy.\n if (instance) return;\n // StrictMode's mount→cleanup→remount cycle destroys the render-time\n // instance in the first cleanup without re-running the render-phase\n // construction. Recreate here and push it through state so every\n // consumer re-subscribes to the live instance.\n if (ref.current === null) {\n ref.current = new Vizu(optionsRef.current!);\n setSelfVizu(ref.current);\n }\n const self = ref.current;\n return () => {\n self.destroy();\n ref.current = null;\n };\n }, [instance]);\n\n return <VizuContext.Provider value={instance ?? selfVizu}>{children}</VizuContext.Provider>;\n}\n\n/** Returns the Vizu instance. Throws if used outside a <VizuProvider>. */\nexport function useVizu(): Vizu {\n const v = useContext(VizuContext);\n if (!v) {\n throw new Error('useVizu must be called inside a <VizuProvider>.');\n }\n return v;\n}\n\n/** Subscribe to a Vizu event for the component's lifetime. Fire-and-forget. */\nexport function useVizuEvent<E extends VizuEventName>(event: E, handler: VizuEventHandler<E>): void {\n const vizu = useVizu();\n // Stable handler ref — re-subscribe only when the event name changes.\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => {\n const off = vizu.on(event, (payload) => handlerRef.current(payload));\n return off;\n }, [vizu, event]);\n}\n\n/** Returns the current comments array and re-renders when it changes. */\nexport function useComments(): VizuComment[] {\n const vizu = useVizu();\n // Stable subscribe + snapshot for useSyncExternalStore.\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n // getSnapshot must return a referentially stable result when the data hasn't changed.\n // We re-derive only when the subscribe fires; meanwhile memoize the last array.\n const cacheRef = useRef<VizuComment[]>(vizu.getComments());\n const getSnapshot = () => {\n const next = vizu.getComments();\n const last = cacheRef.current;\n if (last.length === next.length && last.every((c, i) => c === next[i] || (c.id === next[i].id && c.text === next[i].text))) {\n return last;\n }\n cacheRef.current = next;\n return next;\n };\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** Returns the current user and re-renders when it changes. Also exposes a setter. */\nexport function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void] {\n const vizu = useVizu();\n const subscribe = useMemo(\n () => (cb: () => void) => vizu.on('user:changed', cb),\n [vizu],\n );\n const getSnapshot = () => vizu.getUser();\n const user = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n return [user, (u) => vizu.setUser(u)];\n}\n\n/**\n * Register an action while the component is mounted. The action is removed\n * automatically on unmount. Re-renders DON'T re-register unless `action.id` changes.\n */\nexport function useVizuAction(action: VizuAction): void {\n const vizu = useVizu();\n const actionRef = useRef(action);\n actionRef.current = action;\n useEffect(() => {\n // Wrap so we always call the latest action.onClick / visibleWhen\n vizu.addAction({\n ...action,\n onClick: (ctx) => actionRef.current.onClick(ctx),\n visibleWhen: action.visibleWhen\n ? ({ commentsCount }) => actionRef.current.visibleWhen?.({ commentsCount }) ?? true\n : undefined,\n });\n return () => vizu.removeAction(action.id);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [vizu, action.id]);\n}\n","'use client';\n\nimport {\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport type { Vizu } from '@unhingged/vizu-core';\nimport { useVizu } from './index';\n\nexport type VizuLauncherPosition =\n | 'bottom-right'\n | 'bottom-left'\n | 'top-right'\n | 'top-left';\n\nexport interface VizuLauncherProps {\n /** Text inside the pill. Default: \"Feedback\". */\n label?: string;\n /** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */\n position?: VizuLauncherPosition;\n /** Distance from the two nearest viewport edges, in px. Default: 20. */\n offset?: number;\n /** Show the saved-comment count as a badge. Default: true. */\n showCount?: boolean;\n /**\n * Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar\n * has a disable button, so a second toggle would be redundant. Pass false\n * to keep the pill visible as an on/off toggle instead.\n */\n hideWhenActive?: boolean;\n /** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */\n accent?: string;\n /** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */\n zIndex?: number;\n className?: string;\n /** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */\n style?: CSSProperties;\n /** Replaces the default dot + label + count content. Styling of the pill shell still applies. */\n children?: ReactNode;\n /** Called after the pill toggles Vizu, with the new enabled state. */\n onToggle?: (enabled: boolean) => void;\n}\n\nfunction useVizuEnabled(vizu: Vizu): boolean {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [vizu.on('enabled', cb), vizu.on('disabled', cb)];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.isEnabled();\n return useSyncExternalStore(subscribe, getSnapshot, () => false);\n}\n\nfunction useCommentCount(vizu: Vizu): number {\n const subscribe = useMemo(\n () => (cb: () => void) => {\n const offs = [\n vizu.on('comment:added', cb),\n vizu.on('comment:removed', cb),\n vizu.on('comments:cleared', cb),\n vizu.on('comments:set', cb),\n vizu.on('comments:loaded', cb),\n ];\n return () => offs.forEach((off) => off());\n },\n [vizu],\n );\n const getSnapshot = () => vizu.getComments().length;\n return useSyncExternalStore(subscribe, getSnapshot, () => 0);\n}\n\nconst ACCENT_DEFAULT = '#FF6647';\n\n/**\n * A floating pill the host app renders anywhere inside <VizuProvider>.\n * One click enables Vizu — no keyboard shortcut needed. In cloud mode the\n * click also kicks the sign-in popup (same as the shortcut path), so a\n * signed-out reviewer lands straight in the auth flow.\n *\n * By default the pill hides itself while Vizu is active (Vizu's own toolbar\n * takes over) and reappears when Vizu is disabled.\n */\nexport function VizuLauncher({\n label = 'Feedback',\n position = 'bottom-right',\n offset = 20,\n showCount = true,\n hideWhenActive = true,\n accent = ACCENT_DEFAULT,\n zIndex = 2147483646,\n className,\n style,\n children,\n onToggle,\n}: VizuLauncherProps) {\n const vizu = useVizu();\n const enabled = useVizuEnabled(vizu);\n const count = useCommentCount(vizu);\n const [hovered, setHovered] = useState(false);\n const buttonRef = useRef<HTMLButtonElement>(null);\n\n if (enabled && hideWhenActive) return null;\n\n const [vSide, hSide] = position.split('-') as ['bottom' | 'top', 'left' | 'right'];\n\n const base: CSSProperties = {\n position: 'fixed',\n [vSide]: offset,\n [hSide]: offset,\n zIndex,\n display: 'inline-flex',\n alignItems: 'center',\n gap: 8,\n padding: '9px 16px',\n borderRadius: 999,\n border: `1px solid ${enabled ? accent : hovered ? '#3A3A3A' : '#2A2A2A'}`,\n background: hovered ? '#1A1A1A' : '#0A0A0A',\n color: '#fafafa',\n font: '500 13px/1 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif',\n cursor: 'pointer',\n boxShadow: '0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)',\n userSelect: 'none',\n transition: 'background 120ms, border-color 120ms, transform 120ms',\n transform: hovered ? 'translateY(-1px)' : 'none',\n };\n\n const handleClick = () => {\n vizu.toggle();\n // Enabling from a click is explicit user intent — kick cloud sign-in\n // now (no-op outside cloud mode / when signed in), mirroring the\n // keyboard shortcut's behavior.\n if (vizu.isEnabled()) vizu.requestAuth();\n onToggle?.(vizu.isEnabled());\n buttonRef.current?.blur();\n };\n\n return (\n <button\n ref={buttonRef}\n type=\"button\"\n // Keeps Vizu's highlighter from treating the launcher as a commentable\n // element when it stays visible while Vizu is active (hideWhenActive={false}).\n data-vizu-ignore=\"\"\n className={className}\n style={{ ...base, ...style }}\n onClick={handleClick}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n aria-pressed={enabled}\n title={enabled ? 'Turn off commenting' : 'Comment on this page'}\n >\n {children ?? (\n <>\n <span\n aria-hidden=\"true\"\n style={{\n width: 7,\n height: 7,\n borderRadius: '50%',\n background: accent,\n boxShadow: `0 0 8px ${accent}`,\n flexShrink: 0,\n }}\n />\n <span>{label}</span>\n {showCount && count > 0 && (\n <span\n style={{\n minWidth: 18,\n height: 18,\n padding: '0 5px',\n borderRadius: 9,\n background: '#2A2A2A',\n color: '#fafafa',\n fontSize: 11,\n fontWeight: 600,\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n {count}\n </span>\n )}\n </>\n )}\n </button>\n );\n}\n"],"mappings":"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","names":["useMemo","useRef","useState","useSyncExternalStore","jsx","useRef","useState","useMemo","useSyncExternalStore"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unhingged/vizu-react",
3
- "version": "0.1.23",
3
+ "version": "0.2.0",
4
4
  "description": "React bindings for @unhingged/vizu-core — VizuProvider, useVizu, useComments, useVizuUser, useVizuEvent.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -30,7 +30,7 @@
30
30
  "react": ">=18"
31
31
  },
32
32
  "dependencies": {
33
- "@unhingged/vizu-core": "^0.1.23"
33
+ "@unhingged/vizu-core": "^0.2.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@happy-dom/global-registrator": "^20.9.0",