@goflowstate/widget-sdk 0.3.4 → 0.3.5

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.
@@ -2,9 +2,11 @@
2
2
  * Flow feedback: the bug icon every flow carries (flowstate-docs plan 2026-11-09-flow-feedback).
3
3
  *
4
4
  * The session client mounts this when the session state says `feedbackEnabled`. A round button
5
- * sits in the corner of the flow's own page. A tap takes a screenshot of the page as the person
6
- * sees it, then opens a sheet: the thumbnail, one field, one button. The note goes out through
7
- * the flow's own session (`session.request('feedback', …)`).
5
+ * sits in the corner of the flow's own page. A tap opens a sheet (one field, one button) and
6
+ * takes a screenshot of the page as the person sees it. The sheet says each step: the picture is
7
+ * being taken, it is attached (or there is none, with a way to take it again), the note is
8
+ * sending, it is sent. The note goes out through the flow's own session
9
+ * (`session.request('feedback', …)`).
8
10
  *
9
11
  * It lives in the flow's document, in a shadow root, so it takes the flow's look instead of
10
12
  * bringing one: the flow's fonts by inheritance, and its ground, ink, main button and corner
@@ -1 +1 @@
1
- {"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"feedback.d.ts","sourceRoot":"","sources":["../src/feedback.ts"],"names":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- // @goflowstate/widget-sdk v0.3.4
1
+ // @goflowstate/widget-sdk v0.3.5
2
2
  // https://github.com/goflowstate/goflowstate-sdk
3
3
 
4
4
  var __defProp = Object.defineProperty;
@@ -43,7 +43,9 @@ function createServiceClient(opts) {
43
43
  var FEEDBACK_HOST_ATTR = "data-fs-feedback-host";
44
44
  var SCREENSHOT_MAX_BYTES = 15e4;
45
45
  var SCREENSHOT_MAX_SIDE = 1280;
46
- var CAPTURE_TIMEOUT_MS = 4e3;
46
+ var CAPTURE_TIMEOUT_MS = 15e3;
47
+ var SEND_WAIT_MS = 4e3;
48
+ var SENT_HOLD_MS = 1600;
47
49
  function parseColor(raw) {
48
50
  const value = (raw ?? "").trim().toLowerCase();
49
51
  if (!value || value === "transparent") return null;
@@ -473,11 +475,41 @@ h2 {
473
475
  margin: 2px 0 0; font-family: var(--font-display); font-weight: var(--display-weight);
474
476
  font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking); text-wrap: balance;
475
477
  }
476
- .shot { display: flex; gap: 12px; align-items: center; font-size: 13px; color: var(--ink-dim); }
477
- .shot img { width: 50px; max-height: 108px; object-fit: cover; object-position: top; border-radius: 8px; border: 1px solid var(--line); }
478
- .ghost { display: none; width: 50px; height: 108px; border-radius: 8px; border: 1px solid var(--line); background: var(--fill); }
479
- .shot[data-pending] .ghost { display: block; }
480
- .shot[data-pending] img { display: none; }
478
+ .form { display: contents; }
479
+ .shot { display: flex; gap: 14px; align-items: center; font-size: 14px; }
480
+ .thumb {
481
+ position: relative; flex: none; width: 60px; aspect-ratio: var(--shot-ratio, 0.46); max-height: 124px;
482
+ border-radius: 9px; border: 1px solid var(--line); background: var(--fill);
483
+ }
484
+ .thumb img { display: none; width: 100%; height: 100%; object-fit: cover; object-position: top; border-radius: 8px; }
485
+ .scan, .empty, .badge { display: none; }
486
+ .shot[data-shot="taking"] .thumb { overflow: hidden; }
487
+ .shot[data-shot="taking"] .scan {
488
+ display: block; position: absolute; left: 0; right: 0; top: 0; height: 34%;
489
+ background: linear-gradient(to bottom, transparent, var(--line)); border-bottom: 2px solid var(--ink);
490
+ }
491
+ .shot[data-shot="attached"] .thumb { background: var(--surface); }
492
+ .shot[data-shot="attached"] img { display: block; }
493
+ .shot[data-shot="attached"] .badge {
494
+ display: grid; place-items: center; position: absolute; right: -8px; top: -8px; width: 22px; height: 22px;
495
+ border-radius: 50%; background: #1f7a4d; color: #fff; border: 2px solid var(--surface);
496
+ }
497
+ .badge svg { width: 12px; height: 12px; }
498
+ .shot[data-shot="none"] .thumb { border-style: dashed; background: none; }
499
+ .shot[data-shot="none"] .empty { display: grid; place-items: center; position: absolute; inset: 0; color: var(--ink-dim); }
500
+ .empty svg { width: 22px; height: 22px; }
501
+ .shot-text { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-width: 0; }
502
+ .shot-label { font-weight: 600; color: var(--ink); }
503
+ .shot[data-shot="none"] .shot-label { color: var(--ink-dim); font-weight: inherit; }
504
+ .finder {
505
+ position: fixed; z-index: 2147483002; pointer-events: none;
506
+ top: calc(10px + env(safe-area-inset-top, 0px)); left: 10px; right: 10px; bottom: var(--finder-bottom, 55%);
507
+ }
508
+ .finder i { position: absolute; width: 22px; height: 22px; border: 3px solid #fff; filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.5)); }
509
+ .finder i:nth-child(1) { left: 0; top: 0; border-right: 0; border-bottom: 0; border-top-left-radius: 9px; }
510
+ .finder i:nth-child(2) { right: 0; top: 0; border-left: 0; border-bottom: 0; border-top-right-radius: 9px; }
511
+ .finder i:nth-child(3) { left: 0; bottom: 0; border-right: 0; border-top: 0; border-bottom-left-radius: 9px; }
512
+ .finder i:nth-child(4) { right: 0; bottom: 0; border-left: 0; border-top: 0; border-bottom-right-radius: 9px; }
481
513
  .link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
482
514
  textarea {
483
515
  width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);
@@ -490,7 +522,19 @@ textarea::placeholder { color: var(--ink-dim); }
490
522
  cursor: pointer; border: 0; padding: 15px 14px;
491
523
  background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);
492
524
  }
525
+ .send { display: flex; align-items: center; justify-content: center; gap: 9px; }
493
526
  .send[disabled] { opacity: 0.5; cursor: default; }
527
+ .sheet[data-busy] .send { opacity: 1; }
528
+ .dots { display: inline-flex; gap: 4px; }
529
+ .dots i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; opacity: 0.4; }
530
+ .done { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 14px 0 10px; }
531
+ .ring { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; background: #1f7a4d; color: #fff; }
532
+ .ring svg { width: 26px; height: 26px; }
533
+ .done-title {
534
+ margin: 0; font-family: var(--font-display); font-weight: var(--display-weight);
535
+ font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking);
536
+ }
537
+ .said { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
494
538
  .status { margin: 0; font-size: 13px; color: var(--ink-dim); min-height: 1.4em; }
495
539
  [hidden] { display: none !important; }
496
540
  @media (prefers-reduced-motion: no-preference) {
@@ -500,13 +544,31 @@ textarea::placeholder { color: var(--ink-dim); }
500
544
  }
501
545
  .bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }
