@washi-ui/react 1.0.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/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/index.d.mts +335 -0
- package/dist/index.d.ts +335 -0
- package/dist/index.js +1014 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +981 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marco Chavez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# @washi-ui/react
|
|
2
|
+
|
|
3
|
+
React bindings for the Washi HTML commenting engine. Provides a context provider, hooks, and ready-made UI components for adding pin-based annotations to any iframe-rendered content.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @washi-ui/react @washi-ui/core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Drop-in UI (`WashiProvider` + `WashiFrame` + `WashiUI`)
|
|
16
|
+
|
|
17
|
+
The fastest path. `WashiUI` renders a floating tool bubble, a comments sidebar, and a pin dialog — no custom UI needed.
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { useMemo } from 'react';
|
|
21
|
+
import { WashiProvider, WashiFrame, WashiUI } from '@washi-ui/react';
|
|
22
|
+
import { LocalStorageAdapter } from '@washi-ui/adapters';
|
|
23
|
+
|
|
24
|
+
export default function App() {
|
|
25
|
+
const adapter = useMemo(() => new LocalStorageAdapter('my-page'), []);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<WashiProvider adapter={adapter}>
|
|
29
|
+
<WashiFrame
|
|
30
|
+
src="/content.html"
|
|
31
|
+
style={{ width: '100%', height: '100vh', border: 'none' }}
|
|
32
|
+
/>
|
|
33
|
+
<WashiUI position="bottom-right" />
|
|
34
|
+
</WashiProvider>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Custom UI (`useWashi` hook)
|
|
40
|
+
|
|
41
|
+
For full control over the interface, use the `useWashi` hook directly:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useWashi } from '@washi-ui/react';
|
|
45
|
+
import { LocalStorageAdapter } from '@washi-ui/adapters';
|
|
46
|
+
|
|
47
|
+
const adapter = new LocalStorageAdapter('my-page');
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
const { iframeRef, mode, setMode, comments, addComment } = useWashi({
|
|
51
|
+
adapter,
|
|
52
|
+
onPinPlaced: async ({ x, y }) => {
|
|
53
|
+
// Called when the user clicks the overlay in annotate mode.
|
|
54
|
+
// Show your own dialog, then call addComment.
|
|
55
|
+
const text = prompt('Add a comment:');
|
|
56
|
+
if (text) await addComment({ x, y, text });
|
|
57
|
+
},
|
|
58
|
+
onCommentClick: (comment) => {
|
|
59
|
+
console.log('Pin clicked:', comment.text);
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<button onClick={() => setMode(mode === 'annotate' ? 'view' : 'annotate')}>
|
|
66
|
+
{mode === 'annotate' ? 'Done' : 'Annotate'}
|
|
67
|
+
</button>
|
|
68
|
+
<div style={{ position: 'relative' }}>
|
|
69
|
+
<iframe
|
|
70
|
+
ref={iframeRef}
|
|
71
|
+
src="/content.html"
|
|
72
|
+
style={{ width: '100%', height: '600px', border: 'none' }}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Components
|
|
83
|
+
|
|
84
|
+
### `<WashiProvider>`
|
|
85
|
+
|
|
86
|
+
Context provider that creates and manages the Washi instance. Wrap your iframe and any Washi UI components inside it.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<WashiProvider
|
|
90
|
+
adapter={adapter}
|
|
91
|
+
initialMode="view"
|
|
92
|
+
mountOptions={{ readOnly: false, disableBuiltinDialog: false }}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</WashiProvider>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|------|------|---------|-------------|
|
|
100
|
+
| `adapter` | `WashiAdapter` | required | Storage adapter |
|
|
101
|
+
| `initialMode` | `'view' \| 'annotate'` | `'view'` | Starting mode |
|
|
102
|
+
| `mountOptions` | `MountOptions` | — | Passed to `washi.mount()` |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### `<WashiFrame>`
|
|
107
|
+
|
|
108
|
+
An `<iframe>` wrapper that auto-registers with the nearest `WashiProvider`. Accepts all standard iframe attributes.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<WashiFrame
|
|
112
|
+
src="/content.html"
|
|
113
|
+
style={{ width: '100%', height: '100vh', border: 'none' }}
|
|
114
|
+
/>
|
|
115
|
+
|
|
116
|
+
{/* Or with inline HTML */}
|
|
117
|
+
<WashiFrame
|
|
118
|
+
srcDoc={htmlString}
|
|
119
|
+
style={{ width: '100%', height: '100vh', border: 'none' }}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Must be used inside a `WashiProvider`.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
### `<WashiUI>`
|
|
128
|
+
|
|
129
|
+
All-in-one floating UI layer. Renders `WashiToolBubble`, `WashiCommentsSidebar`, and `WashiPinDialog` together, wired up and ready to use.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<WashiUI position="bottom-right" accentColor="#667eea" />
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| Prop | Type | Default | Description |
|
|
136
|
+
|------|------|---------|-------------|
|
|
137
|
+
| `position` | `WashiToolBubblePosition` | `'bottom-right'` | Corner for the tool bubble and sidebar |
|
|
138
|
+
| `accentColor` | `string` | `'#667eea'` | Accent color for buttons and pins |
|
|
139
|
+
| `showLoader` | `boolean` | `true` | Show a loading spinner while the iframe initialises |
|
|
140
|
+
|
|
141
|
+
Must be used inside a `WashiProvider`.
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `<WashiToolBubble>`
|
|
146
|
+
|
|
147
|
+
The floating pill button that toggles annotate mode and opens the sidebar. Use this directly when building a custom layout with the other primitive components.
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<WashiToolBubble
|
|
151
|
+
position="bottom-right"
|
|
152
|
+
accentColor="#667eea"
|
|
153
|
+
sidebarOpen={sidebarOpen}
|
|
154
|
+
onSidebarToggle={() => setSidebarOpen(o => !o)}
|
|
155
|
+
/>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
| Prop | Type | Default | Description |
|
|
159
|
+
|------|------|---------|-------------|
|
|
160
|
+
| `position` | `WashiToolBubblePosition` | `'bottom-right'` | Viewport corner |
|
|
161
|
+
| `accentColor` | `string` | `'#667eea'` | Active state colour |
|
|
162
|
+
| `sidebarOpen` | `boolean` | `false` | Whether sidebar is open (controls icon state) |
|
|
163
|
+
| `onSidebarToggle` | `() => void` | — | Called when the sidebar button is clicked |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
### `<WashiCommentsSidebar>`
|
|
168
|
+
|
|
169
|
+
A slide-in panel listing all comments with resolve/delete actions.
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
<WashiCommentsSidebar
|
|
173
|
+
open={sidebarOpen}
|
|
174
|
+
onClose={() => setSidebarOpen(false)}
|
|
175
|
+
position="bottom-right"
|
|
176
|
+
accentColor="#667eea"
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
| Prop | Type | Default | Description |
|
|
181
|
+
|------|------|---------|-------------|
|
|
182
|
+
| `open` | `boolean` | required | Controls visibility |
|
|
183
|
+
| `onClose` | `() => void` | required | Called when the close button is clicked |
|
|
184
|
+
| `position` | `WashiToolBubblePosition` | `'bottom-right'` | Which side to slide in from |
|
|
185
|
+
| `accentColor` | `string` | `'#667eea'` | Accent colour for resolved badges |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
### `<WashiPinDialog>`
|
|
190
|
+
|
|
191
|
+
A popover that appears when the user places a pin in annotate mode. Handles text input, calls `addComment`, and switches back to view mode on submit.
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
<WashiPinDialog
|
|
195
|
+
accentColor="#667eea"
|
|
196
|
+
onComment={(comment) => console.log('Created:', comment.id)}
|
|
197
|
+
/>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
| Prop | Type | Default | Description |
|
|
201
|
+
|------|------|---------|-------------|
|
|
202
|
+
| `accentColor` | `string` | `'#667eea'` | Submit button colour |
|
|
203
|
+
| `onComment` | `(comment: Comment) => void` | — | Called after a comment is successfully created |
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### `<CommentList>`
|
|
208
|
+
|
|
209
|
+
Headless list component for rendering comments with custom UI. Reads from context and exposes `onResolve`, `onDelete`, and `onUpdate` action handlers.
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
<CommentList
|
|
213
|
+
renderComment={(comment, { onResolve, onDelete }) => (
|
|
214
|
+
<div key={comment.id} className="comment">
|
|
215
|
+
<p>{comment.text}</p>
|
|
216
|
+
<button onClick={onResolve}>
|
|
217
|
+
{comment.resolved ? 'Unresolve' : 'Resolve'}
|
|
218
|
+
</button>
|
|
219
|
+
<button onClick={onDelete}>Delete</button>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
filter={(c) => !c.resolved}
|
|
223
|
+
sort={(a, b) => b.createdAt - a.createdAt}
|
|
224
|
+
emptyState={<p>No comments yet.</p>}
|
|
225
|
+
/>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
| Prop | Type | Description |
|
|
229
|
+
|------|------|-------------|
|
|
230
|
+
| `renderComment` | `(comment, actions) => ReactNode` | Render function for each comment |
|
|
231
|
+
| `filter` | `(comment) => boolean` | Optional filter predicate |
|
|
232
|
+
| `sort` | `(a, b) => number` | Optional sort comparator |
|
|
233
|
+
| `emptyState` | `ReactNode` | Rendered when no comments match |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## `useWashi(options)`
|
|
238
|
+
|
|
239
|
+
Low-level hook for managing a Washi instance directly. Returns a ref to attach to a plain `<iframe>` element plus all state and methods.
|
|
240
|
+
|
|
241
|
+
### Options
|
|
242
|
+
|
|
243
|
+
| Option | Type | Default | Description |
|
|
244
|
+
|--------|------|---------|-------------|
|
|
245
|
+
| `adapter` | `WashiAdapter` | required | Storage adapter |
|
|
246
|
+
| `initialMode` | `WashiMode` | `'view'` | Starting mode |
|
|
247
|
+
| `mountOptions` | `MountOptions` | — | Passed to `washi.mount()` |
|
|
248
|
+
| `onPinPlaced` | `(event: PinPlacedEvent) => void` | — | Called when overlay is clicked in annotate mode |
|
|
249
|
+
| `onCommentClick` | `(comment: Comment) => void` | — | Called when a pin is clicked |
|
|
250
|
+
| `onCommentUpdate` | `(data) => void` | — | Called after a comment is updated |
|
|
251
|
+
| `onCommentDelete` | `(id: string) => void` | — | Called after a comment is deleted |
|
|
252
|
+
|
|
253
|
+
### Returns
|
|
254
|
+
|
|
255
|
+
| Property | Type | Description |
|
|
256
|
+
|----------|------|-------------|
|
|
257
|
+
| `iframeRef` | `RefObject<HTMLIFrameElement>` | Attach to your `<iframe>` element |
|
|
258
|
+
| `mode` | `WashiMode` | Current mode (`'view'` or `'annotate'`) |
|
|
259
|
+
| `setMode` | `(mode: WashiMode) => void` | Switch modes |
|
|
260
|
+
| `comments` | `Comment[]` | All current comments |
|
|
261
|
+
| `addComment` | `(input: NewComment) => Promise<Comment>` | Add a comment — `id` and `createdAt` are generated automatically |
|
|
262
|
+
| `updateComment` | `(id, updates) => Promise<void>` | Update a comment |
|
|
263
|
+
| `deleteComment` | `(id) => Promise<void>` | Delete a comment |
|
|
264
|
+
| `isReady` | `boolean` | True once the iframe has loaded and Washi has mounted |
|
|
265
|
+
| `error` | `Error \| null` | Any mount or operation error |
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## `useWashiContext()`
|
|
270
|
+
|
|
271
|
+
Access the Washi context from any component inside a `WashiProvider`.
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
function StatusBar() {
|
|
275
|
+
const { mode, comments, isReady } = useWashiContext();
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<div>
|
|
279
|
+
{isReady ? `${comments.length} comments · ${mode} mode` : 'Loading…'}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## TypeScript
|
|
288
|
+
|
|
289
|
+
All types are re-exported from `@washi-ui/react` for convenience:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import type {
|
|
293
|
+
Comment,
|
|
294
|
+
NewComment,
|
|
295
|
+
WashiMode,
|
|
296
|
+
WashiEvent,
|
|
297
|
+
WashiAdapter,
|
|
298
|
+
MountOptions,
|
|
299
|
+
PinPlacedEvent,
|
|
300
|
+
CommentUpdatedEvent,
|
|
301
|
+
CommentDeletedEvent,
|
|
302
|
+
CommentClickedEvent,
|
|
303
|
+
ModeChangedEvent,
|
|
304
|
+
ErrorEvent,
|
|
305
|
+
} from '@washi-ui/react';
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { WashiAdapter, WashiMode, MountOptions, PinPlacedEvent, Comment, NewComment, Washi } from '@washi-ui/core';
|
|
2
|
+
export { Comment, CommentClickedEvent, CommentDeletedEvent, CommentUpdatedEvent, ErrorEvent, ModeChangedEvent, MountOptions, NewComment, PinPlacedEvent, WashiAdapter, WashiEvent, WashiMode } from '@washi-ui/core';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import React$1, { ReactNode, IframeHTMLAttributes } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Options for the useWashi hook
|
|
8
|
+
*/
|
|
9
|
+
interface UseWashiOptions {
|
|
10
|
+
/** Required: The storage adapter for persisting comments */
|
|
11
|
+
adapter: WashiAdapter;
|
|
12
|
+
/** Optional: Initial mode ('view' or 'annotate'). Defaults to 'view' */
|
|
13
|
+
initialMode?: WashiMode;
|
|
14
|
+
/** Optional: Mount options for the Washi instance */
|
|
15
|
+
mountOptions?: MountOptions;
|
|
16
|
+
/** Optional: Callback when a pin is placed on the overlay in annotate mode */
|
|
17
|
+
onPinPlaced?: (event: PinPlacedEvent) => void;
|
|
18
|
+
/** Optional: Callback when a pin is clicked */
|
|
19
|
+
onCommentClick?: (comment: Comment) => void;
|
|
20
|
+
/** Optional: Callback when a comment is updated */
|
|
21
|
+
onCommentUpdate?: (data: {
|
|
22
|
+
id: string;
|
|
23
|
+
updates: Partial<Comment>;
|
|
24
|
+
}) => void;
|
|
25
|
+
/** Optional: Callback when a comment is deleted */
|
|
26
|
+
onCommentDelete?: (id: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Return type of the useWashi hook
|
|
30
|
+
*/
|
|
31
|
+
interface UseWashiReturn {
|
|
32
|
+
/** Ref to attach to the iframe element */
|
|
33
|
+
iframeRef: React.RefObject<HTMLIFrameElement | null>;
|
|
34
|
+
/** Current mode */
|
|
35
|
+
mode: WashiMode;
|
|
36
|
+
/** Function to change the mode */
|
|
37
|
+
setMode: (mode: WashiMode) => void;
|
|
38
|
+
/** Array of all comments */
|
|
39
|
+
comments: Comment[];
|
|
40
|
+
/** Add a new comment. Returns the completed Comment with generated id and createdAt. */
|
|
41
|
+
addComment: (input: NewComment) => Promise<Comment>;
|
|
42
|
+
/** Update an existing comment */
|
|
43
|
+
updateComment: (id: string, updates: Partial<Comment>) => Promise<void>;
|
|
44
|
+
/** Delete a comment */
|
|
45
|
+
deleteComment: (id: string) => Promise<void>;
|
|
46
|
+
/** Whether the Washi instance is mounted and ready */
|
|
47
|
+
isReady: boolean;
|
|
48
|
+
/** Any error that occurred during mounting or operations */
|
|
49
|
+
error: Error | null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* React hook for using Washi comment system.
|
|
53
|
+
*
|
|
54
|
+
* @param options - Configuration options
|
|
55
|
+
* @returns Object with iframe ref, state, and methods
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```tsx
|
|
59
|
+
* function CommentableContent() {
|
|
60
|
+
* const {
|
|
61
|
+
* iframeRef,
|
|
62
|
+
* mode,
|
|
63
|
+
* setMode,
|
|
64
|
+
* comments,
|
|
65
|
+
* addComment,
|
|
66
|
+
* isReady
|
|
67
|
+
* } = useWashi({
|
|
68
|
+
* adapter: new MyAdapter(),
|
|
69
|
+
* onPinPlaced: ({ x, y }) => {
|
|
70
|
+
* // Show comment input dialog at position
|
|
71
|
+
* },
|
|
72
|
+
* onCommentClick: (comment) => {
|
|
73
|
+
* // Show comment details
|
|
74
|
+
* }
|
|
75
|
+
* });
|
|
76
|
+
*
|
|
77
|
+
* return (
|
|
78
|
+
* <div>
|
|
79
|
+
* <button onClick={() => setMode(mode === 'view' ? 'annotate' : 'view')}>
|
|
80
|
+
* Toggle Mode
|
|
81
|
+
* </button>
|
|
82
|
+
* <div style={{ position: 'relative' }}>
|
|
83
|
+
* <iframe ref={iframeRef} src="/content.html" />
|
|
84
|
+
* </div>
|
|
85
|
+
* </div>
|
|
86
|
+
* );
|
|
87
|
+
* }
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
declare function useWashi(options: UseWashiOptions): UseWashiReturn;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Context value provided by WashiProvider
|
|
94
|
+
*/
|
|
95
|
+
interface WashiContextValue {
|
|
96
|
+
/** The Washi instance (null if not mounted) */
|
|
97
|
+
washi: Washi | null;
|
|
98
|
+
/** The registered iframe element (null if not mounted) */
|
|
99
|
+
iframeEl: HTMLIFrameElement | null;
|
|
100
|
+
/** Current mode */
|
|
101
|
+
mode: WashiMode;
|
|
102
|
+
/** Function to change the mode */
|
|
103
|
+
setMode: (mode: WashiMode) => void;
|
|
104
|
+
/** Array of all comments */
|
|
105
|
+
comments: Comment[];
|
|
106
|
+
/** Add a new comment. Returns the completed Comment with generated id and createdAt. */
|
|
107
|
+
addComment: (input: NewComment) => Promise<Comment>;
|
|
108
|
+
/** Update an existing comment */
|
|
109
|
+
updateComment: (id: string, updates: Partial<Comment>) => Promise<void>;
|
|
110
|
+
/** Delete a comment */
|
|
111
|
+
deleteComment: (id: string) => Promise<void>;
|
|
112
|
+
/** Refresh comments from adapter */
|
|
113
|
+
refreshComments: () => void;
|
|
114
|
+
/** Whether the Washi instance is mounted and ready */
|
|
115
|
+
isReady: boolean;
|
|
116
|
+
/** Any error that occurred */
|
|
117
|
+
error: Error | null;
|
|
118
|
+
/** Register an iframe element for mounting */
|
|
119
|
+
registerIframe: (iframe: HTMLIFrameElement | null) => void;
|
|
120
|
+
/** Subscribe to pin placement events (overlay clicked in annotate mode) */
|
|
121
|
+
onPinPlaced: (callback: (event: PinPlacedEvent) => void) => () => void;
|
|
122
|
+
/** Subscribe to comment click events */
|
|
123
|
+
onCommentClick: (callback: (comment: Comment) => void) => () => void;
|
|
124
|
+
/** Set the active pin for highlighting */
|
|
125
|
+
setActivePin: (commentId: string | null) => void;
|
|
126
|
+
/** Get the index of a comment (sorted by createdAt) */
|
|
127
|
+
getCommentIndex: (commentId: string) => number;
|
|
128
|
+
/** Currently selected/active comment (for custom dialog rendering) */
|
|
129
|
+
activeComment: Comment | null;
|
|
130
|
+
/** Set the active comment (for custom dialog rendering) */
|
|
131
|
+
setActiveComment: (comment: Comment | null) => void;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Props for WashiProvider
|
|
135
|
+
*/
|
|
136
|
+
interface WashiProviderProps {
|
|
137
|
+
/** Required: The storage adapter for persisting comments */
|
|
138
|
+
adapter: WashiAdapter;
|
|
139
|
+
/** Optional: Initial mode ('view' or 'annotate'). Defaults to 'view' */
|
|
140
|
+
initialMode?: WashiMode;
|
|
141
|
+
/** Optional: Mount options for the Washi instance */
|
|
142
|
+
mountOptions?: MountOptions;
|
|
143
|
+
/** Child components */
|
|
144
|
+
children: ReactNode;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Provider component for Washi context.
|
|
148
|
+
* Use with WashiFrame and useWashiContext.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```tsx
|
|
152
|
+
* function App() {
|
|
153
|
+
* return (
|
|
154
|
+
* <WashiProvider adapter={new MyAdapter()}>
|
|
155
|
+
* <WashiFrame src="/content.html" />
|
|
156
|
+
* <CommentSidebar />
|
|
157
|
+
* </WashiProvider>
|
|
158
|
+
* );
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
declare function WashiProvider({ adapter, initialMode, mountOptions, children, }: WashiProviderProps): react_jsx_runtime.JSX.Element;
|
|
163
|
+
/**
|
|
164
|
+
* Hook to access Washi context.
|
|
165
|
+
* Must be used within a WashiProvider.
|
|
166
|
+
*
|
|
167
|
+
* @throws Error if used outside of WashiProvider
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```tsx
|
|
171
|
+
* function CommentSidebar() {
|
|
172
|
+
* const { comments, deleteComment } = useWashiContext();
|
|
173
|
+
*
|
|
174
|
+
* return (
|
|
175
|
+
* <ul>
|
|
176
|
+
* {comments.map(c => (
|
|
177
|
+
* <li key={c.id}>
|
|
178
|
+
* {c.text}
|
|
179
|
+
* <button onClick={() => deleteComment(c.id)}>Delete</button>
|
|
180
|
+
* </li>
|
|
181
|
+
* ))}
|
|
182
|
+
* </ul>
|
|
183
|
+
* );
|
|
184
|
+
* }
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
declare function useWashiContext(): WashiContextValue;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Props for WashiFrame component
|
|
191
|
+
*/
|
|
192
|
+
interface WashiFrameProps extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'ref'> {
|
|
193
|
+
/** URL to load in the iframe. Omit when using srcDoc to pass an HTML string. */
|
|
194
|
+
src?: string;
|
|
195
|
+
/** Optional: Additional CSS class name */
|
|
196
|
+
className?: string;
|
|
197
|
+
/** Optional: Inline styles */
|
|
198
|
+
style?: React$1.CSSProperties;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Iframe wrapper component that automatically registers with WashiProvider.
|
|
202
|
+
* Must be used within a WashiProvider.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```tsx
|
|
206
|
+
* <WashiProvider adapter={adapter}>
|
|
207
|
+
* <WashiFrame
|
|
208
|
+
* src="/content.html"
|
|
209
|
+
* style={{ width: '100%', height: '600px', border: 'none' }}
|
|
210
|
+
* />
|
|
211
|
+
* </WashiProvider>
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
declare function WashiFrame({ src, className, style, ...props }: WashiFrameProps): react_jsx_runtime.JSX.Element;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Props for CommentList component
|
|
218
|
+
*/
|
|
219
|
+
interface CommentListProps {
|
|
220
|
+
/**
|
|
221
|
+
* Render function for each comment.
|
|
222
|
+
* Receives the comment and action handlers.
|
|
223
|
+
*/
|
|
224
|
+
renderComment: (comment: Comment, actions: {
|
|
225
|
+
onResolve: () => Promise<void>;
|
|
226
|
+
onDelete: () => Promise<void>;
|
|
227
|
+
onUpdate: (updates: Partial<Comment>) => Promise<void>;
|
|
228
|
+
}) => ReactNode;
|
|
229
|
+
/**
|
|
230
|
+
* Optional: Filter function to show only certain comments
|
|
231
|
+
*/
|
|
232
|
+
filter?: (comment: Comment) => boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Optional: Sort function for comments
|
|
235
|
+
*/
|
|
236
|
+
sort?: (a: Comment, b: Comment) => number;
|
|
237
|
+
/**
|
|
238
|
+
* Optional: Component to render when there are no comments
|
|
239
|
+
*/
|
|
240
|
+
emptyState?: ReactNode;
|
|
241
|
+
/**
|
|
242
|
+
* Optional: CSS class name for the list container
|
|
243
|
+
*/
|
|
244
|
+
className?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Optional: Inline styles for the list container
|
|
247
|
+
*/
|
|
248
|
+
style?: React$1.CSSProperties;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Helper component for rendering a list of comments.
|
|
252
|
+
* Must be used within a WashiProvider.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```tsx
|
|
256
|
+
* <CommentList
|
|
257
|
+
* renderComment={(comment, { onResolve, onDelete }) => (
|
|
258
|
+
* <div key={comment.id} className="comment-item">
|
|
259
|
+
* <p>{comment.text}</p>
|
|
260
|
+
* <div className="actions">
|
|
261
|
+
* <button onClick={onResolve}>
|
|
262
|
+
* {comment.resolved ? 'Unresolve' : 'Resolve'}
|
|
263
|
+
* </button>
|
|
264
|
+
* <button onClick={onDelete}>Delete</button>
|
|
265
|
+
* </div>
|
|
266
|
+
* </div>
|
|
267
|
+
* )}
|
|
268
|
+
* filter={(c) => !c.resolved}
|
|
269
|
+
* sort={(a, b) => b.createdAt - a.createdAt}
|
|
270
|
+
* emptyState={<p>No comments yet</p>}
|
|
271
|
+
* />
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
declare function CommentList({ renderComment, filter, sort, emptyState, className, style, }: CommentListProps): react_jsx_runtime.JSX.Element;
|
|
275
|
+
|
|
276
|
+
type WashiToolBubblePosition = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
|
277
|
+
interface WashiToolBubbleProps {
|
|
278
|
+
position?: WashiToolBubblePosition;
|
|
279
|
+
sidebarOpen?: boolean;
|
|
280
|
+
onSidebarToggle?: () => void;
|
|
281
|
+
accentColor?: string;
|
|
282
|
+
}
|
|
283
|
+
declare function WashiToolBubble({ position, sidebarOpen, onSidebarToggle, accentColor, }: WashiToolBubbleProps): react_jsx_runtime.JSX.Element;
|
|
284
|
+
|
|
285
|
+
interface WashiCommentsSidebarProps {
|
|
286
|
+
open: boolean;
|
|
287
|
+
onClose: () => void;
|
|
288
|
+
/** Which corner the tool bubble is anchored to. Panel slides in from that side and stops above the bubble. Default: 'bottom-right' */
|
|
289
|
+
position?: WashiToolBubblePosition;
|
|
290
|
+
accentColor?: string;
|
|
291
|
+
}
|
|
292
|
+
declare function WashiCommentsSidebar({ open, onClose, position, accentColor, }: WashiCommentsSidebarProps): react_jsx_runtime.JSX.Element | null;
|
|
293
|
+
|
|
294
|
+
interface WashiPinDialogProps {
|
|
295
|
+
accentColor?: string;
|
|
296
|
+
/** Optional callback fired after a comment is successfully created */
|
|
297
|
+
onComment?: (comment: Comment) => void;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Self-contained pin comment dialog.
|
|
301
|
+
*
|
|
302
|
+
* Subscribes to `pin:placed` events from the context and renders a small
|
|
303
|
+
* popover anchored to the clicked position. On submit it calls `addComment`
|
|
304
|
+
* and switches the mode back to 'view' automatically.
|
|
305
|
+
*
|
|
306
|
+
* Drop this anywhere inside a `<WashiProvider>` — no props required.
|
|
307
|
+
*/
|
|
308
|
+
declare function WashiPinDialog({ accentColor, onComment }: WashiPinDialogProps): react_jsx_runtime.JSX.Element | null;
|
|
309
|
+
|
|
310
|
+
interface WashiUIProps {
|
|
311
|
+
/** Corner where the tool bubble (and sidebar) are anchored. Default: 'bottom-right' */
|
|
312
|
+
position?: WashiToolBubblePosition;
|
|
313
|
+
accentColor?: string;
|
|
314
|
+
/** Show a loading spinner while the iframe initialises. Default: true */
|
|
315
|
+
showLoader?: boolean;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* All-in-one Washi UI layer.
|
|
319
|
+
*
|
|
320
|
+
* Renders the tool bubble, comments sidebar, pin dialog, and an optional
|
|
321
|
+
* loading overlay — all wired together. Drop it inside a `<WashiProvider>`.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```tsx
|
|
325
|
+
* <WashiProvider adapter={adapter}>
|
|
326
|
+
* <div style={{ position: 'fixed', inset: 0 }}>
|
|
327
|
+
* <WashiFrame src="/content.html" style={{ width: '100%', height: '100%', border: 'none' }} />
|
|
328
|
+
* </div>
|
|
329
|
+
* <WashiUI />
|
|
330
|
+
* </WashiProvider>
|
|
331
|
+
* ```
|
|
332
|
+
*/
|
|
333
|
+
declare function WashiUI({ position, accentColor, showLoader, }: WashiUIProps): react_jsx_runtime.JSX.Element;
|
|
334
|
+
|
|
335
|
+
export { CommentList, type CommentListProps, type UseWashiOptions, type UseWashiReturn, WashiCommentsSidebar, type WashiCommentsSidebarProps, type WashiContextValue, WashiFrame, type WashiFrameProps, WashiPinDialog, type WashiPinDialogProps, WashiProvider, type WashiProviderProps, WashiToolBubble, type WashiToolBubblePosition, type WashiToolBubbleProps, WashiUI, type WashiUIProps, useWashi, useWashiContext };
|