@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.cjs +170 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.js +169 -15
- package/dist/index.js.map +1 -1
- package/dist/umd/index.min.js +3 -3
- package/dist/umd/index.min.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -455,6 +455,123 @@ function clearFingerprint() {
|
|
|
455
455
|
}
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
// src/context-capture.ts
|
|
459
|
+
var DEFAULT_CONTEXT_CAPTURE_CONFIG = {
|
|
460
|
+
enabled: true,
|
|
461
|
+
maxDepth: 5,
|
|
462
|
+
maxHeadingLength: 100,
|
|
463
|
+
dataAttributeWhitelist: [
|
|
464
|
+
"data-section",
|
|
465
|
+
"data-feature",
|
|
466
|
+
"data-component",
|
|
467
|
+
"data-fv-section",
|
|
468
|
+
"data-fv-feature"
|
|
469
|
+
]
|
|
470
|
+
};
|
|
471
|
+
function findNearestWithId(element, maxDepth) {
|
|
472
|
+
let current = element;
|
|
473
|
+
let depth = 0;
|
|
474
|
+
while (current && depth < maxDepth) {
|
|
475
|
+
if (current.id) {
|
|
476
|
+
return current;
|
|
477
|
+
}
|
|
478
|
+
current = current.parentElement;
|
|
479
|
+
depth++;
|
|
480
|
+
}
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
function findNearestHeading(element, section, maxDepth) {
|
|
484
|
+
if (section) {
|
|
485
|
+
const heading = section.querySelector("h1, h2, h3, h4, h5, h6");
|
|
486
|
+
if (heading) return heading;
|
|
487
|
+
}
|
|
488
|
+
let current = element;
|
|
489
|
+
let depth = 0;
|
|
490
|
+
while (current && depth < maxDepth) {
|
|
491
|
+
let sibling = current.previousElementSibling;
|
|
492
|
+
while (sibling) {
|
|
493
|
+
if (/^H[1-6]$/.test(sibling.tagName)) {
|
|
494
|
+
return sibling;
|
|
495
|
+
}
|
|
496
|
+
sibling = sibling.previousElementSibling;
|
|
497
|
+
}
|
|
498
|
+
current = current.parentElement;
|
|
499
|
+
depth++;
|
|
500
|
+
}
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
function captureDataAttributes(element, whitelist, maxDepth) {
|
|
504
|
+
const result = {};
|
|
505
|
+
let current = element;
|
|
506
|
+
let depth = 0;
|
|
507
|
+
while (current && depth < maxDepth) {
|
|
508
|
+
for (const attr of Array.from(current.attributes)) {
|
|
509
|
+
if (whitelist.includes(attr.name) && !result[attr.name]) {
|
|
510
|
+
result[attr.name] = attr.value;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
current = current.parentElement;
|
|
514
|
+
depth++;
|
|
515
|
+
}
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
function generateSelector(element, maxDepth) {
|
|
519
|
+
const parts = [];
|
|
520
|
+
let current = element;
|
|
521
|
+
let depth = 0;
|
|
522
|
+
while (current && depth < maxDepth && current !== document.body) {
|
|
523
|
+
let selector = current.tagName.toLowerCase();
|
|
524
|
+
if (current.id) {
|
|
525
|
+
selector = `#${current.id}`;
|
|
526
|
+
parts.unshift(selector);
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
if (current.className && typeof current.className === "string") {
|
|
530
|
+
const classes = current.className.split(" ").filter((c) => c.trim()).slice(0, 2);
|
|
531
|
+
if (classes.length) {
|
|
532
|
+
selector += "." + classes.join(".");
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
parts.unshift(selector);
|
|
536
|
+
current = current.parentElement;
|
|
537
|
+
depth++;
|
|
538
|
+
}
|
|
539
|
+
return parts.join(" > ");
|
|
540
|
+
}
|
|
541
|
+
function truncate(str, maxLength) {
|
|
542
|
+
if (str.length <= maxLength) return str;
|
|
543
|
+
return str.substring(0, maxLength - 3) + "...";
|
|
544
|
+
}
|
|
545
|
+
function captureContext(triggerElement, config = DEFAULT_CONTEXT_CAPTURE_CONFIG) {
|
|
546
|
+
if (!config.enabled || !triggerElement || typeof document === "undefined") {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
const context = {};
|
|
550
|
+
const sectionWithId = findNearestWithId(triggerElement, config.maxDepth);
|
|
551
|
+
if (sectionWithId) {
|
|
552
|
+
context.sectionId = sectionWithId.id;
|
|
553
|
+
context.sectionTag = sectionWithId.tagName.toLowerCase();
|
|
554
|
+
}
|
|
555
|
+
const heading = findNearestHeading(triggerElement, sectionWithId, config.maxDepth);
|
|
556
|
+
if (heading) {
|
|
557
|
+
context.nearestHeading = truncate(heading.textContent?.trim() || "", config.maxHeadingLength);
|
|
558
|
+
const level = heading.tagName[1];
|
|
559
|
+
if (level) {
|
|
560
|
+
context.headingLevel = parseInt(level, 10);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const dataAttrs = captureDataAttributes(
|
|
564
|
+
triggerElement,
|
|
565
|
+
config.dataAttributeWhitelist,
|
|
566
|
+
config.maxDepth
|
|
567
|
+
);
|
|
568
|
+
if (Object.keys(dataAttrs).length > 0) {
|
|
569
|
+
context.dataAttributes = dataAttrs;
|
|
570
|
+
}
|
|
571
|
+
context.cssSelector = generateSelector(triggerElement, 3);
|
|
572
|
+
return Object.keys(context).length > 0 ? context : null;
|
|
573
|
+
}
|
|
574
|
+
|
|
458
575
|
// src/feedvalue.ts
|
|
459
576
|
var SUCCESS_AUTO_CLOSE_DELAY_MS = 3e3;
|
|
460
577
|
var VALID_SENTIMENTS = ["angry", "disappointed", "satisfied", "excited"];
|
|
@@ -477,6 +594,7 @@ var _FeedValue = class _FeedValue {
|
|
|
477
594
|
__publicField(this, "apiClient");
|
|
478
595
|
__publicField(this, "emitter");
|
|
479
596
|
__publicField(this, "headless");
|
|
597
|
+
__publicField(this, "contextCaptureConfig");
|
|
480
598
|
__publicField(this, "config");
|
|
481
599
|
__publicField(this, "widgetConfig", null);
|
|
482
600
|
// State
|
|
@@ -506,6 +624,10 @@ var _FeedValue = class _FeedValue {
|
|
|
506
624
|
this.widgetId = options.widgetId;
|
|
507
625
|
this.headless = options.headless ?? false;
|
|
508
626
|
this.config = { ...DEFAULT_CONFIG, ...options.config };
|
|
627
|
+
this.contextCaptureConfig = {
|
|
628
|
+
...DEFAULT_CONTEXT_CAPTURE_CONFIG,
|
|
629
|
+
...options.contextCapture
|
|
630
|
+
};
|
|
509
631
|
this.apiClient = new ApiClient(
|
|
510
632
|
options.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
511
633
|
this.config.debug
|
|
@@ -569,12 +691,28 @@ var _FeedValue = class _FeedValue {
|
|
|
569
691
|
thankYouMessage: configResponse.config.thankYouMessage ?? "Thank you for your feedback!",
|
|
570
692
|
showBranding: configResponse.config.showBranding ?? true,
|
|
571
693
|
customFields: configResponse.config.customFields,
|
|
572
|
-
// Reaction config (for reaction widgets) -
|
|
694
|
+
// Reaction config (for reaction widgets) - pass through all fields
|
|
573
695
|
...configResponse.config.template && { template: configResponse.config.template },
|
|
574
696
|
...configResponse.config.options && { options: configResponse.config.options },
|
|
575
697
|
followUpLabel: configResponse.config.followUpLabel ?? "Tell us more (optional)",
|
|
576
|
-
submitText: configResponse.config.submitText ?? "Send"
|
|
698
|
+
submitText: configResponse.config.submitText ?? "Send",
|
|
699
|
+
// Reaction widget display options (support both camelCase and snake_case from API)
|
|
700
|
+
...(configResponse.config.showLabels !== void 0 || configResponse.config.show_labels !== void 0) && {
|
|
701
|
+
showLabels: configResponse.config.showLabels ?? configResponse.config.show_labels
|
|
702
|
+
},
|
|
703
|
+
...(configResponse.config.buttonSize || configResponse.config.button_size) && {
|
|
704
|
+
buttonSize: configResponse.config.buttonSize ?? configResponse.config.button_size
|
|
705
|
+
},
|
|
706
|
+
...(configResponse.config.followUpTrigger || configResponse.config.follow_up_trigger) && {
|
|
707
|
+
followUpTrigger: configResponse.config.followUpTrigger ?? configResponse.config.follow_up_trigger
|
|
708
|
+
}
|
|
577
709
|
};
|
|
710
|
+
this.log("Built baseConfig:", {
|
|
711
|
+
buttonSize: baseConfig.buttonSize,
|
|
712
|
+
showLabels: baseConfig.showLabels,
|
|
713
|
+
followUpTrigger: baseConfig.followUpTrigger,
|
|
714
|
+
template: baseConfig.template
|
|
715
|
+
});
|
|
578
716
|
this.widgetConfig = {
|
|
579
717
|
widgetId: configResponse.widget_id,
|
|
580
718
|
widgetKey: configResponse.widget_key,
|
|
@@ -582,12 +720,16 @@ var _FeedValue = class _FeedValue {
|
|
|
582
720
|
type: configResponse.type ?? "feedback",
|
|
583
721
|
config: baseConfig,
|
|
584
722
|
styling: {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
723
|
+
// Pass through all styling properties from API
|
|
724
|
+
...configResponse.styling,
|
|
725
|
+
// Apply defaults for required fields (support both camelCase and snake_case from API)
|
|
726
|
+
primaryColor: configResponse.styling.primaryColor ?? configResponse.styling.primary_color ?? "#3b82f6",
|
|
727
|
+
backgroundColor: configResponse.styling.backgroundColor ?? configResponse.styling.background_color ?? "#ffffff",
|
|
728
|
+
textColor: configResponse.styling.textColor ?? configResponse.styling.text_color ?? "#1f2937",
|
|
729
|
+
buttonTextColor: configResponse.styling.buttonTextColor ?? configResponse.styling.button_text_color ?? "#ffffff",
|
|
730
|
+
borderColor: configResponse.styling.borderColor ?? configResponse.styling.border_color ?? "#e5e7eb",
|
|
731
|
+
borderWidth: configResponse.styling.borderWidth ?? configResponse.styling.border_width ?? "1",
|
|
732
|
+
borderRadius: configResponse.styling.borderRadius ?? configResponse.styling.border_radius ?? "8px"
|
|
591
733
|
}
|
|
592
734
|
};
|
|
593
735
|
if (!this.headless && typeof window !== "undefined" && typeof document !== "undefined") {
|
|
@@ -821,12 +963,12 @@ var _FeedValue = class _FeedValue {
|
|
|
821
963
|
getTemplateOptions(template) {
|
|
822
964
|
const templates = {
|
|
823
965
|
thumbs: [
|
|
824
|
-
{ label: "Helpful", value: "helpful", icon: "
|
|
825
|
-
{ label: "Not Helpful", value: "not_helpful", icon: "
|
|
966
|
+
{ label: "Helpful", value: "helpful", icon: "\u{1F44D}", showFollowUp: false },
|
|
967
|
+
{ label: "Not Helpful", value: "not_helpful", icon: "\u{1F44E}", showFollowUp: true }
|
|
826
968
|
],
|
|
827
969
|
helpful: [
|
|
828
|
-
{ label: "Yes", value: "yes", icon: "
|
|
829
|
-
{ label: "No", value: "no", icon: "
|
|
970
|
+
{ label: "Yes", value: "yes", icon: "\u2713", showFollowUp: false },
|
|
971
|
+
{ label: "No", value: "no", icon: "\u2717", showFollowUp: true }
|
|
830
972
|
],
|
|
831
973
|
emoji: [
|
|
832
974
|
{ label: "Angry", value: "angry", icon: "\u{1F620}", showFollowUp: true },
|
|
@@ -848,7 +990,7 @@ var _FeedValue = class _FeedValue {
|
|
|
848
990
|
/**
|
|
849
991
|
* Submit a reaction.
|
|
850
992
|
* @param value - Selected reaction option value
|
|
851
|
-
* @param options - Optional follow-up text
|
|
993
|
+
* @param options - Optional follow-up text and trigger element for context capture
|
|
852
994
|
*/
|
|
853
995
|
async react(value, options) {
|
|
854
996
|
if (!this.state.isReady) {
|
|
@@ -866,13 +1008,25 @@ var _FeedValue = class _FeedValue {
|
|
|
866
1008
|
const validValues = reactionOptions.map((opt) => opt.value).join(", ");
|
|
867
1009
|
throw new Error(`Invalid reaction value. Must be one of: ${validValues}`);
|
|
868
1010
|
}
|
|
1011
|
+
let capturedContext = null;
|
|
1012
|
+
if (options?.triggerElement) {
|
|
1013
|
+
capturedContext = captureContext(options.triggerElement, this.contextCaptureConfig);
|
|
1014
|
+
this.log("Captured context", capturedContext);
|
|
1015
|
+
}
|
|
869
1016
|
this.emitter.emit("react", { value, hasFollowUp: selectedOption.showFollowUp });
|
|
870
1017
|
this.updateState({ isSubmitting: true });
|
|
871
1018
|
try {
|
|
872
1019
|
const reactionData = {
|
|
873
1020
|
value,
|
|
874
1021
|
metadata: {
|
|
875
|
-
page_url: typeof window !== "undefined" ? window.location.href : ""
|
|
1022
|
+
page_url: typeof window !== "undefined" ? window.location.href : "",
|
|
1023
|
+
// Spread captured context into metadata
|
|
1024
|
+
...capturedContext?.sectionId && { section_id: capturedContext.sectionId },
|
|
1025
|
+
...capturedContext?.sectionTag && { section_tag: capturedContext.sectionTag },
|
|
1026
|
+
...capturedContext?.nearestHeading && { nearest_heading: capturedContext.nearestHeading },
|
|
1027
|
+
...capturedContext?.headingLevel && { heading_level: capturedContext.headingLevel },
|
|
1028
|
+
...capturedContext?.dataAttributes && { data_attributes: capturedContext.dataAttributes },
|
|
1029
|
+
...capturedContext?.cssSelector && { css_selector: capturedContext.cssSelector }
|
|
876
1030
|
},
|
|
877
1031
|
...options?.followUp && { followUp: options.followUp }
|
|
878
1032
|
};
|
|
@@ -1494,9 +1648,11 @@ var NEGATIVE_OPTIONS_MAP = {
|
|
|
1494
1648
|
|
|
1495
1649
|
exports.ApiClient = ApiClient;
|
|
1496
1650
|
exports.DEFAULT_API_BASE_URL = DEFAULT_API_BASE_URL;
|
|
1651
|
+
exports.DEFAULT_CONTEXT_CAPTURE_CONFIG = DEFAULT_CONTEXT_CAPTURE_CONFIG;
|
|
1497
1652
|
exports.FeedValue = FeedValue;
|
|
1498
1653
|
exports.NEGATIVE_OPTIONS_MAP = NEGATIVE_OPTIONS_MAP;
|
|
1499
1654
|
exports.TypedEventEmitter = TypedEventEmitter;
|
|
1655
|
+
exports.captureContext = captureContext;
|
|
1500
1656
|
exports.clearFingerprint = clearFingerprint;
|
|
1501
1657
|
exports.generateFingerprint = generateFingerprint;
|
|
1502
1658
|
//# sourceMappingURL=index.cjs.map
|