@nldoc/validation 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/README.md ADDED
@@ -0,0 +1,357 @@
1
+ # @nldoc/validation
2
+
3
+ Validation hooks and components for integrating NLdoc validation with TipTap editors.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @nldoc/validation @nldoc/api @nldoc/types
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Validation Hooks**: React hooks for validating TipTap document content via the NLdoc API
14
+ - **Validation Context**: Provider pattern for sharing validation state across components
15
+ - **UI Components**: Headless UI components for displaying validation results
16
+ - **Positioning & Stacking**: Sophisticated layout system for non-overlapping validation indicators
17
+ - **Bundling**: Groups related validation findings from the same block-level element
18
+ - **TypeScript**: Full TypeScript support with comprehensive type definitions
19
+
20
+ ## Quick Start
21
+
22
+ ### Using ValidationProvider (Recommended)
23
+
24
+ The simplest way to add validation is with `ValidationProvider`. It **automatically tracks editor updates**, validates content with debouncing, and applies findings to the editor—no manual state management required.
25
+
26
+ ```tsx
27
+ import { ValidationProvider, useValidationContext } from '@nldoc/validation'
28
+ import { NLDocStarterKit } from '@nldoc/tiptap-extensions'
29
+ import { useEditor, EditorContent } from '@tiptap/react'
30
+
31
+ function ValidationStatus() {
32
+ const { findings, isValidating } = useValidationContext()
33
+ return <div>{isValidating ? 'Validating...' : `${findings.length} issues`}</div>
34
+ }
35
+
36
+ function MyEditor() {
37
+ const editor = useEditor({
38
+ extensions: [NLDocStarterKit],
39
+ })
40
+
41
+ return (
42
+ <ValidationProvider editor={editor} options={{ debounceDelay: 500 }}>
43
+ <EditorContent editor={editor} />
44
+ <ValidationStatus />
45
+ </ValidationProvider>
46
+ )
47
+ }
48
+ ```
49
+
50
+ **That's it!** ValidationProvider handles:
51
+ - ✅ Tracking editor updates (while filtering out validation transactions)
52
+ - ✅ Debouncing validation requests
53
+ - ✅ Applying findings back to the editor automatically
54
+ - ✅ Preventing infinite validation loops
55
+
56
+ ### Using the useValidation Hook (Advanced)
57
+
58
+ For more control, use the `useValidation` hook directly and manage state yourself:
59
+
60
+ ```tsx
61
+ import { useValidation } from '@nldoc/validation'
62
+ import { useEditor } from '@tiptap/react'
63
+ import { useState } from 'react'
64
+
65
+ function MyEditor() {
66
+ const [editorJSON, setEditorJSON] = useState(null)
67
+
68
+ const editor = useEditor({
69
+ /* ...your extensions */
70
+ onUpdate: ({ editor }) => setEditorJSON(editor.getJSON())
71
+ })
72
+
73
+ const { findings, isValidating, error } = useValidation(editorJSON, {
74
+ debounceDelay: 500,
75
+ debounceMaxWait: 5000,
76
+ })
77
+
78
+ return (
79
+ <div>
80
+ <EditorContent editor={editor} />
81
+ {isValidating && <p>Validating...</p>}
82
+ {findings.length > 0 && <p>{findings.length} issues found</p>}
83
+ </div>
84
+ )
85
+ }
86
+ ```
87
+
88
+ ## API Reference
89
+
90
+ ### Hooks
91
+
92
+ #### `useValidation(editorJSON, options)`
93
+
94
+ Main validation hook that converts TipTap JSON to validation format and fetches validation findings.
95
+
96
+ **Parameters:**
97
+ - `editorJSON: JSONContent | null` - The TipTap editor JSON content
98
+ - `options?: ValidationHookOptions` - Configuration options
99
+
100
+ **Returns:** `Validation`
101
+ - `findings: ValidationFinding[]` - Array of validation findings
102
+ - `isValidating: boolean` - Whether validation is in progress
103
+ - `error: Error | null` - Error if validation failed
104
+
105
+ **Options:**
106
+ - `debounceDelay?: number` - Delay in ms before starting validation (default: 500)
107
+ - `debounceMaxWait?: number` - Maximum wait time in ms before forcing validation (default: 5000)
108
+ - `includeAssets?: boolean` - Whether to include image assets in validation (default: false)
109
+
110
+ #### `useValidationContext()`
111
+
112
+ Access validation state from context. Must be used within a `ValidationProvider`.
113
+
114
+ **Returns:** `Validation`
115
+
116
+ #### `useTiptapValidation(editorJSON, options)`
117
+
118
+ Low-level hook that converts TipTap JSON to validation format with debouncing.
119
+
120
+ ### Components
121
+
122
+ #### `<ValidationProvider>`
123
+
124
+ Provider component that manages validation state and applies findings to the editor.
125
+
126
+ **Automatically tracks editor updates** and validates content with debouncing. Excludes validation transactions to prevent infinite loops.
127
+
128
+ **Props:**
129
+ - `editor: Editor | null` - The TipTap editor instance
130
+ - `editorJSON?: JSONContent | null` - (Optional) Manual editor JSON. If not provided, ValidationProvider tracks updates automatically
131
+ - `options?: ValidationHookOptions` - Validation configuration
132
+ - `autoApplyFindings?: boolean` - Whether to automatically apply findings (default: true)
133
+ - `onApplyFindings?: (editor, findings) => void` - Custom callback to apply findings
134
+
135
+ **Example (Automatic):**
136
+ ```tsx
137
+ <ValidationProvider editor={editor}>
138
+ <EditorContent editor={editor} />
139
+ </ValidationProvider>
140
+ ```
141
+
142
+ **Example (Manual Control):**
143
+ ```tsx
144
+ const [editorJSON, setEditorJSON] = useState(null)
145
+
146
+ <ValidationProvider editor={editor} editorJSON={editorJSON}>
147
+ <EditorContent editor={editor} />
148
+ </ValidationProvider>
149
+ ```
150
+
151
+ #### `<ValidationResult>`
152
+
153
+ Headless component for displaying individual validation results. Highly customizable via render props.
154
+
155
+ **Props:**
156
+ - `findings: ValidationFinding[]` - Array of findings to display
157
+ - `isBundle?: boolean` - Whether this is a bundle of findings
158
+ - `variant?: 'default' | 'stack'` - Display variant
159
+ - `onResolve?: (ids: string[]) => void` - Callback when user resolves findings
160
+ - `onRetrigger?: () => void` - Callback to retrigger validation
161
+ - `renderIcon?: (type, props) => ReactNode` - Custom icon renderer
162
+ - `renderMessage?: (finding, count, isBundle) => ReactNode` - Custom message renderer
163
+ - `renderResolution?: (ids, onResolved) => ReactNode` - Custom resolution renderer
164
+ - `className?: string` - CSS class for styling
165
+ - `styles?: object` - Style overrides for specific elements
166
+
167
+ #### `<ValidationResultSummary>`
168
+
169
+ Main component that orchestrates the layout of all validation results with sophisticated positioning, stacking, and bundling.
170
+
171
+ **Props:**
172
+ - `findings: ValidationFinding[]` - All validation findings
173
+ - `isAnimating?: boolean` - Whether editor is animating (delays layout updates)
174
+ - `positioningConfig?: PositioningConfig` - Configuration for position calculations
175
+ - `buildFindingBlockMap?: (findings) => Map<string, string>` - Custom function to map findings to parent blocks
176
+ - `validationResultProps?: Partial<ValidationResultProps>` - Props to pass to ValidationResult components
177
+ - `resizeDebounceDelay?: number` - Debounce delay for window resize (default: 100ms)
178
+
179
+ ## Utilities
180
+
181
+ ### Positioning
182
+
183
+ ```tsx
184
+ import {
185
+ makePositionedFindings,
186
+ calculateDesiredYLocation,
187
+ } from '@nldoc/validation'
188
+
189
+ const positionedFindings = makePositionedFindings(findings, {
190
+ editorSelector: '#my-editor', // Default: '#tiptap'
191
+ })
192
+ ```
193
+
194
+ ### Bundling
195
+
196
+ ```tsx
197
+ import { computeBundles, BUNDLEABLE_RULES } from '@nldoc/validation'
198
+
199
+ // Rules that support bundling:
200
+ // - 'no-underline'
201
+ // - 'style-bold-in-header'
202
+ // - 'style-italic-in-header'
203
+
204
+ const bundles = computeBundles(positionedFindings, findingBlockMap)
205
+ ```
206
+
207
+ ### Stacking
208
+
209
+ ```tsx
210
+ import {
211
+ createInitialStacksFromItems,
212
+ mergeOverlappingStacksWithBundles,
213
+ positionStacksWithBundles,
214
+ } from '@nldoc/validation'
215
+
216
+ const initialStacks = createInitialStacksFromItems(items)
217
+ const mergedStacks = mergeOverlappingStacksWithBundles(
218
+ initialStacks,
219
+ itemHeights,
220
+ expandedStackIds
221
+ )
222
+ const positioned = positionStacksWithBundles(mergedStacks, stackRefs)
223
+ ```
224
+
225
+ ## Advanced Usage
226
+
227
+ ### Custom Validation Result Display
228
+
229
+ ```tsx
230
+ import { ValidationResult } from '@nldoc/validation'
231
+
232
+ function MyValidationResult({ findings }) {
233
+ return (
234
+ <ValidationResult
235
+ findings={findings}
236
+ renderIcon={(type) => {
237
+ const icons = {
238
+ error: <ErrorIcon />,
239
+ warning: <WarningIcon />,
240
+ suggestion: <InfoIcon />,
241
+ }
242
+ return icons[type]
243
+ }}
244
+ renderMessage={(finding, count) => (
245
+ <div>
246
+ <h4>{finding.rule}</h4>
247
+ <p>{finding.message}</p>
248
+ {count > 1 && <span>{count} occurrences</span>}
249
+ </div>
250
+ )}
251
+ renderResolution={(ids, onResolved) => (
252
+ <button onClick={() => {
253
+ // Custom resolution logic
254
+ resolveFindings(ids)
255
+ onResolved()
256
+ }}>
257
+ Fix Issue
258
+ </button>
259
+ )}
260
+ />
261
+ )
262
+ }
263
+ ```
264
+
265
+ ### Complete Example with TipTap
266
+
267
+ ```tsx
268
+ import { ValidationProvider, useValidationContext } from '@nldoc/validation'
269
+ import { NLDocStarterKit } from '@nldoc/tiptap-extensions'
270
+ import { useEditor, EditorContent } from '@tiptap/react'
271
+
272
+ function ValidationSidebar() {
273
+ const { findings, isValidating, error } = useValidationContext()
274
+
275
+ if (isValidating) return <div>Validating...</div>
276
+ if (error) return <div>Error: {error.message}</div>
277
+
278
+ return (
279
+ <aside>
280
+ <h3>Accessibility Issues</h3>
281
+ {findings.length === 0 ? (
282
+ <p>No issues found!</p>
283
+ ) : (
284
+ <ul>
285
+ {findings.map((finding, i) => (
286
+ <li key={i}>
287
+ [{finding.severity}] {finding.rule}
288
+ </li>
289
+ ))}
290
+ </ul>
291
+ )}
292
+ </aside>
293
+ )
294
+ }
295
+
296
+ function MyEditor() {
297
+ const editor = useEditor({
298
+ extensions: [
299
+ NLDocStarterKit.configure({
300
+ // Configure your extensions
301
+ }),
302
+ ],
303
+ content: '<p>Start typing...</p>',
304
+ })
305
+
306
+ return (
307
+ <ValidationProvider
308
+ editor={editor}
309
+ options={{
310
+ debounceDelay: 500,
311
+ debounceMaxWait: 5000,
312
+ includeAssets: false,
313
+ }}
314
+ >
315
+ <div style={{ display: 'flex' }}>
316
+ <EditorContent editor={editor} />
317
+ <ValidationSidebar />
318
+ </div>
319
+ </ValidationProvider>
320
+ )
321
+ }
322
+ ```
323
+
324
+ **Key Points:**
325
+ - No manual state management needed
326
+ - ValidationProvider automatically tracks updates
327
+ - Findings are automatically applied to the editor
328
+ - Validation transactions are filtered to prevent loops
329
+ - Use `useValidationContext()` anywhere within the provider to access validation state
330
+
331
+ ## TypeScript
332
+
333
+ All exports are fully typed. Key types:
334
+
335
+ ```typescript
336
+ import type {
337
+ Validation,
338
+ ValidationHookOptions,
339
+ ValidationResultProps,
340
+ ValidationResultSummaryProps,
341
+ PositionedFinding,
342
+ Bundle,
343
+ Stack,
344
+ StackableItem,
345
+ PositioningConfig,
346
+ } from '@nldoc/validation'
347
+ ```
348
+
349
+ ## Related Packages
350
+
351
+ - `@nldoc/tiptap-extensions` - Custom TipTap extensions including ValidationFindingsExtension
352
+ - `@nldoc/api` - API client for NLdoc services
353
+ - `@nldoc/types` - Shared TypeScript types
354
+
355
+ ## License
356
+
357
+ EUPL-1.2