@feedvalue/core 0.1.11 → 0.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.
package/dist/index.d.cts CHANGED
@@ -1,9 +1,63 @@
1
+ /**
2
+ * @feedvalue/core - Context Capture
3
+ *
4
+ * Captures DOM context from a trigger element for enhanced feedback metadata.
5
+ * This allows understanding WHERE on a page the user reacted, not just which page.
6
+ */
7
+ /**
8
+ * Context capture configuration
9
+ */
10
+ interface ContextCaptureConfig {
11
+ /** Enable automatic context capture (default: true) */
12
+ enabled: boolean;
13
+ /** Maximum parent traversal depth (default: 5) */
14
+ maxDepth: number;
15
+ /** Maximum heading text length (default: 100) */
16
+ maxHeadingLength: number;
17
+ /** Data attribute whitelist (default: ['data-section', 'data-feature', 'data-component']) */
18
+ dataAttributeWhitelist: string[];
19
+ }
20
+ declare const DEFAULT_CONTEXT_CAPTURE_CONFIG: ContextCaptureConfig;
21
+ /**
22
+ * Captured context data from DOM traversal
23
+ */
24
+ interface CapturedContext {
25
+ /** ID of nearest parent element with an id attribute */
26
+ sectionId?: string;
27
+ /** Tag name of the section element (e.g., 'section', 'article', 'div') */
28
+ sectionTag?: string;
29
+ /** Text content of nearest heading (h1-h6) */
30
+ nearestHeading?: string;
31
+ /** Level of the nearest heading (1-6) */
32
+ headingLevel?: number;
33
+ /** Captured data-* attributes from element and parents */
34
+ dataAttributes?: Record<string, string>;
35
+ /** CSS selector path to the trigger element (for debugging) */
36
+ cssSelector?: string;
37
+ }
38
+ /**
39
+ * Capture DOM context from a trigger element
40
+ *
41
+ * @param triggerElement - The element that triggered the reaction (e.g., the button)
42
+ * @param config - Configuration for context capture
43
+ * @returns Captured context object or null if disabled/unavailable
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Capture context from a button click
48
+ * const context = captureContext(event.currentTarget, config);
49
+ * // Returns: { sectionId: 'installation', nearestHeading: 'Installation', ... }
50
+ * ```
51
+ */
52
+ declare function captureContext(triggerElement: Element | null, config?: ContextCaptureConfig): CapturedContext | null;
53
+
1
54
  /**
2
55
  * @feedvalue/core - Type Definitions
3
56
  *
4
57
  * Core types for the FeedValue SDK. These types are shared across
5
58
  * all framework packages (React, Vue) and the vanilla API.
6
59
  */
60
+
7
61
  /**
8
62
  * Widget position on screen
9
63
  */
@@ -43,6 +97,8 @@ interface WidgetStyling {
43
97
  backgroundColor: string;
44
98
  textColor: string;
45
99
  buttonTextColor: string;
100
+ borderColor: string;
101
+ borderWidth: string;
46
102
  borderRadius: string;
47
103
  customCSS?: string | undefined;
48
104
  }
@@ -91,6 +147,14 @@ interface FeedValueOptions {
91
147
  * @default false
92
148
  */
93
149
  headless?: boolean | undefined;
150
+ /**
151
+ * Context capture configuration for reaction widgets.
152
+ * When enabled, captures DOM context (section ID, nearest heading, data attributes)
153
+ * from the trigger element when react() is called.
154
+ *
155
+ * @default { enabled: true, maxDepth: 5, maxHeadingLength: 100, dataAttributeWhitelist: ['data-section', 'data-feature', 'data-component', 'data-fv-section', 'data-fv-feature'] }
156
+ */
157
+ contextCapture?: Partial<ContextCaptureConfig> | undefined;
94
158
  }
95
159
  /**
96
160
  * Runtime configuration (can be changed after init)
@@ -425,10 +489,11 @@ interface FeedValueInstance {
425
489
  /**
426
490
  * Submit a reaction.
427
491
  * @param value - Selected reaction option value
428
- * @param options - Optional follow-up text
492
+ * @param options - Optional follow-up text and trigger element for context capture
429
493
  */
