@inploi/plugin-feedback 6.1.0 → 6.2.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.
@@ -0,0 +1 @@
1
+ export {};
@@ -31,9 +31,18 @@ export type FeedbackPluginParams = {
31
31
  negative: PaletteParams;
32
32
  };
33
33
  terms: Partial<FeedbackTranslationTerms>;
34
+ locale?: string;
34
35
  size: 'sm' | 'md' | 'lg';
36
+ /**
37
+ * The rating to show, for a host that draws the same key's ratings in more than one place: each follows the one
38
+ * rating (pass back what `onScore` reports). Absent, the widget keeps its own.
39
+ */
40
+ score?: 0 | 1 | -1;
35
41
  onLeaveComment?: () => void;
42
+ /** A rating, once it has been logged. */
36
43
  onRate?: (rate: 0 | 1 | -1) => void;
44
+ /** The rating as it's pressed (1 or -1), or cleared (0: pressed again, which logs nothing, or its log failed). */
45
+ onScore?: (score: 0 | 1 | -1) => void;
37
46
  };
38
47
  export interface FeedbackPluginRenderParams extends FeedbackPluginParams {
39
48
  host?: HTMLElement;
@@ -62,6 +71,8 @@ export declare const feedbackPlugin: () => ({ apiClient, logger, analytics }: {
62
71
  }>;
63
72
  /** Renders the Job Alerts Plugin inside the container element. */
64
73
  render: (params: FeedbackPluginRenderParams) => Promise<void>;
74
+ /** Release one target's widget (and its portals), leaving any other target's. */
75
+ unmount: (target: HTMLElement) => void;
65
76
  /** Release components, portals and document listeners when an owning widget unmounts. */
66
77
  destroy: () => void;
67
78
  '~brand': "@inploi/plugin-feedback";
package/dist/i18n.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export declare const TranslationProvider: (props: {
2
2
  terms?: Partial<import('@inploi/i18n/translation').TranslationDict<{
3
3
  readonly a11y_label: "How was your experience?";
4
+ readonly positive_label: "Good";
5
+ readonly negative_label: "Bad";
4
6
  readonly positive_heading: "Thanks!";
5
7
  readonly negative_heading: "We’re sorry to hear that";
6
8
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -10,11 +12,14 @@ export declare const TranslationProvider: (props: {
10
12
  readonly skip: "No, thanks";
11
13
  readonly submitted_heading: "Thank you!";
12
14
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
15
+ readonly comment_minimum: "Please type more characters in the comment box";
13
16
  }>> | undefined;
14
17
  locale: string | undefined;
15
18
  children?: import('preact').ComponentChildren;
16
19
  }) => import("preact").JSX.Element, createTFunction: <T extends {
17
20
  readonly a11y_label: "How was your experience?";
21
+ readonly positive_label: "Good";
22
+ readonly negative_label: "Bad";
18
23
  readonly positive_heading: "Thanks!";
19
24
  readonly negative_heading: "We’re sorry to hear that";
20
25
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -24,8 +29,11 @@ export declare const TranslationProvider: (props: {
24
29
  readonly skip: "No, thanks";
25
30
  readonly submitted_heading: "Thank you!";
26
31
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
32
+ readonly comment_minimum: "Please type more characters in the comment box";
27
33
  } = {
28
34
  readonly a11y_label: "How was your experience?";
35
+ readonly positive_label: "Good";
36
+ readonly negative_label: "Bad";
29
37
  readonly positive_heading: "Thanks!";
30
38
  readonly negative_heading: "We’re sorry to hear that";
31
39
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -35,9 +43,12 @@ export declare const TranslationProvider: (props: {
35
43
  readonly skip: "No, thanks";
36
44
  readonly submitted_heading: "Thank you!";
37
45
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
46
+ readonly comment_minimum: "Please type more characters in the comment box";
38
47
  }>(options: {
39
48
  overrides: Partial<import('@inploi/i18n/translation').TranslationDict<{
40
49
  readonly a11y_label: "How was your experience?";
50
+ readonly positive_label: "Good";
51
+ readonly negative_label: "Bad";
41
52
  readonly positive_heading: "Thanks!";
42
53
  readonly negative_heading: "We’re sorry to hear that";
43
54
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -47,10 +58,13 @@ export declare const TranslationProvider: (props: {
47
58
  readonly skip: "No, thanks";
48
59
  readonly submitted_heading: "Thank you!";
49
60
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
61
+ readonly comment_minimum: "Please type more characters in the comment box";
50
62
  }>>;
51
63
  locale: string | undefined;
52
64
  }) => import('@inploi/i18n/translation').TFn<T>, useTranslation: <T extends {
53
65
  readonly a11y_label: "How was your experience?";
66
+ readonly positive_label: "Good";
67
+ readonly negative_label: "Bad";
54
68
  readonly positive_heading: "Thanks!";
55
69
  readonly negative_heading: "We’re sorry to hear that";
56
70
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -60,8 +74,11 @@ export declare const TranslationProvider: (props: {
60
74
  readonly skip: "No, thanks";
61
75
  readonly submitted_heading: "Thank you!";
62
76
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
77
+ readonly comment_minimum: "Please type more characters in the comment box";
63
78
  } = {
64
79
  readonly a11y_label: "How was your experience?";
80
+ readonly positive_label: "Good";
81
+ readonly negative_label: "Bad";
65
82
  readonly positive_heading: "Thanks!";
66
83
  readonly negative_heading: "We’re sorry to hear that";
67
84
  readonly positive_text: "Would you like to tell us more about your experience?";
@@ -71,6 +88,7 @@ export declare const TranslationProvider: (props: {
71
88
  readonly skip: "No, thanks";
72
89
  readonly submitted_heading: "Thank you!";
73
90
  readonly submitted_text: "Your feedback has been sent and will be used to improve our products.";
91
+ readonly comment_minimum: "Please type more characters in the comment box";
74
92
  }>() => {
75
93
  t: import('@inploi/i18n/translation').TFn<T>;
76
94
  locale: string | undefined;
@@ -5348,6 +5348,8 @@ var createTranslation = (defaultTerms) => {
5348
5348
  };
5349
5349
  const { TranslationProvider, createTFunction, useTranslation } = createTranslation({
5350
5350
  a11y_label: "How was your experience?",
5351
+ positive_label: "Good",
5352
+ negative_label: "Bad",
5351
5353
  positive_heading: "Thanks!",
5352
5354
  negative_heading: "We’re sorry to hear that",
5353
5355
  positive_text: "Would you like to tell us more about your experience?",
@@ -5356,7 +5358,8 @@ const { TranslationProvider, createTFunction, useTranslation } = createTranslati
5356
5358
  submitting: "Sending feedback...",
5357
5359
  skip: "No, thanks",
5358
5360
  submitted_heading: "Thank you!",
5359
- submitted_text: "Your feedback has been sent and will be used to improve our products."
5361
+ submitted_text: "Your feedback has been sent and will be used to improve our products.",
5362
+ comment_minimum: "Please type more characters in the comment box"
5360
5363
  });
5361
5364
  var COLOR_SHADES = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
5362
5365
  var OKLCH_PRESETS = {
@@ -5617,12 +5620,12 @@ function noop() {
5617
5620
  }
5618
5621
  const options = [
5619
5622
  {
5620
- label: "Good",
5623
+ label: "positive_label",
5621
5624
  icon: /* @__PURE__ */ u$1(Icon, { name: "happy" }),
5622
5625
  value: 1,
5623
5626
  variant: "positive"
5624
5627
  },
5625
- { label: "Bad", icon: /* @__PURE__ */ u$1(Icon, { name: "sad" }), value: -1, variant: "negative" }
5628
+ { label: "negative_label", icon: /* @__PURE__ */ u$1(Icon, { name: "sad" }), value: -1, variant: "negative" }
5626
5629
  ];
5627
5630
  const submitWithMetaEnter = (e2) => {
5628
5631
  const isSubmitCombo = e2.key === "Enter" && e2.metaKey;
@@ -5640,41 +5643,65 @@ const Label = () => {
5640
5643
  const FeedbackWidget = ({ analytics, params, logger }) => {
5641
5644
  const variants = getThemeVariants(params.theme).join(" ");
5642
5645
  const id = g$1();
5643
- const [score, setScore] = d(0);
5646
+ const ratings = A$1(null);
5647
+ const t2 = T$1(
5648
+ () => createTFunction({ overrides: params.terms ?? {}, locale: params.locale }),
5649
+ [params.terms, params.locale]
5650
+ );
5651
+ const [score, setScore] = d(params.score ?? 0);
5652
+ const presses = A$1(0);
5653
+ const held = A$1(params.score);
5654
+ held.current = params.score;
5655
+ y(() => {
5656
+ if (params.score !== void 0)
5657
+ setScore(params.score);
5658
+ }, [params.score]);
5644
5659
  const [open, setOpen] = d();
5645
- return /* @__PURE__ */ u$1(TranslationProvider, { locale: void 0, terms: params.terms, children: /* @__PURE__ */ u$1("div", { role: "group", "aria-labelledby": `${id}-label`, class: variants, children: [
5660
+ return /* @__PURE__ */ u$1(TranslationProvider, { locale: params.locale, terms: params.terms, children: /* @__PURE__ */ u$1("div", { role: "group", "aria-labelledby": `${id}-label`, class: variants, children: [
5646
5661
  /* @__PURE__ */ u$1("span", { id: `${id}-label`, className: "sr-only", children: /* @__PURE__ */ u$1(Label, {}) }),
5647
- /* @__PURE__ */ u$1("div", { class: "isolate flex", children: options.map((option) => /* @__PURE__ */ u$1(
5662
+ /* @__PURE__ */ u$1("div", { ref: ratings, class: "isolate flex", children: options.map((option) => /* @__PURE__ */ u$1(
5648
5663
  $cb5cc270b50c6fcd$export$be92b6f5f03c0fe9,
5649
5664
  {
5650
5665
  open: open === option.value,
5651
- onOpenChange: () => {
5652
- if (!open)
5653
- return setOpen(option.value);
5654
- setOpen(void 0);
5666
+ onOpenChange: (next) => {
5667
+ if (next)
5668
+ setOpen(option.value);
5669
+ else
5670
+ setOpen((current) => current === option.value ? void 0 : current);
5655
5671
  },
5656
5672
  children: [
5657
5673
  /* @__PURE__ */ u$1(
5658
5674
  $cb5cc270b50c6fcd$export$41fb9f06171c75f4,
5659
5675
  {
5660
- "aria-label": option.label,
5676
+ "aria-label": t2(option.label),
5661
5677
  "aria-pressed": score === option.value,
5662
5678
  class: buttonClass({ colorScheme: option.variant, size: params.size }),
5663
5679
  onClick: (e2) => {
5680
+ var _a, _b;
5664
5681
  const newVal = option.value === score ? 0 : option.value;
5682
+ const press = ++presses.current;
5665
5683
  setScore(newVal);
5666
5684
  if (newVal === 0) {
5667
5685
  e2.preventDefault();
5668
5686
  setOpen(void 0);
5687
+ (_a = params.onScore) == null ? void 0 : _a.call(params, 0);
5669
5688
  return;
5670
5689
  }
5671
- analytics.log({ event: "FEEDBACK", properties: { key: params.key, score: newVal } }).then(() => {
5672
- var _a;
5673
- (_a = params.onRate) == null ? void 0 : _a.call(params, newVal);
5674
- }).catch((e22) => {
5675
- logger.error(e22);
5676
- setScore(0);
5677
- });
5690
+ (_b = params.onScore) == null ? void 0 : _b.call(params, newVal);
5691
+ analytics.log({ event: "FEEDBACK", properties: { key: params.key, score: newVal } }).then(
5692
+ () => {
5693
+ var _a2;
5694
+ return (_a2 = params.onRate) == null ? void 0 : _a2.call(params, newVal);
5695
+ },
5696
+ (e22) => {
5697
+ var _a2;
5698
+ logger.error(e22);
5699
+ if (press !== presses.current || held.current !== void 0 && held.current !== newVal)
5700
+ return;
5701
+ setScore(0);
5702
+ (_a2 = params.onScore) == null ? void 0 : _a2.call(params, 0);
5703
+ }
5704
+ );
5678
5705
  },
5679
5706
  children: /* @__PURE__ */ u$1("div", { class: "ease-expo-out duration-medium scale-75 transition-transform group-aria-[pressed=true]:scale-100", children: option.icon })
5680
5707
  }
@@ -5682,10 +5709,26 @@ const FeedbackWidget = ({ analytics, params, logger }) => {
5682
5709
  /* @__PURE__ */ u$1($cb5cc270b50c6fcd$export$602eac185826482c, { container: params.host, children: /* @__PURE__ */ u$1("div", { "data-widget": FEEDBACK_WIDGET_ID, "data-key": params.key, children: /* @__PURE__ */ u$1("div", { class: clsx$1("absolute isolate z-50", variants), children: /* @__PURE__ */ u$1(
5683
5710
  $cb5cc270b50c6fcd$export$7c6e2c02157bb7d2,
5684
5711
  {
5712
+ "aria-labelledby": `${id}-${option.value}-heading`,
5713
+ onInteractOutside: (event) => {
5714
+ var _a;
5715
+ const target = event.detail.originalEvent.target;
5716
+ if (target instanceof Element && ((_a = ratings.current) == null ? void 0 : _a.contains(target.closest("button[aria-pressed]"))))
5717
+ event.preventDefault();
5718
+ },
5685
5719
  collisionPadding: 16,
5686
5720
  class: "data-[state=open]:animate-slideDownAndFade bg-lowest ring-neutral-5 shadow-surface-lg isolate w-64 max-w-[var(--radix-popover-content-available-width)] rounded-lg p-2 ring-1 will-change-[transform,opacity]",
5687
5721
  sideOffset: 5,
5688
- children: /* @__PURE__ */ u$1(CommentForm, { analytics, params, onClose: () => setOpen(0), score })
5722
+ children: /* @__PURE__ */ u$1(
5723
+ CommentForm,
5724
+ {
5725
+ analytics,
5726
+ params,
5727
+ onClose: () => setOpen(0),
5728
+ score,
5729
+ headingId: `${id}-${option.value}-heading`
5730
+ }
5731
+ )
5689
5732
  }
5690
5733
  ) }) }) })
5691
5734
  ]
@@ -5698,20 +5741,40 @@ const CommentForm = ({
5698
5741
  params,
5699
5742
  onClose,
5700
5743
  score,
5701
- analytics
5744
+ analytics,
5745
+ headingId
5702
5746
  }) => {
5703
5747
  const { t: t2 } = useTranslation();
5704
5748
  const id = g$1();
5749
+ const form = A$1(null);
5750
+ const success = A$1(null);
5751
+ const transferFocus = A$1(false);
5705
5752
  const [submission, setSubmission] = d({ state: "idle" });
5753
+ _(() => {
5754
+ var _a;
5755
+ if (submission.state === "success" && transferFocus.current)
5756
+ (_a = success.current) == null ? void 0 : _a.focus({ preventScroll: true });
5757
+ }, [submission.state]);
5706
5758
  if (submission.state === "success") {
5707
- return /* @__PURE__ */ u$1("div", { class: "text-balance flex flex-col gap-1 p-4 text-center", children: [
5708
- /* @__PURE__ */ u$1("p", { class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2("submitted_heading") }),
5709
- /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2("submitted_text") })
5710
- ] });
5759
+ return /* @__PURE__ */ u$1(
5760
+ "div",
5761
+ {
5762
+ ref: success,
5763
+ tabIndex: -1,
5764
+ role: "status",
5765
+ "aria-labelledby": headingId,
5766
+ class: "flex flex-col gap-1 text-balance p-4 text-center",
5767
+ children: [
5768
+ /* @__PURE__ */ u$1("p", { id: headingId, class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2("submitted_heading") }),
5769
+ /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2("submitted_text") })
5770
+ ]
5771
+ }
5772
+ );
5711
5773
  }
5712
5774
  return /* @__PURE__ */ u$1(
5713
5775
  "form",
5714
5776
  {
5777
+ ref: form,
5715
5778
  class: "flex flex-col gap-2.5 p-2",
5716
5779
  onSubmit: (e2) => {
5717
5780
  e2.preventDefault();
@@ -5719,7 +5782,7 @@ const CommentForm = ({
5719
5782
  const formData = new FormData(e2.currentTarget);
5720
5783
  const comment = formData.get("comment");
5721
5784
  if (typeof comment !== "string" || comment.length <= 5) {
5722
- setSubmission({ state: "error", error: "Please type more characters in the comment box" });
5785
+ setSubmission({ state: "error", error: t2("comment_minimum") });
5723
5786
  return;
5724
5787
  }
5725
5788
  Promise.all([
@@ -5727,8 +5790,9 @@ const CommentForm = ({
5727
5790
  /** Min delay of 500ms so it feels like it’s doing stuff */
5728
5791
  new Promise((resolve) => setTimeout(resolve, 1e3))
5729
5792
  ]).then(() => {
5730
- var _a;
5793
+ var _a, _b, _c;
5731
5794
  (_a = params.onLeaveComment) == null ? void 0 : _a.call(params);
5795
+ transferFocus.current = Boolean((_c = (_b = form.current) == null ? void 0 : _b.closest('[role="dialog"]')) == null ? void 0 : _c.contains(document.activeElement));
5732
5796
  setSubmission({ state: "success" });
5733
5797
  }).catch((e22) => {
5734
5798
  if (e22 instanceof Error) {
@@ -5742,7 +5806,7 @@ const CommentForm = ({
5742
5806
  /* @__PURE__ */ u$1("input", { type: "hidden", name: "key", value: params.key }),
5743
5807
  /* @__PURE__ */ u$1("fieldset", { class: "flex flex-col gap-4", children: [
5744
5808
  /* @__PURE__ */ u$1("label", { htmlFor: id, class: "flex flex-col gap-1", children: [
5745
- /* @__PURE__ */ u$1("p", { class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2(score === -1 ? "negative_heading" : "positive_heading") }),
5809
+ /* @__PURE__ */ u$1("p", { id: headingId, class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2(score === -1 ? "negative_heading" : "positive_heading") }),
5746
5810
  /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2(score === -1 ? "negative_text" : "positive_text") })
5747
5811
  ] }),
5748
5812
  /* @__PURE__ */ u$1("div", { class: "flex flex-col gap-2", children: [
@@ -5756,7 +5820,7 @@ const CommentForm = ({
5756
5820
  id
5757
5821
  }
5758
5822
  ),
5759
- submission.state === "error" ? /* @__PURE__ */ u$1("div", { role: "alert", class: "text-balance flex gap-1 text-xs", children: [
5823
+ submission.state === "error" ? /* @__PURE__ */ u$1("div", { role: "alert", class: "flex gap-1 text-balance text-xs", children: [
5760
5824
  /* @__PURE__ */ u$1(Icon, { name: "warning", class: "size-[1lh] flex-none" }),
5761
5825
  submission.error
5762
5826
  ] }) : null
@@ -6760,6 +6824,11 @@ const feedbackPlugin = () => sdk.createPlugin(({ apiClient, logger, analytics })
6760
6824
  targets.add(target);
6761
6825
  fn2(_$2(App, { params }), target);
6762
6826
  },
6827
+ /** Release one target's widget (and its portals), leaving any other target's. */
6828
+ unmount: (target) => {
6829
+ J$1(null, target);
6830
+ targets.delete(target);
6831
+ },
6763
6832
  /** Release components, portals and document listeners when an owning widget unmounts. */
6764
6833
  destroy: () => {
6765
6834
  for (const target of targets)
@@ -5346,6 +5346,8 @@ var createTranslation = (defaultTerms) => {
5346
5346
  };
5347
5347
  const { TranslationProvider, createTFunction, useTranslation } = createTranslation({
5348
5348
  a11y_label: "How was your experience?",
5349
+ positive_label: "Good",
5350
+ negative_label: "Bad",
5349
5351
  positive_heading: "Thanks!",
5350
5352
  negative_heading: "We’re sorry to hear that",
5351
5353
  positive_text: "Would you like to tell us more about your experience?",
@@ -5354,7 +5356,8 @@ const { TranslationProvider, createTFunction, useTranslation } = createTranslati
5354
5356
  submitting: "Sending feedback...",
5355
5357
  skip: "No, thanks",
5356
5358
  submitted_heading: "Thank you!",
5357
- submitted_text: "Your feedback has been sent and will be used to improve our products."
5359
+ submitted_text: "Your feedback has been sent and will be used to improve our products.",
5360
+ comment_minimum: "Please type more characters in the comment box"
5358
5361
  });
5359
5362
  var COLOR_SHADES = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"];
5360
5363
  var OKLCH_PRESETS = {
@@ -5615,12 +5618,12 @@ function noop() {
5615
5618
  }
5616
5619
  const options = [
5617
5620
  {
5618
- label: "Good",
5621
+ label: "positive_label",
5619
5622
  icon: /* @__PURE__ */ u$1(Icon, { name: "happy" }),
5620
5623
  value: 1,
5621
5624
  variant: "positive"
5622
5625
  },
5623
- { label: "Bad", icon: /* @__PURE__ */ u$1(Icon, { name: "sad" }), value: -1, variant: "negative" }
5626
+ { label: "negative_label", icon: /* @__PURE__ */ u$1(Icon, { name: "sad" }), value: -1, variant: "negative" }
5624
5627
  ];
5625
5628
  const submitWithMetaEnter = (e2) => {
5626
5629
  const isSubmitCombo = e2.key === "Enter" && e2.metaKey;
@@ -5638,41 +5641,65 @@ const Label = () => {
5638
5641
  const FeedbackWidget = ({ analytics, params, logger }) => {
5639
5642
  const variants = getThemeVariants(params.theme).join(" ");
5640
5643
  const id = g$1();
5641
- const [score, setScore] = d(0);
5644
+ const ratings = A$1(null);
5645
+ const t2 = T$1(
5646
+ () => createTFunction({ overrides: params.terms ?? {}, locale: params.locale }),
5647
+ [params.terms, params.locale]
5648
+ );
5649
+ const [score, setScore] = d(params.score ?? 0);
5650
+ const presses = A$1(0);
5651
+ const held = A$1(params.score);
5652
+ held.current = params.score;
5653
+ y(() => {
5654
+ if (params.score !== void 0)
5655
+ setScore(params.score);
5656
+ }, [params.score]);
5642
5657
  const [open, setOpen] = d();
5643
- return /* @__PURE__ */ u$1(TranslationProvider, { locale: void 0, terms: params.terms, children: /* @__PURE__ */ u$1("div", { role: "group", "aria-labelledby": `${id}-label`, class: variants, children: [
5658
+ return /* @__PURE__ */ u$1(TranslationProvider, { locale: params.locale, terms: params.terms, children: /* @__PURE__ */ u$1("div", { role: "group", "aria-labelledby": `${id}-label`, class: variants, children: [
5644
5659
  /* @__PURE__ */ u$1("span", { id: `${id}-label`, className: "sr-only", children: /* @__PURE__ */ u$1(Label, {}) }),
5645
- /* @__PURE__ */ u$1("div", { class: "isolate flex", children: options.map((option) => /* @__PURE__ */ u$1(
5660
+ /* @__PURE__ */ u$1("div", { ref: ratings, class: "isolate flex", children: options.map((option) => /* @__PURE__ */ u$1(
5646
5661
  $cb5cc270b50c6fcd$export$be92b6f5f03c0fe9,
5647
5662
  {
5648
5663
  open: open === option.value,
5649
- onOpenChange: () => {
5650
- if (!open)
5651
- return setOpen(option.value);
5652
- setOpen(void 0);
5664
+ onOpenChange: (next) => {
5665
+ if (next)
5666
+ setOpen(option.value);
5667
+ else
5668
+ setOpen((current) => current === option.value ? void 0 : current);
5653
5669
  },
5654
5670
  children: [
5655
5671
  /* @__PURE__ */ u$1(
5656
5672
  $cb5cc270b50c6fcd$export$41fb9f06171c75f4,
5657
5673
  {
5658
- "aria-label": option.label,
5674
+ "aria-label": t2(option.label),
5659
5675
  "aria-pressed": score === option.value,
5660
5676
  class: buttonClass({ colorScheme: option.variant, size: params.size }),
5661
5677
  onClick: (e2) => {
5678
+ var _a, _b;
5662
5679
  const newVal = option.value === score ? 0 : option.value;
5680
+ const press = ++presses.current;
5663
5681
  setScore(newVal);
5664
5682
  if (newVal === 0) {
5665
5683
  e2.preventDefault();
5666
5684
  setOpen(void 0);
5685
+ (_a = params.onScore) == null ? void 0 : _a.call(params, 0);
5667
5686
  return;
5668
5687
  }
5669
- analytics.log({ event: "FEEDBACK", properties: { key: params.key, score: newVal } }).then(() => {
5670
- var _a;
5671
- (_a = params.onRate) == null ? void 0 : _a.call(params, newVal);
5672
- }).catch((e22) => {
5673
- logger.error(e22);
5674
- setScore(0);
5675
- });
5688
+ (_b = params.onScore) == null ? void 0 : _b.call(params, newVal);
5689
+ analytics.log({ event: "FEEDBACK", properties: { key: params.key, score: newVal } }).then(
5690
+ () => {
5691
+ var _a2;
5692
+ return (_a2 = params.onRate) == null ? void 0 : _a2.call(params, newVal);
5693
+ },
5694
+ (e22) => {
5695
+ var _a2;
5696
+ logger.error(e22);
5697
+ if (press !== presses.current || held.current !== void 0 && held.current !== newVal)
5698
+ return;
5699
+ setScore(0);
5700
+ (_a2 = params.onScore) == null ? void 0 : _a2.call(params, 0);
5701
+ }
5702
+ );
5676
5703
  },
5677
5704
  children: /* @__PURE__ */ u$1("div", { class: "ease-expo-out duration-medium scale-75 transition-transform group-aria-[pressed=true]:scale-100", children: option.icon })
5678
5705
  }
@@ -5680,10 +5707,26 @@ const FeedbackWidget = ({ analytics, params, logger }) => {
5680
5707
  /* @__PURE__ */ u$1($cb5cc270b50c6fcd$export$602eac185826482c, { container: params.host, children: /* @__PURE__ */ u$1("div", { "data-widget": FEEDBACK_WIDGET_ID, "data-key": params.key, children: /* @__PURE__ */ u$1("div", { class: clsx$1("absolute isolate z-50", variants), children: /* @__PURE__ */ u$1(
5681
5708
  $cb5cc270b50c6fcd$export$7c6e2c02157bb7d2,
5682
5709
  {
5710
+ "aria-labelledby": `${id}-${option.value}-heading`,
5711
+ onInteractOutside: (event) => {
5712
+ var _a;
5713
+ const target = event.detail.originalEvent.target;
5714
+ if (target instanceof Element && ((_a = ratings.current) == null ? void 0 : _a.contains(target.closest("button[aria-pressed]"))))
5715
+ event.preventDefault();
5716
+ },
5683
5717
  collisionPadding: 16,
5684
5718
  class: "data-[state=open]:animate-slideDownAndFade bg-lowest ring-neutral-5 shadow-surface-lg isolate w-64 max-w-[var(--radix-popover-content-available-width)] rounded-lg p-2 ring-1 will-change-[transform,opacity]",
5685
5719
  sideOffset: 5,
5686
- children: /* @__PURE__ */ u$1(CommentForm, { analytics, params, onClose: () => setOpen(0), score })
5720
+ children: /* @__PURE__ */ u$1(
5721
+ CommentForm,
5722
+ {
5723
+ analytics,
5724
+ params,
5725
+ onClose: () => setOpen(0),
5726
+ score,
5727
+ headingId: `${id}-${option.value}-heading`
5728
+ }
5729
+ )
5687
5730
  }
5688
5731
  ) }) }) })
5689
5732
  ]
@@ -5696,20 +5739,40 @@ const CommentForm = ({
5696
5739
  params,
5697
5740
  onClose,
5698
5741
  score,
5699
- analytics
5742
+ analytics,
5743
+ headingId
5700
5744
  }) => {
5701
5745
  const { t: t2 } = useTranslation();
5702
5746
  const id = g$1();
5747
+ const form = A$1(null);
5748
+ const success = A$1(null);
5749
+ const transferFocus = A$1(false);
5703
5750
  const [submission, setSubmission] = d({ state: "idle" });
5751
+ _(() => {
5752
+ var _a;
5753
+ if (submission.state === "success" && transferFocus.current)
5754
+ (_a = success.current) == null ? void 0 : _a.focus({ preventScroll: true });
5755
+ }, [submission.state]);
5704
5756
  if (submission.state === "success") {
5705
- return /* @__PURE__ */ u$1("div", { class: "text-balance flex flex-col gap-1 p-4 text-center", children: [
5706
- /* @__PURE__ */ u$1("p", { class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2("submitted_heading") }),
5707
- /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2("submitted_text") })
5708
- ] });
5757
+ return /* @__PURE__ */ u$1(
5758
+ "div",
5759
+ {
5760
+ ref: success,
5761
+ tabIndex: -1,
5762
+ role: "status",
5763
+ "aria-labelledby": headingId,
5764
+ class: "flex flex-col gap-1 text-balance p-4 text-center",
5765
+ children: [
5766
+ /* @__PURE__ */ u$1("p", { id: headingId, class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2("submitted_heading") }),
5767
+ /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2("submitted_text") })
5768
+ ]
5769
+ }
5770
+ );
5709
5771
  }
5710
5772
  return /* @__PURE__ */ u$1(
5711
5773
  "form",
5712
5774
  {
5775
+ ref: form,
5713
5776
  class: "flex flex-col gap-2.5 p-2",
5714
5777
  onSubmit: (e2) => {
5715
5778
  e2.preventDefault();
@@ -5717,7 +5780,7 @@ const CommentForm = ({
5717
5780
  const formData = new FormData(e2.currentTarget);
5718
5781
  const comment = formData.get("comment");
5719
5782
  if (typeof comment !== "string" || comment.length <= 5) {
5720
- setSubmission({ state: "error", error: "Please type more characters in the comment box" });
5783
+ setSubmission({ state: "error", error: t2("comment_minimum") });
5721
5784
  return;
5722
5785
  }
5723
5786
  Promise.all([
@@ -5725,8 +5788,9 @@ const CommentForm = ({
5725
5788
  /** Min delay of 500ms so it feels like it’s doing stuff */
5726
5789
  new Promise((resolve) => setTimeout(resolve, 1e3))
5727
5790
  ]).then(() => {
5728
- var _a;
5791
+ var _a, _b, _c;
5729
5792
  (_a = params.onLeaveComment) == null ? void 0 : _a.call(params);
5793
+ transferFocus.current = Boolean((_c = (_b = form.current) == null ? void 0 : _b.closest('[role="dialog"]')) == null ? void 0 : _c.contains(document.activeElement));
5730
5794
  setSubmission({ state: "success" });
5731
5795
  }).catch((e22) => {
5732
5796
  if (e22 instanceof Error) {
@@ -5740,7 +5804,7 @@ const CommentForm = ({
5740
5804
  /* @__PURE__ */ u$1("input", { type: "hidden", name: "key", value: params.key }),
5741
5805
  /* @__PURE__ */ u$1("fieldset", { class: "flex flex-col gap-4", children: [
5742
5806
  /* @__PURE__ */ u$1("label", { htmlFor: id, class: "flex flex-col gap-1", children: [
5743
- /* @__PURE__ */ u$1("p", { class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2(score === -1 ? "negative_heading" : "positive_heading") }),
5807
+ /* @__PURE__ */ u$1("p", { id: headingId, class: "text-neutral-12 text-pretty text-base font-semibold leading-tight tracking-tight", children: t2(score === -1 ? "negative_heading" : "positive_heading") }),
5744
5808
  /* @__PURE__ */ u$1("p", { class: "text-neutral-11 text-pretty text-sm leading-tight", children: t2(score === -1 ? "negative_text" : "positive_text") })
5745
5809
  ] }),
5746
5810
  /* @__PURE__ */ u$1("div", { class: "flex flex-col gap-2", children: [
@@ -5754,7 +5818,7 @@ const CommentForm = ({
5754
5818
  id
5755
5819
  }
5756
5820
  ),
5757
- submission.state === "error" ? /* @__PURE__ */ u$1("div", { role: "alert", class: "text-balance flex gap-1 text-xs", children: [
5821
+ submission.state === "error" ? /* @__PURE__ */ u$1("div", { role: "alert", class: "flex gap-1 text-balance text-xs", children: [
5758
5822
  /* @__PURE__ */ u$1(Icon, { name: "warning", class: "size-[1lh] flex-none" }),
5759
5823
  submission.error
5760
5824
  ] }) : null
@@ -6758,6 +6822,11 @@ const feedbackPlugin = () => createPlugin(({ apiClient, logger, analytics }) =>
6758
6822
  targets.add(target);
6759
6823
  fn2(_$2(App, { params }), target);
6760
6824
  },
6825
+ /** Release one target's widget (and its portals), leaving any other target's. */
6826
+ unmount: (target) => {
6827
+ J$1(null, target);
6828
+ targets.delete(target);
6829
+ },
6761
6830
  /** Release components, portals and document listeners when an owning widget unmounts. */
6762
6831
  destroy: () => {
6763
6832
  for (const target of targets)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inploi/plugin-feedback",
3
- "version": "6.1.0",
3
+ "version": "6.2.0",
4
4
  "type": "module",
5
5
  "main": "dist/plugin-feedback.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,9 +56,9 @@
56
56
  "clsx": "^2.0.0",
57
57
  "zustand": "^4.5.2",
58
58
  "zustand-context": "^1.0.0",
59
- "@inploi/core": "1.21.0",
59
+ "@inploi/core": "1.22.0",
60
60
  "@inploi/design-tokens": "0.2.1",
61
- "@inploi/sdk": "2.3.2",
61
+ "@inploi/sdk": "2.7.0",
62
62
  "eslint-config-custom": "0.1.0",
63
63
  "@inploi/i18n": "0.1.5"
64
64
  },
@@ -73,6 +73,7 @@
73
73
  "build:cdn": "VITE_MOCKS=false vite build --mode=cdn",
74
74
  "preview": "vite preview",
75
75
  "_test": "bun test src/",
76
+ "test:coverage": "bun test src/ --coverage --coverage-reporter=lcov",
76
77
  "_test:watch": "bun test src/ --watch",
77
78
  "_test:int": "playwright test",
78
79
  "_test:ui": "playwright test --ui",