@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 +357 -0
- package/dist/index.d.ts +388 -0
- package/dist/index.js +6 -0
- package/dist/index.mjs +922 -0
- package/package.json +51 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { default as default_2 } from 'react';
|
|
2
|
+
import { Editor } from '@tiptap/react';
|
|
3
|
+
import { JSONContent } from '@tiptap/react';
|
|
4
|
+
import { PropsWithChildren } from 'react';
|
|
5
|
+
import { UseConversion } from '@nldoc/api';
|
|
6
|
+
import { ValidationFinding } from '@nldoc/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A Bundle represents multiple findings from the same block-level element.
|
|
10
|
+
* Bundles are created before stacking and can be stacked with other bundles/findings.
|
|
11
|
+
*/
|
|
12
|
+
export declare interface Bundle {
|
|
13
|
+
id: string;
|
|
14
|
+
type: 'bundle';
|
|
15
|
+
findings: PositionedFinding[];
|
|
16
|
+
blockId: string;
|
|
17
|
+
rule: string;
|
|
18
|
+
desiredYLocation: number;
|
|
19
|
+
yLocation: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Rules that support bundling by block-level elements.
|
|
24
|
+
* These findings will be grouped by their parent block (paragraph, heading, table cell).
|
|
25
|
+
*/
|
|
26
|
+
export declare const BUNDLEABLE_RULES: string[];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Stable sort comparator for positioned findings.
|
|
30
|
+
* Uses resource ID as tiebreaker to prevent position flip-flopping when findings have same Y location.
|
|
31
|
+
*/
|
|
32
|
+
export declare function byIncreasingDesiredYLocation(a: PositionedFinding, b: PositionedFinding): number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Calculates the desired Y location for a finding.
|
|
36
|
+
*
|
|
37
|
+
* @param finding - The validation finding to position
|
|
38
|
+
* @param config - Configuration options
|
|
39
|
+
* @returns The Y location in pixels, or null if element not found
|
|
40
|
+
*/
|
|
41
|
+
export declare function calculateDesiredYLocation(finding: ValidationFinding, config?: PositioningConfig): number | null;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Groups findings by their parent block ID for bundleable rules.
|
|
45
|
+
* Only creates bundles for groups with 2+ findings.
|
|
46
|
+
*
|
|
47
|
+
* @param positionedFindings - Array of findings with position information
|
|
48
|
+
* @param findingBlockMap - Maps finding resourceId to parent block ID
|
|
49
|
+
* @returns Array of bundles, each containing findings from the same block
|
|
50
|
+
*/
|
|
51
|
+
export declare function computeBundles(positionedFindings: PositionedFinding[], findingBlockMap: Map<string, string>): Bundle[];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates initial "stacks" from stackable items (bundles and findings).
|
|
55
|
+
* Each item starts as its own stack.
|
|
56
|
+
*
|
|
57
|
+
* @param items - Array of bundles and findings
|
|
58
|
+
* @returns Array of individual stacks (one per item)
|
|
59
|
+
*/
|
|
60
|
+
export declare function createInitialStacksFromItems(items: StackableItem[]): Stack[];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a set of finding resource IDs that are part of bundles.
|
|
64
|
+
* Used to exclude these findings from stacking.
|
|
65
|
+
*
|
|
66
|
+
* @param bundles - Array of bundles
|
|
67
|
+
* @returns Set of resource IDs that are bundled
|
|
68
|
+
*/
|
|
69
|
+
export declare function getBundledFindingIds(bundles: Bundle[]): Set<string>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Type guard to check if a stackable item is a bundle.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isBundle(item: StackableItem): item is Bundle;
|
|
75
|
+
|
|
76
|
+
export declare function makePositionedFinding(finding: ValidationFinding, config?: PositioningConfig): PositionedFinding;
|
|
77
|
+
|
|
78
|
+
export declare function makePositionedFindings(findings: ValidationFinding[], config?: PositioningConfig): PositionedFinding[];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Merges stacks based on whether their DESIRED positions would overlap.
|
|
82
|
+
* Works with stacks containing bundles and/or findings.
|
|
83
|
+
*
|
|
84
|
+
* @param stacks - Array of individual stacks
|
|
85
|
+
* @param itemHeights - Map of item ID to measured height (for bundles and findings)
|
|
86
|
+
* @param expandedStackIds - Set of stack IDs that should remain expanded
|
|
87
|
+
* @param previousStacks - Stacks from previous render for ID stability
|
|
88
|
+
* @returns Array of merged stacks
|
|
89
|
+
*/
|
|
90
|
+
export declare function mergeOverlappingStacksWithBundles(stacks: Stack[], itemHeights: Map<string, number>, expandedStackIds?: Set<string>, previousStacks?: Stack[]): Stack[];
|
|
91
|
+
|
|
92
|
+
export declare interface PositionedFinding extends ValidationFinding {
|
|
93
|
+
desiredYLocation: number;
|
|
94
|
+
yLocation?: number;
|
|
95
|
+
hidden: boolean;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configuration for positioning calculations
|
|
100
|
+
*/
|
|
101
|
+
export declare interface PositioningConfig {
|
|
102
|
+
/**
|
|
103
|
+
* CSS selector for the editor container element
|
|
104
|
+
* @default '#tiptap'
|
|
105
|
+
*/
|
|
106
|
+
editorSelector?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Positions stacks to prevent overlaps using actual rendered heights.
|
|
111
|
+
*
|
|
112
|
+
* @param stacks - Array of stacks to position
|
|
113
|
+
* @param stackRefs - DOM refs for stack components
|
|
114
|
+
* @returns Array of stacks with adjusted yLocation
|
|
115
|
+
*/
|
|
116
|
+
export declare function positionStacksWithBundles(stacks: Stack[], stackRefs: Record<string, HTMLDivElement | null>): Stack[];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* A Stack represents one or more stackable items that should be displayed together.
|
|
120
|
+
* Stacks can contain any mix of bundles and individual findings of the same rule.
|
|
121
|
+
*/
|
|
122
|
+
export declare interface Stack {
|
|
123
|
+
id: string;
|
|
124
|
+
type: 'stack';
|
|
125
|
+
items: StackableItem[];
|
|
126
|
+
rule: string;
|
|
127
|
+
isExpandable: boolean;
|
|
128
|
+
isExpanded: boolean;
|
|
129
|
+
desiredYLocation: number;
|
|
130
|
+
yLocation: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A stackable item can be either a bundle or an individual finding.
|
|
135
|
+
* Both are actionable in one click and can be grouped together.
|
|
136
|
+
*/
|
|
137
|
+
export declare type StackableItem = Bundle | PositionedFinding;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Converts TipTap editor JSON to validation format with debouncing
|
|
141
|
+
*
|
|
142
|
+
* @param editorJSON - The TipTap editor JSON content
|
|
143
|
+
* @param options - Configuration options for validation
|
|
144
|
+
* @returns Conversion state from the API
|
|
145
|
+
*/
|
|
146
|
+
export declare function useTiptapValidation(editorJSON: JSONContent | null, options?: ValidationHookOptions): UseConversion;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Validates TipTap document content by converting it to validation JSON.
|
|
150
|
+
* All derived values are synchronously memoized; no promises leave the hook.
|
|
151
|
+
*
|
|
152
|
+
* @param editorJSON - The TipTap editor JSON content to validate
|
|
153
|
+
* @param options - Configuration options for validation
|
|
154
|
+
* @returns Validation state including findings, loading state, and errors
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* ```tsx
|
|
158
|
+
* const editor = useEditor({ extensions })
|
|
159
|
+
* const { isValidating, findings, error } = useValidation(editor?.getJSON() ?? null)
|
|
160
|
+
*
|
|
161
|
+
* useEffect(() => {
|
|
162
|
+
* if (findings.length > 0) {
|
|
163
|
+
* editor?.commands.setValidationFindings(findings)
|
|
164
|
+
* }
|
|
165
|
+
* }, [editor, findings])
|
|
166
|
+
* ```
|
|
167
|
+
*/
|
|
168
|
+
export declare function useValidation(editorJSON: JSONContent | null, options?: ValidationHookOptions): Validation;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Hook to access validation state from context
|
|
172
|
+
* Must be used within a ValidationProvider
|
|
173
|
+
*
|
|
174
|
+
* @returns Current validation state
|
|
175
|
+
* @throws Error if used outside of ValidationProvider
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```tsx
|
|
179
|
+
* function ValidationStatus() {
|
|
180
|
+
* const { findings, isValidating } = useValidationContext()
|
|
181
|
+
* return <div>{isValidating ? 'Validating...' : `${findings.length} issues`}</div>
|
|
182
|
+
* }
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
export declare function useValidationContext(): Validation;
|
|
186
|
+
|
|
187
|
+
export declare interface Validation {
|
|
188
|
+
isValidating: boolean;
|
|
189
|
+
findings: ValidationFinding[];
|
|
190
|
+
error: Error | null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export declare const ValidationContext: default_2.Context<Validation>;
|
|
194
|
+
|
|
195
|
+
export declare interface ValidationHookOptions {
|
|
196
|
+
/**
|
|
197
|
+
* Delay in milliseconds before starting validation after editor changes
|
|
198
|
+
* @default 500
|
|
199
|
+
*/
|
|
200
|
+
debounceDelay?: number;
|
|
201
|
+
/**
|
|
202
|
+
* Maximum wait time in milliseconds before forcing validation
|
|
203
|
+
* @default 5000
|
|
204
|
+
*/
|
|
205
|
+
debounceMaxWait?: number;
|
|
206
|
+
/**
|
|
207
|
+
* Whether to include image assets in the validation request
|
|
208
|
+
* @default false
|
|
209
|
+
*/
|
|
210
|
+
includeAssets?: boolean;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Provider component that manages validation state and applies findings to the editor
|
|
215
|
+
*
|
|
216
|
+
* Automatically tracks editor updates and validates content with debouncing.
|
|
217
|
+
* Excludes validation transactions to prevent infinite loops.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* Basic usage (automatic tracking):
|
|
221
|
+
* ```tsx
|
|
222
|
+
* const editor = useEditor({ extensions: [NLDocStarterKit] })
|
|
223
|
+
*
|
|
224
|
+
* <ValidationProvider editor={editor}>
|
|
225
|
+
* <EditorContent editor={editor} />
|
|
226
|
+
* <ValidationSidebar />
|
|
227
|
+
* </ValidationProvider>
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* Advanced usage (manual control):
|
|
232
|
+
* ```tsx
|
|
233
|
+
* const editor = useEditor({ extensions: [NLDocStarterKit] })
|
|
234
|
+
* const [editorJSON, setEditorJSON] = useState(null)
|
|
235
|
+
*
|
|
236
|
+
* <ValidationProvider editor={editor} editorJSON={editorJSON}>
|
|
237
|
+
* <EditorContent editor={editor} />
|
|
238
|
+
* </ValidationProvider>
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
export declare function ValidationProvider({ children, editor, editorJSON: providedEditorJSON, options, autoApplyFindings, onApplyFindings, }: ValidationProviderProps): default_2.JSX.Element;
|
|
242
|
+
|
|
243
|
+
declare interface ValidationProviderProps extends PropsWithChildren {
|
|
244
|
+
/**
|
|
245
|
+
* The TipTap editor instance (optional if editorJSON is provided)
|
|
246
|
+
*/
|
|
247
|
+
editor?: Editor | null;
|
|
248
|
+
/**
|
|
249
|
+
* The editor JSON content to validate (optional)
|
|
250
|
+
* If not provided, ValidationProvider will automatically track editor updates internally,
|
|
251
|
+
* excluding transactions from setValidationFindings to prevent infinite loops.
|
|
252
|
+
* Only provide this if you need custom control over which updates trigger validation.
|
|
253
|
+
*/
|
|
254
|
+
editorJSON?: JSONContent | null;
|
|
255
|
+
/**
|
|
256
|
+
* Validation configuration options
|
|
257
|
+
*/
|
|
258
|
+
options?: ValidationHookOptions;
|
|
259
|
+
/**
|
|
260
|
+
* Whether to automatically apply findings to the editor
|
|
261
|
+
* @default true
|
|
262
|
+
*/
|
|
263
|
+
autoApplyFindings?: boolean;
|
|
264
|
+
/**
|
|
265
|
+
* Callback to apply findings to the editor
|
|
266
|
+
* By default, this will call editor.commands.setValidationFindings if available
|
|
267
|
+
*/
|
|
268
|
+
onApplyFindings?: (editor: Editor, findings: ValidationFinding[]) => void;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export declare const ValidationResult: default_2.FC<ValidationResultProps>;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Component for displaying accessibility validation results.
|
|
275
|
+
* Handles both single findings, bundles, and collapsed stacks.
|
|
276
|
+
*
|
|
277
|
+
* This component:
|
|
278
|
+
* - Shows a toggle button that expands or collapses the detailed message
|
|
279
|
+
* - Handles resolution actions through onResolve callback
|
|
280
|
+
* - Closes on outside clicks and Escape key presses (default variant)
|
|
281
|
+
* - Uses `aria-controls`, `aria-expanded`, and `aria-live` attributes for accessibility
|
|
282
|
+
*
|
|
283
|
+
* @param findings - Array of validation findings (single finding or bundle)
|
|
284
|
+
* @param isBundle - Whether this is a bundle of findings (affects message formatting)
|
|
285
|
+
* @param variant - Display variant: 'default' for normal display, 'stack' for collapsed stack preview
|
|
286
|
+
* @param stackAmount - Number to display in count badge (stack variant only)
|
|
287
|
+
* @param stackIsExpanded - Whether the stack is expanded (stack variant only)
|
|
288
|
+
* @param stackOnToggle - Toggle callback for stack (stack variant only)
|
|
289
|
+
* @param onResolve - Callback when user triggers resolution for findings
|
|
290
|
+
* @param onRetrigger - Callback to retrigger validation
|
|
291
|
+
* @param isExpanded - Whether the result is expanded (default variant)
|
|
292
|
+
* @param onToggle - Toggle callback (default variant)
|
|
293
|
+
* @param labels - Text labels for UI elements
|
|
294
|
+
* @param renderIcon - Function to render icons
|
|
295
|
+
* @param renderMessage - Function to render finding message
|
|
296
|
+
* @param renderResolution - Function to render resolution component
|
|
297
|
+
*/
|
|
298
|
+
export declare interface ValidationResultProps {
|
|
299
|
+
findings: ValidationFinding[];
|
|
300
|
+
isBundle?: boolean;
|
|
301
|
+
variant?: 'default' | 'stack';
|
|
302
|
+
stackAmount?: number;
|
|
303
|
+
stackIsExpanded?: boolean;
|
|
304
|
+
stackOnToggle?: () => void;
|
|
305
|
+
onResolve?: (ids: string[]) => void;
|
|
306
|
+
onRetrigger?: () => void;
|
|
307
|
+
isExpanded?: boolean;
|
|
308
|
+
onToggle?: () => void;
|
|
309
|
+
labels?: {
|
|
310
|
+
toggleFinding?: string;
|
|
311
|
+
toggleStackOpen?: string;
|
|
312
|
+
toggleStackClosed?: string;
|
|
313
|
+
severity?: {
|
|
314
|
+
error?: string;
|
|
315
|
+
warning?: string;
|
|
316
|
+
suggestion?: string;
|
|
317
|
+
};
|
|
318
|
+
};
|
|
319
|
+
renderIcon?: (type: 'error' | 'warning' | 'suggestion' | 'close' | 'chevron', props?: {
|
|
320
|
+
size?: 'small' | 'medium' | 'large';
|
|
321
|
+
}) => default_2.ReactNode;
|
|
322
|
+
renderMessage?: (finding: ValidationFinding, count: number, isBundle: boolean) => default_2.ReactNode;
|
|
323
|
+
renderResolution?: (findingIds: string[], onResolved: () => void) => default_2.ReactNode;
|
|
324
|
+
className?: string;
|
|
325
|
+
styles?: {
|
|
326
|
+
outerWrapper?: string;
|
|
327
|
+
toggleButton?: string;
|
|
328
|
+
toggleButtonSelected?: string;
|
|
329
|
+
dropShadow?: string;
|
|
330
|
+
amount?: string;
|
|
331
|
+
result?: string;
|
|
332
|
+
resultSeverity?: string;
|
|
333
|
+
resultSelected?: string;
|
|
334
|
+
label?: string;
|
|
335
|
+
message?: string;
|
|
336
|
+
messageWrapper?: string;
|
|
337
|
+
openValidations?: string;
|
|
338
|
+
resolution?: string;
|
|
339
|
+
stack?: string;
|
|
340
|
+
stackDecorationTwoItems?: string;
|
|
341
|
+
stackDecorationManyItems?: string;
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Main component for displaying validation results summary with stacking and bundling.
|
|
347
|
+
*
|
|
348
|
+
* This component orchestrates the complex layout calculation process through multiple phases:
|
|
349
|
+
* 1. MEASURING: Render findings invisibly to measure their heights
|
|
350
|
+
* 2. BUNDLING: Group findings by parent block (paragraph, heading, etc.)
|
|
351
|
+
* 3. MEASURING_BUNDLES: Render bundles invisibly to measure their heights
|
|
352
|
+
* 4. STACKING: Create initial vertical stacks for overlapping items
|
|
353
|
+
* 5. MERGING: Merge overlapping stacks of the same rule
|
|
354
|
+
* 6. POSITIONING: Calculate final positions to prevent overlaps
|
|
355
|
+
* 7. IDLE: Render the final layout
|
|
356
|
+
*/
|
|
357
|
+
export declare const ValidationResultSummary: default_2.FC<ValidationResultSummaryProps>;
|
|
358
|
+
|
|
359
|
+
export declare interface ValidationResultSummaryProps {
|
|
360
|
+
/** Validation findings to display */
|
|
361
|
+
findings: ValidationFinding[];
|
|
362
|
+
/** Whether the editor is animating (used to delay layout updates) */
|
|
363
|
+
isAnimating?: boolean;
|
|
364
|
+
/** Configuration for positioning calculations */
|
|
365
|
+
positioningConfig?: PositioningConfig;
|
|
366
|
+
/** Callback to build a map from finding resource IDs to their parent block IDs */
|
|
367
|
+
buildFindingBlockMap?: (findings: ValidationFinding[]) => Map<string, string>;
|
|
368
|
+
/** Props to pass to ValidationResult components */
|
|
369
|
+
validationResultProps?: Partial<ValidationResultProps>;
|
|
370
|
+
/** Custom component to render instead of ValidationResult (receives all ValidationResultProps) */
|
|
371
|
+
resultComponent?: default_2.ComponentType<ValidationResultProps>;
|
|
372
|
+
/** Custom component to render collapse button for expanded stacks */
|
|
373
|
+
collapseButtonComponent?: default_2.ComponentType<{
|
|
374
|
+
onClick: () => void;
|
|
375
|
+
}>;
|
|
376
|
+
/** Debounce delay for window resize in ms */
|
|
377
|
+
resizeDebounceDelay?: number;
|
|
378
|
+
/** CSS class name for the container */
|
|
379
|
+
className?: string;
|
|
380
|
+
/** Styles for the container and child elements */
|
|
381
|
+
styles?: {
|
|
382
|
+
container?: string;
|
|
383
|
+
comment?: string;
|
|
384
|
+
findingInExpandedStack?: string;
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const Re=require("@nldoc/types"),b=require("react"),ve=require("zod"),J=require("@nldoc/api"),Te=require("use-debounce"),xe=require("@nldoc/tiptap-extensions");function de(n,t={}){const{debounceDelay:r=500,debounceMaxWait:o=5e3,includeAssets:s=!1}=t,[i]=Te.useDebounce(n,r,{maxWait:o}),l=b.useMemo(()=>{if(!i)return null;const p=s?i:{...i,attrs:{...i.attrs,assets:{}}};return new Blob([JSON.stringify(p)],{type:J.ContentType.Tiptap})},[i,s]);return J.useConversion(l,J.ContentType.Validation)}const _e=new Error("An unknown error occurred"),Ne=ve.z.array(Re.ValidationFinding);function ue(n,t={}){const r=de(n,t),[o,s]=b.useState(!1),[i,l]=b.useState([]),[p,k]=b.useState(null);return b.useEffect(()=>{if(r.status==="error"){s(!1),k(r.error);return}if(r.status==="idle"){s(!1);return}if(r.status!=="done"){s(!0);return}try{const E=Ne.parse(JSON.parse(r.content));l(E),k(null)}catch(E){k(E instanceof Error?E:_e)}finally{s(!1)}},[r]),{isValidating:o,findings:i,error:p}}var V={exports:{}},P={};var re;function ye(){if(re)return P;re=1;var n=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(o,s,i){var l=null;if(i!==void 0&&(l=""+i),s.key!==void 0&&(l=""+s.key),"key"in s){i={};for(var p in s)p!=="key"&&(i[p]=s[p])}else i=s;return s=i.ref,{$$typeof:n,type:o,key:l,ref:s!==void 0?s:null,props:i}}return P.Fragment=t,P.jsx=r,P.jsxs=r,P}var B={};var se;function Ae(){return se||(se=1,process.env.NODE_ENV!=="production"&&(function(){function n(e){if(e==null)return null;if(typeof e=="function")return e.$$typeof===Q?null:e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case A:return"Fragment";case S:return"Profiler";case c:return"StrictMode";case N:return"Suspense";case Z:return"SuspenseList";case H:return"Activity"}if(typeof e=="object")switch(typeof e.tag=="number"&&console.error("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),e.$$typeof){case m:return"Portal";case u:return e.displayName||"Context";case h:return(e._context.displayName||"Context")+".Consumer";case O:var f=e.render;return e=e.displayName,e||(e=f.displayName||f.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case D:return f=e.displayName||null,f!==null?f:n(e.type)||"Memo";case U:f=e._payload,e=e._init;try{return n(e(f))}catch{}}return null}function t(e){return""+e}function r(e){try{t(e);var f=!1}catch{f=!0}if(f){f=console;var I=f.error,T=typeof Symbol=="function"&&Symbol.toStringTag&&e[Symbol.toStringTag]||e.constructor.name||"Object";return I.call(f,"The provided key is an unsupported type %s. This value must be coerced to a string before using it here.",T),t(e)}}function o(e){if(e===A)return"<>";if(typeof e=="object"&&e!==null&&e.$$typeof===U)return"<...>";try{var f=n(e);return f?"<"+f+">":"<...>"}catch{return"<...>"}}function s(){var e=M.A;return e===null?null:e.getOwner()}function i(){return Error("react-stack-top-frame")}function l(e){if(G.call(e,"key")){var f=Object.getOwnPropertyDescriptor(e,"key").get;if(f&&f.isReactWarning)return!1}return e.key!==void 0}function p(e,f){function I(){j||(j=!0,console.error("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)",f))}I.isReactWarning=!0,Object.defineProperty(e,"key",{get:I,configurable:!0})}function k(){var e=n(this.type);return $[e]||($[e]=!0,console.error("Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release.")),e=this.props.ref,e!==void 0?e:null}function E(e,f,I,T,F,W){var x=I.ref;return e={$$typeof:v,type:e,key:f,props:I,_owner:T},(x!==void 0?x:null)!==null?Object.defineProperty(e,"ref",{enumerable:!1,get:k}):Object.defineProperty(e,"ref",{enumerable:!1,value:null}),e._store={},Object.defineProperty(e._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:0}),Object.defineProperty(e,"_debugInfo",{configurable:!1,enumerable:!1,writable:!0,value:null}),Object.defineProperty(e,"_debugStack",{configurable:!1,enumerable:!1,writable:!0,value:F}),Object.defineProperty(e,"_debugTask",{configurable:!1,enumerable:!1,writable:!0,value:W}),Object.freeze&&(Object.freeze(e.props),Object.freeze(e)),e}function a(e,f,I,T,F,W){var x=f.children;if(x!==void 0)if(T)if(Y(x)){for(T=0;T<x.length;T++)g(x[T]);Object.freeze&&Object.freeze(x)}else console.error("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else g(x);if(G.call(f,"key")){x=n(e);var w=Object.keys(f).filter(function(ke){return ke!=="key"});T=0<w.length?"{key: someKey, "+w.join(": ..., ")+": ...}":"{key: someKey}",te[x+T]||(w=0<w.length?"{"+w.join(": ..., ")+": ...}":"{}",console.error(`A props object containing a "key" prop is being spread into JSX:
|
|
2
|
+
let props = %s;
|
|
3
|
+
<%s {...props} />
|
|
4
|
+
React keys must be passed directly to JSX without using spread:
|
|
5
|
+
let props = %s;
|
|
6
|
+
<%s key={someKey} {...props} />`,T,x,w,x),te[x+T]=!0)}if(x=null,I!==void 0&&(r(I),x=""+I),l(f)&&(r(f.key),x=""+f.key),"key"in f){I={};for(var q in f)q!=="key"&&(I[q]=f[q])}else I=f;return x&&p(I,typeof e=="function"?e.displayName||e.name||"Unknown":e),E(e,x,I,s(),F,W)}function g(e){_(e)?e._store&&(e._store.validated=1):typeof e=="object"&&e!==null&&e.$$typeof===U&&(e._payload.status==="fulfilled"?_(e._payload.value)&&e._payload.value._store&&(e._payload.value._store.validated=1):e._store&&(e._store.validated=1))}function _(e){return typeof e=="object"&&e!==null&&e.$$typeof===v}var R=b,v=Symbol.for("react.transitional.element"),m=Symbol.for("react.portal"),A=Symbol.for("react.fragment"),c=Symbol.for("react.strict_mode"),S=Symbol.for("react.profiler"),h=Symbol.for("react.consumer"),u=Symbol.for("react.context"),O=Symbol.for("react.forward_ref"),N=Symbol.for("react.suspense"),Z=Symbol.for("react.suspense_list"),D=Symbol.for("react.memo"),U=Symbol.for("react.lazy"),H=Symbol.for("react.activity"),Q=Symbol.for("react.client.reference"),M=R.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,G=Object.prototype.hasOwnProperty,Y=Array.isArray,L=console.createTask?console.createTask:function(){return null};R={react_stack_bottom_frame:function(e){return e()}};var j,$={},ee=R.react_stack_bottom_frame.bind(R,i)(),ne=L(o(i)),te={};B.Fragment=A,B.jsx=function(e,f,I){var T=1e4>M.recentlyCreatedOwnerStacks++;return a(e,f,I,!1,T?Error("react-stack-top-frame"):ee,T?L(o(e)):ne)},B.jsxs=function(e,f,I){var T=1e4>M.recentlyCreatedOwnerStacks++;return a(e,f,I,!0,T?Error("react-stack-top-frame"):ee,T?L(o(e)):ne)}})()),B}var oe;function Le(){return oe||(oe=1,process.env.NODE_ENV==="production"?V.exports=ye():V.exports=Ae()),V.exports}var d=Le();const z=b.createContext({findings:[],isValidating:!1,error:null});function je({children:n,editor:t,editorJSON:r,options:o,autoApplyFindings:s=!0,onApplyFindings:i}){const[l,p]=b.useState(()=>r!==void 0?null:t?.getJSON()??null);b.useEffect(()=>{if(!t||r!==void 0)return;const a=({editor:g,transaction:_})=>{_.getMeta(xe.TransactionMeta.blockValidation)!==!0&&p(g.getJSON())};return t.on("update",a),()=>{t.off("update",a)}},[t,r]);const E=ue(r??l,o);return b.useEffect(()=>{if(!(!t||!s))if(i)i(t,E.findings);else{const a=t.commands;a.setValidationFindings&&a.setValidationFindings(E.findings)}},[t,E.findings,s,i]),d.jsx(z.Provider,{value:E,children:n})}function Oe(){return b.useContext(z)}const le=({findings:n,isBundle:t=!1,variant:r="default",stackAmount:o,stackIsExpanded:s,stackOnToggle:i,onResolve:l,onRetrigger:p,isExpanded:k,onToggle:E,labels:a={},renderIcon:g,renderMessage:_,renderResolution:R,className:v="",styles:m={}})=>{const A=b.useRef(null),[c,S]=b.useState(!1),h=r==="stack";if(h&&(o===void 0||s===void 0||!i))return console.error('ValidationResult: stackAmount, stackIsExpanded, and stackOnToggle are required when variant="stack"'),null;const u=h?s:k??c,O=h?i:E??(()=>S(!c)),N=n[0],D=g??(L=>{const j={error:"⚠️",warning:"⚠️",suggestion:"ℹ️",close:"✕",chevron:"▼"};return d.jsx("span",{children:j[L]})}),H=_??((L,j,$)=>$?`${j} ${L.rule} findings`:j>1?`${j} ${L.rule} findings`:L.rule),M=R??((L,j)=>d.jsx("button",{onClick:()=>{l&&l(L),j()},children:"Resolve"})),G=()=>{p&&p()},Y=d.jsxs("div",{className:`${m.outerWrapper??""} ${v}`,ref:h?null:A,children:[d.jsx("button",{className:`${m.toggleButton??""} ${u?m.toggleButtonSelected??"":""}`,onClick:O,"aria-controls":`comment-${t?"":"inner-"}${N.resourceId}`,"aria-expanded":u,"aria-label":a.toggleFinding??"Toggle finding details","data-testid":"toggle-button",children:d.jsx("div",{className:m.dropShadow??"",children:h?d.jsx("div",{className:m.amount??"",children:d.jsx("strong",{children:o})}):D(u?"close":N.severity)})}),d.jsxs("div",{className:`${m.result??""} ${m.resultSeverity?`${m.resultSeverity}-${N.severity}`:""} ${u?m.resultSelected??"":""}`,id:`comment-${t?"":"inner-"}${N.resourceId}`,tabIndex:-1,"aria-live":"polite",children:[d.jsxs("div",{className:m.messageWrapper??"",children:[d.jsxs("div",{className:m.label??"",children:[D(N.severity),d.jsx("strong",{children:a.severity?.[N.severity]??N.severity})]}),d.jsx("div",{className:m.message??"",children:H(N,h?o??0:n.length,t)})]}),h?d.jsx("div",{className:m.openValidations??"",children:d.jsx("button",{onClick:i,children:a.toggleStackOpen??"Open validations"})}):d.jsx("div",{className:m.resolution??"",children:M(n.map(L=>L.resourceId),G)})]})]});return h?d.jsxs("div",{className:m.stack??"",children:[Y,o===2?d.jsx("div",{className:m.stackDecorationTwoItems??""}):d.jsx("div",{className:m.stackDecorationManyItems??"",children:d.jsx("div",{className:m.stackDecorationManyItems??""})})]}):Y},fe=["no-underline","style-bold-in-header","style-italic-in-header"];function pe(n,t){const r=new Map;for(const s of n){if(!fe.includes(s.rule)||s.hidden)continue;const i=t.get(s.resourceId);if(!i)continue;const l=r.get(i);l?l.push(s):r.set(i,[s])}const o=[];for(const[s,i]of r){if(i.length<2)continue;const l=[...i].sort((p,k)=>p.desiredYLocation-k.desiredYLocation);o.push({id:crypto.randomUUID(),type:"bundle",findings:l,blockId:s,rule:l[0].rule,desiredYLocation:l[0].desiredYLocation,yLocation:l[0].desiredYLocation})}return o}function me(n){const t=new Set;for(const r of n)for(const o of r.findings)t.add(o.resourceId);return t}function ge(n,t={}){const{editorSelector:r="#tiptap"}=t,o=document.getElementById(n.resourceId);if(!o)return null;const s=document.querySelector(r);if(!s)throw new Error(`Editor not found: ${r} element does not exist`);const i=s.getBoundingClientRect().top;return o.getBoundingClientRect().top-i}function Ee(n,t){const o=ge(n,t)??0;return{...n,desiredYLocation:o,yLocation:o,hidden:!1}}function K(n,t){return n.map(r=>Ee(r,t)).sort(Se)}function Se(n,t){const r=n.desiredYLocation-t.desiredYLocation;return Math.abs(r)>.01?r:n.resourceId.localeCompare(t.resourceId)}function X(n){return"type"in n&&n.type==="bundle"}function ie(n){return X(n)?`bundle:${n.blockId}`:n.resourceId}function C(n){return n.desiredYLocation}function he(n){return n.map(t=>({id:crypto.randomUUID(),type:"stack",items:[t],rule:t.rule,isExpandable:!1,isExpanded:!1,desiredYLocation:C(t),yLocation:C(t)}))}function Ie(n,t,r=new Set,o=[]){if(n.length===0)return[];const s=[...n].sort((_,R)=>_.desiredYLocation-R.desiredYLocation),i=[...o],l=[];let p=[],k=null,E=0,a=0,g=0;for(const _ of s){const R=_.items[0],v=R.rule,m=C(R),A=Ce(R,t),c=Math.max(m,g);if(p.length===0){p=[R],k=v,E=c,a=A,g=c+A;continue}const S=v===k,h=c<=E+a;S&&h?(p.push(R),g=E+a):(l.push(ae(p,r,i)),p=[R],k=v,E=c,a=A,g=c+A)}return p.length>0&&l.push(ae(p,r,i)),l}function Ce(n,t){return X(n)?t.get(n.id)??0:t.get(n.resourceId)??0}function ae(n,t,r){const o=[...n].sort((l,p)=>C(l)-C(p)),s=we(o,r),i=s?s.id:crypto.randomUUID();return{id:i,type:"stack",items:o,rule:o[0].rule,isExpandable:o.length>1,isExpanded:t.has(i),desiredYLocation:C(o[0]),yLocation:C(o[0])}}function we(n,t){if(t.length===0)return null;const r=new Set(n.map(ie));let o=null,s=0,i=-1;for(let l=0;l<t.length;l++){const p=t[l],k=new Set(p.items.map(ie));let E=0;for(const g of r)k.has(g)&&E++;const a=E/Math.max(r.size,k.size);a>s&&(s=a,o=p,i=l)}return s>=.5&&i>=0?(t.splice(i,1),o):null}function be(n,t){const r=n.map(o=>({...o}));for(let o=1;o<r.length;o++){const s=r[o-1],i=r[o],l=t[s.id]?.offsetHeight??0,p=s.yLocation+l;i.yLocation=Math.max(i.desiredYLocation,p)}return r}const y={IDLE:"idle",MEASURING:"measuring",BUNDLING:"bundling",MEASURING_BUNDLES:"measuring-bundles",STACKING:"stacking",MERGING:"merging",POSITIONING:"positioning"},De={phase:y.IDLE,bundles:[],stacks:[],previousStacks:[],expandedStackIds:new Set,itemHeights:new Map,findingBlockMap:new Map,findings:[]},ce=(n,t)=>({...n,phase:y.MEASURING,findings:t,previousStacks:n.stacks,stacks:[],bundles:[]});function Me(n,t){switch(t.type){case"FINDINGS_UPDATED":case"ANIMATION_FINISHED":return n.findings===t.findings?n:ce(n,t.findings);case"RECALCULATE":return ce(n,n.findings);case"HEIGHTS_MEASURED":return{...n,phase:y.BUNDLING,itemHeights:t.heights};case"BLOCK_MAP_BUILT":return{...n,findingBlockMap:t.blockMap};case"BUNDLES_CREATED":return{...n,phase:y.MEASURING_BUNDLES,bundles:t.bundles};case"BUNDLE_HEIGHTS_MEASURED":return{...n,phase:y.STACKING,itemHeights:new Map([...n.itemHeights,...t.heights])};case"STACKS_CREATED":return{...n,phase:y.MERGING,stacks:t.stacks};case"STACKS_MERGED":return{...n,phase:y.POSITIONING,stacks:t.stacks};case"STACKS_POSITIONED":return{...n,phase:y.IDLE,stacks:t.stacks};case"TOGGLE_STACK":{const r=new Set(n.expandedStackIds);return r.has(t.stackId)?r.delete(t.stackId):r.add(t.stackId),{...n,expandedStackIds:r,phase:y.STACKING,previousStacks:n.stacks}}default:return n}}function Pe(n,t){let r;return()=>{clearTimeout(r),r=setTimeout(n,t)}}const Be=({findings:n,isAnimating:t=!1,positioningConfig:r,buildFindingBlockMap:o,validationResultProps:s={},resultComponent:i,collapseButtonComponent:l,resizeDebounceDelay:p=100,className:k="",styles:E={}})=>{const[a,g]=b.useReducer(Me,De),_=i??le,R=l,v=b.useRef({findings:{},bundles:{},stacks:{}}),m=b.useCallback(()=>g({type:"RECALCULATE"}),[]);b.useEffect(()=>{t||g({type:"ANIMATION_FINISHED",findings:n})},[t,n]),b.useEffect(()=>{g({type:"FINDINGS_UPDATED",findings:n}),g(o?{type:"BLOCK_MAP_BUILT",blockMap:o(n)}:{type:"BLOCK_MAP_BUILT",blockMap:new Map})},[n,o]),b.useEffect(()=>{const c=Pe(m,p);return window.addEventListener("resize",c),()=>window.removeEventListener("resize",c)},[m,p]),b.useLayoutEffect(()=>{const c=K(a.findings,r);switch(a.phase){case y.MEASURING:{const S=new Map;c.forEach(h=>{const u=v.current.findings[h.resourceId];u&&S.set(h.resourceId,u.offsetHeight)}),g({type:"HEIGHTS_MEASURED",heights:S});break}case y.BUNDLING:{const S=pe(c,a.findingBlockMap);g({type:"BUNDLES_CREATED",bundles:S});break}case y.MEASURING_BUNDLES:{const S=new Map;a.bundles.forEach(h=>{const u=v.current.bundles[h.id];u&&S.set(h.id,u.offsetHeight)}),g({type:"BUNDLE_HEIGHTS_MEASURED",heights:S});break}case y.STACKING:{const S=me(a.bundles),h=c.filter(O=>!S.has(O.resourceId)),u=he([...a.bundles,...h]);g({type:"STACKS_CREATED",stacks:u});break}case y.MERGING:{const S=Ie(a.stacks,a.itemHeights,a.expandedStackIds,a.previousStacks);g({type:"STACKS_MERGED",stacks:S});break}case y.POSITIONING:{const S=be(a.stacks,v.current.stacks);g({type:"STACKS_POSITIONED",stacks:S});break}}},[a.phase,a.findings,a.bundles,a.stacks,a.findingBlockMap,a.itemHeights,a.expandedStackIds,a.previousStacks,r]);const A={position:"absolute",visibility:"hidden",pointerEvents:"none"};return d.jsxs("section",{className:`${E.container??""} ${k}`,tabIndex:-1,"data-testid":"validation-results-summary",children:[a.phase===y.MEASURING&&K(a.findings,r).map(c=>d.jsx("div",{ref:S=>{v.current.findings[c.resourceId]=S},style:A,children:d.jsx(_,{...s,findings:[c],onRetrigger:m})},c.resourceId)),a.phase===y.MEASURING_BUNDLES&&a.bundles.map(c=>d.jsx("div",{className:E.comment??"",ref:S=>{v.current.bundles[c.id]=S},style:A,children:d.jsx(_,{...s,findings:c.findings,onRetrigger:m,isBundle:!0})},c.id)),a.stacks.map(c=>{const S=c.items.length>1,h=c.items.flatMap(u=>"type"in u&&u.type==="bundle"?u.findings:[u]);return d.jsx("div",{"data-testid":`comment-item-${c.id}`,className:E.comment??"",ref:u=>{v.current.stacks[c.id]=u},style:{top:`${c.yLocation}px`,position:"absolute"},children:S&&!c.isExpanded?d.jsxs(d.Fragment,{children:[c.items.map(u=>"type"in u&&u.type==="bundle"?d.jsx("div",{ref:N=>{v.current.bundles[u.id]=N},style:A,children:d.jsx(_,{...s,findings:u.findings,onRetrigger:m,isBundle:!0})},u.id):d.jsx("div",{ref:N=>{v.current.findings[u.resourceId]=N},style:A,children:d.jsx(_,{...s,findings:[u],onRetrigger:m})},u.resourceId)),d.jsx(_,{...s,variant:"stack",findings:h,stackAmount:c.items.length,stackIsExpanded:!1,stackOnToggle:()=>g({type:"TOGGLE_STACK",stackId:c.id}),onRetrigger:m})]}):d.jsxs(d.Fragment,{children:[c.items.map(u=>"type"in u&&u.type==="bundle"?d.jsx("div",{ref:N=>{v.current.bundles[u.id]=N},className:c.isExpanded?E.findingInExpandedStack:void 0,children:d.jsx(_,{...s,findings:u.findings,onRetrigger:m,isBundle:!0})},u.id):d.jsx("div",{ref:N=>{v.current.findings[u.resourceId]=N},className:c.isExpanded?E.findingInExpandedStack:void 0,children:d.jsx(_,{...s,findings:[u],onRetrigger:m})},u.resourceId)),S&&c.isExpanded&&R&&d.jsx(R,{onClick:()=>g({type:"TOGGLE_STACK",stackId:c.id})}),S&&c.isExpanded&&!R&&d.jsx("div",{children:d.jsx("button",{onClick:()=>g({type:"TOGGLE_STACK",stackId:c.id}),children:s.labels?.toggleStackClosed??"Collapse"})})]})},c.id)})]})};exports.BUNDLEABLE_RULES=fe;exports.ValidationContext=z;exports.ValidationProvider=je;exports.ValidationResult=le;exports.ValidationResultSummary=Be;exports.byIncreasingDesiredYLocation=Se;exports.calculateDesiredYLocation=ge;exports.computeBundles=pe;exports.createInitialStacksFromItems=he;exports.getBundledFindingIds=me;exports.isBundle=X;exports.makePositionedFinding=Ee;exports.makePositionedFindings=K;exports.mergeOverlappingStacksWithBundles=Ie;exports.positionStacksWithBundles=be;exports.useTiptapValidation=de;exports.useValidation=ue;exports.useValidationContext=Oe;
|