@deepcitation/deepcitation-js 1.1.13 → 1.1.15

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.
@@ -1,7 +1,7 @@
1
1
  import React, { type ReactNode } from "react";
2
2
  import { type CitationStatus } from "../types/citation.js";
3
3
  import type { Verification } from "../types/verification.js";
4
- import type { BaseCitationProps, CitationEventHandlers, CitationRenderProps, CitationVariant } from "./types.js";
4
+ import type { BaseCitationProps, CitationBehaviorConfig, CitationEventHandlers, CitationRenderProps, CitationVariant } from "./types.js";
5
5
  import "./styles.css";
6
6
  export type { CitationVariant } from "./types.js";
7
7
  /**
@@ -74,6 +74,30 @@ export type { CitationVariant } from "./types.js";
74
74
  * popoverPosition="hidden"
75
75
  * />
76
76
  * ```
77
+ *
78
+ * @example Custom click behavior (replaces default)
79
+ * ```tsx
80
+ * <CitationComponent
81
+ * citation={citation}
82
+ * verification={verificationResult}
83
+ * behaviorConfig={{
84
+ * onClick: (context, event) => {
85
+ * if (context.hasImage) {
86
+ * return { setImageExpanded: true };
87
+ * }
88
+ * }
89
+ * }}
90
+ * />
91
+ * ```
92
+ *
93
+ * @example Disable all click behavior
94
+ * ```tsx
95
+ * <CitationComponent
96
+ * citation={citation}
97
+ * verification={verificationResult}
98
+ * behaviorConfig={{ onClick: () => false }}
99
+ * />
100
+ * ```
77
101
  */