502
546
  .sheet { animation: rise 0.22s ease-out; }
503
- .shot[data-pending] .ghost { animation: wait 1.1s ease-in-out infinite alternate; }
504
- @keyframes wait { from { opacity: 0.45; } to { opacity: 1; } }
547
+ .shot[data-shot="taking"] .scan { animation: fsf-scan 1.3s cubic-bezier(0.45, 0, 0.55, 1) infinite; }
548
+ @keyframes fsf-scan { from { transform: translateY(-100%); } to { transform: translateY(300%); } }
549
+ .finder i { animation: fsf-frame 0.9s ease-in-out infinite alternate; }
550
+ @keyframes fsf-frame { from { opacity: 0.55; } to { opacity: 1; } }
551
+ .shot[data-shot="attached"] img { animation: fsf-land 0.28s cubic-bezier(0.2, 0.8, 0.2, 1); }
552
+ @keyframes fsf-land { from { transform: scale(1.14); opacity: 0; } to { transform: none; opacity: 1; } }
553
+ .shot[data-shot="attached"] .badge { animation: fsf-pop 0.24s cubic-bezier(0.2, 0.8, 0.2, 1) 0.14s backwards; }
554
+ .ring { animation: fsf-pop 0.26s cubic-bezier(0.2, 0.8, 0.2, 1); }
555
+ @keyframes fsf-pop { from { transform: scale(0.4); opacity: 0; } to { transform: none; opacity: 1; } }
556
+ .dots i { animation: fsf-dot 1s ease-in-out infinite; }
557
+ .dots i:nth-child(2) { animation-delay: 0.15s; }
558
+ .dots i:nth-child(3) { animation-delay: 0.3s; }
559
+ @keyframes fsf-dot { 30% { opacity: 1; transform: translateY(-2px); } 60% { opacity: 0.4; transform: none; } }
505
560
  @keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
