@usero/sdk 0.2.0 → 0.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.
package/dist/react.cjs CHANGED
@@ -161,6 +161,25 @@ function getGradientEnd(color) {
161
161
  return `#${[shiftedR, shiftedG, shiftedB].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
162
162
  }
163
163
 
164
+ // src/plugin.ts
165
+ function createPluginLogger(name) {
166
+ const prefix = `[usero:${name}]`;
167
+ return {
168
+ debug: (...args) => {
169
+ if (typeof console !== "undefined") console.debug(prefix, ...args);
170
+ },
171
+ info: (...args) => {
172
+ if (typeof console !== "undefined") console.info(prefix, ...args);
173
+ },
174
+ warn: (...args) => {
175
+ if (typeof console !== "undefined") console.warn(prefix, ...args);
176
+ },
177
+ error: (...args) => {
178
+ if (typeof console !== "undefined") console.error(prefix, ...args);
179
+ }
180
+ };
181
+ }
182
+
164
183
  // src/validation.ts
165
184
  function validateFeedbackSubmission(data) {
166
185
  const errors = [];
@@ -196,8 +215,8 @@ var FEEDBACK_CSS = `
196
215
  .fb-es {
197
216
  display: flex;
198
217
  justify-content: center;
199
- gap: 15px;
200
- padding-bottom: 10px;
218
+ gap: 12px;
219
+ padding-bottom: 8px;
201
220
  }
202
221
 
203
222
  .fb-ec {
@@ -288,7 +307,7 @@ var FEEDBACK_CSS = `
288
307
 
289
308
  .fb-sub {
290
309
  width: 100%;
291
- padding: 16px 24px;
310
+ padding: 12px 24px;
292
311
  border: none;
293
312
  border-radius: 12px;
294
313
  font-size: 15px;
@@ -316,7 +335,7 @@ var FEEDBACK_CSS = `
316
335
  }
317
336
 
318
337
  .fb-cnt {
319
- padding: 24px;
338
+ padding: 20px 24px 16px;
320
339
  overflow: auto;
321
340
  max-height: calc(90vh - 48px);
322
341
  }
@@ -329,22 +348,29 @@ var FEEDBACK_CSS = `
329
348
 
330
349
  .fb-ta {
331
350
  width: 100%;
332
- min-height: 100px;
333
- padding: 12px;
351
+ min-height: 80px;
352
+ padding: 10px;
334
353
  border-radius: 8px;
335
354
  font-size: 14px;
336
355
  font-family: inherit;
337
356
  outline: none;
338
357
  resize: vertical;
339
358
  transition: border-color 150ms ease;
340
- margin-bottom: 4px;
359
+ margin-bottom: 2px;
341
360
  box-sizing: border-box;
342
361
  }
343
362
 
363
+ .fb-toolrow {
364
+ display: flex;
365
+ align-items: center;
366
+ justify-content: space-between;
367
+ gap: 12px;
368
+ margin-bottom: 8px;
369
+ }
370
+
344
371
  .fb-charcount {
345
372
  font-size: 12px;
346
373
  margin-left: auto;
347
- margin-bottom: 8px;
348
374
  text-align: right;
349
375
  }
350
376
 
@@ -355,8 +381,8 @@ var FEEDBACK_CSS = `
355
381
  .fb-email {
356
382
  display: flex;
357
383
  flex-direction: column;
358
- gap: 8px;
359
- margin-bottom: 16px;
384
+ gap: 6px;
385
+ margin-bottom: 10px;
360
386
  }
361
387
 
362
388
  .fb-email-lbl {
@@ -492,8 +518,8 @@ var FEEDBACK_CSS = `
492
518
  .fb-up {
493
519
  display: flex;
494
520
  flex-direction: column;
495
- gap: 8px;
496
- margin-bottom: 12px;
521
+ gap: 6px;
522
+ margin-bottom: 8px;
497
523
  }
498
524
 
499
525
  .fb-upb {
@@ -595,15 +621,15 @@ var FEEDBACK_CSS = `
595
621
  .fb-pnl-base {
596
622
  width: 100% !important;
597
623
  max-width: none !important;
598
- top: 5vh !important;
599
- max-height: 70vh !important;
624
+ top: 4vh !important;
625
+ max-height: 92vh !important;
600
626
  }
601
- .fb-cnt { padding: 20px !important; max-height: calc(100vh - 80px) !important; }
602
- .fb-ta { font-size: 16px !important; min-height: 80px !important; }
627
+ .fb-cnt { padding: 16px 18px 14px !important; max-height: calc(100vh - 40px) !important; }
628
+ .fb-ta { font-size: 16px !important; min-height: 64px !important; }
603
629
  .fb-ttl { font-size: 18px !important; }
604
630
  .fb-ei { font-size: 24px !important; }
605
631
  .fb-el { font-size: 11px !important; }
606
- .fb-sub { padding: 14px 20px !important; font-size: 16px !important; }
632
+ .fb-sub { padding: 12px 20px !important; font-size: 16px !important; }
607
633
  }
608
634
  `;
609
635
 
@@ -640,6 +666,21 @@ function escapeHtml(value) {
640
666
  }
641
667
  });
642
668
  }
669
+ function mergePluginPatches(base, patches) {
670
+ let result = base;
671
+ for (const patch of patches) {
672
+ if (!patch || typeof patch !== "object") continue;
673
+ const { metadata: patchMetadata, ...rest } = patch;
674
+ result = { ...result, ...rest };
675
+ if (patchMetadata && typeof patchMetadata === "object") {
676
+ result.metadata = {
677
+ ...result.metadata ?? {},
678
+ ...patchMetadata
679
+ };
680
+ }
681
+ }
682
+ return result;
683
+ }
643
684
  function readStoredEmail() {
644
685
  if (typeof window === "undefined") return "";
645
686
  try {
@@ -664,7 +705,8 @@ function initUseroFeedbackWidget(props) {
664
705
  close: () => {
665
706
  },
666
707
  update: () => {
667
- }
708
+ },
709
+ whenReady: () => Promise.resolve()
668
710
  };
669
711
  }
670
712
  const { clientId, baseUrl } = props;
@@ -679,7 +721,8 @@ function initUseroFeedbackWidget(props) {
679
721
  close: () => {
680
722
  },
681
723
  update: () => {
682
- }
724
+ },
725
+ whenReady: () => Promise.resolve()
683
726
  };
684
727
  }
685
728
  let position = props.position ?? "right";
@@ -696,6 +739,34 @@ function initUseroFeedbackWidget(props) {
696
739
  let onOpen = props.onOpen;
697
740
  let onClose = props.onClose;
698
741
  const apiClient = new FeedbackApiClient(baseUrl);
742
+ const pluginList = props.plugins ?? [];
743
+ const pluginStores = /* @__PURE__ */ new Map();
744
+ const pluginContexts = /* @__PURE__ */ new Map();
745
+ const initPromises = [];
746
+ for (const plugin of pluginList) {
747
+ const ctx = {
748
+ clientId,
749
+ baseUrl: baseUrl ?? DEFAULT_API_URL,
750
+ logger: createPluginLogger(plugin.name),
751
+ getStore: () => pluginStores.get(plugin.name),
752
+ setStore: (value) => {
753
+ pluginStores.set(plugin.name, value);
754
+ }
755
+ };
756
+ pluginContexts.set(plugin.name, ctx);
757
+ if (plugin.onInit) {
758
+ const settled = (async () => {
759
+ try {
760
+ await plugin.onInit?.(ctx);
761
+ } catch (err) {
762
+ ctx.logger.error("onInit threw", err);
763
+ }
764
+ })();
765
+ initPromises.push(settled);
766
+ }
767
+ }
768
+ const readyPromise = initPromises.length === 0 ? Promise.resolve() : Promise.all(initPromises).then(() => {
769
+ });
699
770
  let isOpen = false;
700
771
  let selectedRating = void 0;
701
772
  let comment = "";
@@ -814,8 +885,24 @@ function initUseroFeedbackWidget(props) {
814
885
  setSubmitMessage({ type: "error", text: validation.errors.join(", ") });
815
886
  return;
816
887
  }
888
+ let enrichedSubmission = submission;
889
+ if (pluginList.length > 0) {
890
+ const patchPromises = pluginList.map(async (plugin) => {
891
+ if (!plugin.onFeedbackSubmit) return void 0;
892
+ const ctx = pluginContexts.get(plugin.name);
893
+ if (!ctx) return void 0;
894
+ try {
895
+ return await plugin.onFeedbackSubmit(ctx, submission);
896
+ } catch (err) {
897
+ ctx.logger.error("onFeedbackSubmit threw", err);
898
+ return void 0;
899
+ }
900
+ });
901
+ const patches = await Promise.all(patchPromises);
902
+ enrichedSubmission = mergePluginPatches(submission, patches);
903
+ }
817
904
  try {
818
- const response = await apiClient.submitFeedback(submission);
905
+ const response = await apiClient.submitFeedback(enrichedSubmission);
819
906
  if (response.success) {
820
907
  if (shareEmail && userEmail) writeStoredEmail(userEmail);
821
908
  onSubmit?.(feedbackData);
@@ -872,17 +959,26 @@ function initUseroFeedbackWidget(props) {
872
959
  const cls = ["fb-ec", sel && "fb-ec--sel"].filter(Boolean).join(" ");
873
960
  return `
874
961
  <div class="${cls}" style="background:${bg}">
875
- <button type="button" class="fb-eb" data-rating="${r}" role="radio" aria-checked="${sel}" aria-label="${r}: ${RATING_LABELS[r]}">
962
+ <button type="button" class="fb-eb" data-rating="${r}" role="radio" aria-checked="${sel}" aria-label="${r}: ${RATING_LABELS[r]}" style="color:${theme.text}">
876
963
  <div class="fb-ei"><span role="img" aria-label="${RATING_LABELS[r]}">${EMOJI_MAP[r]}</span></div>
877
- <div class="fb-el">${RATING_LABELS[r]}</div>
964
+ <div class="fb-el" style="color:${theme.text}">${RATING_LABELS[r]}</div>
878
965
  </button>
879
966
  </div>
880
967
  `;
881
968
  }).join("");
882
969
  const messageHtml = submitMessage ? `<div class="fb-msg fb-msg--header ${submitMessage.type === "success" ? "fb-msg--ok" : "fb-msg--err"}">${submitMessage.type === "success" ? "\u2713" : "\u26A0"} ${escapeHtml(submitMessage.text)}</div>` : "";
883
- const screenshotBlockHtml = showScreenshotOption ? (() => {
970
+ const uploadBtnHtml = showScreenshotOption ? (() => {
884
971
  const atMax = screenshots.length >= MAX_SCREENSHOTS;
885
972
  const btnDisabled = isUploadingScreenshot || atMax;
973
+ return `
974
+ <input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
975
+ <button type="button" class="fb-upb ${btnDisabled ? "fb-upb--dis" : ""}" data-role="screenshot-pick" ${btnDisabled ? "disabled" : ""} style="border:1px solid ${theme.border};color:${theme.text};">
976
+ ${isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot"}
977
+ </button>
978
+ `;
979
+ })() : "";
980
+ const uploadExtrasHtml = showScreenshotOption ? (() => {
981
+ const atMax = screenshots.length >= MAX_SCREENSHOTS;
886
982
  const previewsHtml = screenshots.map(
887
983
  (shot, i) => `
888
984
  <div class="fb-sp">
@@ -893,16 +989,7 @@ function initUseroFeedbackWidget(props) {
893
989
  ).join("");
894
990
  const errorHtml = screenshotError ? `<div class="fb-upe">\u26A0 ${escapeHtml(screenshotError)}</div>` : "";
895
991
  const limitHtml = atMax ? `<div class="fb-sl">Max ${MAX_SCREENSHOTS}</div>` : "";
896
- const extrasHtml = screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
897
- return `
898
- <div class="fb-up">
899
- <input type="file" accept="image/*" data-role="screenshot-input" style="display:none;" aria-label="Choose screenshot" />
900
- <button type="button" class="fb-upb ${btnDisabled ? "fb-upb--dis" : ""}" data-role="screenshot-pick" ${btnDisabled ? "disabled" : ""} style="border:1px solid ${theme.border};color:${theme.text};">
901
- ${isUploadingScreenshot ? '<span class="fb-ups"></span> Uploading...' : "\u{1F4F7} Add screenshot"}
902
- </button>
903
- ${extrasHtml}
904
- </div>
905
- `;
992
+ return screenshotError || screenshots.length > 0 || atMax ? `<div class="fb-up-extras">${errorHtml}${screenshots.length > 0 ? `<div class="fb-ss">${previewsHtml}</div>` : ""}${limitHtml}</div>` : "";
906
993
  })() : "";
