featuredrop 1.2.0 → 1.3.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.
Files changed (84) hide show
  1. package/README.md +171 -0
  2. package/dist/admin.cjs +212 -0
  3. package/dist/admin.cjs.map +1 -0
  4. package/dist/admin.d.cts +176 -0
  5. package/dist/admin.d.ts +176 -0
  6. package/dist/admin.js +207 -0
  7. package/dist/admin.js.map +1 -0
  8. package/dist/angular.cjs +13 -3
  9. package/dist/angular.cjs.map +1 -1
  10. package/dist/angular.d.cts +4 -0
  11. package/dist/angular.d.ts +4 -0
  12. package/dist/angular.js +13 -3
  13. package/dist/angular.js.map +1 -1
  14. package/dist/bridges.cjs +401 -0
  15. package/dist/bridges.cjs.map +1 -0
  16. package/dist/bridges.d.cts +194 -0
  17. package/dist/bridges.d.ts +194 -0
  18. package/dist/bridges.js +394 -0
  19. package/dist/bridges.js.map +1 -0
  20. package/dist/ci.cjs +328 -0
  21. package/dist/ci.cjs.map +1 -0
  22. package/dist/ci.d.cts +176 -0
  23. package/dist/ci.d.ts +176 -0
  24. package/dist/ci.js +324 -0
  25. package/dist/ci.js.map +1 -0
  26. package/dist/featuredrop.cjs +139 -18
  27. package/dist/featuredrop.cjs.map +1 -1
  28. package/dist/flags.cjs +51 -0
  29. package/dist/flags.cjs.map +1 -0
  30. package/dist/flags.d.cts +48 -0
  31. package/dist/flags.d.ts +48 -0
  32. package/dist/flags.js +47 -0
  33. package/dist/flags.js.map +1 -0
  34. package/dist/index.cjs +2583 -665
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +743 -206
  37. package/dist/index.d.ts +743 -206
  38. package/dist/index.js +2552 -666
  39. package/dist/index.js.map +1 -1
  40. package/dist/preact.cjs +710 -209
  41. package/dist/preact.cjs.map +1 -1
  42. package/dist/preact.d.cts +67 -120
  43. package/dist/preact.d.ts +67 -120
  44. package/dist/preact.js +696 -195
  45. package/dist/preact.js.map +1 -1
  46. package/dist/react.cjs +710 -209
  47. package/dist/react.cjs.map +1 -1
  48. package/dist/react.d.cts +67 -120
  49. package/dist/react.d.ts +67 -120
  50. package/dist/react.js +696 -195
  51. package/dist/react.js.map +1 -1
  52. package/dist/schema.cjs +78 -1
  53. package/dist/schema.cjs.map +1 -1
  54. package/dist/schema.d.cts +142 -0
  55. package/dist/schema.d.ts +142 -0
  56. package/dist/schema.js +78 -1
  57. package/dist/schema.js.map +1 -1
  58. package/dist/solid.cjs +13 -3
  59. package/dist/solid.cjs.map +1 -1
  60. package/dist/solid.d.cts +4 -0
  61. package/dist/solid.d.ts +4 -0
  62. package/dist/solid.js +13 -3
  63. package/dist/solid.js.map +1 -1
  64. package/dist/svelte.cjs +13 -3
  65. package/dist/svelte.cjs.map +1 -1
  66. package/dist/svelte.js +13 -3
  67. package/dist/svelte.js.map +1 -1
  68. package/dist/testing.cjs +136 -15
  69. package/dist/testing.cjs.map +1 -1
  70. package/dist/testing.d.cts +22 -0
  71. package/dist/testing.d.ts +22 -0
  72. package/dist/testing.js +136 -15
  73. package/dist/testing.js.map +1 -1
  74. package/dist/vue.cjs +13 -3
  75. package/dist/vue.cjs.map +1 -1
  76. package/dist/vue.js +13 -3
  77. package/dist/vue.js.map +1 -1
  78. package/dist/web-components.cjs +14 -4
  79. package/dist/web-components.cjs.map +1 -1
  80. package/dist/web-components.d.cts +4 -0
  81. package/dist/web-components.d.ts +4 -0
  82. package/dist/web-components.js +14 -4
  83. package/dist/web-components.js.map +1 -1
  84. package/package.json +59 -1
package/dist/react.js CHANGED
@@ -1,10 +1,138 @@
1
1
  "use client";
2
- import { createContext, useRef, useState, useMemo, useCallback, useEffect, useContext } from 'react';
2
+ import { createContext, useRef, useState, useMemo, useCallback, useEffect, useContext, Component } from 'react';
3
3
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
4
4
  import { createRequire } from 'module';
5
5
 
6
6
  // src/react/provider.tsx
7
7
 
8
+ // src/animation.ts
9
+ var injectedAnimationStyles = false;
10
+ function ensureFeatureDropAnimationStyles() {
11
+ if (injectedAnimationStyles || typeof document === "undefined") return;
12
+ injectedAnimationStyles = true;
13
+ const style = document.createElement("style");
14
+ style.setAttribute("data-featuredrop-animations", "true");
15
+ style.textContent = `
16
+ @keyframes featuredrop-enter-fade-up {
17
+ from { opacity: 0; transform: translateY(8px); }
18
+ to { opacity: 1; transform: translateY(0); }
19
+ }
20
+ @keyframes featuredrop-enter-scale {
21
+ from { opacity: 0; transform: scale(0.96); }
22
+ to { opacity: 1; transform: scale(1); }
23
+ }
24
+ @keyframes featuredrop-enter-panel {
25
+ from { opacity: 0; transform: translateX(14px); }
26
+ to { opacity: 1; transform: translateX(0); }
27
+ }
28
+ @keyframes featuredrop-enter-pop {
29
+ 0% { opacity: 0; transform: translateY(12px) scale(0.94); }
30
+ 70% { opacity: 1; transform: translateY(-2px) scale(1.02); }
31
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
32
+ }
33
+ @keyframes featuredrop-pulse {
34
+ 0%, 100% { opacity: 1; }
35
+ 50% { opacity: 0.55; }
36
+ }
37
+ @keyframes featuredrop-pulse-playful {
38
+ 0%, 100% { opacity: 1; transform: scale(1); }
39
+ 40% { opacity: 0.7; transform: scale(1.08); }
40
+ 70% { opacity: 0.9; transform: scale(0.96); }
41
+ }
42
+ @keyframes featuredrop-beacon-pulse {
43
+ 0%, 100% { transform: scale(1); opacity: 0.65; }
44
+ 50% { transform: scale(1.5); opacity: 0; }
45
+ }
46
+ @keyframes featuredrop-beacon-pop-pulse {
47
+ 0%, 100% { transform: scale(1); opacity: 0.72; }
48
+ 45% { transform: scale(1.65); opacity: 0.08; }
49
+ 75% { transform: scale(0.95); opacity: 0.28; }
50
+ }
51
+ @keyframes featuredrop-exit-fade-down {
52
+ from { opacity: 1; transform: translateY(0); }
53
+ to { opacity: 0; transform: translateY(8px); }
54
+ }
55
+ @keyframes featuredrop-exit-scale {
56
+ from { opacity: 1; transform: scale(1); }
57
+ to { opacity: 0; transform: scale(0.96); }
58
+ }
59
+ @keyframes featuredrop-exit-panel {
60
+ from { opacity: 1; transform: translateX(0); }
61
+ to { opacity: 0; transform: translateX(14px); }
62
+ }
63
+ @keyframes featuredrop-exit-pop {
64
+ from { opacity: 1; transform: translateY(0) scale(1); }
65
+ to { opacity: 0; transform: translateY(10px) scale(0.96); }
66
+ }
67
+ `;
68
+ document.head.appendChild(style);
69
+ }
70
+ function prefersReducedMotion() {
71
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
72
+ return false;
73
+ }
74
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
75
+ }
76
+ function resolveAnimationPreset(preset = "normal", options) {
77
+ if (options?.reducedMotion) return "none";
78
+ return preset;
79
+ }
80
+ function getEnterAnimation(preset, surface) {
81
+ if (preset === "none") return void 0;
82
+ if (preset === "subtle") {
83
+ if (surface === "panel") return "featuredrop-enter-panel 180ms ease-out";
84
+ if (surface === "modal") return "featuredrop-enter-scale 180ms ease-out";
85
+ return "featuredrop-enter-fade-up 170ms ease-out";
86
+ }
87
+ if (preset === "playful") {
88
+ if (surface === "panel") return "featuredrop-enter-panel 320ms cubic-bezier(0.2, 0.9, 0.2, 1)";
89
+ return "featuredrop-enter-pop 300ms cubic-bezier(0.22, 1.4, 0.36, 1)";
90
+ }
91
+ if (surface === "panel") return "featuredrop-enter-panel 240ms cubic-bezier(0.2, 0.9, 0.2, 1)";
92
+ if (surface === "modal") return "featuredrop-enter-scale 220ms cubic-bezier(0.2, 0.9, 0.2, 1)";
93
+ return "featuredrop-enter-fade-up 210ms cubic-bezier(0.2, 0.9, 0.2, 1)";
94
+ }
95
+ function getExitAnimation(preset, surface) {
96
+ if (preset === "none") return void 0;
97
+ if (preset === "subtle") {
98
+ if (surface === "panel") return "featuredrop-exit-panel 150ms ease-in forwards";
99
+ if (surface === "modal") return "featuredrop-exit-scale 150ms ease-in forwards";
100
+ return "featuredrop-exit-fade-down 140ms ease-in forwards";
101
+ }
102
+ if (preset === "playful") {
103
+ if (surface === "panel") return "featuredrop-exit-panel 260ms ease-in forwards";
104
+ return "featuredrop-exit-pop 240ms ease-in forwards";
105
+ }
106
+ if (surface === "panel") return "featuredrop-exit-panel 200ms ease-in forwards";
107
+ if (surface === "modal") return "featuredrop-exit-scale 190ms ease-in forwards";
108
+ return "featuredrop-exit-fade-down 180ms ease-in forwards";
109
+ }
110
+ function getPulseAnimation(preset, surface = "beacon") {
111
+ if (preset === "none") return void 0;
112
+ if (surface === "dot") {
113
+ if (preset === "subtle") return "featuredrop-pulse 2.6s ease-in-out infinite";
114
+ if (preset === "playful") {
115
+ return "featuredrop-pulse-playful 1.8s cubic-bezier(0.22, 1.4, 0.36, 1) infinite";
116
+ }
117
+ return "featuredrop-pulse 2s ease-in-out infinite";
118
+ }
119
+ if (preset === "subtle") return "featuredrop-beacon-pulse 2.6s ease-in-out infinite";
120
+ if (preset === "playful") {
121
+ return "featuredrop-beacon-pop-pulse 1.8s cubic-bezier(0.22, 1.4, 0.36, 1) infinite";
122
+ }
123
+ return "featuredrop-beacon-pulse 2s ease-in-out infinite";
124
+ }
125
+ function getAnimationDurationMs(preset, surface, phase) {
126
+ if (preset === "none") return 0;
127
+ const animation = getExitAnimation(preset, surface);
128
+ if (!animation) return 0;
129
+ const msMatch = animation.match(/(\d+)ms/);
130
+ if (msMatch?.[1]) return Number(msMatch[1]);
131
+ const sMatch = animation.match(/(\d+(?:\.\d+)?)s/);
132
+ if (sMatch?.[1]) return Math.round(Number(sMatch[1]) * 1e3);
133
+ return 0;
134
+ }
135
+
8
136
  // src/semver.ts