506
561
  }
507
562
  `;
508
563
  var mounted = null;
509
564
  var feedbackOptedOut = (doc = document) => doc.documentElement.getAttribute("data-fs-feedback") === "off";
565
+ var CHECK_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>';
566
+ var CAMERA_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="6.5" width="17" height="12.5" rx="3"/><circle cx="12" cy="12.8" r="3.2"/><path d="M9 6.5l1-2h4l1 2"/></svg>';
567
+ var SHOT_LABEL = {
568
+ taking: "Taking a screenshot",
569
+ attached: "Screenshot attached",
570
+ none: "No screenshot"
571
+ };
510
572
  var SCROLL_OPTIONS = { capture: true, passive: true };
511
573
  function mountFlowFeedback(options) {
512
574
  if (typeof document === "undefined" || !document.body) return null;
@@ -519,14 +581,22 @@ function mountFlowFeedback(options) {
519
581
  <button type="button" class="bug" data-state="float" aria-label="Send feedback on this page">${BUG_ICON}</button>
520
582
  <div class="probe" aria-hidden="true"></div>
521
583
  <div class="scrim" hidden></div>
522
- <div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" hidden>
584
+ <div class="finder" aria-hidden="true" hidden><i></i><i></i><i></i><i></i></div>
585
+ <div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" data-view="form" hidden>
523
586
  <div class="grab" aria-hidden="true"></div>
524
- <h2 id="fsf-title">Feedback on this page</h2>
525
- <div class="shot" hidden><span class="ghost" aria-hidden="true"></span><img alt="Screenshot of this page" /><span><span class="shot-label">Screenshot attached</span><br /><button type="button" class="link remove">Remove screenshot</button></span></div>
526
- <textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
527
- <p class="status" role="status" aria-live="polite"></p>
528
- <button type="button" class="send" disabled>Send feedback</button>
529
- <button type="button" class="link tuck">Tuck the button to the side</button>
587
+ <div class="form">
588
+ <h2 id="fsf-title">Feedback on this page</h2>
589
+ <div class="shot" data-shot="none">
590
+ <span class="thumb"><span class="scan" aria-hidden="true"></span><img alt="Screenshot of this page" /><span class="empty" aria-hidden="true">${CAMERA_ICON}</span><span class="badge" aria-hidden="true">${CHECK_ICON}</span></span>
591
+ <span class="shot-text"><span class="shot-label" role="status" aria-live="polite">No screenshot</span><button type="button" class="link remove" hidden>Remove</button><button type="button" class="link retake" hidden>Take screenshot</button></span>
592
+ </div>
593
+ <textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
594
+ <p class="status" role="status" aria-live="polite"></p>
595
+ <button type="button" class="send" disabled><span class="send-label">Send feedback</span><span class="dots" aria-hidden="true" hidden><i></i><i></i><i></i></span></button>
596
+ <button type="button" class="link tuck">Tuck the button to the side</button>
597
+ </div>
598
+ <div class="done" hidden><span class="ring">${CHECK_ICON}</span><p class="done-title" aria-hidden="true">Feedback sent</p></div>
599
+ <p class="said" role="status" aria-live="polite"></p>
530
600
  </div>`;
