@deepcitation/deepcitation-js 1.1.13 → 1.1.14

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,29 @@ export type { CitationVariant } from "./types.js";
74
74
  * popoverPosition="hidden"
75
75
  * />
76
76
  * ```
77
+ *
78
+ * @example Customize click behavior - disable image expand
79
+ * ```tsx
80
+ * <CitationComponent
81
+ * citation={citation}
82
+ * verification={verificationResult}
83
+ * behaviorConfig={{ disableImageExpand: true }}
84
+ * />
85
+ * ```
86
+ *
87
+ * @example Custom click handler with default behavior
88
+ * ```tsx
89
+ * <CitationComponent
90
+ * citation={citation}
91
+ * verification={verificationResult}
92
+ * behaviorConfig={{
93
+ * onClick: (context, event) => {
94
+ * // Log analytics, then let default behavior proceed
95
+ * analytics.track('citation_clicked', { key: context.citationKey });
96
+ * }
97
+ * }}
98
+ * />
99
+ * ```
77
100
  */
78
101
  export interface CitationComponentProps extends BaseCitationProps {
79
102
  /**
@@ -97,8 +120,22 @@ export interface CitationComponentProps extends BaseCitationProps {
97
120
  displayBrackets?: boolean;
98
121
  /**
99
122
  * Event handlers for citation interactions.
123
+ * These are always called regardless of behaviorConfig settings.
100
124
  */
101
125
  eventHandlers?: CitationEventHandlers;
126
+ /**
127
+ * Configuration for customizing default click/hover behaviors.
128
+ * Use this to disable or extend the built-in behaviors.
129
+ *
130
+ * Default behaviors:
131
+ * - Hover: Shows zoom-in cursor when popover is pinned and has image
132
+ * - Click 1: Pins the popover open (stays visible without hover)
133
+ * - Click 2: Opens full-size image overlay (if image available)
134
+ * - Click 3: Closes image and unpins popover
135
+ *
136
+ * @see CitationBehaviorConfig for all options
137
+ */
138
+ behaviorConfig?: CitationBehaviorConfig;
102
139
  /**
103
140
  * Enable mobile touch handlers.
104
141
  * @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,9 +247,63 @@ 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();
282
+ const context = getBehaviorContext();
283
+ // Call custom onClick handler first (if provided)
284
+ if (behaviorConfig?.onClick) {
285
+ const result = behaviorConfig.onClick(context, e);
286
+ // If custom handler returns actions, apply them and skip default behavior
287
+ if (result && typeof result === "object") {
288
+ applyBehaviorActions(result);
289
+ // Always call eventHandlers.onClick regardless of custom behavior
290
+ eventHandlers?.onClick?.(citation, citationKey, e);
291
+ return;
292
+ }
293
+ // If custom handler returns false, skip default behavior entirely
294
+ if (result === false) {
295
+ // Always call eventHandlers.onClick regardless of custom behavior
296
+ eventHandlers?.onClick?.(citation, citationKey, e);
297
+ return;
298
+ }
299
+ // Otherwise (undefined/void), proceed with default behavior
300
+ }
301
+ // Check if click behavior is completely disabled
302
+ if (behaviorConfig?.disableClickBehavior) {
303
+ eventHandlers?.onClick?.(citation, citationKey, e);
304
+ return;
305
+ }
306
+ // Default click behavior
253
307
  // If we have a verification image
254
308
  if (verification?.verificationImageBase64) {
255
309
  if (expandedImageSrc) {
@@ -258,27 +312,36 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
258
312
  setIsTooltipExpanded(false);
259
313
  }
260
314
  else if (isTooltipExpanded) {
261
- // Already pinned - second click expands image
262
- setExpandedImageSrc(verification.verificationImageBase64);
315
+ // Already pinned - second click expands image (unless disabled)
316
+ if (!behaviorConfig?.disableImageExpand) {
317
+ setExpandedImageSrc(verification.verificationImageBase64);
318
+ }
263
319
  }
264
320
  else {
265
- // First click - just pin the popover open
266
- setIsTooltipExpanded(true);
321
+ // First click - just pin the popover open (unless disabled)
322
+ if (!behaviorConfig?.disablePopoverPin) {
323
+ setIsTooltipExpanded(true);
324
+ }
267
325
  }
268
326
  }
269
327
  else {
270
328
  // No image - toggle phrases expansion for miss/partial tooltips
271
- setIsTooltipExpanded((prev) => !prev);
272
- setIsPhrasesExpanded((prev) => !prev);
329
+ if (!behaviorConfig?.disablePopoverPin) {
330
+ setIsTooltipExpanded((prev) => !prev);
331
+ setIsPhrasesExpanded((prev) => !prev);
332
+ }
273
333
  }
274
334
  eventHandlers?.onClick?.(citation, citationKey, e);
275
335
  }, [
276
336
  eventHandlers,
337
+ behaviorConfig,
277
338
  citation,
278
339
  citationKey,
279
340
  verification?.verificationImageBase64,
280
341
  expandedImageSrc,
281
342
  isTooltipExpanded,
343
+ getBehaviorContext,
344
+ applyBehaviorActions,
282
345
  ]);
283
346
  const status = getCitationStatus(verification ?? null);
284
347
  // const { isVerified, isPending } = status;
@@ -297,11 +360,19 @@ export const CitationComponent = forwardRef(({ citation, children, className, di
297
360
  const foundStatusClass = useMemo(() => getFoundStatusClass(status), [status]);
298
361
  // Event handlers
299
362
  const handleMouseEnter = useCallback(() => {
363
+ // Call custom onHover.onEnter handler (if provided)
364
+ if (behaviorConfig?.onHover?.onEnter) {
365
+ behaviorConfig.onHover.onEnter(getBehaviorContext());
366
+ }
300
367
  eventHandlers?.onMouseEnter?.(citation, citationKey);
301
- }, [eventHandlers, citation, citationKey]);
368
+ }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
302
369
  const handleMouseLeave = useCallback(() => {
370
+ // Call custom onHover.onLeave handler (if provided)
371
+ if (behaviorConfig?.onHover?.onLeave) {
372
+ behaviorConfig.onHover.onLeave(getBehaviorContext());
373
+ }
303
374
  eventHandlers?.onMouseLeave?.(citation, citationKey);
304
- }, [eventHandlers, citation, citationKey]);
375
+ }, [eventHandlers, behaviorConfig, citation, citationKey, getBehaviorContext]);
305
376
  const handleTouchEnd = useCallback((e) => {
306
377
  if (isMobile) {
307
378
  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,147 @@ 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
+ * @example Use defaults (no config needed)
234
+ * ```tsx
235
+ * <CitationComponent citation={citation} verification={verification} />
236
+ * ```
237
+ *
238
+ * @example Disable click-to-expand image (popover still pins on click)
239
+ * ```tsx
240
+ * <CitationComponent
241
+ * citation={citation}
242
+ * verification={verification}
243
+ * behaviorConfig={{ disableImageExpand: true }}
244
+ * />
245
+ * ```
246
+ *
247
+ * @example Disable all click behavior
248
+ * ```tsx
249
+ * <CitationComponent
250
+ * citation={citation}
251
+ * verification={verification}
252
+ * behaviorConfig={{ disableClickBehavior: true }}
253
+ * />
254
+ * ```
255
+ *
256
+ * @example Custom click behavior (extend defaults)
257
+ * ```tsx
258
+ * <CitationComponent
259
+ * citation={citation}
260
+ * verification={verification}
261
+ * behaviorConfig={{
262
+ * onClick: (context, event) => {
263
+ * // Log analytics
264
+ * analytics.track('citation_clicked', { key: context.citationKey });
265
+ * // Return nothing to use default behavior
266
+ * }
267
+ * }}
268
+ * />
269
+ * ```
270
+ *
271
+ * @example Override click behavior completely
272
+ * ```tsx
273
+ * <CitationComponent
274
+ * citation={citation}
275
+ * verification={verification}
276
+ * behaviorConfig={{
277
+ * onClick: (context, event) => {
278
+ * // Custom behavior: always open image immediately
279
+ * if (context.hasImage) {
280
+ * return { setImageExpanded: true };
281
+ * }
282
+ * return false; // Prevent default behavior
283
+ * }
284
+ * }}
285
+ * />
286
+ * ```
287
+ */
288
+ export interface CitationBehaviorConfig {
289
+ /**
290
+ * Disable the default click behavior entirely.
291
+ * When true, clicks won't pin popovers or expand images.
292
+ * Your eventHandlers.onClick will still be called.
293
+ * @default false
294
+ */
295
+ disableClickBehavior?: boolean;
296
+ /**
297
+ * Disable the click-to-expand image behavior.
298
+ * First click still pins the popover, but second click won't expand the image.
299
+ * @default false
300
+ */
301
+ disableImageExpand?: boolean;
302
+ /**
303
+ * Disable the popover pinning behavior.
304
+ * Clicks won't pin the popover open; it will only show on hover.
305
+ * @default false
306
+ */
307
+ disablePopoverPin?: boolean;
308
+ /**
309
+ * Custom click behavior handler.
310
+ *
311
+ * - Return `CitationBehaviorActions` to override default behavior with specific actions
312
+ * - Return `false` to prevent default behavior entirely
313
+ * - Return `void`/`undefined` to let default behavior proceed
314
+ *
315
+ * This is called BEFORE the default behavior, allowing you to:
316
+ * 1. Add side effects (analytics, etc.) and let defaults proceed (return void)
317
+ * 2. Completely override behavior (return actions or false)
318
+ *
319
+ * Note: eventHandlers.onClick is always called regardless of this handler's return value.
320
+ */
321
+ onClick?: CitationClickBehavior;
322
+ /**
323
+ * Custom hover behavior handlers.
324
+ * These are called in addition to (not instead of) eventHandlers.onMouseEnter/Leave.
325
+ */
326
+ onHover?: CitationHoverBehavior;
327
+ }
187
328
  /**
188
329
  * Props for the tooltip wrapper component
189
330
  */
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.14",
4
4
  "description": "DeepCitation JavaScript SDK for deterministic AI citation verification",
5
5
  "type": "module",
6
6
  "private": false,