@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 +20 -30
- package/dist/index.cjs +16 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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 (
|
|
31
|
-
const [user
|
|
29
|
+
// 1. Read current user (set automatically after workspace sign-in)
|
|
30
|
+
const [user] = useVizuUser();
|
|
32
31
|
|
|
33
|
-
// 2.
|
|
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
|
-
//
|
|
35
|
+
// 3. Listen to events — mirror activity into your own systems
|
|
42
36
|
useVizuEvent('comment:added', ({ comment }) => {
|
|
43
|
-
|
|
37
|
+
analytics.track('vizu_comment', { id: comment.id });
|
|
44
38
|
});
|
|
45
39
|
|
|
46
|
-
//
|
|
40
|
+
// 4. Register a pill action
|
|
47
41
|
useVizuAction({
|
|
48
|
-
id: '
|
|
49
|
-
label: '
|
|
42
|
+
id: 'copy-prompt',
|
|
43
|
+
label: 'Copy as prompt',
|
|
50
44
|
variant: 'primary',
|
|
51
|
-
onClick: (ctx) =>
|
|
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
|
-
//
|
|
49
|
+
// 5. Imperative access
|
|
59
50
|
const vizu = useVizu();
|
|
60
51
|
useEffect(() => {
|
|
61
|
-
//
|
|
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 (
|
|
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={{
|
|
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
|
-
##
|
|
102
|
+
## Cloud workspace
|
|
113
103
|
|
|
114
|
-
|
|
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={{
|
|
118
|
-
<VizuProvider options={{
|
|
107
|
+
<VizuProvider options={{ workspace: 'my-team' }}>...
|
|
108
|
+
<VizuProvider options={{ workspace: 'my-team', autoSignIn: false }}>... // never auto-open the popup
|
|
119
109
|
```
|
|
120
110
|
|
|
121
|
-
|
|
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={{
|
|
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 [
|
|
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
|
-
|
|
186
|
+
ref.current = new import_vizu_core.Vizu(optionsRef.current);
|
|
187
|
+
setSelfVizu(ref.current);
|
|
182
188
|
}
|
|
183
|
-
const
|
|
189
|
+
const self = ref.current;
|
|
184
190
|
return () => {
|
|
185
|
-
|
|
191
|
+
self.destroy();
|
|
186
192
|
ref.current = null;
|
|
187
193
|
};
|
|
188
|
-
}, []);
|
|
189
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VizuContext.Provider, { value:
|
|
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);
|
package/dist/index.cjs.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":["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,
|
|
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,
|
|
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 [
|
|
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
|
-
|
|
172
|
+
ref.current = new Vizu(optionsRef.current);
|
|
173
|
+
setSelfVizu(ref.current);
|
|
168
174
|
}
|
|
169
|
-
const
|
|
175
|
+
const self = ref.current;
|
|
170
176
|
return () => {
|
|
171
|
-
|
|
177
|
+
self.destroy();
|
|
172
178
|
ref.current = null;
|
|
173
179
|
};
|
|
174
|
-
}, []);
|
|
175
|
-
return /* @__PURE__ */ jsx2(VizuContext.Provider, { value:
|
|
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.
|
|
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.
|
|
33
|
+
"@unhingged/vizu-core": "^0.2.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@happy-dom/global-registrator": "^20.9.0",
|