531
601
  const q = (selector) => root.querySelector(selector);
532
602
  const bug = q(".bug");
@@ -536,6 +606,13 @@ function mountFlowFeedback(options) {
536
606
  const img = q(".shot img");
537
607
  const shotLabel = q(".shot-label");
538
608
  const remove = q(".remove");
609
+ const retake = q(".retake");
610
+ const finder = q(".finder");
611
+ const form = q(".form");
612
+ const done = q(".done");
613
+ const said = q(".said");
614
+ const sendLabel = q(".send-label");
615
+ const dots = q(".dots");
539
616
  const text = q("textarea");
540
617
  const status = q(".status");
541
618
  const send = q(".send");
@@ -761,14 +838,54 @@ function mountFlowFeedback(options) {
761
838
  };
762
839
  let round = 0;
763
840
  let pending = null;
764
- const setShot = (url) => {
765
- screenshot = url;
766
- shot.hidden = url === null;
767
- shot.removeAttribute("data-pending");
768
- shotLabel.textContent = "Screenshot attached";
769
- if (url) img.src = url;
841
+ const setShot = (state, url = null) => {
842
+ screenshot = state === "attached" ? url : null;
843
+ shot.dataset.shot = state;
844
+ shotLabel.textContent = SHOT_LABEL[state];
845
+ remove.hidden = state !== "attached" || busy;
846
+ retake.hidden = state !== "none" || busy;
847
+ finder.hidden = state !== "taking";
848
+ if (screenshot) img.src = screenshot;
770
849
  else img.removeAttribute("src");
771
850
  };
851
+ const setBusy = (next) => {
852
+ busy = next;
853
+ if (busy) sheet.setAttribute("data-busy", "");
854
+ else sheet.removeAttribute("data-busy");
855
+ text.readOnly = busy;
856
+ sendLabel.textContent = busy ? "Sending" : "Send feedback";
857
+ dots.hidden = !busy;
858
+ const state = shot.dataset.shot;
859
+ remove.hidden = state !== "attached" || busy;
860
+ retake.hidden = state !== "none" || busy;
861
+ sync();
862
+ };
863
+ const setView = (view) => {
864
+ sheet.dataset.view = view;
865
+ form.hidden = view !== "form";
866
+ done.hidden = view !== "sent";
867
+ said.textContent = view === "sent" ? "Feedback sent" : "";
868
+ };
869
+ const take = () => {
870
+ const mine = round += 1;
871
+ setShot("taking");
872
+ host.style.setProperty("--finder-bottom", `${sheet.offsetHeight + 10}px`);
873
+ host.style.setProperty(
874
+ "--shot-ratio",
875
+ String(Math.max(0.3, Math.min(1, window.innerWidth / Math.max(1, window.innerHeight))))
876
+ );
877
+ const capture = options.capture ?? (() => captureViewport(theme.ground));
878
+ const work = withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS).then((url) => {
879
+ if (mine !== round) return;
880
+ pending = null;
881
+ setShot(url ? "attached" : "none", url);
882
+ const active = root.activeElement;
883
+ if (!busy && (!active || active === bug || active === retake || active === remove))
884
+ text.focus();
885
+ });
886
+ pending = work;
887
+ return work;
888
+ };
772
889
  const close = () => {
773
890
  if (closeTimer) clearTimeout(closeTimer);
774
891
  closeTimer = null;
@@ -777,10 +894,11 @@ function mountFlowFeedback(options) {
777
894
  sheet.hidden = true;
778
895
  scrim.hidden = true;
779
896
  bug.hidden = false;
780
- setShot(null);
897
+ setBusy(false);
898
+ setShot("none");
899
+ setView("form");
781
900
  text.value = "";
782
901
  status.textContent = "";
783
- busy = false;
784
902
  sync();
785
903
  schedulePlace();
786
904
  };
@@ -788,34 +906,34 @@ function mountFlowFeedback(options) {
788
906
  if (!sheet.hidden) return pending ?? Promise.resolve();
789
907
  if (busy) return Promise.resolve();
790
908
  paint();
791
- const mine = round += 1;
792
909
  bug.hidden = true;
793
- setShot(null);
794
- shot.hidden = false;
795
- shot.setAttribute("data-pending", "");
796
- shotLabel.textContent = "Taking a screenshot";
910
+ setView("form");
797
911
  tuck.hidden = spot.state !== "float";
798
912
  scrim.hidden = false;
799
913
  sheet.hidden = false;
800
914
  sync();
801
- const capture = options.capture ?? (() => captureViewport(theme.ground));
802
- pending = withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS).then((url) => {
803
- if (mine !== round) return;
804
- pending = null;
805
- setShot(url);
806
- if (!root.activeElement || root.activeElement === bug) text.focus();
807
- });
808
- return pending;
915
+ return take();
809
916
  };
810
917
  const submit = async () => {
811
918
  const message = text.value.trim();
812
919
  if (!message || busy) return;
813
- busy = true;
814
- sync();
920
+ setBusy(true);
815
921
  status.textContent = "";
816
- const mine = round;
817
- if (pending) await pending;
818
- if (mine !== round) return;
922
+ let mine = round;
923
+ if (pending) {
924
+ let timer;
925
+ await Promise.race([
926
+ pending,
927
+ new Promise((resolve) => timer = setTimeout(resolve, SEND_WAIT_MS))
928
+ ]);
929
+ clearTimeout(timer);
930
+ if (mine !== round) return;
931
+ if (pending) {
932
+ mine = round += 1;
933
+ pending = null;
934
+ setShot("none");
935
+ }
936
+ }
819
937
  try {
820
938
  await options.submit({
821
939
  message,
@@ -824,15 +942,16 @@ function mountFlowFeedback(options) {
824
942
  pagePath: window.location.pathname.slice(-200),
825
943
  viewport: { width: window.innerWidth, height: window.innerHeight }
826
944
  });
827
- status.textContent = "Sent.";
945
+ if (mine !== round) return;
946
+ setView("sent");
828
947
  closeTimer = setTimeout(() => {
829
948
  close();
830
949
  bug.focus();
831
- }, 1200);
950
+ }, SENT_HOLD_MS);
832
951
  } catch {
833
- busy = false;
952
+ if (mine !== round) return;
953
+ setBusy(false);
834
954
  status.textContent = "That did not send. Try again.";
835
- sync();
836
955
  }
837
956
  };
838
957
  bug.addEventListener("click", () => {
@@ -866,7 +985,15 @@ function mountFlowFeedback(options) {
866
985
  remove.addEventListener("click", () => {
867
986
  round += 1;
868
987
  pending = null;
869
- setShot(null);
988
+ setShot("none");
989
+ retake.focus();
990
+ });
991
+ retake.addEventListener("click", () => void take());
992
+ sheet.addEventListener("click", () => {
993
+ if (sheet.dataset.view === "sent") {
994
+ close();
995
+ bug.focus();
996
+ }
870
997
  });
871
998
  text.addEventListener("input", sync);
872
999
  send.addEventListener("click", () => void submit());