430
494
  react(value: string, options?: {
431
495
  followUp?: string;
496
+ triggerElement?: Element | null;
432
497
  }): Promise<void>;
433
498
  /**
434
499
  * Check if widget is a reaction type
@@ -450,6 +515,8 @@ interface FeedValueInstance {
450
515
  setConfig(config: Partial<FeedValueConfig>): void;
451
516
  /** Get current configuration */
452
517
  getConfig(): FeedValueConfig;
518
+ /** Get widget configuration (from API) */
519
+ getWidgetConfig(): WidgetConfig | null;
453
520
  /** Subscribe to state changes */
454
521
  subscribe(callback: () => void): () => void;
455
522
  /** Get current state snapshot */
@@ -493,6 +560,7 @@ declare class FeedValue implements FeedValueInstance {
493
560
  private readonly apiClient;
494
561
  private readonly emitter;
495
562
  private readonly headless;
563
+ private readonly contextCaptureConfig;
496
564
  private config;
497
565
  private widgetConfig;
498
566
  private state;
@@ -567,10 +635,11 @@ declare class FeedValue implements FeedValueInstance {
567
635
  /**
568
636
  * Submit a reaction.
569
637
  * @param value - Selected reaction option value
570
- * @param options - Optional follow-up text
638
+ * @param options - Optional follow-up text and trigger element for context capture
571
639
  */
572
640
  react(value: string, options?: {
573
641
  followUp?: string;
642
+ triggerElement?: Element | null;
574
643
  }): Promise<void>;
575
644
  /**
576
645
  * Check if widget is a reaction type
@@ -848,4 +917,4 @@ declare function generateFingerprint(): string;
848
917
  */
849
918
  declare function clearFingerprint(): void;
850
919
 
851
- export { ApiClient, type ButtonSize, type ConfigResponse, type CustomField, type CustomFieldType, DEFAULT_API_BASE_URL, type EmojiSentiment, type EventHandler, FeedValue, type FeedValueConfig, type FeedValueEvents, type FeedValueInstance, type FeedValueOptions, type FeedValueState, type FeedbackData, type FeedbackMetadata, type FeedbackResponse, type FollowUpTrigger, NEGATIVE_OPTIONS_MAP, type ReactionBorderRadius, type ReactionBorderWidth, type ReactionConfig, type ReactionData, type ReactionMetadata, type ReactionOption, type ReactionResponse, type ReactionState, type ReactionStyling, type ReactionTemplate, type SubmissionUserData, type TriggerIconType, TypedEventEmitter, type UserData, type UserTraits, type WidgetConfig, type WidgetPosition, type WidgetStyling, type WidgetTheme, type WidgetType, type WidgetUIConfig, clearFingerprint, generateFingerprint };
920
+ export { ApiClient, type ButtonSize, type CapturedContext, type ConfigResponse, type ContextCaptureConfig, type CustomField, type CustomFieldType, DEFAULT_API_BASE_URL, DEFAULT_CONTEXT_CAPTURE_CONFIG, type EmojiSentiment, type EventHandler, FeedValue, type FeedValueConfig, type FeedValueEvents, type FeedValueInstance, type FeedValueOptions, type FeedValueState, type FeedbackData, type FeedbackMetadata, type FeedbackResponse, type FollowUpTrigger, NEGATIVE_OPTIONS_MAP, type ReactionBorderRadius, type ReactionBorderWidth, type ReactionConfig, type ReactionData, type ReactionMetadata, type ReactionOption, type ReactionResponse, type ReactionState, type ReactionStyling, type ReactionTemplate, type SubmissionUserData, type TriggerIconType, TypedEventEmitter, type UserData, type UserTraits, type WidgetConfig, type WidgetPosition, type WidgetStyling, type WidgetTheme, type WidgetType, type WidgetUIConfig, captureContext, clearFingerprint, generateFingerprint };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,63 @@
1
+ /**
2
+ * @feedvalue/core - Context Capture
3
+ *
4
+ * Captures DOM context from a trigger element for enhanced feedback metadata.
5
+ * This allows understanding WHERE on a page the user reacted, not just which page.
6
+ */
7
+ /**
8
+ * Context capture configuration
9
+ */
10
+ interface ContextCaptureConfig {
11
+ /** Enable automatic context capture (default: true) */
12
+ enabled: boolean;
13
+ /** Maximum parent traversal depth (default: 5) */
14
+ maxDepth: number;
15
+ /** Maximum heading text length (default: 100) */
16
+ maxHeadingLength: number;
17
+ /** Data attribute whitelist (default: ['data-section', 'data-feature', 'data-component']) */
18
+ dataAttributeWhitelist: string[];
19
+ }
20
+ declare const DEFAULT_CONTEXT_CAPTURE_CONFIG: ContextCaptureConfig;
21
+ /**
22
+ * Captured context data from DOM traversal
23
+ */
24
+ interface CapturedContext {
25
+ /** ID of nearest parent element with an id attribute */
26
+ sectionId?: string;
27
+ /** Tag name of the section element (e.g., 'section', 'article', 'div') */
28
+ sectionTag?: string;
29
+ /** Text content of nearest heading (h1-h6) */
30
+ nearestHeading?: string;
31
+ /** Level of the nearest heading (1-6) */
32
+ headingLevel?: number;
33
+ /** Captured data-* attributes from element and parents */
34
+ dataAttributes?: Record<string, string>;
35
+ /** CSS selector path to the trigger element (for debugging) */
36
+ cssSelector?: string;
37
+ }
38
+ /**
39
+ * Capture DOM context from a trigger element
40
+ *
41
+ * @param triggerElement - The element that triggered the reaction (e.g., the button)
42
+ * @param config - Configuration for context capture
43
+ * @returns Captured context object or null if disabled/unavailable
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // Capture context from a button click
48
+ * const context = captureContext(event.currentTarget, config);
49
+ * // Returns: { sectionId: 'installation', nearestHeading: 'Installation', ... }
50
+ * ```
51
+ */
52
+ declare function captureContext(triggerElement: Element | null, config?: ContextCaptureConfig): CapturedContext | null;
53
+
1
54
  /**
2
55
  * @feedvalue/core - Type Definitions
3
56
  *
4
57
  * Core types for the FeedValue SDK. These types are shared across
5
58
  * all framework packages (React, Vue) and the vanilla API.
6
59
  */
60
+
7
61
  /**
8
62
  * Widget position on screen
9
63
  */
@@ -43,6 +97,8 @@ interface WidgetStyling {
43
97
  backgroundColor: string;
44
98
  textColor: string;
45
99
  buttonTextColor: string;
100
+ borderColor: string;
101
+ borderWidth: string;
46
102
  borderRadius: string;
47
103
  customCSS?: string | undefined;
48
104
  }
@@ -91,6 +147,14 @@ interface FeedValueOptions {
91
147
  * @default false
92
148
  */
93
149
  headless?: boolean | undefined;
150
+ /**
151
+ * Context capture configuration for reaction widgets.
152
+ * When enabled, captures DOM context (section ID, nearest heading, data attributes)
153
+ * from the trigger element when react() is called.
154
+ *
155
+ * @default { enabled: true, maxDepth: 5, maxHeadingLength: 100, dataAttributeWhitelist: ['data-section', 'data-feature', 'data-component', 'data-fv-section', 'data-fv-feature'] }
156
+ */
157
+ contextCapture?: Partial<ContextCaptureConfig> | undefined;
94
158
  }
95
159
  /**
96
160
  * Runtime configuration (can be changed after init)
@@ -425,10 +489,11 @@ interface FeedValueInstance {
425
489
  /**
426
490
  * Submit a reaction.
427
491
  * @param value - Selected reaction option value
428
- * @param options - Optional follow-up text
492
+ * @param options - Optional follow-up text and trigger element for context capture
429
493
  */
430
494
  react(value: string, options?: {
431
495
  followUp?: string;
496
+ triggerElement?: Element | null;
432
497
  }): Promise<void>;
433
498
  /**
434
499
  * Check if widget is a reaction type
@@ -450,6 +515,8 @@ interface FeedValueInstance {
450
515
  setConfig(config: Partial<FeedValueConfig>): void;
451
516
  /** Get current configuration */
452
517
  getConfig(): FeedValueConfig;
518
+ /** Get widget configuration (from API) */
519
+ getWidgetConfig(): WidgetConfig | null;
453
520
  /** Subscribe to state changes */
454
521
  subscribe(callback: () => void): () => void;
455
522
  /** Get current state snapshot */
@@ -493,6 +560,7 @@ declare class FeedValue implements FeedValueInstance {
493
560
  private readonly apiClient;
494
561
  private readonly emitter;
495
562
  private readonly headless;
563
+ private readonly contextCaptureConfig;
496
564
  private config;
497
565
  private widgetConfig;
498
566
  private state;
@@ -567,10 +635,11 @@ declare class FeedValue implements FeedValueInstance {
567
635
  /**
568
636
  * Submit a reaction.
569
637
  * @param value - Selected reaction option value
570
- * @param options - Optional follow-up text
638
+ * @param options - Optional follow-up text and trigger element for context capture
571
639
  */
572
640
  react(value: string, options?: {
573
641
  followUp?: string;
642
+ triggerElement?: Element | null;
574
643
  }): Promise<void>;
575
644
  /**
576
645
  * Check if widget is a reaction type
@@ -848,4 +917,4 @@ declare function generateFingerprint(): string;
848
917
  */
849
918
  declare function clearFingerprint(): void;
850
919
 
851
- export { ApiClient, type ButtonSize, type ConfigResponse, type CustomField, type CustomFieldType, DEFAULT_API_BASE_URL, type EmojiSentiment, type EventHandler, FeedValue, type FeedValueConfig, type FeedValueEvents, type FeedValueInstance, type FeedValueOptions, type FeedValueState, type FeedbackData, type FeedbackMetadata, type FeedbackResponse, type FollowUpTrigger, NEGATIVE_OPTIONS_MAP, type ReactionBorderRadius, type ReactionBorderWidth, type ReactionConfig, type ReactionData, type ReactionMetadata, type ReactionOption, type ReactionResponse, type ReactionState, type ReactionStyling, type ReactionTemplate, type SubmissionUserData, type TriggerIconType, TypedEventEmitter, type UserData, type UserTraits, type WidgetConfig, type WidgetPosition, type WidgetStyling, type WidgetTheme, type WidgetType, type WidgetUIConfig, clearFingerprint, generateFingerprint };
920
+ export { ApiClient, type ButtonSize, type CapturedContext, type ConfigResponse, type ContextCaptureConfig, type CustomField, type CustomFieldType, DEFAULT_API_BASE_URL, DEFAULT_CONTEXT_CAPTURE_CONFIG, type EmojiSentiment, type EventHandler, FeedValue, type FeedValueConfig, type FeedValueEvents, type FeedValueInstance, type FeedValueOptions, type FeedValueState, type FeedbackData, type FeedbackMetadata, type FeedbackResponse, type FollowUpTrigger, NEGATIVE_OPTIONS_MAP, type ReactionBorderRadius, type ReactionBorderWidth, type ReactionConfig, type ReactionData, type ReactionMetadata, type ReactionOption, type ReactionResponse, type ReactionState, type ReactionStyling, type ReactionTemplate, type SubmissionUserData, type TriggerIconType, TypedEventEmitter, type UserData, type UserTraits, type WidgetConfig, type WidgetPosition, type WidgetStyling, type WidgetTheme, type WidgetType, type WidgetUIConfig, captureContext, clearFingerprint, generateFingerprint };
package/dist/index.js CHANGED
@@ -453,6 +453,123 @@ function clearFingerprint() {
453
453
  }
454
454
  }
455
455
 
456
+ // src/context-capture.ts
457
+ var DEFAULT_CONTEXT_CAPTURE_CONFIG = {
458
+ enabled: true,
459
+ maxDepth: 5,
460
+ maxHeadingLength: 100,
461
+ dataAttributeWhitelist: [
462
+ "data-section",
463
+ "data-feature",
464
+ "data-component",
465
+ "data-fv-section",
466
+ "data-fv-feature"
467
+ ]
468
+ };
469
+ function findNearestWithId(element, maxDepth) {
470
+ let current = element;
471
+ let depth = 0;
472
+ while (current && depth < maxDepth) {
473
+ if (current.id) {
474
+ return current;
475
+ }
476
+ current = current.parentElement;
477
+ depth++;
478
+ }
479
+ return null;
480
+ }
481
+ function findNearestHeading(element, section, maxDepth) {
482
+ if (section) {
483
+ const heading = section.querySelector("h1, h2, h3, h4, h5, h6");
484
+ if (heading) return heading;
485
+ }
486
+ let current = element;
487
+ let depth = 0;
488
+ while (current && depth < maxDepth) {
489
+ let sibling = current.previousElementSibling;
490
+ while (sibling) {
491
+ if (/^H[1-6]$/.test(sibling.tagName)) {
492
+ return sibling;
493
+ }
494
+ sibling = sibling.previousElementSibling;
495
+ }
496
+ current = current.parentElement;
497
+ depth++;
498
+ }
499
+ return null;
500
+ }
501
+ function captureDataAttributes(element, whitelist, maxDepth) {
502
+ const result = {};
503
+ let current = element;
504
+ let depth = 0;
505
+ while (current && depth < maxDepth) {
506
+ for (const attr of Array.from(current.attributes)) {
507
+ if (whitelist.includes(attr.name) && !result[attr.name]) {
508
+ result[attr.name] = attr.value;
509
+ }
510
+ }
511
+ current = current.parentElement;
512
+ depth++;
513
+ }
514
+ return result;
515
+ }
516
+ function generateSelector(element, maxDepth) {
517
+ const parts = [];
518
+ let current = element;
519
+ let depth = 0;
520
+ while (current && depth < maxDepth && current !== document.body) {
521
+ let selector = current.tagName.toLowerCase();
522
+ if (current.id) {
523
+ selector = `#${current.id}`;
524
+ parts.unshift(selector);
525
+ break;
526
+ }
527
+ if (current.className && typeof current.className === "string") {
528
+ const classes = current.className.split(" ").filter((c) => c.trim()).slice(0, 2);
529
+ if (classes.length) {
530
+ selector += "." + classes.join(".");
531
+ }
532
+ }
533
+ parts.unshift(selector);
534
+ current = current.parentElement;
535
+ depth++;
536
+ }
537
+ return parts.join(" > ");
538
+ }
539
+ function truncate(str, maxLength) {
540
+ if (str.length <= maxLength) return str;
541
+ return str.substring(0, maxLength - 3) + "...";
542
+ }
543
+ function captureContext(triggerElement, config = DEFAULT_CONTEXT_CAPTURE_CONFIG) {
544
+ if (!config.enabled || !triggerElement || typeof document === "undefined") {
545
+ return null;
546
+ }
547
+ const context = {};
548
+ const sectionWithId = findNearestWithId(triggerElement, config.maxDepth);
549
+ if (sectionWithId) {
550
+ context.sectionId = sectionWithId.id;
551
+ context.sectionTag = sectionWithId.tagName.toLowerCase();
552
+ }
553
+ const heading = findNearestHeading(triggerElement, sectionWithId, config.maxDepth);
554
+ if (heading) {
555
+ context.nearestHeading = truncate(heading.textContent?.trim() || "", config.maxHeadingLength);
556
+ const level = heading.tagName[1];
557
+ if (level) {
558
+ context.headingLevel = parseInt(level, 10);
559
+ }
560
+ }
561
+ const dataAttrs = captureDataAttributes(
562
+ triggerElement,
563
+ config.dataAttributeWhitelist,
564
+ config.maxDepth
565
+ );
566
+ if (Object.keys(dataAttrs).length > 0) {
567
+ context.dataAttributes = dataAttrs;
568
+ }
569
+ context.cssSelector = generateSelector(triggerElement, 3);
570
+ return Object.keys(context).length > 0 ? context : null;
571
+ }
572
+
456
573
  // src/feedvalue.ts
457
574
  var SUCCESS_AUTO_CLOSE_DELAY_MS = 3e3;
458
575
  var VALID_SENTIMENTS = ["angry", "disappointed", "satisfied", "excited"];
@@ -475,6 +592,7 @@ var _FeedValue = class _FeedValue {
475
592
  __publicField(this, "apiClient");
476
593
  __publicField(this, "emitter");
477
594
  __publicField(this, "headless");
595
+ __publicField(this, "contextCaptureConfig");
478
596
  __publicField(this, "config");
479
597
  __publicField(this, "widgetConfig", null);
480
598
  // State
@@ -504,6 +622,10 @@ var _FeedValue = class _FeedValue {
504
622
  this.widgetId = options.widgetId;
505
623
  this.headless = options.headless ?? false;
506
624
  this.config = { ...DEFAULT_CONFIG, ...options.config };
625
+ this.contextCaptureConfig = {
626
+ ...DEFAULT_CONTEXT_CAPTURE_CONFIG,
627
+ ...options.contextCapture
628
+ };
507
629
  this.apiClient = new ApiClient(
508
630
  options.apiBaseUrl ?? DEFAULT_API_BASE_URL,
509
631
  this.config.debug
@@ -567,12 +689,28 @@ var _FeedValue = class _FeedValue {
567
689
  thankYouMessage: configResponse.config.thankYouMessage ?? "Thank you for your feedback!",
568
690
  showBranding: configResponse.config.showBranding ?? true,
569
691
  customFields: configResponse.config.customFields,
570
- // Reaction config (for reaction widgets) - only include if defined
692
+ // Reaction config (for reaction widgets) - pass through all fields
571
693
  ...configResponse.config.template && { template: configResponse.config.template },
572
694
  ...configResponse.config.options && { options: configResponse.config.options },
573
695
  followUpLabel: configResponse.config.followUpLabel ?? "Tell us more (optional)",
574
- submitText: configResponse.config.submitText ?? "Send"
696
+ submitText: configResponse.config.submitText ?? "Send",
697
+ // Reaction widget display options (support both camelCase and snake_case from API)
698
+ ...(configResponse.config.showLabels !== void 0 || configResponse.config.show_labels !== void 0) && {
699
+ showLabels: configResponse.config.showLabels ?? configResponse.config.show_labels
700
+ },
701
+ ...(configResponse.config.buttonSize || configResponse.config.button_size) && {
702
+ buttonSize: configResponse.config.buttonSize ?? configResponse.config.button_size
703
+ },
704
+ ...(configResponse.config.followUpTrigger || configResponse.config.follow_up_trigger) && {
705
+ followUpTrigger: configResponse.config.followUpTrigger ?? configResponse.config.follow_up_trigger
706
+ }
575
707
  };
708
+ this.log("Built baseConfig:", {
709
+ buttonSize: baseConfig.buttonSize,
710
+ showLabels: baseConfig.showLabels,
711
+ followUpTrigger: baseConfig.followUpTrigger,
712
+ template: baseConfig.template
713
+ });
576
714
  this.widgetConfig = {
577
715
  widgetId: configResponse.widget_id,
578
716
  widgetKey: configResponse.widget_key,
@@ -580,12 +718,16 @@ var _FeedValue = class _FeedValue {
580
718
  type: configResponse.type ?? "feedback",
581
719
  config: baseConfig,
582
720
  styling: {
583
- primaryColor: configResponse.styling.primaryColor ?? "#3b82f6",
584
- backgroundColor: configResponse.styling.backgroundColor ?? "#ffffff",
585
- textColor: configResponse.styling.textColor ?? "#1f2937",
586
- buttonTextColor: configResponse.styling.buttonTextColor ?? "#ffffff",
587
- borderRadius: configResponse.styling.borderRadius ?? "8px",
588
- customCSS: configResponse.styling.customCSS
721
+ // Pass through all styling properties from API
722
+ ...configResponse.styling,
723
+ // Apply defaults for required fields (support both camelCase and snake_case from API)
724
+ primaryColor: configResponse.styling.primaryColor ?? configResponse.styling.primary_color ?? "#3b82f6",
725
+ backgroundColor: configResponse.styling.backgroundColor ?? configResponse.styling.background_color ?? "#ffffff",
726
+ textColor: configResponse.styling.textColor ?? configResponse.styling.text_color ?? "#1f2937",
727
+ buttonTextColor: configResponse.styling.buttonTextColor ?? configResponse.styling.button_text_color ?? "#ffffff",
728
+ borderColor: configResponse.styling.borderColor ?? configResponse.styling.border_color ?? "#e5e7eb",
729
+ borderWidth: configResponse.styling.borderWidth ?? configResponse.styling.border_width ?? "1",
730
+ borderRadius: configResponse.styling.borderRadius ?? configResponse.styling.border_radius ?? "8px"
589
731
  }
590
732
  };
591
733
  if (!this.headless && typeof window !== "undefined" && typeof document !== "undefined") {
@@ -819,12 +961,12 @@ var _FeedValue = class _FeedValue {
819
961
  getTemplateOptions(template) {
820
962
  const templates = {
821
963
  thumbs: [
822
- { label: "Helpful", value: "helpful", icon: "thumbs-up", showFollowUp: false },
823
- { label: "Not Helpful", value: "not_helpful", icon: "thumbs-down", showFollowUp: true }
964
+ { label: "Helpful", value: "helpful", icon: "\u{1F44D}", showFollowUp: false },
965
+ { label: "Not Helpful", value: "not_helpful", icon: "\u{1F44E}", showFollowUp: true }
824
966
  ],
825
967
  helpful: [
826
- { label: "Yes", value: "yes", icon: "check", showFollowUp: false },
827
- { label: "No", value: "no", icon: "x", showFollowUp: true }
968
+ { label: "Yes", value: "yes", icon: "\u2713", showFollowUp: false },
969
+ { label: "No", value: "no", icon: "\u2717", showFollowUp: true }
828
970
  ],
829
971
  emoji: [
830
972
  { label: "Angry", value: "angry", icon: "\u{1F620}", showFollowUp: true },
@@ -846,7 +988,7 @@ var _FeedValue = class _FeedValue {
846
988
  /**
847
989
  * Submit a reaction.
848
990
  * @param value - Selected reaction option value
849
- * @param options - Optional follow-up text
991
+ * @param options - Optional follow-up text and trigger element for context capture
850
992
  */
851
993
  async react(value, options) {
852
994
  if (!this.state.isReady) {
@@ -864,13 +1006,25 @@ var _FeedValue = class _FeedValue {
864
1006
  const validValues = reactionOptions.map((opt) => opt.value).join(", ");
865
1007
  throw new Error(`Invalid reaction value. Must be one of: ${validValues}`);
866
1008
  }
1009
+ let capturedContext = null;
1010
+ if (options?.triggerElement) {
1011
+ capturedContext = captureContext(options.triggerElement, this.contextCaptureConfig);
1012
+ this.log("Captured context", capturedContext);
1013
+ }
867
1014
  this.emitter.emit("react", { value, hasFollowUp: selectedOption.showFollowUp });
868
1015
  this.updateState({ isSubmitting: true });
869
1016
  try {
870
1017
  const reactionData = {
871
1018
  value,
872
1019
  metadata: {
873
- page_url: typeof window !== "undefined" ? window.location.href : ""
1020
+ page_url: typeof window !== "undefined" ? window.location.href : "",
1021
+ // Spread captured context into metadata
1022
+ ...capturedContext?.sectionId && { section_id: capturedContext.sectionId },
1023
+ ...capturedContext?.sectionTag && { section_tag: capturedContext.sectionTag },
1024
+ ...capturedContext?.nearestHeading && { nearest_heading: capturedContext.nearestHeading },
1025
+ ...capturedContext?.headingLevel && { heading_level: capturedContext.headingLevel },
1026
+ ...capturedContext?.dataAttributes && { data_attributes: capturedContext.dataAttributes },
1027
+ ...capturedContext?.cssSelector && { css_selector: capturedContext.cssSelector }
874
1028
  },
875
1029
  ...options?.followUp && { followUp: options.followUp }
876
1030
  };
@@ -1490,6 +1644,6 @@ var NEGATIVE_OPTIONS_MAP = {
1490
1644
  rating: ["1", "2"]
1491
1645
  };
1492
1646
 
1493
- export { ApiClient, DEFAULT_API_BASE_URL, FeedValue, NEGATIVE_OPTIONS_MAP, TypedEventEmitter, clearFingerprint, generateFingerprint };
1647
+ export { ApiClient, DEFAULT_API_BASE_URL, DEFAULT_CONTEXT_CAPTURE_CONFIG, FeedValue, NEGATIVE_OPTIONS_MAP, TypedEventEmitter, captureContext, clearFingerprint, generateFingerprint };
1494
1648
  //# sourceMappingURL=index.js.map
1495
1649
  //# sourceMappingURL=index.js.map