@unhingged/vizu-react 0.1.20 → 0.1.22
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 +35 -1
- package/dist/index.cjs +159 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +41 -2
- package/dist/index.d.ts +41 -2
- package/dist/index.js +162 -15
- package/dist/index.js.map +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -66,9 +66,43 @@ function YourPage() {
|
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
+
## Floating launcher pill
|
|
70
|
+
|
|
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):
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { VizuProvider, VizuLauncher } from '@vizu/react';
|
|
75
|
+
|
|
76
|
+
<VizuProvider options={{ namespace: 'my-site' }}>
|
|
77
|
+
<YourPage />
|
|
78
|
+
<VizuLauncher />
|
|
79
|
+
</VizuProvider>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
By default it sits bottom-right (Vizu's own toolbar takes bottom-center), shows the saved-comment count as a badge, and hides itself while Vizu is active — reappearing when Vizu is disabled. Everything is tweakable:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
<VizuLauncher
|
|
86
|
+
label="Leave feedback" // pill text, default "Feedback"
|
|
87
|
+
position="bottom-left" // bottom-right | bottom-left | top-right | top-left
|
|
88
|
+
offset={32} // px from the viewport edges, default 20
|
|
89
|
+
showCount={false} // hide the comment-count badge
|
|
90
|
+
hideWhenActive={false} // keep it visible as an on/off toggle
|
|
91
|
+
accent="#7C5CFF" // dot + active border color
|
|
92
|
+
onToggle={(enabled) => track('vizu_toggled', { enabled })}
|
|
93
|
+
/>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or replace the content entirely while keeping the pill shell and behavior:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<VizuLauncher>💬 Comments</VizuLauncher>
|
|
100
|
+
```
|
|
101
|
+
|
|
69
102
|
## What you get
|
|
70
103
|
|
|
71
104
|
- `<VizuProvider>` — mounts a single Vizu instance for the subtree (lazy, destroyed on unmount)
|
|
105
|
+
- `<VizuLauncher>` — floating pill that enables Vizu with a click (see above)
|
|
72
106
|
- `useVizu()` — the instance itself for imperative calls (`vizu.enable()`, `vizu.clearAll()`, etc.)
|
|
73
107
|
- `useComments()` — subscribes to all comment-list events, returns `VizuComment[]`
|
|
74
108
|
- `useVizuUser()` — `[user, setUser]` tuple, re-renders on `user:changed`
|
|
@@ -88,7 +122,7 @@ Or skip storage entirely and own it in your effects — listen to `comment:added
|
|
|
88
122
|
|
|
89
123
|
## Next.js (App Router)
|
|
90
124
|
|
|
91
|
-
`VizuProvider` is a client component (it uses refs + effects). Put it inside a layout that opts in:
|
|
125
|
+
`VizuProvider` is a client component (it uses refs + effects) and is StrictMode-safe — dev-mode's mount→cleanup→remount cycle recreates the instance instead of leaving a destroyed one in context. Put it inside a layout that opts in:
|
|
92
126
|
|
|
93
127
|
```tsx
|
|
94
128
|
// app/(commentable)/layout.tsx
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.tsx
|
|
22
22
|
var src_exports = {};
|
|
23
23
|
__export(src_exports, {
|
|
24
|
+
VizuLauncher: () => VizuLauncher,
|
|
24
25
|
VizuProvider: () => VizuProvider,
|
|
25
26
|
useComments: () => useComments,
|
|
26
27
|
useVizu: () => useVizu,
|
|
@@ -29,26 +30,166 @@ __export(src_exports, {
|
|
|
29
30
|
useVizuUser: () => useVizuUser
|
|
30
31
|
});
|
|
31
32
|
module.exports = __toCommonJS(src_exports);
|
|
32
|
-
var
|
|
33
|
+
var import_react2 = require("react");
|
|
33
34
|
var import_vizu_core = require("@unhingged/vizu-core");
|
|
35
|
+
|
|
36
|
+
// src/launcher.tsx
|
|
37
|
+
var import_react = require("react");
|
|
34
38
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
35
|
-
|
|
39
|
+
function useVizuEnabled(vizu) {
|
|
40
|
+
const subscribe = (0, import_react.useMemo)(
|
|
41
|
+
() => (cb) => {
|
|
42
|
+
const offs = [vizu.on("enabled", cb), vizu.on("disabled", cb)];
|
|
43
|
+
return () => offs.forEach((off) => off());
|
|
44
|
+
},
|
|
45
|
+
[vizu]
|
|
46
|
+
);
|
|
47
|
+
const getSnapshot = () => vizu.isEnabled();
|
|
48
|
+
return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, () => false);
|
|
49
|
+
}
|
|
50
|
+
function useCommentCount(vizu) {
|
|
51
|
+
const subscribe = (0, import_react.useMemo)(
|
|
52
|
+
() => (cb) => {
|
|
53
|
+
const offs = [
|
|
54
|
+
vizu.on("comment:added", cb),
|
|
55
|
+
vizu.on("comment:removed", cb),
|
|
56
|
+
vizu.on("comments:cleared", cb),
|
|
57
|
+
vizu.on("comments:set", cb),
|
|
58
|
+
vizu.on("comments:loaded", cb)
|
|
59
|
+
];
|
|
60
|
+
return () => offs.forEach((off) => off());
|
|
61
|
+
},
|
|
62
|
+
[vizu]
|
|
63
|
+
);
|
|
64
|
+
const getSnapshot = () => vizu.getComments().length;
|
|
65
|
+
return (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, () => 0);
|
|
66
|
+
}
|
|
67
|
+
var ACCENT_DEFAULT = "#FF6647";
|
|
68
|
+
function VizuLauncher({
|
|
69
|
+
label = "Feedback",
|
|
70
|
+
position = "bottom-right",
|
|
71
|
+
offset = 20,
|
|
72
|
+
showCount = true,
|
|
73
|
+
hideWhenActive = true,
|
|
74
|
+
accent = ACCENT_DEFAULT,
|
|
75
|
+
zIndex = 2147483646,
|
|
76
|
+
className,
|
|
77
|
+
style,
|
|
78
|
+
children,
|
|
79
|
+
onToggle
|
|
80
|
+
}) {
|
|
81
|
+
const vizu = useVizu();
|
|
82
|
+
const enabled = useVizuEnabled(vizu);
|
|
83
|
+
const count = useCommentCount(vizu);
|
|
84
|
+
const [hovered, setHovered] = (0, import_react.useState)(false);
|
|
85
|
+
const buttonRef = (0, import_react.useRef)(null);
|
|
86
|
+
if (enabled && hideWhenActive) return null;
|
|
87
|
+
const [vSide, hSide] = position.split("-");
|
|
88
|
+
const base = {
|
|
89
|
+
position: "fixed",
|
|
90
|
+
[vSide]: offset,
|
|
91
|
+
[hSide]: offset,
|
|
92
|
+
zIndex,
|
|
93
|
+
display: "inline-flex",
|
|
94
|
+
alignItems: "center",
|
|
95
|
+
gap: 8,
|
|
96
|
+
padding: "9px 16px",
|
|
97
|
+
borderRadius: 999,
|
|
98
|
+
border: `1px solid ${enabled ? accent : hovered ? "#3A3A3A" : "#2A2A2A"}`,
|
|
99
|
+
background: hovered ? "#1A1A1A" : "#0A0A0A",
|
|
100
|
+
color: "#fafafa",
|
|
101
|
+
font: '500 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
|
|
102
|
+
cursor: "pointer",
|
|
103
|
+
boxShadow: "0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)",
|
|
104
|
+
userSelect: "none",
|
|
105
|
+
transition: "background 120ms, border-color 120ms, transform 120ms",
|
|
106
|
+
transform: hovered ? "translateY(-1px)" : "none"
|
|
107
|
+
};
|
|
108
|
+
const handleClick = () => {
|
|
109
|
+
vizu.toggle();
|
|
110
|
+
if (vizu.isEnabled()) vizu.requestAuth();
|
|
111
|
+
onToggle?.(vizu.isEnabled());
|
|
112
|
+
buttonRef.current?.blur();
|
|
113
|
+
};
|
|
114
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
ref: buttonRef,
|
|
118
|
+
type: "button",
|
|
119
|
+
"data-vizu-ignore": "",
|
|
120
|
+
className,
|
|
121
|
+
style: { ...base, ...style },
|
|
122
|
+
onClick: handleClick,
|
|
123
|
+
onMouseEnter: () => setHovered(true),
|
|
124
|
+
onMouseLeave: () => setHovered(false),
|
|
125
|
+
"aria-pressed": enabled,
|
|
126
|
+
title: enabled ? "Turn off commenting" : "Comment on this page",
|
|
127
|
+
children: children ?? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
128
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
129
|
+
"span",
|
|
130
|
+
{
|
|
131
|
+
"aria-hidden": "true",
|
|
132
|
+
style: {
|
|
133
|
+
width: 7,
|
|
134
|
+
height: 7,
|
|
135
|
+
borderRadius: "50%",
|
|
136
|
+
background: accent,
|
|
137
|
+
boxShadow: `0 0 8px ${accent}`,
|
|
138
|
+
flexShrink: 0
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
),
|
|
142
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: label }),
|
|
143
|
+
showCount && count > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
144
|
+
"span",
|
|
145
|
+
{
|
|
146
|
+
style: {
|
|
147
|
+
minWidth: 18,
|
|
148
|
+
height: 18,
|
|
149
|
+
padding: "0 5px",
|
|
150
|
+
borderRadius: 9,
|
|
151
|
+
background: "#2A2A2A",
|
|
152
|
+
color: "#fafafa",
|
|
153
|
+
fontSize: 11,
|
|
154
|
+
fontWeight: 600,
|
|
155
|
+
display: "inline-flex",
|
|
156
|
+
alignItems: "center",
|
|
157
|
+
justifyContent: "center"
|
|
158
|
+
},
|
|
159
|
+
children: count
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
] })
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/index.tsx
|
|
168
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
169
|
+
var VizuContext = (0, import_react2.createContext)(null);
|
|
36
170
|
function VizuProvider({ options, children }) {
|
|
37
|
-
const
|
|
171
|
+
const optionsRef = (0, import_react2.useRef)(options);
|
|
172
|
+
optionsRef.current = options;
|
|
173
|
+
const ref = (0, import_react2.useRef)(null);
|
|
38
174
|
if (ref.current === null) {
|
|
39
175
|
ref.current = new import_vizu_core.Vizu(options ?? {});
|
|
40
176
|
}
|
|
41
|
-
const vizu = ref.current;
|
|
42
|
-
(0,
|
|
177
|
+
const [vizu, setVizu] = (0, import_react2.useState)(ref.current);
|
|
178
|
+
(0, import_react2.useEffect)(() => {
|
|
179
|
+
if (ref.current === null) {
|
|
180
|
+
ref.current = new import_vizu_core.Vizu(optionsRef.current ?? {});
|
|
181
|
+
setVizu(ref.current);
|
|
182
|
+
}
|
|
183
|
+
const instance = ref.current;
|
|
43
184
|
return () => {
|
|
44
|
-
|
|
185
|
+
instance.destroy();
|
|
45
186
|
ref.current = null;
|
|
46
187
|
};
|
|
47
188
|
}, []);
|
|
48
|
-
return /* @__PURE__ */ (0,
|
|
189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VizuContext.Provider, { value: vizu, children });
|
|
49
190
|
}
|
|
50
191
|
function useVizu() {
|
|
51
|
-
const v = (0,
|
|
192
|
+
const v = (0, import_react2.useContext)(VizuContext);
|
|
52
193
|
if (!v) {
|
|
53
194
|
throw new Error("useVizu must be called inside a <VizuProvider>.");
|
|
54
195
|
}
|
|
@@ -56,16 +197,16 @@ function useVizu() {
|
|
|
56
197
|
}
|
|
57
198
|
function useVizuEvent(event, handler) {
|
|
58
199
|
const vizu = useVizu();
|
|
59
|
-
const handlerRef = (0,
|
|
200
|
+
const handlerRef = (0, import_react2.useRef)(handler);
|
|
60
201
|
handlerRef.current = handler;
|
|
61
|
-
(0,
|
|
202
|
+
(0, import_react2.useEffect)(() => {
|
|
62
203
|
const off = vizu.on(event, (payload) => handlerRef.current(payload));
|
|
63
204
|
return off;
|
|
64
205
|
}, [vizu, event]);
|
|
65
206
|
}
|
|
66
207
|
function useComments() {
|
|
67
208
|
const vizu = useVizu();
|
|
68
|
-
const subscribe = (0,
|
|
209
|
+
const subscribe = (0, import_react2.useMemo)(
|
|
69
210
|
() => (cb) => {
|
|
70
211
|
const offs = [
|
|
71
212
|
vizu.on("comment:added", cb),
|
|
@@ -78,7 +219,7 @@ function useComments() {
|
|
|
78
219
|
},
|
|
79
220
|
[vizu]
|
|
80
221
|
);
|
|
81
|
-
const cacheRef = (0,
|
|
222
|
+
const cacheRef = (0, import_react2.useRef)(vizu.getComments());
|
|
82
223
|
const getSnapshot = () => {
|
|
83
224
|
const next = vizu.getComments();
|
|
84
225
|
const last = cacheRef.current;
|
|
@@ -88,23 +229,23 @@ function useComments() {
|
|
|
88
229
|
cacheRef.current = next;
|
|
89
230
|
return next;
|
|
90
231
|
};
|
|
91
|
-
return (0,
|
|
232
|
+
return (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
92
233
|
}
|
|
93
234
|
function useVizuUser() {
|
|
94
235
|
const vizu = useVizu();
|
|
95
|
-
const subscribe = (0,
|
|
236
|
+
const subscribe = (0, import_react2.useMemo)(
|
|
96
237
|
() => (cb) => vizu.on("user:changed", cb),
|
|
97
238
|
[vizu]
|
|
98
239
|
);
|
|
99
240
|
const getSnapshot = () => vizu.getUser();
|
|
100
|
-
const user = (0,
|
|
241
|
+
const user = (0, import_react2.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
|
|
101
242
|
return [user, (u) => vizu.setUser(u)];
|
|
102
243
|
}
|
|
103
244
|
function useVizuAction(action) {
|
|
104
245
|
const vizu = useVizu();
|
|
105
|
-
const actionRef = (0,
|
|
246
|
+
const actionRef = (0, import_react2.useRef)(action);
|
|
106
247
|
actionRef.current = action;
|
|
107
|
-
(0,
|
|
248
|
+
(0, import_react2.useEffect)(() => {
|
|
108
249
|
vizu.addAction({
|
|
109
250
|
...action,
|
|
110
251
|
onClick: (ctx) => actionRef.current.onClick(ctx),
|
|
@@ -115,6 +256,7 @@ function useVizuAction(action) {
|
|
|
115
256
|
}
|
|
116
257
|
// Annotate the CommonJS export names for ESM import in node:
|
|
117
258
|
0 && (module.exports = {
|
|
259
|
+
VizuLauncher,
|
|
118
260
|
VizuProvider,
|
|
119
261
|
useComments,
|
|
120
262
|
useVizu,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\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\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 // We need a stable instance across renders. Use a ref so re-renders don't construct new ones.\n const ref = useRef<Vizu | null>(null);\n if (ref.current === null) {\n ref.current = new Vizu(options ?? {});\n }\n const vizu = ref.current;\n\n useEffect(() => {\n return () => {\n vizu.destroy();\n ref.current = null;\n };\n // Intentionally only run on unmount. Recreating Vizu mid-tree would lose listeners + state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\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"],"mappings":"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","names":[]}
|
|
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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
3
|
import { VizuOptions, VizuComment, Vizu, 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
|
+
type VizuLauncherPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
7
|
+
interface VizuLauncherProps {
|
|
8
|
+
/** Text inside the pill. Default: "Feedback". */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */
|
|
11
|
+
position?: VizuLauncherPosition;
|
|
12
|
+
/** Distance from the two nearest viewport edges, in px. Default: 20. */
|
|
13
|
+
offset?: number;
|
|
14
|
+
/** Show the saved-comment count as a badge. Default: true. */
|
|
15
|
+
showCount?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar
|
|
18
|
+
* has a disable button, so a second toggle would be redundant. Pass false
|
|
19
|
+
* to keep the pill visible as an on/off toggle instead.
|
|
20
|
+
*/
|
|
21
|
+
hideWhenActive?: boolean;
|
|
22
|
+
/** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */
|
|
23
|
+
accent?: string;
|
|
24
|
+
/** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */
|
|
25
|
+
zIndex?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
/** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */
|
|
28
|
+
style?: CSSProperties;
|
|
29
|
+
/** Replaces the default dot + label + count content. Styling of the pill shell still applies. */
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
/** Called after the pill toggles Vizu, with the new enabled state. */
|
|
32
|
+
onToggle?: (enabled: boolean) => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A floating pill the host app renders anywhere inside <VizuProvider>.
|
|
36
|
+
* One click enables Vizu — no keyboard shortcut needed. In cloud mode the
|
|
37
|
+
* click also kicks the sign-in popup (same as the shortcut path), so a
|
|
38
|
+
* signed-out reviewer lands straight in the auth flow.
|
|
39
|
+
*
|
|
40
|
+
* By default the pill hides itself while Vizu is active (Vizu's own toolbar
|
|
41
|
+
* takes over) and reappears when Vizu is disabled.
|
|
42
|
+
*/
|
|
43
|
+
declare function VizuLauncher({ label, position, offset, showCount, hideWhenActive, accent, zIndex, className, style, children, onToggle, }: VizuLauncherProps): react_jsx_runtime.JSX.Element | null;
|
|
44
|
+
|
|
6
45
|
interface VizuProviderProps {
|
|
7
46
|
options?: VizuOptions;
|
|
8
47
|
children?: ReactNode;
|
|
@@ -27,4 +66,4 @@ declare function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void];
|
|
|
27
66
|
*/
|
|
28
67
|
declare function useVizuAction(action: VizuAction): void;
|
|
29
68
|
|
|
30
|
-
export { VizuProvider, type VizuProviderProps, useComments, useVizu, useVizuAction, useVizuEvent, useVizuUser };
|
|
69
|
+
export { VizuLauncher, type VizuLauncherPosition, type VizuLauncherProps, VizuProvider, type VizuProviderProps, useComments, useVizu, useVizuAction, useVizuEvent, useVizuUser };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,47 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
3
|
import { VizuOptions, VizuComment, Vizu, 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
|
+
type VizuLauncherPosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
7
|
+
interface VizuLauncherProps {
|
|
8
|
+
/** Text inside the pill. Default: "Feedback". */
|
|
9
|
+
label?: string;
|
|
10
|
+
/** Which corner the pill floats in. Default: 'bottom-right' (Vizu's own toolbar sits bottom-center). */
|
|
11
|
+
position?: VizuLauncherPosition;
|
|
12
|
+
/** Distance from the two nearest viewport edges, in px. Default: 20. */
|
|
13
|
+
offset?: number;
|
|
14
|
+
/** Show the saved-comment count as a badge. Default: true. */
|
|
15
|
+
showCount?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Hide the pill while Vizu is enabled. Default: true — Vizu's own toolbar
|
|
18
|
+
* has a disable button, so a second toggle would be redundant. Pass false
|
|
19
|
+
* to keep the pill visible as an on/off toggle instead.
|
|
20
|
+
*/
|
|
21
|
+
hideWhenActive?: boolean;
|
|
22
|
+
/** Accent for the dot + active border. Default: Vizu's accent (#FF6647). */
|
|
23
|
+
accent?: string;
|
|
24
|
+
/** Stacking context. Default: 2147483646 (one below Vizu's own overlay). */
|
|
25
|
+
zIndex?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
/** Merged over the pill's base styles — positioning included, so `style={{ inset: ... }}` can relocate it entirely. */
|
|
28
|
+
style?: CSSProperties;
|
|
29
|
+
/** Replaces the default dot + label + count content. Styling of the pill shell still applies. */
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
/** Called after the pill toggles Vizu, with the new enabled state. */
|
|
32
|
+
onToggle?: (enabled: boolean) => void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A floating pill the host app renders anywhere inside <VizuProvider>.
|
|
36
|
+
* One click enables Vizu — no keyboard shortcut needed. In cloud mode the
|
|
37
|
+
* click also kicks the sign-in popup (same as the shortcut path), so a
|
|
38
|
+
* signed-out reviewer lands straight in the auth flow.
|
|
39
|
+
*
|
|
40
|
+
* By default the pill hides itself while Vizu is active (Vizu's own toolbar
|
|
41
|
+
* takes over) and reappears when Vizu is disabled.
|
|
42
|
+
*/
|
|
43
|
+
declare function VizuLauncher({ label, position, offset, showCount, hideWhenActive, accent, zIndex, className, style, children, onToggle, }: VizuLauncherProps): react_jsx_runtime.JSX.Element | null;
|
|
44
|
+
|
|
6
45
|
interface VizuProviderProps {
|
|
7
46
|
options?: VizuOptions;
|
|
8
47
|
children?: ReactNode;
|
|
@@ -27,4 +66,4 @@ declare function useVizuUser(): [VizuUser | null, (u: VizuUser | null) => void];
|
|
|
27
66
|
*/
|
|
28
67
|
declare function useVizuAction(action: VizuAction): void;
|
|
29
68
|
|
|
30
|
-
export { VizuProvider, type VizuProviderProps, useComments, useVizu, useVizuAction, useVizuEvent, useVizuUser };
|
|
69
|
+
export { VizuLauncher, type VizuLauncherPosition, type VizuLauncherProps, VizuProvider, type VizuProviderProps, useComments, useVizu, useVizuAction, useVizuEvent, useVizuUser };
|
package/dist/index.js
CHANGED
|
@@ -5,28 +5,174 @@ import {
|
|
|
5
5
|
createContext,
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
|
-
useMemo,
|
|
9
|
-
useRef,
|
|
10
|
-
|
|
8
|
+
useMemo as useMemo2,
|
|
9
|
+
useRef as useRef2,
|
|
10
|
+
useState as useState2,
|
|
11
|
+
useSyncExternalStore as useSyncExternalStore2
|
|
11
12
|
} from "react";
|
|
12
13
|
import {
|
|
13
14
|
Vizu
|
|
14
15
|
} from "@unhingged/vizu-core";
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
// src/launcher.tsx
|
|
18
|
+
import {
|
|
19
|
+
useMemo,
|
|
20
|
+
useRef,
|
|
21
|
+
useState,
|
|
22
|
+
useSyncExternalStore
|
|
23
|
+
} from "react";
|
|
24
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
25
|
+
function useVizuEnabled(vizu) {
|
|
26
|
+
const subscribe = useMemo(
|
|
27
|
+
() => (cb) => {
|
|
28
|
+
const offs = [vizu.on("enabled", cb), vizu.on("disabled", cb)];
|
|
29
|
+
return () => offs.forEach((off) => off());
|
|
30
|
+
},
|
|
31
|
+
[vizu]
|
|
32
|
+
);
|
|
33
|
+
const getSnapshot = () => vizu.isEnabled();
|
|
34
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
35
|
+
}
|
|
36
|
+
function useCommentCount(vizu) {
|
|
37
|
+
const subscribe = useMemo(
|
|
38
|
+
() => (cb) => {
|
|
39
|
+
const offs = [
|
|
40
|
+
vizu.on("comment:added", cb),
|
|
41
|
+
vizu.on("comment:removed", cb),
|
|
42
|
+
vizu.on("comments:cleared", cb),
|
|
43
|
+
vizu.on("comments:set", cb),
|
|
44
|
+
vizu.on("comments:loaded", cb)
|
|
45
|
+
];
|
|
46
|
+
return () => offs.forEach((off) => off());
|
|
47
|
+
},
|
|
48
|
+
[vizu]
|
|
49
|
+
);
|
|
50
|
+
const getSnapshot = () => vizu.getComments().length;
|
|
51
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => 0);
|
|
52
|
+
}
|
|
53
|
+
var ACCENT_DEFAULT = "#FF6647";
|
|
54
|
+
function VizuLauncher({
|
|
55
|
+
label = "Feedback",
|
|
56
|
+
position = "bottom-right",
|
|
57
|
+
offset = 20,
|
|
58
|
+
showCount = true,
|
|
59
|
+
hideWhenActive = true,
|
|
60
|
+
accent = ACCENT_DEFAULT,
|
|
61
|
+
zIndex = 2147483646,
|
|
62
|
+
className,
|
|
63
|
+
style,
|
|
64
|
+
children,
|
|
65
|
+
onToggle
|
|
66
|
+
}) {
|
|
67
|
+
const vizu = useVizu();
|
|
68
|
+
const enabled = useVizuEnabled(vizu);
|
|
69
|
+
const count = useCommentCount(vizu);
|
|
70
|
+
const [hovered, setHovered] = useState(false);
|
|
71
|
+
const buttonRef = useRef(null);
|
|
72
|
+
if (enabled && hideWhenActive) return null;
|
|
73
|
+
const [vSide, hSide] = position.split("-");
|
|
74
|
+
const base = {
|
|
75
|
+
position: "fixed",
|
|
76
|
+
[vSide]: offset,
|
|
77
|
+
[hSide]: offset,
|
|
78
|
+
zIndex,
|
|
79
|
+
display: "inline-flex",
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
gap: 8,
|
|
82
|
+
padding: "9px 16px",
|
|
83
|
+
borderRadius: 999,
|
|
84
|
+
border: `1px solid ${enabled ? accent : hovered ? "#3A3A3A" : "#2A2A2A"}`,
|
|
85
|
+
background: hovered ? "#1A1A1A" : "#0A0A0A",
|
|
86
|
+
color: "#fafafa",
|
|
87
|
+
font: '500 13px/1 -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif',
|
|
88
|
+
cursor: "pointer",
|
|
89
|
+
boxShadow: "0 8px 24px rgba(0,0,0,.4), inset 0 0 0 1px rgba(255,255,255,.04)",
|
|
90
|
+
userSelect: "none",
|
|
91
|
+
transition: "background 120ms, border-color 120ms, transform 120ms",
|
|
92
|
+
transform: hovered ? "translateY(-1px)" : "none"
|
|
93
|
+
};
|
|
94
|
+
const handleClick = () => {
|
|
95
|
+
vizu.toggle();
|
|
96
|
+
if (vizu.isEnabled()) vizu.requestAuth();
|
|
97
|
+
onToggle?.(vizu.isEnabled());
|
|
98
|
+
buttonRef.current?.blur();
|
|
99
|
+
};
|
|
100
|
+
return /* @__PURE__ */ jsx(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
ref: buttonRef,
|
|
104
|
+
type: "button",
|
|
105
|
+
"data-vizu-ignore": "",
|
|
106
|
+
className,
|
|
107
|
+
style: { ...base, ...style },
|
|
108
|
+
onClick: handleClick,
|
|
109
|
+
onMouseEnter: () => setHovered(true),
|
|
110
|
+
onMouseLeave: () => setHovered(false),
|
|
111
|
+
"aria-pressed": enabled,
|
|
112
|
+
title: enabled ? "Turn off commenting" : "Comment on this page",
|
|
113
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
114
|
+
/* @__PURE__ */ jsx(
|
|
115
|
+
"span",
|
|
116
|
+
{
|
|
117
|
+
"aria-hidden": "true",
|
|
118
|
+
style: {
|
|
119
|
+
width: 7,
|
|
120
|
+
height: 7,
|
|
121
|
+
borderRadius: "50%",
|
|
122
|
+
background: accent,
|
|
123
|
+
boxShadow: `0 0 8px ${accent}`,
|
|
124
|
+
flexShrink: 0
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
129
|
+
showCount && count > 0 && /* @__PURE__ */ jsx(
|
|
130
|
+
"span",
|
|
131
|
+
{
|
|
132
|
+
style: {
|
|
133
|
+
minWidth: 18,
|
|
134
|
+
height: 18,
|
|
135
|
+
padding: "0 5px",
|
|
136
|
+
borderRadius: 9,
|
|
137
|
+
background: "#2A2A2A",
|
|
138
|
+
color: "#fafafa",
|
|
139
|
+
fontSize: 11,
|
|
140
|
+
fontWeight: 600,
|
|
141
|
+
display: "inline-flex",
|
|
142
|
+
alignItems: "center",
|
|
143
|
+
justifyContent: "center"
|
|
144
|
+
},
|
|
145
|
+
children: count
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
] })
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/index.tsx
|
|
154
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
16
155
|
var VizuContext = createContext(null);
|
|
17
156
|
function VizuProvider({ options, children }) {
|
|
18
|
-
const
|
|
157
|
+
const optionsRef = useRef2(options);
|
|
158
|
+
optionsRef.current = options;
|
|
159
|
+
const ref = useRef2(null);
|
|
19
160
|
if (ref.current === null) {
|
|
20
161
|
ref.current = new Vizu(options ?? {});
|
|
21
162
|
}
|
|
22
|
-
const vizu = ref.current;
|
|
163
|
+
const [vizu, setVizu] = useState2(ref.current);
|
|
23
164
|
useEffect(() => {
|
|
165
|
+
if (ref.current === null) {
|
|
166
|
+
ref.current = new Vizu(optionsRef.current ?? {});
|
|
167
|
+
setVizu(ref.current);
|
|
168
|
+
}
|
|
169
|
+
const instance = ref.current;
|
|
24
170
|
return () => {
|
|
25
|
-
|
|
171
|
+
instance.destroy();
|
|
26
172
|
ref.current = null;
|
|
27
173
|
};
|
|
28
174
|
}, []);
|
|
29
|
-
return /* @__PURE__ */
|
|
175
|
+
return /* @__PURE__ */ jsx2(VizuContext.Provider, { value: vizu, children });
|
|
30
176
|
}
|
|
31
177
|
function useVizu() {
|
|
32
178
|
const v = useContext(VizuContext);
|
|
@@ -37,7 +183,7 @@ function useVizu() {
|
|
|
37
183
|
}
|
|
38
184
|
function useVizuEvent(event, handler) {
|
|
39
185
|
const vizu = useVizu();
|
|
40
|
-
const handlerRef =
|
|
186
|
+
const handlerRef = useRef2(handler);
|
|
41
187
|
handlerRef.current = handler;
|
|
42
188
|
useEffect(() => {
|
|
43
189
|
const off = vizu.on(event, (payload) => handlerRef.current(payload));
|
|
@@ -46,7 +192,7 @@ function useVizuEvent(event, handler) {
|
|
|
46
192
|
}
|
|
47
193
|
function useComments() {
|
|
48
194
|
const vizu = useVizu();
|
|
49
|
-
const subscribe =
|
|
195
|
+
const subscribe = useMemo2(
|
|
50
196
|
() => (cb) => {
|
|
51
197
|
const offs = [
|
|
52
198
|
vizu.on("comment:added", cb),
|
|
@@ -59,7 +205,7 @@ function useComments() {
|
|
|
59
205
|
},
|
|
60
206
|
[vizu]
|
|
61
207
|
);
|
|
62
|
-
const cacheRef =
|
|
208
|
+
const cacheRef = useRef2(vizu.getComments());
|
|
63
209
|
const getSnapshot = () => {
|
|
64
210
|
const next = vizu.getComments();
|
|
65
211
|
const last = cacheRef.current;
|
|
@@ -69,21 +215,21 @@ function useComments() {
|
|
|
69
215
|
cacheRef.current = next;
|
|
70
216
|
return next;
|
|
71
217
|
};
|
|
72
|
-
return
|
|
218
|
+
return useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
|
|
73
219
|
}
|
|
74
220
|
function useVizuUser() {
|
|
75
221
|
const vizu = useVizu();
|
|
76
|
-
const subscribe =
|
|
222
|
+
const subscribe = useMemo2(
|
|
77
223
|
() => (cb) => vizu.on("user:changed", cb),
|
|
78
224
|
[vizu]
|
|
79
225
|
);
|
|
80
226
|
const getSnapshot = () => vizu.getUser();
|
|
81
|
-
const user =
|
|
227
|
+
const user = useSyncExternalStore2(subscribe, getSnapshot, getSnapshot);
|
|
82
228
|
return [user, (u) => vizu.setUser(u)];
|
|
83
229
|
}
|
|
84
230
|
function useVizuAction(action) {
|
|
85
231
|
const vizu = useVizu();
|
|
86
|
-
const actionRef =
|
|
232
|
+
const actionRef = useRef2(action);
|
|
87
233
|
actionRef.current = action;
|
|
88
234
|
useEffect(() => {
|
|
89
235
|
vizu.addAction({
|
|
@@ -95,6 +241,7 @@ function useVizuAction(action) {
|
|
|
95
241
|
}, [vizu, action.id]);
|
|
96
242
|
}
|
|
97
243
|
export {
|
|
244
|
+
VizuLauncher,
|
|
98
245
|
VizuProvider,
|
|
99
246
|
useComments,
|
|
100
247
|
useVizu,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\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\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 // We need a stable instance across renders. Use a ref so re-renders don't construct new ones.\n const ref = useRef<Vizu | null>(null);\n if (ref.current === null) {\n ref.current = new Vizu(options ?? {});\n }\n const vizu = ref.current;\n\n useEffect(() => {\n return () => {\n vizu.destroy();\n ref.current = null;\n };\n // Intentionally only run on unmount. Recreating Vizu mid-tree would lose listeners + state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\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"],"mappings":"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","names":[]}
|
|
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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhingged/vizu-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "React bindings for @unhingged/vizu-core — VizuProvider, useVizu, useComments, useVizuUser, useVizuEvent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -23,17 +23,21 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsup",
|
|
26
|
-
"dev": "tsup --watch"
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"test": "bun test"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"react": ">=18"
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@unhingged/vizu-core": "
|
|
33
|
+
"@unhingged/vizu-core": "0.1.21"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
36
|
+
"@happy-dom/global-registrator": "^20.9.0",
|
|
35
37
|
"@types/react": "^18.3.0",
|
|
38
|
+
"happy-dom": "^20.9.0",
|
|
36
39
|
"react": "^18.3.1",
|
|
40
|
+
"react-dom": "^18.3.1",
|
|
37
41
|
"tsup": "^8.3.5",
|
|
38
42
|
"typescript": "^5.6.3"
|
|
39
43
|
},
|