907
994
  const emailBlockHtml = showEmailOption ? `
908
995
  <div class="fb-email">
@@ -925,8 +1012,11 @@ function initUseroFeedbackWidget(props) {
925
1012
  <form data-role="form">
926
1013
  <div class="fb-es" role="radiogroup" aria-label="Rate experience">${ratingsHtml}</div>
927
1014
  <textarea class="fb-ta" data-role="comment" placeholder="${escapeHtml(placeholder)}" aria-label="Comments" maxlength="1000" rows="2" style="border:1px solid ${theme.border};color:${theme.text};background-color:${theme.background};">${escapeHtml(comment)}</textarea>
928
- <div class="fb-charcount${lowChars ? " fb-charcount--low" : ""}" data-role="charcount" style="color:${lowChars ? "#dc2626" : theme.text};opacity:${lowChars ? 1 : 0.6};">${remaining} chars remaining</div>
929
- ${screenshotBlockHtml}
1015
+ <div class="fb-toolrow">
1016
+ ${uploadBtnHtml}
1017
+ <div class="fb-charcount${lowChars ? " fb-charcount--low" : ""}" data-role="charcount" style="color:${lowChars ? "#dc2626" : theme.text};opacity:${lowChars ? 1 : 0.6};">${remaining} chars remaining</div>
1018
+ </div>
1019
+ ${uploadExtrasHtml ? `<div class="fb-up">${uploadExtrasHtml}</div>` : ""}
930
1020
  ${emailBlockHtml}
931
1021
  <button class="fb-sub ${submitDisabled ? "fb-sub--dis" : ""}" type="submit" aria-label="Submit" ${submitDisabled ? "disabled" : ""} style="${submitStyle}">
932
1022
  ${isSubmitting ? '<span class="fb-spin"></span>' : ""}
@@ -1057,10 +1147,23 @@ function initUseroFeedbackWidget(props) {
1057
1147
  destroyed = true;
1058
1148
  document.removeEventListener("keydown", onKeyDown);
1059
1149
  detachMqlListener();
1150
+ for (const plugin of pluginList) {
1151
+ if (!plugin.onDestroy) continue;
1152
+ const ctx = pluginContexts.get(plugin.name);
1153
+ if (!ctx) continue;
1154
+ try {
1155
+ plugin.onDestroy(ctx);
1156
+ } catch (err) {
1157
+ ctx.logger.error("onDestroy threw", err);
1158
+ }
1159
+ }
1160
+ pluginStores.clear();
1161
+ pluginContexts.clear();
1060
1162
  host.remove();
1061
1163
  },
1062
1164
  open,
1063
1165
  close,
1166
+ whenReady: () => readyPromise,
1064
1167
  update: (next) => {
1065
1168
  if (destroyed) return;
1066
1169
  let needsRender = false;