9
137
  var SEMVER_REGEX = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/;
10
138
  function parseSemver(input) {
@@ -226,6 +354,20 @@ function isVersionMatch(feature, appVersion) {
226
354
  if (v.showNewUntil && compareSemver(appVersion, v.showNewUntil) >= 0) return false;
227
355
  return true;
228
356
  }
357
+ function isFlagMatch(feature, flagBridge, userContext) {
358
+ if (!feature.flagKey) return true;
359
+ if (!flagBridge) return false;
360
+ try {
361
+ return flagBridge.isEnabled(feature.flagKey, userContext);
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
366
+ function isProductMatch(feature, product) {
367
+ if (!feature.product || feature.product === "*") return true;
368
+ if (!product) return false;
369
+ return feature.product === product;
370
+ }
229
371
  function isDependencyMatch(feature, dismissedIds, dependencyState) {
230
372
  const dependsOn = feature.dependsOn;
231
373
  if (!dependsOn) return true;
@@ -250,11 +392,13 @@ function isDependencyMatch(feature, dismissedIds, dependencyState) {
250
392
  }
251
393
  return true;
252
394
  }
253
- function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
395
+ function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
254
396
  if (dismissedIds.has(feature.id)) return false;
255
397
  if (!isAudienceMatch(feature, userContext, matchAudience)) return false;
256
398
  if (!isDependencyMatch(feature, dismissedIds, dependencyState)) return false;
257
399
  if (!isVersionMatch(feature, appVersion)) return false;
400
+ if (!isFlagMatch(feature, flagBridge, userContext)) return false;
401
+ if (!isProductMatch(feature, product)) return false;
258
402
  if (!isTriggerMatch(feature.trigger, triggerContext)) return false;
259
403
  const nowMs = now.getTime();
260
404
  if (feature.publishAt) {
@@ -270,7 +414,7 @@ function isNew(feature, watermark, dismissedIds, now = /* @__PURE__ */ new Date(
270
414
  }
271
415
  return true;
272
416
  }
273
- function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext) {
417
+ function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), userContext, matchAudience, appVersion, dependencyState, triggerContext, flagBridge, product) {
274
418
  const watermark = storage.getWatermark();
275
419
  const dismissedIds = storage.getDismissedIds();
276
420
  return manifest.filter(
@@ -283,7 +427,9 @@ function getNewFeatures(manifest, storage, now = /* @__PURE__ */ new Date(), use
283
427
  matchAudience,
284
428
  appVersion,
285
429
  dependencyState,
286
- triggerContext
430
+ triggerContext,
431
+ flagBridge,
432
+ product
287
433
  )
288
434
  );
289
435
  }
@@ -343,6 +489,7 @@ var EN_TRANSLATIONS = {
343
489
  loadMore: "Load more",
344
490
  share: "Share",
345
491
  skipToEntries: "Skip to changelog entries",
492
+ newFeatureCount: (count) => count === 0 ? "No new features" : `${count} new feature${count === 1 ? "" : "s"}`,
346
493
  stepOf: (current, total) => `Step ${current} of ${total}`,
347
494
  back: "Back",
348
495
  next: "Next",
@@ -593,16 +740,133 @@ var SIMPLE_TRANSLATIONS = {
593
740
  askLater: "\u092C\u093E\u0926 \u092E\u0947\u0902 \u092A\u0942\u091B\u0947\u0902"
594
741
  }
595
742
  };
743
+ var RTL_LANGUAGES = /* @__PURE__ */ new Set(["ar", "fa", "he", "ur"]);
744
+ var STEP_OF_TRANSLATIONS = {
745
+ en: EN_TRANSLATIONS.stepOf,
746
+ es: (current, total) => `Paso ${current} de ${total}`,
747
+ fr: (current, total) => `Etape ${current} sur ${total}`,
748
+ de: (current, total) => `Schritt ${current} von ${total}`,
749
+ pt: (current, total) => `Etapa ${current} de ${total}`,
750
+ "zh-cn": (current, total) => `\u7B2C ${current} / ${total} \u6B65`,
751
+ ja: (current, total) => `${total}\u4E2D${current}\u756A\u76EE`,
752
+ ko: (current, total) => `${total}\uB2E8\uACC4 \uC911 ${current}\uB2E8\uACC4`,
753
+ ar: (current, total) => `\u0627\u0644\u062E\u0637\u0648\u0629 ${current} \u0645\u0646 ${total}`,
754
+ hi: (current, total) => `${total} \u092E\u0947\u0902 \u0938\u0947 \u091A\u0930\u0923 ${current}`
755
+ };
756
+ var NEW_FEATURE_COUNT_TRANSLATIONS = {
757
+ en: EN_TRANSLATIONS.newFeatureCount,
758
+ es: (count) => count === 0 ? "Sin novedades" : `${count} novedad${count === 1 ? "" : "es"}`,
759
+ fr: (count) => count === 0 ? "Aucune nouveaute" : `${count} nouveaute${count === 1 ? "" : "s"}`,
760
+ de: (count) => count === 0 ? "Keine neuen Features" : `${count} ${count === 1 ? "neues Feature" : "neue Features"}`,
761
+ pt: (count) => count === 0 ? "Sem novidades" : `${count} novidade${count === 1 ? "" : "s"}`,
762
+ "zh-cn": (count) => count === 0 ? "\u6682\u65E0\u66F4\u65B0" : `${count} \u6761\u65B0\u66F4\u65B0`,
763
+ ja: (count) => count === 0 ? "\u65B0\u7740\u306F\u3042\u308A\u307E\u305B\u3093" : `\u65B0\u7740 ${count} \u4EF6`,
764
+ ko: (count) => count === 0 ? "\uC0C8 \uC18C\uC2DD \uC5C6\uC74C" : `\uC0C8 \uC18C\uC2DD ${count}\uAC1C`,
765
+ ar: (count) => {
766
+ if (count === 0) return "\u0644\u0627 \u062A\u0648\u062C\u062F \u0645\u064A\u0632\u0627\u062A \u062C\u062F\u064A\u062F\u0629";
767
+ const category = new Intl.PluralRules("ar").select(count);
768
+ if (category === "one") return "\u0645\u064A\u0632\u0629 \u062C\u062F\u064A\u062F\u0629 \u0648\u0627\u062D\u062F\u0629";
769
+ if (category === "two") return "\u0645\u064A\u0632\u062A\u0627\u0646 \u062C\u062F\u064A\u062F\u062A\u0627\u0646";
770
+ return `${count} \u0645\u064A\u0632\u0627\u062A \u062C\u062F\u064A\u062F\u0629`;
771
+ },
772
+ hi: (count) => count === 0 ? "\u0915\u094B\u0908 \u0928\u092F\u093E \u0905\u092A\u0921\u0947\u091F \u0928\u0939\u0940\u0902" : `${count} ${count === 1 ? "\u0928\u092F\u093E \u0905\u092A\u0921\u0947\u091F" : "\u0928\u090F \u0905\u092A\u0921\u0947\u091F"}`
773
+ };
774
+ function resolveLocale(locale) {
775
+ const normalized = (locale ?? "en").toLowerCase();
776
+ if (normalized === "en" || normalized.startsWith("en-")) return "en";
777
+ if (Object.prototype.hasOwnProperty.call(SIMPLE_TRANSLATIONS, normalized)) {
778
+ return normalized;
779
+ }
780
+ const base = normalized.split("-")[0];
781
+ if (base === "en") return "en";
782
+ if (Object.prototype.hasOwnProperty.call(SIMPLE_TRANSLATIONS, base)) {
783
+ return base;
784
+ }
785
+ return "en";
786
+ }
787
+ function getLocaleDirection(locale) {
788
+ const resolved = resolveLocale(locale);
789
+ const base = resolved.split("-")[0];
790
+ return RTL_LANGUAGES.has(base) ? "rtl" : "ltr";
791
+ }
792
+ function formatDateForLocale(value, locale, options = {
793
+ month: "short",
794
+ day: "numeric",
795
+ year: "numeric"
796
+ }) {
797
+ const date = value instanceof Date ? value : new Date(value);
798
+ if (Number.isNaN(date.getTime())) return "";
799
+ const resolved = resolveLocale(locale);
800
+ try {
801
+ return new Intl.DateTimeFormat(resolved, options).format(date);
802
+ } catch {
803
+ return date.toLocaleDateString(void 0, options);
804
+ }
805
+ }
806
+ function formatRelativeTimeForLocale(value, locale, options) {
807
+ const target = value instanceof Date ? value : new Date(value);
808
+ if (Number.isNaN(target.getTime())) return "";
809
+ const nowInput = options?.now;
810
+ const nowDate = nowInput instanceof Date ? nowInput : /* @__PURE__ */ new Date();
811
+ if (Number.isNaN(nowDate.getTime())) return "";
812
+ const diffMs = target.getTime() - nowDate.getTime();
813
+ const absDiff = Math.abs(diffMs);
814
+ const minute = 6e4;
815
+ const hour = 60 * minute;
816
+ const day = 24 * hour;
817
+ const week = 7 * day;
818
+ const month = 30 * day;
819
+ const year = 365 * day;
820
+ let unit = "second";
821
+ let divisor = 1e3;
822
+ if (absDiff >= year) {
823
+ unit = "year";
824
+ divisor = year;
825
+ } else if (absDiff >= month) {
826
+ unit = "month";
827
+ divisor = month;
828
+ } else if (absDiff >= week) {
829
+ unit = "week";
830
+ divisor = week;
831
+ } else if (absDiff >= day) {
832
+ unit = "day";
833
+ divisor = day;
834
+ } else if (absDiff >= hour) {
835
+ unit = "hour";
836
+ divisor = hour;
837
+ } else if (absDiff >= minute) {
838
+ unit = "minute";
839
+ divisor = minute;
840
+ }
841
+ const relativeValue = Math.round(diffMs / divisor);
842
+ const resolvedLocale = resolveLocale(locale);
843
+ try {
844
+ const formatter = new Intl.RelativeTimeFormat(resolvedLocale, {
845
+ numeric: options?.numeric ?? "auto",
846
+ style: options?.style ?? "long"
847
+ });
848
+ return formatter.format(relativeValue, unit);
849
+ } catch {
850
+ const fallback = formatDateForLocale(target, resolvedLocale);
851
+ return fallback || target.toISOString();
852
+ }
853
+ }
596
854
  function resolveTranslations(locale, overrides) {
597
- const normalizedLocale = (locale ?? "en").toLowerCase();
598
- const base = SIMPLE_TRANSLATIONS[normalizedLocale] ?? SIMPLE_TRANSLATIONS[normalizedLocale.split("-")[0]] ?? {};
855
+ const resolvedLocale = resolveLocale(locale);
856
+ const base = resolvedLocale === "en" ? {} : SIMPLE_TRANSLATIONS[resolvedLocale] ?? {};
857
+ const stepOf = overrides?.stepOf ?? STEP_OF_TRANSLATIONS[resolvedLocale] ?? STEP_OF_TRANSLATIONS.en;
858
+ const newFeatureCount = overrides?.newFeatureCount ?? NEW_FEATURE_COUNT_TRANSLATIONS[resolvedLocale] ?? NEW_FEATURE_COUNT_TRANSLATIONS.en;
599
859
  return {
600
860
  ...EN_TRANSLATIONS,
601
861
  ...base,
602
862
  ...overrides ?? {},
603
- stepOf: overrides?.stepOf ?? EN_TRANSLATIONS.stepOf
863
+ stepOf,
864
+ newFeatureCount
604
865
  };
605
866
  }
867
+ ({
868
+ ...SIMPLE_TRANSLATIONS
869
+ });
606
870
 
607
871
  // src/variants.ts
608
872
  var VARIANT_META_KEY = "featuredropVariant";
@@ -756,12 +1020,14 @@ function computeFeatureState({
756
1020
  userContext,
757
1021
  matchAudience,
758
1022
  appVersion,
1023
+ product,
759
1024
  throttle,
760
1025
  sessionStartedAt,
761
1026
  quietMode,
762
1027
  seenFeatureIds,
763
1028
  clickedFeatureIds,
764
- triggerContext
1029
+ triggerContext,
1030
+ flagBridge
765
1031
  }) {
766
1032
  const dismissedIds = storage.getDismissedIds();
767
1033
  const allFeatures = getNewFeatures(
@@ -776,7 +1042,9 @@ function computeFeatureState({
776
1042
  clickedIds: clickedFeatureIds,
777
1043
  dismissedIds
778
1044
  },
779
- triggerContext
1045
+ triggerContext,
1046
+ flagBridge,
1047
+ product
780
1048
  );
781
1049
  const throttled = applyAnnouncementThrottle(
782
1050
  allFeatures,
@@ -797,13 +1065,17 @@ function FeatureDropProvider({
797
1065
  manifest,
798
1066
  storage,
799
1067
  analytics,
1068
+ onError,
800
1069
  userContext,
801
1070
  matchAudience: matchAudienceFn,
802
1071
  appVersion,
1072
+ product,
803
1073
  throttle,
804
1074
  variantKey,
805
1075
  collector,
1076
+ flagBridge,
806
1077
  locale = "en",
1078
+ animation = "normal",
807
1079
  translations: translationOverrides,
808
1080
  children
809
1081
  }) {
@@ -832,9 +1104,15 @@ function FeatureDropProvider({
832
1104
  () => readIdSet(CLICKED_FEATURES_STORAGE_KEY)
833
1105
  );
834
1106
  const resolvedVariantKey = useMemo(() => getOrCreateVariantKey(variantKey), [variantKey]);
1107
+ const resolvedLocale = useMemo(() => resolveLocale(locale), [locale]);
1108
+ const direction = useMemo(() => getLocaleDirection(resolvedLocale), [resolvedLocale]);
1109
+ const resolvedAnimation = useMemo(
1110
+ () => resolveAnimationPreset(animation, { reducedMotion: prefersReducedMotion() }),
1111
+ [animation]
1112
+ );
835
1113
  const translations = useMemo(
836
- () => resolveTranslations(locale, translationOverrides),
837
- [locale, translationOverrides]
1114
+ () => resolveTranslations(resolvedLocale, translationOverrides),
1115
+ [resolvedLocale, translationOverrides]
838
1116
  );
839
1117
  const resolvedManifest = useMemo(
840
1118
  () => applyFeatureVariants(manifest, resolvedVariantKey),
@@ -848,6 +1126,7 @@ function FeatureDropProvider({
848
1126
  userContext,
849
1127
  matchAudience: matchAudienceFn,
850
1128
  appVersion,
1129
+ product,
851
1130
  throttle,
852
1131
  sessionStartedAt: sessionStartedAtRef.current,
853
1132
  quietMode: readQuietMode(),
@@ -858,7 +1137,8 @@ function FeatureDropProvider({
858
1137
  if (!engine) return void 0;
859
1138
  engine.setElapsedMs(Date.now() - sessionStartedAtRef.current);
860
1139
  return engine.getContext();
861
- })()
1140
+ })(),
1141
+ flagBridge
862
1142
  })
863
1143
  );
864
1144
  const recompute = useCallback(() => {
@@ -876,12 +1156,14 @@ function FeatureDropProvider({
876
1156
  userContext,
877
1157
  matchAudience: matchAudienceFn,
878
1158
  appVersion,
1159
+ product,
879
1160
  throttle,
880
1161
  sessionStartedAt: sessionStartedAtRef.current,
881
1162
  quietMode,
882
1163
  seenFeatureIds,
883
1164
  clickedFeatureIds,
884
- triggerContext
1165
+ triggerContext,
1166
+ flagBridge
885
1167
  })
886
1168
  );
887
1169
  }, [
@@ -890,11 +1172,13 @@ function FeatureDropProvider({
890
1172
  userContext,
891
1173
  matchAudienceFn,
892
1174
  appVersion,
1175
+ product,
893
1176
  throttle,
894
1177
  quietMode,
895
1178
  seenFeatureIds,
896
1179
  clickedFeatureIds,
897
- triggerVersion
1180
+ triggerVersion,
1181
+ flagBridge
898
1182
  ]);
899
1183
  useEffect(() => {
900
1184
  recompute();
@@ -1101,6 +1385,15 @@ function FeatureDropProvider({
1101
1385
  variant: event.variant ?? (feature ? getFeatureVariantName(feature) : void 0)
1102
1386
  });
1103
1387
  }, [collector, resolvedManifest]);
1388
+ const reportError = useCallback(
1389
+ (error, context) => {
1390
+ onError?.(error, context);
1391
+ if (typeof process !== "undefined" && process.env.NODE_ENV !== "production") {
1392
+ console.warn("[featuredrop] component error", context?.component, error);
1393
+ }
1394
+ },
1395
+ [onError]
1396
+ );
1104
1397
  const trackUsageEvent = useCallback((event, delta = 1) => {
1105
1398
  if (!event) return;
1106
1399
  triggerEngineRef.current?.trackUsage(event, delta);
@@ -1164,7 +1457,10 @@ function FeatureDropProvider({
1164
1457
  releaseSpotlightSlot,
1165
1458
  activeSpotlightCount: activeSpotlightIds.size,
1166
1459
  trackAdoptionEvent,
1167
- locale,
1460
+ reportError,
1461
+ locale: resolvedLocale,
1462
+ direction,
1463
+ animation: resolvedAnimation,
1168
1464
  translations,
1169
1465
  trackUsageEvent,
1170
1466
  trackTriggerEvent,
@@ -1195,7 +1491,10 @@ function FeatureDropProvider({
1195
1491
  releaseSpotlightSlot,
1196
1492
  activeSpotlightIds.size,
1197
1493
  trackAdoptionEvent,
1198
- locale,
1494
+ reportError,
1495
+ resolvedLocale,
1496
+ direction,
1497
+ resolvedAnimation,
1199
1498
  translations,
1200
1499
  trackUsageEvent,
1201
1500
  trackTriggerEvent,
@@ -1914,6 +2213,34 @@ function useSurvey(id) {
1914
2213
  [askLater, hide, show, snapshot]
1915
2214
  );
1916
2215
  }
2216
+ var FeatureDropComponentBoundary = class extends Component {
2217
+ state = { hasError: false };
2218
+ componentDidCatch(error, info) {
2219
+ this.setState({ hasError: true });
2220
+ this.props.onError?.(error, info);
2221
+ }
2222
+ render() {
2223
+ if (this.state.hasError) return null;
2224
+ return this.props.children;
2225
+ }
2226
+ };
2227
+ function withFeatureDropBoundary(ComponentImpl, componentName) {
2228
+ function Wrapped(props) {
2229
+ const context = useContext(FeatureDropContext);
2230
+ const onError = useCallback(
2231
+ (error, info) => {
2232
+ context?.reportError(error, {
2233
+ component: componentName,
2234
+ componentStack: info.componentStack ?? void 0
2235
+ });
2236
+ },
2237
+ [context]
2238
+ );
2239
+ return /* @__PURE__ */ jsx(FeatureDropComponentBoundary, { onError, children: /* @__PURE__ */ jsx(ComponentImpl, { ...props }) });
2240
+ }
2241
+ Wrapped.displayName = `FeatureDropBoundary(${componentName})`;
2242
+ return Wrapped;
2243
+ }
1917
2244
  var baseStyles = {
1918
2245
  display: "inline-flex",
1919
2246
  alignItems: "center",
@@ -1964,15 +2291,25 @@ function NewBadge({
1964
2291
  style,
1965
2292
  children
1966
2293
  }) {
2294
+ const context = useContext(FeatureDropContext);
2295
+ const pulsePreset = useMemo(
2296
+ () => resolveAnimationPreset(context?.animation ?? "normal", {
2297
+ reducedMotion: prefersReducedMotion()
2298
+ }),
2299
+ [context?.animation]
2300
+ );
2301
+ const dotPulseAnimation = useMemo(() => getPulseAnimation(pulsePreset, "dot"), [pulsePreset]);
2302
+ useEffect(() => {
2303
+ ensureFeatureDropAnimationStyles();
2304
+ }, []);
1967
2305
  if (children) {
1968
2306
  return /* @__PURE__ */ jsx(Fragment, { children: children({ isNew: show }) });
1969
2307
  }
1970
2308
  if (!show) return null;
1971
2309
  const handleClick = dismissOnClick && onDismiss ? onDismiss : void 0;
1972
- const reduceMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1973
2310
  const variantStyles = variant === "dot" ? {
1974
2311
  ...dotStyles,
1975
- animation: reduceMotion ? "none" : dotStyles.animation
2312
+ animation: dotPulseAnimation ?? "none"
1976
2313
  } : variant === "count" ? countStyles : pillStyles;
1977
2314
  const content = variant === "dot" ? null : variant === "count" ? count ?? 0 : label;
1978
2315
  const ariaLabel = variant === "count" ? `${count ?? 0} new features` : "New feature";
@@ -2525,14 +2862,6 @@ var TYPE_COLORS = {
2525
2862
  fix: { color: "#92400e", bg: "rgba(245, 158, 11, 0.15)" },
2526
2863
  breaking: { color: "#991b1b", bg: "rgba(239, 68, 68, 0.15)" }
2527
2864
  };
2528
- function formatDate(iso) {
2529
- const d = new Date(iso);
2530
- return d.toLocaleDateString(void 0, {
2531
- month: "short",
2532
- day: "numeric",
2533
- year: "numeric"
2534
- });
2535
- }
2536
2865
  function getFocusableElements(container) {
2537
2866
  const nodes = container.querySelectorAll(
2538
2867
  [
@@ -2555,7 +2884,9 @@ function DefaultEntry({
2555
2884
  reactions,
2556
2885
  userReaction,
2557
2886
  canReact,
2558
- react
2887
+ react,
2888
+ locale,
2889
+ dateFormat
2559
2890
  }) {
2560
2891
  const typeStyle = feature.type ? TYPE_COLORS[feature.type] ?? TYPE_COLORS.feature : null;
2561
2892
  const descriptionHtml = feature.description ? parseDescription(feature.description) : null;
@@ -2605,7 +2936,7 @@ function DefaultEntry({
2605
2936
  }
2606
2937
  ),
2607
2938
  feature.category && /* @__PURE__ */ jsx("span", { children: feature.category }),
2608
- /* @__PURE__ */ jsx("span", { children: formatDate(feature.releasedAt) }),
2939
+ /* @__PURE__ */ jsx("span", { children: dateFormat === "relative" ? formatRelativeTimeForLocale(feature.releasedAt, locale) : formatDateForLocale(feature.releasedAt, locale) }),
2609
2940
  versionLabel && /* @__PURE__ */ jsxs("span", { children: [
2610
2941
  "v",
2611
2942
  versionLabel
@@ -2652,6 +2983,7 @@ function ChangelogWidget({
2652
2983
  showMarkAll = true,
2653
2984
  emptyLabel,
2654
2985
  maxHeight = "400px",
2986
+ dateFormat = "absolute",
2655
2987
  analytics,
2656
2988
  className,
2657
2989
  style,
@@ -2671,14 +3003,19 @@ function ChangelogWidget({
2671
3003
  markFeatureSeen,
2672
3004
  markFeatureClicked,
2673
3005
  trackAdoptionEvent,
3006
+ locale,
3007
+ direction,
3008
+ animation,
2674
3009
  translations
2675
3010
  } = useFeatureDrop();
2676
3011
  const [isOpen, setIsOpen] = useState(false);
3012
+ const [isClosing, setIsClosing] = useState(false);
2677
3013
  const [, setReactionVersion] = useState(0);
2678
3014
  const containerRef = useRef(null);
2679
3015
  const dialogRef = useRef(null);
2680
3016
  const triggerRef = useRef(null);
2681
3017
  const lastFocusedElementRef = useRef(null);
3018
+ const closeTimerRef = useRef(null);
2682
3019
  const widgetIdRef = useRef(`featuredrop-widget-${Math.random().toString(36).slice(2, 10)}`);
2683
3020
  const themeVariables = useThemeVariables(theme);
2684
3021
  const resolvedTitle = title ?? translations.whatsNewTitle;
@@ -2687,7 +3024,27 @@ function ChangelogWidget({
2687
3024
  const resolvedEmptyLabel = emptyLabel ?? translations.allCaughtUp;
2688
3025
  const dialogId = `${widgetIdRef.current}-dialog`;
2689
3026
  const titleId = `${widgetIdRef.current}-title`;
3027
+ const countLabel = translations.newFeatureCount(newCount);
3028
+ const dialogEnterAnimation = useMemo(
3029
+ () => getEnterAnimation(animation, variant),
3030
+ [animation, variant]
3031
+ );
3032
+ const dialogExitAnimation = useMemo(
3033
+ () => getExitAnimation(animation, variant),
3034
+ [animation, variant]
3035
+ );
3036
+ useEffect(() => {
3037
+ ensureFeatureDropAnimationStyles();
3038
+ return () => {
3039
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
3040
+ };
3041
+ }, []);
2690
3042
  const open = useCallback(() => {
3043
+ if (closeTimerRef.current) {
3044
+ clearTimeout(closeTimerRef.current);
3045
+ closeTimerRef.current = null;
3046
+ }
3047
+ setIsClosing(false);
2691
3048
  if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) {
2692
3049
  lastFocusedElementRef.current = document.activeElement;
2693
3050
  }
@@ -2695,7 +3052,21 @@ function ChangelogWidget({
2695
3052
  analytics?.onWidgetOpened?.();
2696
3053
  }, [analytics]);
2697
3054
  const close = useCallback(() => {
3055
+ if (closeTimerRef.current) {
3056
+ clearTimeout(closeTimerRef.current);
3057
+ closeTimerRef.current = null;
3058
+ }
2698
3059
  setIsOpen(false);
3060
+ const exitDuration = getAnimationDurationMs(animation, variant);
3061
+ if (exitDuration > 0) {
3062
+ setIsClosing(true);
3063
+ closeTimerRef.current = setTimeout(() => {
3064
+ setIsClosing(false);
3065
+ closeTimerRef.current = null;
3066
+ }, exitDuration);
3067
+ } else {
3068
+ setIsClosing(false);
3069
+ }
2699
3070
  analytics?.onWidgetClosed?.();
2700
3071
  const returnTarget = triggerRef.current ?? lastFocusedElementRef.current;
2701
3072
  if (returnTarget) {
@@ -2707,7 +3078,7 @@ function ChangelogWidget({
2707
3078
  returnTarget.focus();
2708
3079
  }
2709
3080
  }
2710
- }, [analytics]);
3081
+ }, [animation, analytics, variant]);
2711
3082
  const toggle = useCallback(() => {
2712
3083
  if (isOpen) {
2713
3084
  close();
@@ -2844,6 +3215,7 @@ function ChangelogWidget({
2844
3215
  "data-featuredrop-widget": true,
2845
3216
  className,
2846
3217
  style: widgetRootStyle,
3218
+ dir: direction,
2847
3219
  children: [
2848
3220
  renderTrigger ? renderTrigger({ count: newCount, onClick: toggle }) : /* @__PURE__ */ jsxs(
2849
3221
  "button",
@@ -2852,7 +3224,7 @@ function ChangelogWidget({
2852
3224
  onClick: toggle,
2853
3225
  style: triggerButtonStyles,
2854
3226
  "data-featuredrop-trigger": true,
2855
- "aria-label": `${resolvedTriggerLabel}${newCount > 0 ? ` \u2014 ${newCount} new` : ""}`,
3227
+ "aria-label": `${resolvedTriggerLabel}${newCount > 0 ? ` \u2014 ${countLabel}` : ""}`,
2856
3228
  "aria-haspopup": "dialog",
2857
3229
  "aria-expanded": isOpen,
2858
3230
  "aria-controls": dialogId,
@@ -2862,8 +3234,8 @@ function ChangelogWidget({
2862
3234
  ]
2863
3235
  }
2864
3236
  ),
2865
- /* @__PURE__ */ jsx("span", { style: srOnlyStyles, "aria-live": "polite", "aria-atomic": "true", children: newCount > 0 ? `${newCount} new feature${newCount === 1 ? "" : "s"}` : "No new features" }),
2866
- isOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
3237
+ /* @__PURE__ */ jsx("span", { style: srOnlyStyles, "aria-live": "polite", "aria-atomic": "true", children: countLabel }),
3238
+ (isOpen || isClosing) && /* @__PURE__ */ jsxs(Fragment, { children: [
2867
3239
  variant !== "popover" && /* @__PURE__ */ jsx(
2868
3240
  "div",
2869
3241
  {
@@ -2878,7 +3250,10 @@ function ChangelogWidget({
2878
3250
  {
2879
3251
  id: dialogId,
2880
3252
  ref: dialogRef,
2881
- style: dialogContainerStyles,
3253
+ style: {
3254
+ ...dialogContainerStyles,
3255
+ animation: isClosing ? dialogExitAnimation : dialogEnterAnimation
3256
+ },
2882
3257
  "data-featuredrop-container": variant,
2883
3258
  role: "dialog",
2884
3259
  "aria-labelledby": titleId,
@@ -2917,6 +3292,8 @@ function ChangelogWidget({
2917
3292
  feature,
2918
3293
  dismiss: () => handleDismiss(feature.id),
2919
3294
  onFeatureClick: () => handleFeatureClick(feature),
3295
+ locale,
3296
+ dateFormat,
2920
3297
  reactions: showReactions ? getReactionCounts(feature.id, reactions) : void 0,
2921
3298
  userReaction: showReactions ? getUserReaction(feature.id) : null,
2922
3299
  canReact: showReactions ? !getUserReaction(feature.id) : void 0,
@@ -2954,8 +3331,7 @@ var beaconPulseStyles = {
2954
3331
  inset: "-4px",
2955
3332
  borderRadius: "50%",
2956
3333
  border: "2px solid var(--featuredrop-beacon-color, #f59e0b)",
2957
- opacity: 0.6,
2958
- animation: "featuredrop-spotlight-pulse 2s ease-in-out infinite"
3334
+ opacity: 0.6
2959
3335
  };
2960
3336
  var tooltipStyles = {
2961
3337
  position: "absolute",
@@ -2991,29 +3367,6 @@ var tooltipDismissStyles = {
2991
3367
  backgroundColor: "var(--featuredrop-tooltip-dismiss-bg, #f3f4f6)",
2992
3368
  color: "var(--featuredrop-tooltip-dismiss-color, #374151)"
2993
3369
  };
2994
- var injectedKeyframes = false;
2995
- function injectKeyframes() {
2996
- if (injectedKeyframes || typeof document === "undefined") return;
2997
- injectedKeyframes = true;
2998
- const style = document.createElement("style");
2999
- style.textContent = `
3000
- @keyframes featuredrop-spotlight-pulse {
3001
- 0%, 100% { transform: scale(1); opacity: 0.6; }
3002
- 50% { transform: scale(1.6); opacity: 0; }
3003
- }
3004
- @keyframes featuredrop-pulse {
3005
- 0%, 100% { opacity: 1; }
3006
- 50% { opacity: 0.5; }
3007
- }
3008
- `;
3009
- document.head.appendChild(style);
3010
- }
3011
- function prefersReducedMotion() {
3012
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
3013
- return false;
3014
- }
3015
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3016
- }
3017
3370
  function Spotlight({
3018
3371
  featureId,
3019
3372
  targetRef,
@@ -3033,7 +3386,9 @@ function Spotlight({
3033
3386
  markFeatureSeen,
3034
3387
  acquireSpotlightSlot,
3035
3388
  releaseSpotlightSlot,
3036
- activeSpotlightCount
3389
+ activeSpotlightCount,
3390
+ animation,
3391
+ translations
3037
3392
  } = useFeatureDrop();
3038
3393
  const feature = newFeatures.find((f) => f.id === featureId);
3039
3394
  const [isTooltipOpen, setTooltipOpen] = useState(false);
@@ -3042,13 +3397,20 @@ function Spotlight({
3042
3397
  const timerRef = useRef(null);
3043
3398
  const beaconRef = useRef(null);
3044
3399
  const instanceIdRef = useRef(`featuredrop-spotlight-${Math.random().toString(36).slice(2, 10)}`);
3045
- const reduceMotion = useMemo(() => prefersReducedMotion(), []);
3400
+ const beaconPulseAnimation = useMemo(
3401
+ () => getPulseAnimation(animation, "beacon"),
3402
+ [animation]
3403
+ );
3404
+ const tooltipEnterAnimation = useMemo(
3405
+ () => getEnterAnimation(animation, "popover"),
3406
+ [animation]
3407
+ );
3046
3408
  const isActive = !!feature;
3047
3409
  const tooltipId = `${instanceIdRef.current}-tooltip`;
3048
3410
  const tooltipTitleId = `${instanceIdRef.current}-title`;
3049
3411
  const tooltipDescId = `${instanceIdRef.current}-description`;
3050
3412
  useEffect(() => {
3051
- injectKeyframes();
3413
+ ensureFeatureDropAnimationStyles();
3052
3414
  }, []);
3053
3415
  useEffect(() => {
3054
3416
  if (!isActive) {
@@ -3205,7 +3567,7 @@ function Spotlight({
3205
3567
  {
3206
3568
  style: {
3207
3569
  ...beaconPulseStyles,
3208
- animation: reduceMotion ? "none" : beaconPulseStyles.animation
3570
+ animation: beaconPulseAnimation ?? "none"
3209
3571
  }
3210
3572
  }
3211
3573
  )
@@ -3215,7 +3577,12 @@ function Spotlight({
3215
3577
  "div",
3216
3578
  {
3217
3579
  id: tooltipId,
3218
- style: { ...tooltipStyles, position: "fixed", ...tooltipPosition },
3580
+ style: {
3581
+ ...tooltipStyles,
3582
+ position: "fixed",
3583
+ ...tooltipPosition,
3584
+ animation: tooltipEnterAnimation
3585
+ },
3219
3586
  "data-featuredrop-tooltip": true,
3220
3587
  role: "dialog",
3221
3588
  "aria-modal": "false",
@@ -3237,7 +3604,7 @@ function Spotlight({
3237
3604
  type: "button",
3238
3605
  onClick: handleDismiss,
3239
3606
  style: tooltipDismissStyles,
3240
- children: "Got it"
3607
+ children: translations.gotIt
3241
3608
  }
3242
3609
  )
3243
3610
  ] })
@@ -3438,8 +3805,7 @@ var toastStyles = {
3438
3805
  border: "1px solid var(--featuredrop-toast-border, #e5e7eb)",
3439
3806
  width: "var(--featuredrop-toast-width, 340px)",
3440
3807
  maxWidth: "calc(100vw - 32px)",
3441
- fontFamily: "var(--featuredrop-font-family, inherit)",
3442
- animation: "featuredrop-toast-enter 0.3s ease-out"
3808
+ fontFamily: "var(--featuredrop-font-family, inherit)"
3443
3809
  };
3444
3810
  var toastBodyStyles = {
3445
3811
  flex: 1,
@@ -3486,25 +3852,6 @@ var TYPE_INDICATORS = {
3486
3852
  fix: { color: "#f59e0b", icon: "\u{1F527}" },
3487
3853
  breaking: { color: "#ef4444", icon: "\u26A0\uFE0F" }
3488
3854
  };
3489
- var injectedToastKeyframes = false;
3490
- function injectToastKeyframes() {
3491
- if (injectedToastKeyframes || typeof document === "undefined") return;
3492
- injectedToastKeyframes = true;
3493
- const style = document.createElement("style");
3494
- style.textContent = `
3495
- @keyframes featuredrop-toast-enter {
3496
- from { opacity: 0; transform: translateY(8px) scale(0.96); }
3497
- to { opacity: 1; transform: translateY(0) scale(1); }
3498
- }
3499
- `;
3500
- document.head.appendChild(style);
3501
- }
3502
- function prefersReducedMotion2() {
3503
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
3504
- return false;
3505
- }
3506
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3507
- }
3508
3855
  function Toast({
3509
3856
  featureIds,
3510
3857
  maxVisible = 3,
@@ -3523,14 +3870,21 @@ function Toast({
3523
3870
  markFeatureClicked,
3524
3871
  trackAdoptionEvent,
3525
3872
  getRemainingToastSlots,
3526
- markToastsShown
3873
+ markToastsShown,
3874
+ animation
3527
3875
  } = useFeatureDrop();
3528
3876
  const [localDismissed, setLocalDismissed] = useState(/* @__PURE__ */ new Set());
3877
+ const [closingIds, setClosingIds] = useState(/* @__PURE__ */ new Set());
3529
3878
  const timersRef = useRef(/* @__PURE__ */ new Map());
3530
3879
  const shownIdsRef = useRef(/* @__PURE__ */ new Set());
3531
- const reduceMotion = useMemo(() => prefersReducedMotion2(), []);
3880
+ const toastEnterAnimation = useMemo(() => getEnterAnimation(animation, "toast"), [animation]);
3881
+ const toastExitAnimation = useMemo(() => getExitAnimation(animation, "toast"), [animation]);
3882
+ const toastExitDuration = useMemo(
3883
+ () => getAnimationDurationMs(animation, "toast"),
3884
+ [animation]
3885
+ );
3532
3886
  useEffect(() => {
3533
- injectToastKeyframes();
3887
+ ensureFeatureDropAnimationStyles();
3534
3888
  }, []);
3535
3889
  const toastFeatures = newFeaturesSorted.filter((f) => {
3536
3890
  if (localDismissed.has(f.id)) return false;
@@ -3550,8 +3904,14 @@ function Toast({
3550
3904
  return true;
3551
3905
  });
3552
3906
  }, [baseVisible, remainingSlots]);
3553
- const handleDismiss = useCallback(
3907
+ const finalizeDismiss = useCallback(
3554
3908
  (id) => {
3909
+ setClosingIds((previous) => {
3910
+ if (!previous.has(id)) return previous;
3911
+ const next = new Set(previous);
3912
+ next.delete(id);
3913
+ return next;
3914
+ });
3555
3915
  setLocalDismissed((prev) => new Set(prev).add(id));
3556
3916
  dismiss(id);
3557
3917
  const feature = newFeaturesSorted.find((f) => f.id === id);
@@ -3561,9 +3921,40 @@ function Toast({
3561
3921
  clearTimeout(timer);
3562
3922
  timersRef.current.delete(id);
3563
3923
  }
3924
+ const exitTimer = timersRef.current.get(`exit:${id}`);
3925
+ if (exitTimer) {
3926
+ clearTimeout(exitTimer);
3927
+ timersRef.current.delete(`exit:${id}`);
3928
+ }
3564
3929
  },
3565
3930
  [dismiss, newFeaturesSorted, analytics]
3566
3931
  );
3932
+ const handleDismiss = useCallback((id) => {
3933
+ if (closingIds.has(id)) return;
3934
+ if (toastExitDuration > 0 && toastExitAnimation) {
3935
+ const existingAutoTimer = timersRef.current.get(id);
3936
+ if (existingAutoTimer) {
3937
+ clearTimeout(existingAutoTimer);
3938
+ timersRef.current.delete(id);
3939
+ }
3940
+ setClosingIds((previous) => {
3941
+ if (previous.has(id)) return previous;
3942
+ const next = new Set(previous);
3943
+ next.add(id);
3944
+ return next;
3945
+ });
3946
+ const existing = timersRef.current.get(`exit:${id}`);
3947
+ if (existing) clearTimeout(existing);
3948
+ timersRef.current.set(
3949
+ `exit:${id}`,
3950
+ setTimeout(() => {
3951
+ finalizeDismiss(id);
3952
+ }, toastExitDuration)
3953
+ );
3954
+ return;
3955
+ }
3956
+ finalizeDismiss(id);
3957
+ }, [closingIds, finalizeDismiss, toastExitAnimation, toastExitDuration]);
3567
3958
  const handleDismissAllLocal = useCallback(() => {
3568
3959
  for (const f of visibleToasts) {
3569
3960
  handleDismiss(f.id);
@@ -3572,14 +3963,14 @@ function Toast({
3572
3963
  useEffect(() => {
3573
3964
  if (autoDismissMs <= 0) return;
3574
3965
  for (const f of visibleToasts) {
3575
- if (!timersRef.current.has(f.id)) {
3966
+ if (!timersRef.current.has(f.id) && !closingIds.has(f.id)) {
3576
3967
  timersRef.current.set(
3577
3968
  f.id,
3578
3969
  setTimeout(() => handleDismiss(f.id), autoDismissMs)
3579
3970
  );
3580
3971
  }
3581
3972
  }
3582
- }, [visibleToasts, autoDismissMs, handleDismiss]);
3973
+ }, [visibleToasts, autoDismissMs, closingIds, handleDismiss]);
3583
3974
  useEffect(() => {
3584
3975
  const newlyShownIds = [];
3585
3976
  for (const feature of visibleToasts) {
@@ -3635,7 +4026,7 @@ function Toast({
3635
4026
  "data-featuredrop-toast": feature.id,
3636
4027
  style: {
3637
4028
  ...toastStyles,
3638
- animation: reduceMotion ? "none" : toastStyles.animation
4029
+ animation: closingIds.has(feature.id) ? toastExitAnimation : toastEnterAnimation
3639
4030
  },
3640
4031
  role: "status",
3641
4032
  children: [
@@ -3774,17 +4165,12 @@ var reactionButtonStyles2 = {
3774
4165
  cursor: "pointer",
3775
4166
  fontSize: "12px"
3776
4167
  };
3777
- function defaultFormatDate(date) {
3778
- const d = new Date(date);
3779
- return d.toLocaleDateString(void 0, {
3780
- month: "short",
3781
- day: "numeric",
3782
- year: "numeric"
3783
- });
4168
+ function defaultFormatDate(date, locale) {
4169
+ return formatDateForLocale(date, locale);
3784
4170
  }
3785
4171
  function EntryCard({
3786
4172
  entry,
3787
- formatDate: formatDate2,
4173
+ formatDate,
3788
4174
  basePath,
3789
4175
  shareLabel,
3790
4176
  showReactions,
@@ -3840,7 +4226,7 @@ function EntryCard({
3840
4226
  )
3841
4227
  ] }),
3842
4228
  /* @__PURE__ */ jsxs("div", { style: metaRowStyles, children: [
3843
- /* @__PURE__ */ jsx("span", { children: formatDate2(entry.releasedAt) }),
4229
+ /* @__PURE__ */ jsx("span", { children: formatDate(entry.releasedAt) }),
3844
4230
  entry.version && /* @__PURE__ */ jsxs("span", { children: [
3845
4231
  "v",
3846
4232
  typeof entry.version === "string" ? entry.version : entry.version.introduced ?? entry.version.showNewUntil ?? ""
@@ -3907,7 +4293,8 @@ function ChangelogPage({
3907
4293
  categories,
3908
4294
  emptyState,
3909
4295
  renderEntry,
3910
- formatDate: formatDate2 = defaultFormatDate,
4296
+ formatDate,
4297
+ dateFormat = "absolute",
3911
4298
  basePath,
3912
4299
  manifest: manifestProp,
3913
4300
  className,
@@ -3917,7 +4304,7 @@ function ChangelogPage({
3917
4304
  reactions = [...DEFAULT_REACTIONS],
3918
4305
  onReaction
3919
4306
  }) {
3920
- const { manifest: contextManifest, translations } = useFeatureDrop();
4307
+ const { manifest: contextManifest, locale, direction, translations } = useFeatureDrop();
3921
4308
  const themeVariables = useThemeVariables(theme);
3922
4309
  const manifest = useMemo(
3923
4310
  () => manifestProp ?? contextManifest ?? [],
@@ -3981,6 +4368,10 @@ function ChangelogPage({
3981
4368
  if (typeof window !== "undefined") return window.location.pathname;
3982
4369
  return "";
3983
4370
  }, [basePath]);
4371
+ const resolvedFormatDate = useMemo(
4372
+ () => formatDate ?? ((value) => dateFormat === "relative" ? formatRelativeTimeForLocale(value, locale) : defaultFormatDate(value, locale)),
4373
+ [dateFormat, formatDate, locale]
4374
+ );
3984
4375
  useEffect(() => {
3985
4376
  if (typeof window === "undefined") return;
3986
4377
  const hash = window.location.hash;
@@ -4014,7 +4405,7 @@ function ChangelogPage({
4014
4405
  EntryCard,
4015
4406
  {
4016
4407
  entry,
4017
- formatDate: formatDate2,
4408
+ formatDate: resolvedFormatDate,
4018
4409
  basePath: resolvedBasePath,
4019
4410
  showReactions,
4020
4411
  shareLabel: translations.share,
@@ -4059,60 +4450,69 @@ function ChangelogPage({
4059
4450
  () => ({ ...themeVariables ?? {}, ...pageStyles, ...style ?? {} }),
4060
4451
  [themeVariables, style]
4061
4452
  );
4062
- return /* @__PURE__ */ jsxs("div", { "data-featuredrop-changelog-page": true, className, style: containerStyles2, children: [
4063
- /* @__PURE__ */ jsx("a", { href: `#${listId}`, style: skipLinkStyles, children: translations.skipToEntries }),
4064
- /* @__PURE__ */ jsxs("div", { style: headerStyles2, children: [
4065
- /* @__PURE__ */ jsx("h2", { style: { margin: 0 }, children: translations.changelogTitle }),
4066
- showSearch && /* @__PURE__ */ jsx(
4067
- "input",
4068
- {
4069
- style: searchStyles,
4070
- type: "search",
4071
- placeholder: translations.searchPlaceholder,
4072
- value: query,
4073
- onChange: (e) => setQuery(e.target.value),
4074
- "aria-label": "Search changelog updates"
4075
- }
4076
- )
4077
- ] }),
4078
- showFilters && /* @__PURE__ */ jsxs("div", { style: filterBarStyles, children: [
4079
- allCategories.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4080
- /* @__PURE__ */ jsx(
4081
- "button",
4082
- {
4083
- type: "button",
4084
- style: activeCategory ? pillStyles2 : pillActiveStyles,
4085
- onClick: () => setActiveCategory(null),
4086
- children: translations.allCategories
4087
- }
4088
- ),
4089
- allCategories.map((cat) => /* @__PURE__ */ jsx(
4090
- "button",
4091
- {
4092
- type: "button",
4093
- style: activeCategory === cat ? pillActiveStyles : pillStyles2,
4094
- onClick: () => setActiveCategory(cat),
4095
- children: cat
4096
- },
4097
- cat
4098
- ))
4099
- ] }),
4100
- ["feature", "improvement", "fix", "breaking"].map((t) => /* @__PURE__ */ jsx(
4101
- "button",
4102
- {
4103
- type: "button",
4104
- style: activeType === t ? pillActiveStyles : pillStyles2,
4105
- onClick: () => setActiveType((prev) => prev === t ? null : t),
4106
- children: t
4107
- },
4108
- t
4109
- ))
4110
- ] }),
4111
- visible.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "32px 0", textAlign: "center", color: "#6b7280" }, children: emptyState ?? translations.noUpdatesYet }) : /* @__PURE__ */ jsxs("div", { id: listId, onKeyDown: handleListKeyDown, children: [
4112
- renderList(),
4113
- paginationControls()
4114
- ] })
4115
- ] });
4453
+ return /* @__PURE__ */ jsxs(
4454
+ "div",
4455
+ {
4456
+ "data-featuredrop-changelog-page": true,
4457
+ className,
4458
+ style: containerStyles2,
4459
+ dir: direction,
4460
+ children: [
4461
+ /* @__PURE__ */ jsx("a", { href: `#${listId}`, style: skipLinkStyles, children: translations.skipToEntries }),
4462
+ /* @__PURE__ */ jsxs("div", { style: headerStyles2, children: [
4463
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0 }, children: translations.changelogTitle }),
4464
+ showSearch && /* @__PURE__ */ jsx(
4465
+ "input",
4466
+ {
4467
+ style: searchStyles,
4468
+ type: "search",
4469
+ placeholder: translations.searchPlaceholder,
4470
+ value: query,
4471
+ onChange: (e) => setQuery(e.target.value),
4472
+ "aria-label": "Search changelog updates"
4473
+ }
4474
+ )
4475
+ ] }),
4476
+ showFilters && /* @__PURE__ */ jsxs("div", { style: filterBarStyles, children: [
4477
+ allCategories.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
4478
+ /* @__PURE__ */ jsx(
4479
+ "button",
4480
+ {
4481
+ type: "button",
4482
+ style: activeCategory ? pillStyles2 : pillActiveStyles,
4483
+ onClick: () => setActiveCategory(null),
4484
+ children: translations.allCategories
4485
+ }
4486
+ ),
4487
+ allCategories.map((cat) => /* @__PURE__ */ jsx(
4488
+ "button",
4489
+ {
4490
+ type: "button",
4491
+ style: activeCategory === cat ? pillActiveStyles : pillStyles2,
4492
+ onClick: () => setActiveCategory(cat),
4493
+ children: cat
4494
+ },
4495
+ cat
4496
+ ))
4497
+ ] }),
4498
+ ["feature", "improvement", "fix", "breaking"].map((t) => /* @__PURE__ */ jsx(
4499
+ "button",
4500
+ {
4501
+ type: "button",
4502
+ style: activeType === t ? pillActiveStyles : pillStyles2,
4503
+ onClick: () => setActiveType((prev) => prev === t ? null : t),
4504
+ children: t
4505
+ },
4506
+ t
4507
+ ))
4508
+ ] }),
4509
+ visible.length === 0 ? /* @__PURE__ */ jsx("div", { style: { padding: "32px 0", textAlign: "center", color: "#6b7280" }, children: emptyState ?? translations.noUpdatesYet }) : /* @__PURE__ */ jsxs("div", { id: listId, onKeyDown: handleListKeyDown, children: [
4510
+ renderList(),
4511
+ paginationControls()
4512
+ ] })
4513
+ ]
4514
+ }
4515
+ );
4116
4516
  }
4117
4517
  var tourBoxStyles = {
4118
4518
  position: "fixed",
@@ -4235,6 +4635,10 @@ function Tour({
4235
4635
  children
4236
4636
  }) {
4237
4637
  const context = useContext(FeatureDropContext);
4638
+ const dialogAnimation = useMemo(
4639
+ () => getEnterAnimation(context?.animation ?? "normal", "popover"),
4640
+ [context?.animation]
4641
+ );
4238
4642
  const [isActive, setIsActive] = useState(false);
4239
4643
  const [stepIndex, setStepIndex] = useState(-1);
4240
4644
  const [targetRect, setTargetRect] = useState(null);
@@ -4347,6 +4751,9 @@ function Tour({
4347
4751
  void openStep(next);
4348
4752
  context?.markTourShown();
4349
4753
  }, [context, findValidStep, id, openStep, persistence]);
4754
+ useEffect(() => {
4755
+ ensureFeatureDropAnimationStyles();
4756
+ }, []);
4350
4757
  useEffect(() => {
4351
4758
  if (!isActive || !step) return;
4352
4759
  const updateRect = () => {
@@ -4500,9 +4907,11 @@ function Tour({
4500
4907
  "aria-labelledby": titleId,
4501
4908
  "aria-describedby": showProgress ? `${contentId} ${progressId}` : contentId,
4502
4909
  "data-featuredrop-tour": id,
4910
+ dir: context?.direction,
4503
4911
  tabIndex: -1,
4504
4912
  style: {
4505
4913
  ...tourBoxStyles,
4914
+ animation: dialogAnimation,
4506
4915
  top: tooltipPos.top,
4507
4916
  left: tooltipPos.left
4508
4917
  },
@@ -4960,6 +5369,7 @@ function Hotspot({
4960
5369
  frequency = "once",
4961
5370
  children
4962
5371
  }) {
5372
+ const featureDrop = useContext(FeatureDropContext);
4963
5373
  const group = useContext(TooltipGroupContext);
4964
5374
  const [open, setOpen] = useState(false);
4965
5375
  const [dismissed, setDismissed] = useState(false);
@@ -4967,6 +5377,23 @@ function Hotspot({
4967
5377
  const hoverTimer = useRef(null);
4968
5378
  const beaconRef = useRef(null);
4969
5379
  const instanceIdRef = useRef(`featuredrop-hotspot-${Math.random().toString(36).slice(2, 10)}`);
5380
+ const animation = useMemo(
5381
+ () => resolveAnimationPreset(featureDrop?.animation ?? "normal", {
5382
+ reducedMotion: prefersReducedMotion()
5383
+ }),
5384
+ [featureDrop?.animation]
5385
+ );
5386
+ const beaconPulseAnimation = useMemo(
5387
+ () => getPulseAnimation(animation, "dot"),
5388
+ [animation]
5389
+ );
5390
+ const tooltipEnterAnimation = useMemo(
5391
+ () => getEnterAnimation(animation, "popover"),
5392
+ [animation]
5393
+ );
5394
+ useEffect(() => {
5395
+ ensureFeatureDropAnimationStyles();
5396
+ }, []);
4970
5397
  useEffect(() => {
4971
5398
  if (frequency === "once" && isDismissedOnce(id)) {
4972
5399
  setDismissed(true);
@@ -5075,6 +5502,7 @@ function Hotspot({
5075
5502
  boxShadow: "0 0 0 2px rgba(17,24,39,0.1)",
5076
5503
  cursor: "pointer",
5077
5504
  zIndex: "var(--featuredrop-hotspot-beacon-z-index, 10000)",
5505
+ animation: beaconPulseAnimation ?? "none",
5078
5506
  ...beaconTypeStyles[type]
5079
5507
  }
5080
5508
  }
@@ -5086,7 +5514,12 @@ function Hotspot({
5086
5514
  role: "dialog",
5087
5515
  "aria-modal": "false",
5088
5516
  "data-featuredrop-hotspot-tooltip": id,
5089
- style: { ...tooltipStyles2, top: tooltipTop, left: tooltipLeft },
5517
+ style: {
5518
+ ...tooltipStyles2,
5519
+ top: tooltipTop,
5520
+ left: tooltipLeft,
5521
+ animation: tooltipEnterAnimation
5522
+ },
5090
5523
  onMouseLeave: closeTooltip,
5091
5524
  children: [
5092
5525
  /* @__PURE__ */ jsx("div", { style: { fontSize: "13px", color: "#374151", lineHeight: 1.5 }, children }),
@@ -5172,7 +5605,7 @@ function FeedbackWidget({
5172
5605
  style,
5173
5606
  children
5174
5607
  }) {
5175
- const { trackAdoptionEvent, translations } = useFeatureDrop();
5608
+ const { trackAdoptionEvent, direction, animation, translations } = useFeatureDrop();
5176
5609
  const [isOpen, setIsOpen] = useState(false);
5177
5610
  const [text, setText] = useState("");
5178
5611
  const [category, setCategory] = useState(categories[0] ?? "other");
@@ -5186,6 +5619,7 @@ function FeedbackWidget({
5186
5619
  const lastFocusedElementRef = useRef(null);
5187
5620
  const instanceIdRef = useRef(`featuredrop-feedback-${Math.random().toString(36).slice(2, 10)}`);
5188
5621
  const rateLimitKey = useMemo(() => getRateLimitKey(featureId), [featureId]);
5622
+ const panelAnimation = useMemo(() => getEnterAnimation(animation, "popover"), [animation]);
5189
5623
  const resolvedTriggerLabel = triggerLabel ?? translations.feedbackTrigger;
5190
5624
  const resolvedTitle = title ?? translations.feedbackTitle;
5191
5625
  const isRateLimited = useMemo(() => {
@@ -5222,6 +5656,9 @@ function FeedbackWidget({
5222
5656
  window.removeEventListener("keydown", onKeyDown);
5223
5657
  };
5224
5658
  }, [close, isOpen]);
5659
+ useEffect(() => {
5660
+ ensureFeatureDropAnimationStyles();
5661
+ }, []);
5225
5662
  const captureScreenshot = useCallback(async () => {
5226
5663
  if (!showScreenshot) return;
5227
5664
  try {
@@ -5341,10 +5778,11 @@ function FeedbackWidget({
5341
5778
  id: `${instanceIdRef.current}-panel`,
5342
5779
  "data-featuredrop-feedback-widget": true,
5343
5780
  className,
5344
- style: { ...panelStyles, ...style },
5781
+ style: { ...panelStyles, animation: panelAnimation, ...style },
5345
5782
  role: "dialog",
5346
5783
  "aria-modal": "false",
5347
5784
  "aria-labelledby": titleId,
5785
+ dir: direction,
5348
5786
  children: [
5349
5787
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: "8px" }, children: [
5350
5788
  /* @__PURE__ */ jsx("strong", { id: titleId, children: resolvedTitle }),
@@ -5354,7 +5792,7 @@ function FeedbackWidget({
5354
5792
  type: "button",
5355
5793
  onClick: close,
5356
5794
  style: { border: "none", background: "transparent", cursor: "pointer" },
5357
- "aria-label": "Close feedback",
5795
+ "aria-label": translations.close,
5358
5796
  children: "x"
5359
5797
  }
5360
5798
  )
@@ -5586,6 +6024,7 @@ function AnnouncementModal({
5586
6024
  }) {
5587
6025
  const context = useContext(FeatureDropContext);
5588
6026
  const [isOpen, setIsOpen] = useState(defaultOpen);
6027
+ const [isClosing, setIsClosing] = useState(false);
5589
6028
  const [slideIndex, setSlideIndex] = useState(0);
5590
6029
  const [blockedByFrequency, setBlockedByFrequency] = useState(false);
5591
6030
  const [isMobile, setIsMobile] = useState(
@@ -5593,6 +6032,7 @@ function AnnouncementModal({
5593
6032
  );
5594
6033
  const modalRef = useRef(null);
5595
6034
  const lastFocusedElementRef = useRef(null);
6035
+ const closeTimerRef = useRef(null);
5596
6036
  const instanceIdRef = useRef(`featuredrop-announcement-${Math.random().toString(36).slice(2, 10)}`);
5597
6037
  const resolvedFeature = useMemo(() => {
5598
6038
  if (feature) return feature;
@@ -5608,8 +6048,15 @@ function AnnouncementModal({
5608
6048
  );
5609
6049
  const currentSlide = resolvedSlides[slideIndex] ?? null;
5610
6050
  const t = context?.translations;
6051
+ const enterAnimation = getEnterAnimation(context?.animation ?? "normal", "modal");
6052
+ const exitAnimation = getExitAnimation(context?.animation ?? "normal", "modal");
5611
6053
  const open = useCallback(() => {
5612
6054
  if (blockedByFrequency || resolvedSlides.length === 0) return;
6055
+ if (closeTimerRef.current) {
6056
+ clearTimeout(closeTimerRef.current);
6057
+ closeTimerRef.current = null;
6058
+ }
6059
+ setIsClosing(false);
5613
6060
  if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) {
5614
6061
  lastFocusedElementRef.current = document.activeElement;
5615
6062
  }
@@ -5632,7 +6079,22 @@ function AnnouncementModal({
5632
6079
  open();
5633
6080
  }, [defaultOpen, open]);
5634
6081
  const close = useCallback(() => {
6082
+ if (closeTimerRef.current) {
6083
+ clearTimeout(closeTimerRef.current);
6084
+ closeTimerRef.current = null;
6085
+ }
5635
6086
  setIsOpen(false);
6087
+ const animationPreset = context?.animation ?? "normal";
6088
+ const exitDuration = getAnimationDurationMs(animationPreset, "modal");
6089
+ if (exitDuration > 0) {
6090
+ setIsClosing(true);
6091
+ closeTimerRef.current = setTimeout(() => {
6092
+ setIsClosing(false);
6093
+ closeTimerRef.current = null;
6094
+ }, exitDuration);
6095
+ } else {
6096
+ setIsClosing(false);
6097
+ }
5636
6098
  const returnTarget = lastFocusedElementRef.current;
5637
6099
  if (returnTarget) {
5638
6100
  if (typeof requestAnimationFrame === "function") {
@@ -5641,10 +6103,10 @@ function AnnouncementModal({
5641
6103
  returnTarget.focus();
5642
6104
  }
5643
6105
  }
5644
- }, []);
6106
+ }, [context?.animation]);
5645
6107
  const dismiss = useCallback(() => {
5646
- close();
5647
6108
  setBlockedByFrequency(true);
6109
+ close();
5648
6110
  if (frequency === "once") {
5649
6111
  setDismissedOnce2(modalId);
5650
6112
  } else if (frequency === "every-session") {
@@ -5677,12 +6139,17 @@ function AnnouncementModal({
5677
6139
  }, [frequency, modalId]);
5678
6140
  useEffect(() => {
5679
6141
  if (trigger !== "auto") return;
5680
- if (blockedByFrequency || isOpen || resolvedSlides.length === 0) return;
6142
+ if (blockedByFrequency || isOpen || isClosing || resolvedSlides.length === 0) return;
6143
+ if (frequency === "once" && isDismissedOnce2(modalId)) return;
6144
+ if (frequency === "every-session" && sessionDismissed2.has(modalId)) return;
5681
6145
  if (resolvedFeature && resolvedFeature.priority !== "critical") return;
5682
6146
  open();
5683
6147
  }, [
6148
+ frequency,
5684
6149
  blockedByFrequency,
6150
+ isClosing,
5685
6151
  isOpen,
6152
+ modalId,
5686
6153
  open,
5687
6154
  resolvedFeature,
5688
6155
  resolvedSlides.length,
@@ -5699,6 +6166,12 @@ function AnnouncementModal({
5699
6166
  window.removeEventListener("resize", updateViewportMode);
5700
6167
  };
5701
6168
  }, [mobileBreakpoint]);
6169
+ useEffect(() => {
6170
+ ensureFeatureDropAnimationStyles();
6171
+ return () => {
6172
+ if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
6173
+ };
6174
+ }, []);
5702
6175
  useEffect(() => {
5703
6176
  if (!isOpen) return;
5704
6177
  const modalEl = modalRef.current;
@@ -5766,7 +6239,7 @@ function AnnouncementModal({
5766
6239
  if (children) {
5767
6240
  return /* @__PURE__ */ jsx(Fragment, { children: children(renderProps) });
5768
6241
  }
5769
- if (!isOpen || !currentSlide || blockedByFrequency) return null;
6242
+ if (!(isOpen || isClosing) || !currentSlide) return null;
5770
6243
  const embeddedVideo = toEmbedUrl(currentSlide.videoUrl);
5771
6244
  const descriptionHtml = currentSlide.description ? parseDescription(currentSlide.description) : null;
5772
6245
  const totalSlides = resolvedSlides.length;
@@ -5792,9 +6265,11 @@ function AnnouncementModal({
5792
6265
  "aria-describedby": currentSlide.description ? descriptionId : void 0,
5793
6266
  "data-featuredrop-announcement-modal": modalId,
5794
6267
  className,
6268
+ dir: context?.direction,
5795
6269
  tabIndex: -1,
5796
6270
  style: {
5797
6271
  ...isMobile ? mobileModalStyles : desktopModalStyles,
6272
+ animation: isClosing ? exitAnimation : enterAnimation,
5798
6273
  ...style
5799
6274
  },
5800
6275
  children: /* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "16px" : "20px" }, children: [
@@ -5964,8 +6439,7 @@ var beaconStyles2 = {
5964
6439
  border: "2px solid #fff",
5965
6440
  background: "#f59e0b",
5966
6441
  boxShadow: "0 0 0 2px rgba(17,24,39,0.12)",
5967
- zIndex: "var(--featuredrop-spotlight-chain-beacon-z-index, 10010)",
5968
- animation: "featuredrop-chain-pulse 1.8s ease-in-out infinite"
6442
+ zIndex: "var(--featuredrop-spotlight-chain-beacon-z-index, 10010)"
5969
6443
  };
5970
6444
  var tooltipStyles3 = {
5971
6445
  position: "fixed",
@@ -5977,25 +6451,6 @@ var tooltipStyles3 = {
5977
6451
  boxShadow: "0 10px 30px rgba(0,0,0,0.16)",
5978
6452
  width: "min(90vw, 320px)"
5979
6453
  };
5980
- var injectedKeyframes2 = false;
5981
- function injectKeyframes2() {
5982
- if (injectedKeyframes2 || typeof document === "undefined") return;
5983
- injectedKeyframes2 = true;
5984
- const style = document.createElement("style");
5985
- style.textContent = `
5986
- @keyframes featuredrop-chain-pulse {
5987
- 0%, 100% { transform: scale(1); opacity: 1; }
5988
- 50% { transform: scale(1.35); opacity: 0.65; }
5989
- }
5990
- `;
5991
- document.head.appendChild(style);
5992
- }
5993
- function prefersReducedMotion3() {
5994
- if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
5995
- return false;
5996
- }
5997
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
5998
- }
5999
6454
  function getFocusableElements4(container) {
6000
6455
  const nodes = container.querySelectorAll(
6001
6456
  [
@@ -6024,12 +6479,26 @@ function SpotlightChain({
6024
6479
  onSkip,
6025
6480
  children
6026
6481
  }) {
6482
+ const featureDrop = useContext(FeatureDropContext);
6027
6483
  const [isActive, setIsActive] = useState(false);
6028
6484
  const [stepIndex, setStepIndex] = useState(-1);
6029
6485
  const [rect, setRect] = useState(null);
6030
6486
  const autoTimerRef = useRef(null);
6031
6487
  const dialogRef = useRef(null);
6032
- const reduceMotion = useMemo(() => prefersReducedMotion3(), []);
6488
+ const animation = useMemo(
6489
+ () => resolveAnimationPreset(featureDrop?.animation ?? "normal", {
6490
+ reducedMotion: prefersReducedMotion()
6491
+ }),
6492
+ [featureDrop?.animation]
6493
+ );
6494
+ const beaconPulseAnimation = useMemo(
6495
+ () => getPulseAnimation(animation, "beacon"),
6496
+ [animation]
6497
+ );
6498
+ const tooltipEnterAnimation = useMemo(
6499
+ () => getEnterAnimation(animation, "popover"),
6500
+ [animation]
6501
+ );
6033
6502
  const instanceIdRef = useRef(`featuredrop-spotlight-chain-${Math.random().toString(36).slice(2, 10)}`);
6034
6503
  const step = stepIndex >= 0 ? steps[stepIndex] ?? null : null;
6035
6504
  const findValidStep = useCallback((start2) => {
@@ -6079,7 +6548,7 @@ function SpotlightChain({
6079
6548
  setRect(null);
6080
6549
  }, [onSkip, step, stepIndex]);
6081
6550
  useEffect(() => {
6082
- injectKeyframes2();
6551
+ ensureFeatureDropAnimationStyles();
6083
6552
  }, []);
6084
6553
  useEffect(() => {
6085
6554
  if (!startOnMount) return;
@@ -6186,7 +6655,7 @@ function SpotlightChain({
6186
6655
  "data-featuredrop-spotlight-chain-beacon": step.id ?? stepIndex,
6187
6656
  style: {
6188
6657
  ...beaconStyles2,
6189
- animation: reduceMotion ? "none" : beaconStyles2.animation,
6658
+ animation: beaconPulseAnimation ?? "none",
6190
6659
  top: rect.top - 7,
6191
6660
  left: rect.right - 7
6192
6661
  }
@@ -6202,7 +6671,12 @@ function SpotlightChain({
6202
6671
  "aria-describedby": contentId,
6203
6672
  "data-featuredrop-spotlight-chain": step.id ?? stepIndex,
6204
6673
  tabIndex: -1,
6205
- style: { ...tooltipStyles3, top: tooltipTop, left: tooltipLeft },
6674
+ style: {
6675
+ ...tooltipStyles3,
6676
+ top: tooltipTop,
6677
+ left: tooltipLeft,
6678
+ animation: tooltipEnterAnimation
6679
+ },
6206
6680
  children: [
6207
6681
  step.title && /* @__PURE__ */ jsx("p", { id: titleId, style: { margin: "0 0 6px", fontWeight: 700, fontSize: "15px" }, children: step.title }),
6208
6682
  /* @__PURE__ */ jsx("div", { id: contentId, style: { margin: "0 0 10px", fontSize: "14px", color: "#4b5563" }, children: step.content }),
@@ -6219,7 +6693,7 @@ function SpotlightChain({
6219
6693
  padding: "6px 10px",
6220
6694
  cursor: "pointer"
6221
6695
  },
6222
- children: "Skip"
6696
+ children: featureDrop?.translations.skip ?? "Skip"
6223
6697
  }
6224
6698
  ),
6225
6699
  /* @__PURE__ */ jsx(
@@ -6235,7 +6709,7 @@ function SpotlightChain({
6235
6709
  padding: "6px 10px",
6236
6710
  cursor: "pointer"
6237
6711
  },
6238
- children: stepIndex >= steps.length - 1 ? "Got it" : "Next"
6712
+ children: stepIndex >= steps.length - 1 ? featureDrop?.translations.gotIt ?? "Got it" : featureDrop?.translations.next ?? "Next"
6239
6713
  }
6240
6714
  )
6241
6715
  ] })
@@ -6422,6 +6896,11 @@ function Survey({
6422
6896
  const askLaterCooldownDays = triggerRules?.askLaterCooldownDays ?? DEFAULT_ASK_LATER_DAYS;
6423
6897
  const scale = useMemo(() => resolveScale(type), [type]);
6424
6898
  const translations = context?.translations;
6899
+ const direction = context?.direction ?? "ltr";
6900
+ const panelAnimation = useMemo(
6901
+ () => getEnterAnimation(context?.animation ?? "normal", "modal"),
6902
+ [context?.animation]
6903
+ );
6425
6904
  const canShow = useMemo(() => {
6426
6905
  const now = Date.now();
6427
6906
  const cooldownUntil = parseIsoTime(readStorageValue3(cooldownStorageKey(id)));
@@ -6511,6 +6990,9 @@ function Survey({
6511
6990
  if (!defaultOpen) return;
6512
6991
  show({ force: true });
6513
6992
  }, [defaultOpen, show]);
6993
+ useEffect(() => {
6994
+ ensureFeatureDropAnimationStyles();
6995
+ }, []);
6514
6996
  useEffect(() => {
6515
6997
  if (!isOpen) return;
6516
6998
  const panel = panelRef.current;
@@ -6686,12 +7168,13 @@ function Survey({
6686
7168
  ref: panelRef,
6687
7169
  "data-featuredrop-survey": id,
6688
7170
  className,
6689
- style: { ...panelStyles2, ...style },
7171
+ style: { ...panelStyles2, animation: panelAnimation, ...style },
6690
7172
  role: "dialog",
6691
7173
  "aria-modal": "false",
6692
7174
  "aria-labelledby": surveyTitleId,
6693
7175
  "aria-describedby": prompt ? surveyDescriptionId : void 0,
6694
7176
  tabIndex: -1,
7177
+ dir: direction,
6695
7178
  children: [
6696
7179
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: "8px" }, children: [
6697
7180
  /* @__PURE__ */ jsx("strong", { id: surveyTitleId, children: surveyTitle }),
@@ -7255,6 +7738,24 @@ function FeatureRequestForm({
7255
7738
  );
7256
7739
  }
7257
7740
 
7258
- export { AnnouncementModal, Banner, ChangelogPage, ChangelogWidget, Checklist, FeatureDropContext, FeatureDropProvider, FeatureRequestButton, FeatureRequestForm, FeedbackWidget, Hotspot, NewBadge, Spotlight, SpotlightChain, Survey, ThemeProvider, Toast, TooltipGroup, Tour, useAdoptionAnalytics, useChecklist, useFeatureDrop, useNewCount, useNewFeature, useSurvey, useTabNotification, useTour, useTourSequencer };
7741
+ // src/react/index.ts
7742
+ var NewBadge2 = withFeatureDropBoundary(NewBadge, "NewBadge");
7743
+ var ChangelogWidget2 = withFeatureDropBoundary(ChangelogWidget, "ChangelogWidget");
7744
+ var Spotlight2 = withFeatureDropBoundary(Spotlight, "Spotlight");
7745
+ var Banner2 = withFeatureDropBoundary(Banner, "Banner");
7746
+ var Toast2 = withFeatureDropBoundary(Toast, "Toast");
7747
+ var ChangelogPage2 = withFeatureDropBoundary(ChangelogPage, "ChangelogPage");
7748
+ var Tour2 = withFeatureDropBoundary(Tour, "Tour");
7749
+ var Checklist2 = withFeatureDropBoundary(Checklist, "Checklist");
7750
+ var Hotspot2 = withFeatureDropBoundary(Hotspot, "Hotspot");
7751
+ var TooltipGroup2 = withFeatureDropBoundary(TooltipGroup, "TooltipGroup");
7752
+ var FeedbackWidget2 = withFeatureDropBoundary(FeedbackWidget, "FeedbackWidget");
7753
+ var AnnouncementModal2 = withFeatureDropBoundary(AnnouncementModal, "AnnouncementModal");
7754
+ var SpotlightChain2 = withFeatureDropBoundary(SpotlightChain, "SpotlightChain");
7755
+ var Survey2 = withFeatureDropBoundary(Survey, "Survey");
7756
+ var FeatureRequestButton2 = withFeatureDropBoundary(FeatureRequestButton, "FeatureRequestButton");
7757
+ var FeatureRequestForm2 = withFeatureDropBoundary(FeatureRequestForm, "FeatureRequestForm");
7758
+
7759
+ export { AnnouncementModal2 as AnnouncementModal, Banner2 as Banner, ChangelogPage2 as ChangelogPage, ChangelogWidget2 as ChangelogWidget, Checklist2 as Checklist, FeatureDropContext, FeatureDropProvider, FeatureRequestButton2 as FeatureRequestButton, FeatureRequestForm2 as FeatureRequestForm, FeedbackWidget2 as FeedbackWidget, Hotspot2 as Hotspot, NewBadge2 as NewBadge, Spotlight2 as Spotlight, SpotlightChain2 as SpotlightChain, Survey2 as Survey, ThemeProvider, Toast2 as Toast, TooltipGroup2 as TooltipGroup, Tour2 as Tour, useAdoptionAnalytics, useChecklist, useFeatureDrop, useNewCount, useNewFeature, useSurvey, useTabNotification, useTour, useTourSequencer };
7259
7760
  //# sourceMappingURL=react.js.map
7260
7761
  //# sourceMappingURL=react.js.map