78
102
  export interface CitationComponentProps extends BaseCitationProps {
79
103
  /**
@@ -97,8 +121,22 @@ export interface CitationComponentProps extends BaseCitationProps {
97
121
  displayBrackets?: boolean;
98
122
  /**
99
123
  * Event handlers for citation interactions.
124
+ * These are always called regardless of behaviorConfig settings.
100
125
  */
101
126
  eventHandlers?: CitationEventHandlers;
127
+ /**
128
+ * Configuration for customizing default click/hover behaviors.
129
+ * Use this to disable or extend the built-in behaviors.
130
+ *
131
+ * Default behaviors:
132
+ * - Hover: Shows zoom-in cursor when popover is pinned and has image
133
+ * - Click 1: Pins the popover open (stays visible without hover)
134
+ * - Click 2: Opens full-size image overlay (if image available)
135
+ * - Click 3: Closes image and unpins popover
136
+ *
137
+ * @see CitationBehaviorConfig for all options
138
+ */
139
+ behaviorConfig?: CitationBehaviorConfig;
102
140
  /**
103
141
  * Enable mobile touch handlers.
104
142
  * @default false
@@ -198,7 +198,7 @@ const DefaultPopoverContent = ({ citation, verification, status, onImageClick, }
198
198
  * This means partial matches have blue text (because they were found) but
199
199
  * an orange indicator (because they didn't match exactly).
200
200
  */
201
- export const CitationComponent = forwardRef(({ citation, children, className, displayKeySpan = true, displayBrackets = true, fallbackDisplay, verification, variant = "brackets", eventHandlers, isMobile = false, renderIndicator, renderContent, popoverPosition = "top", renderPopoverContent, }, ref) => {
201
+ export const CitationComponent = forwardRef(({ citation, children, className, displayKeySpan = true, displayBrackets = true, fallbackDisplay, verification, variant = "brackets", eventHandlers, behaviorConfig, isMobile = false, renderIndicator, renderContent, popoverPosition = "top", renderPopoverContent, }, ref) => {
202
202
  const containerRef = useRef(null);
203
203
  const wrapperRef = useRef(null);
204
204
  const [expandedImageSrc, setExpandedImageSrc] = useState(null);
@@ -247,10 +247,52 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
247
247
  }, [isTooltipExpanded]);
248
248
  const citationKey = useMemo(() => generateCitationKey(citation), [citation]);
249
249
  const citationInstanceId = useMemo(() => generateCitationInstanceId(citationKey), [citationKey]);
250
+ // Create behavior context for custom handlers
251
+ const getBehaviorContext = useCallback(() => ({
252
+ citation,
253
+ citationKey,
254
+ verification: verification ?? null,
255
+ isTooltipExpanded,
256
+ isImageExpanded: !!expandedImageSrc,
257
+ hasImage: !!verification?.verificationImageBase64,
258
+ }), [citation, citationKey, verification, isTooltipExpanded, expandedImageSrc]);
259
+ // Apply behavior actions from custom handler
260
+ const applyBehaviorActions = useCallback((actions) => {
261
+ if (actions.setTooltipExpanded !== undefined) {
262
+ setIsTooltipExpanded(actions.setTooltipExpanded);
263
+ }
264
+ if (actions.setImageExpanded !== undefined) {
265
+ if (typeof actions.setImageExpanded === "string") {
266
+ setExpandedImageSrc(actions.setImageExpanded);
267
+ }
268
+ else if (actions.setImageExpanded === true && verification?.verificationImageBase64) {
269
+ setExpandedImageSrc(verification.verificationImageBase64);
270
+ }
271
+ else if (actions.setImageExpanded === false) {
272
+ setExpandedImageSrc(null);
273
+ }
274
+ }
275
+ if (actions.setPhrasesExpanded !== undefined) {
276
+ setIsPhrasesExpanded(actions.setPhrasesExpanded);
277
+ }
278
+ }, [verification?.verificationImageBase64]);
250
279
  const handleToggleTooltip = useCallback((e) => {
251
280
  e.preventDefault();
252
281
  e.stopPropagation();
253
- // If we have a verification image
282
+ const context = getBehaviorContext();
283
+ // If custom onClick handler is provided, it REPLACES default behavior
284
+ if (behaviorConfig?.onClick) {
285
+ const result = behaviorConfig.onClick(context, e);
286
+ // If custom handler returns actions, apply them
287
+ if (result && typeof result === "object") {
288
+ applyBehaviorActions(result);
289
+ }
290
+ // If returns false or void, no state changes
291
+ // Always call eventHandlers.onClick regardless of custom behavior
292
+ eventHandlers?.onClick?.(citation, citationKey, e);
293
+ return;
294
+ }
295
+ // Default click behavior (only runs when no custom onClick is provided)
254
296
  if (verification?.verificationImageBase64) {
255
297
  if (expandedImageSrc) {
256
298
  // Image is open - close it and unpin
@@ -262,7 +304,7 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
262
304
  setExpandedImageSrc(verification.verificationImageBase64);
263
305
  }
264
306
  else {
265
- // First click - just pin the popover open
307
+ // First click - pin the popover open
266
308
  setIsTooltipExpanded(true);
267
309
  }
268
310
  }
@@ -274,11 +316,14 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
274
316
  eventHandlers?.onClick?.(citation, citationKey, e);
275
317
  }, [
276
318
  eventHandlers,
319
+ behaviorConfig,
277
320
  citation,
278
321
  citationKey,
279
322
  verification?.verificationImageBase64,
280
323
  expandedImageSrc,
281
324
  isTooltipExpanded,
325
+ getBehaviorContext,
326
+ applyBehaviorActions,
282
327
  ]);
283
328
  const status = getCitationStatus(verification ?? null);
284
329
  // const { isVerified, isPending } = status;
@@ -297,11 +342,19 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
297
342
  const foundStatusClass = useMemo(() => getFoundStatusClass(status), [status]);
298
343
  // Event handlers
299
344
  const handleMouseEnter = useCallback(() => {
345
+ // Call custom onHover.onEnter handler (if provided)
346
+ if (behaviorConfig?.onHover?.onEnter) {
347
+ behaviorConfig.onHover.onEnter(getBehaviorContext());
348
+ }
300
349
  eventHandlers?.onMouseEnter?.(citation, citationKey);
301
- }, [eventHandlers, citation, citationKey]);
350
+ }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
302
351
  const handleMouseLeave = useCallback(() => {
352
+ // Call custom onHover.onLeave handler (if provided)
353
+ if (behaviorConfig?.onHover?.onLeave) {
354
+ behaviorConfig.onHover.onLeave(getBehaviorContext());
355
+ }
303
356
  eventHandlers?.onMouseLeave?.(citation, citationKey);
304
- }, [eventHandlers, citation, citationKey]);
357
+ }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
305
358
  const handleTouchEnd = useCallback((e) => {
306
359
  if (isMobile) {
307
360
  e.preventDefault();
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @packageDocumentation
12
12
  */
13
- export type { CitationContentProps, CitationRenderProps, CitationTooltipProps, CitationStyles, CitationStateClasses, CitationCursorClasses, CitationEventHandlers, CitationVariant as CitationVariantType, UrlFetchStatus, UrlCitationMeta, UrlCitationProps, } from "./types.js";
13
+ export type { CitationContentProps, CitationRenderProps, CitationTooltipProps, CitationStyles, CitationStateClasses, CitationCursorClasses, CitationEventHandlers, CitationVariant as CitationVariantType, UrlFetchStatus, UrlCitationMeta, UrlCitationProps, CitationBehaviorConfig, CitationBehaviorContext, CitationBehaviorActions, CitationClickBehavior, CitationHoverBehavior, } from "./types.js";
14
14
  export { extractDomain, isBlockedStatus, isErrorStatus, isVerifiedStatus, } from "./UrlCitationComponent.js";
15
15
  export { generateCitationKey, generateCitationInstanceId, getCitationDisplayText, getCitationKeySpanText, classNames, CITATION_X_PADDING, CITATION_Y_PADDING, } from "./utils.js";
16
16
  export { CitationComponent, MemoizedCitationComponent, type CitationVariant, type CitationComponentProps, } from "./CitationComponent.js";
@@ -184,6 +184,94 @@ export interface CitationEventHandlers {
184
184
  /** Called on touch end (mobile) */
185
185
  onTouchEnd?: (citation: Citation, citationKey: string, event: React.TouchEvent) => void;
186
186
  }
187
+ /**
188
+ * Context provided to behavior handlers for making decisions.
189
+ */
190
+ export interface CitationBehaviorContext {
191
+ /** The citation data */
192
+ citation: Citation;
193
+ /** Unique key for this citation */
194
+ citationKey: string;
195
+ /** Verification result if available */
196
+ verification: Verification | null;
197
+ /** Whether the popover is currently pinned open */
198
+ isTooltipExpanded: boolean;
199
+ /** Whether the full-size image overlay is currently open */
200
+ isImageExpanded: boolean;
201
+ /** Whether a verification image is available */
202
+ hasImage: boolean;
203
+ }
204
+ /**
205
+ * Actions that can be performed by the citation component.
206
+ * These are returned by behavior handlers to control component state.
207
+ */
208
+ export interface CitationBehaviorActions {
209
+ /** Pin or unpin the popover (keeps it visible without hover) */
210
+ setTooltipExpanded?: boolean;
211
+ /** Open or close the full-size image overlay */
212
+ setImageExpanded?: boolean | string;
213
+ /** Expand or collapse the search phrases list (for miss/partial states) */
214
+ setPhrasesExpanded?: boolean;
215
+ }
216
+ /**
217
+ * Configuration for click behavior.
218
+ * Return actions to perform, or `false` to prevent default behavior.
219
+ */
220
+ export type CitationClickBehavior = (context: CitationBehaviorContext, event: React.MouseEvent | React.TouchEvent) => CitationBehaviorActions | false | void;
221
+ /**
222
+ * Configuration for hover behavior.
223
+ */
224
+ export interface CitationHoverBehavior {
225
+ /** Called when mouse enters the citation */
226
+ onEnter?: (context: CitationBehaviorContext) => void;
227
+ /** Called when mouse leaves the citation */
228
+ onLeave?: (context: CitationBehaviorContext) => void;
229
+ }
230
+ /**
231
+ * Configuration for customizing default citation behaviors.
232
+ *
233
+ * When you provide `onClick` or `onHover`, they REPLACE the corresponding default behaviors.
234
+ * Use `eventHandlers` for side effects that should run alongside defaults.
235
+ *
236
+ * @example Custom click behavior (replaces default)
237
+ * ```tsx
238
+ * <CitationComponent
239
+ * citation={citation}
240
+ * verification={verification}
241
+ * behaviorConfig={{
242
+ * onClick: (context, event) => {
243
+ * if (context.hasImage) {
244
+ * return { setImageExpanded: true };
245
+ * }
246
+ * }
247
+ * }}
248
+ * />
249
+ * ```
250
+ *
251
+ * @example Disable click behavior entirely
252
+ * ```tsx
253
+ * <CitationComponent
254
+ * citation={citation}
255
+ * verification={verification}
256
+ * behaviorConfig={{ onClick: () => false }}
257
+ * />
258
+ * ```
259
+ */
260
+ export interface CitationBehaviorConfig {
261
+ /**
262
+ * Custom click behavior handler. When provided, REPLACES the default click behavior.
263
+ *
264
+ * Return values:
265
+ * - `CitationBehaviorActions`: Apply specific state changes
266
+ * - `false`: Prevent any state changes
267
+ * - `void`/`undefined`: No state changes
268
+ */
269
+ onClick?: CitationClickBehavior;
270
+ /**
271
+ * Custom hover behavior handlers. When provided, REPLACE the default hover behavior.
272
+ */
273
+ onHover?: CitationHoverBehavior;
274
+ }
187
275
  /**
188
276
  * Props for the tooltip wrapper component
189
277
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepcitation/deepcitation-js",
3
- "version": "1.1.13",
3
+ "version": "1.1.15",
4
4
  "description": "DeepCitation JavaScript SDK for deterministic AI citation verification",
5
5
  "type": "module",
6
6
  "private": false,