@goflowstate/widget-sdk 0.3.2 → 0.3.4

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.
@@ -23,6 +23,12 @@ export interface FlowFeedbackTheme {
23
23
  fontBody: string;
24
24
  fontDisplay: string;
25
25
  displayWeight: string;
26
+ /** Letter spacing of the flow's titles, in em, so it scales to the sheet's title size. */
27
+ displayTracking: string;
28
+ fontButton: string;
29
+ buttonWeight: string;
30
+ buttonTracking: string;
31
+ buttonTransform: string;
26
32
  radius: string;
27
33
  buttonRadius: string;
28
34
  dark: boolean;
@@ -41,7 +47,8 @@ export interface FlowFeedbackOptions {
41
47
  /** Sends the report. The session client passes `session.request('feedback', report)`. */
42
48
  submit: (report: FlowFeedbackReport) => Promise<unknown>;
43
49
  /** Renders the page to a JPEG data URL, or null when it cannot. Defaults to the built-in
44
- * rasterizer; tests and unusual hosts pass their own. */
50
+ * rasterizer; tests and unusual hosts pass their own. It runs while the sheet is already
51
+ * open, so a host's own capture must leave the feedback host element out of the picture. */
45
52
  capture?: () => Promise<string | null>;
46
53
  /** Called once the icon is on the page, so a host page can hide its own fallback. */
47
54
  onReady?: () => void;
@@ -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.2
1
+ // @goflowstate/widget-sdk v0.3.4
2
2
  // https://github.com/goflowstate/goflowstate-sdk
3
3
 
4
4
  var __defProp = Object.defineProperty;
@@ -121,6 +121,46 @@ var primaryButton = (doc, ground) => {
121
121
  });
122
122
  return best;
123
123
  };
124
+ var TEXT_SAMPLE_LIMIT = 600;
125
+ var DISPLAY_MIN_PX = 22;
126
+ var sizeOf = (style) => {
127
+ const n = parseFloat(style.fontSize);
128
+ return Number.isFinite(n) && n > 0 ? n : 16;
129
+ };
130
+ var textSamples = (doc) => {
131
+ const out = [];
132
+ const all = doc.body.querySelectorAll("*");
133
+ for (let i = 0; i < all.length && out.length < TEXT_SAMPLE_LIMIT; i += 1) {
134
+ const el = all[i];
135
+ if (el.tagName === "SCRIPT" || el.tagName === "STYLE" || inOurs(el)) continue;
136
+ let chars = 0;
137
+ for (const node of Array.from(el.childNodes)) {
138
+ if (node.nodeType === 3) chars += (node.textContent ?? "").trim().length;
139
+ }
140
+ if (chars === 0 || !visible(el)) continue;
141
+ const style = styleOf(el);
142
+ if (!style.fontFamily) continue;
143
+ out.push({
144
+ family: style.fontFamily,
145
+ size: sizeOf(style),
146
+ chars,
147
+ style,
148
+ heading: /^H[1-3]$/.test(el.tagName)
149
+ });
150
+ }
151
+ return out;
152
+ };
153
+ var trackingEm = (style, min, max, fallback) => {
154
+ const px = style ? parseFloat(style.letterSpacing) : NaN;
155
+ const em = style && Number.isFinite(px) ? px / sizeOf(style) : fallback;
156
+ const held = Math.min(max, Math.max(min, em));
157
+ return held === 0 ? "0em" : `${Math.round(held * 1e3) / 1e3}em`;
158
+ };
159
+ var weightIn = (style, min, max, fallback) => {
160
+ const raw = style?.fontWeight ?? "";
161
+ const n = raw === "bold" ? 700 : raw === "normal" ? 400 : parseFloat(raw);
162
+ return Number.isFinite(n) ? String(Math.min(max, Math.max(min, n))) : fallback;
163
+ };
124
164
  function readFlowTheme(doc = document) {
125
165
  const body = doc.body;
126
166
  const stage = body.querySelector(":scope > :not(script):not(style):not([data-fs-feedback-host])");
@@ -143,12 +183,32 @@ function readFlowTheme(doc = document) {
143
183
  const pinnedCtaInk = parseColor(rootVar(doc, ["--fs-feedback-cta-ink"]));
144
184
  const sampledCtaInk = !pinnedCta && sampledCta && buttonStyle ? parseColor(buttonStyle.color) : null;
145
185
  const ctaInk = pinnedCtaInk ?? (sampledCtaInk && contrast(sampledCtaInk, cta) >= 3 ? sampledCtaInk : null) ?? (luminance(cta) > 0.4 ? BLACK : WHITE);
146
- const heading = Array.from(doc.querySelectorAll('h1, h2, [class*="title"]')).find(
186
+ const samples = textSamples(doc);
187
+ const headings = Array.from(doc.querySelectorAll('h1, h2, [class*="title"]')).filter(
147
188
  (el) => !inOurs(el) && visible(el)
148
189
  );
149
- const headingStyle = heading ? styleOf(heading) : null;
150
- const fontBody = rootVar(doc, ["--fs-feedback-font"]) || (stage ? styleOf(stage).fontFamily : "") || bodyStyle.fontFamily || rootVar(doc, ["--sb-font-body", "--font-body"]) || "system-ui, sans-serif";
190
+ const largest = (items, size) => items.reduce(
191
+ (best, item) => best === null || size(item) > size(best) ? item : best,
192
+ null
193
+ );
194
+ const bigHeading = largest(
195
+ headings.filter((el) => sizeOf(styleOf(el)) >= DISPLAY_MIN_PX),
196
+ (el) => sizeOf(styleOf(el))
197
+ );
198
+ const bigLine = largest(
199
+ samples.filter((t) => t.size >= DISPLAY_MIN_PX && t.chars >= 3),
200
+ (t) => t.size
201
+ );
202
+ const headingStyle = bigHeading ? styleOf(bigHeading) : bigLine?.style ?? (headings[0] ? styleOf(headings[0]) : null);
203
+ const byFamily = /* @__PURE__ */ new Map();
204
+ for (const t of samples) {
205
+ if (t.size < DISPLAY_MIN_PX && !t.heading)
206
+ byFamily.set(t.family, (byFamily.get(t.family) ?? 0) + t.chars);
207
+ }
208
+ const readBody = largest(Array.from(byFamily), ([, chars]) => chars)?.[0] ?? "";
209
+ const fontBody = rootVar(doc, ["--fs-feedback-font"]) || readBody || (stage ? styleOf(stage).fontFamily : "") || bodyStyle.fontFamily || rootVar(doc, ["--sb-font-body", "--font-body"]) || "system-ui, sans-serif";
151
210
  const fontDisplay = rootVar(doc, ["--fs-feedback-font-display"]) || headingStyle?.fontFamily || rootVar(doc, ["--sb-font-display", "--sb-font-title", "--font-display"]) || fontBody;
211
+ const buttonType = buttonStyle?.fontFamily ? buttonStyle : null;
152
212
  const field = Array.from(
153
213
  doc.querySelectorAll('textarea, input[type="text"], input:not([type])')
154
214
  ).find((el) => !inOurs(el) && visible(el));
@@ -172,6 +232,11 @@ function readFlowTheme(doc = document) {
172
232
  fontBody,
173
233
  fontDisplay,
174
234
  displayWeight: headingStyle?.fontWeight || "500",
235
+ displayTracking: trackingEm(headingStyle, -0.04, 0.02, -0.01),
236
+ fontButton: buttonType?.fontFamily || fontBody,
237
+ buttonWeight: weightIn(buttonType, 500, 800, "600"),
238
+ buttonTracking: trackingEm(buttonType, -0.02, 0.08, 0),
239
+ buttonTransform: buttonType?.textTransform === "uppercase" ? "uppercase" : "none",
175
240
  radius,
176
241
  buttonRadius: buttonRadius === null ? "999px" : `${Math.min(buttonRadius, 999)}px`,
177
242
  dark
@@ -197,9 +262,10 @@ function encodeScreenshot(canvas) {
197
262
  return null;
198
263
  }
199
264
  async function captureViewport(ground) {
200
- const { domToCanvas } = await import("modern-screenshot");
201
265
  const width = window.innerWidth;
202
266
  const height = window.innerHeight;
267
+ const { scrollX, scrollY } = window;
268
+ const { domToCanvas } = await import("modern-screenshot");
203
269
  const scale = Math.min(
204
270
  window.devicePixelRatio || 1,
205
271
  SCREENSHOT_MAX_SIDE / Math.max(width, height)
@@ -209,7 +275,7 @@ async function captureViewport(ground) {
209
275
  height,
210
276
  scale,
211
277
  backgroundColor: ground,
212
- style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)` },
278
+ style: { transform: `translate(${-scrollX}px, ${-scrollY}px)` },
213
279
  filter: (node) => !(node instanceof Element && node.hasAttribute(FEEDBACK_HOST_ATTR))
214
280
  });
215
281
  return encodeScreenshot(canvas);
@@ -403,9 +469,15 @@ var STYLE = `
403
469
  font-family: var(--font-body); font-size: 15px; line-height: 1.4;
404
470
  }
405
471
  .grab { width: 36px; height: 4px; border-radius: 2px; background: var(--line); align-self: center; }
406
- h2 { margin: 0; font-family: var(--font-display); font-weight: var(--display-weight); font-size: 19px; line-height: 1.2; }
472
+ h2 {
473
+ margin: 2px 0 0; font-family: var(--font-display); font-weight: var(--display-weight);
474
+ font-size: 22px; line-height: 1.15; letter-spacing: var(--display-tracking); text-wrap: balance;
475
+ }
407
476
  .shot { display: flex; gap: 12px; align-items: center; font-size: 13px; color: var(--ink-dim); }
408
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; }
409
481
  .link { background: none; border: 0; padding: 0; font: inherit; color: var(--ink); text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
410
482
  textarea {
411
483
  width: 100%; min-height: 84px; resize: none; font: inherit; font-size: 16px; color: var(--ink);
@@ -413,7 +485,9 @@ textarea {
413
485
  }
414
486
  textarea::placeholder { color: var(--ink-dim); }
415
487
  .send {
416
- font: inherit; font-weight: 500; cursor: pointer; border: 0; padding: 14px;
488
+ font-family: var(--font-button); font-weight: var(--button-weight); font-size: 17px; line-height: 1.2;
489
+ letter-spacing: var(--button-tracking); text-transform: var(--button-transform);
490
+ cursor: pointer; border: 0; padding: 15px 14px;
417
491
  background: var(--cta); color: var(--cta-ink); border-radius: var(--button-radius);
418
492
  }
419
493
  .send[disabled] { opacity: 0.5; cursor: default; }
@@ -426,6 +500,8 @@ textarea::placeholder { color: var(--ink-dim); }
426
500
  }
427
501
  .bug svg { transition: transform 0.1s ease-out, width 0.22s ease, height 0.22s ease; }
428
502
  .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; } }
429
505
  @keyframes rise { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
430
506
  }
431
507
  `;
@@ -446,7 +522,7 @@ function mountFlowFeedback(options) {
446
522
  <div class="sheet" role="dialog" aria-modal="true" aria-labelledby="fsf-title" hidden>
447
523
  <div class="grab" aria-hidden="true"></div>
448
524
  <h2 id="fsf-title">Feedback on this page</h2>
449
- <div class="shot" hidden><img alt="Screenshot of this page" /><span><span>Screenshot attached</span><br /><button type="button" class="link remove">Remove screenshot</button></span></div>
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>
450
526
  <textarea aria-labelledby="fsf-title" placeholder="What should change?" maxlength="2000"></textarea>
451
527
  <p class="status" role="status" aria-live="polite"></p>
452
528
  <button type="button" class="send" disabled>Send feedback</button>
@@ -458,6 +534,7 @@ function mountFlowFeedback(options) {
458
534
  const sheet = q(".sheet");
459
535
  const shot = q(".shot");
460
536
  const img = q(".shot img");
537
+ const shotLabel = q(".shot-label");
461
538
  const remove = q(".remove");
462
539
  const text = q("textarea");
463
540
  const status = q(".status");
@@ -668,6 +745,11 @@ function mountFlowFeedback(options) {
668
745
  "--font-body": theme.fontBody,
669
746
  "--font-display": theme.fontDisplay,
670
747
  "--display-weight": theme.displayWeight,
748
+ "--display-tracking": theme.displayTracking,
749
+ "--font-button": theme.fontButton,
750
+ "--button-weight": theme.buttonWeight,
751
+ "--button-tracking": theme.buttonTracking,
752
+ "--button-transform": theme.buttonTransform,
671
753
  "--radius": theme.radius,
672
754
  "--button-radius": theme.buttonRadius
673
755
  };
@@ -677,15 +759,21 @@ function mountFlowFeedback(options) {
677
759
  const sync = () => {
678
760
  send.disabled = busy || text.value.trim().length === 0;
679
761
  };
762
+ let round = 0;
763
+ let pending = null;
680
764
  const setShot = (url) => {
681
765
  screenshot = url;
682
766
  shot.hidden = url === null;
767
+ shot.removeAttribute("data-pending");
768
+ shotLabel.textContent = "Screenshot attached";
683
769
  if (url) img.src = url;
684
770
  else img.removeAttribute("src");
685
771
  };
686
772
  const close = () => {
687
773
  if (closeTimer) clearTimeout(closeTimer);
688
774
  closeTimer = null;
775
+ round += 1;
776
+ pending = null;
689
777
  sheet.hidden = true;
690
778
  scrim.hidden = true;
691
779
  bug.hidden = false;
@@ -696,20 +784,28 @@ function mountFlowFeedback(options) {
696
784
  sync();
697
785
  schedulePlace();
698
786
  };
699
- const open = async () => {
700
- if (!sheet.hidden || busy) return;
701
- busy = true;
787
+ const open = () => {
788
+ if (!sheet.hidden) return pending ?? Promise.resolve();
789
+ if (busy) return Promise.resolve();
702
790
  paint();
791
+ const mine = round += 1;
703
792
  bug.hidden = true;
704
- const capture = options.capture ?? (() => captureViewport(theme.ground));
705
- const url = await withTimeout(Promise.resolve().then(capture), CAPTURE_TIMEOUT_MS);
706
- busy = false;
707
- setShot(url);
793
+ setShot(null);
794
+ shot.hidden = false;
795
+ shot.setAttribute("data-pending", "");
796
+ shotLabel.textContent = "Taking a screenshot";
708
797
  tuck.hidden = spot.state !== "float";
709
798
  scrim.hidden = false;
710
799
  sheet.hidden = false;
711
800
  sync();
712
- text.focus();
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;
713
809
  };
714
810
  const submit = async () => {
715
811
  const message = text.value.trim();
@@ -717,6 +813,9 @@ function mountFlowFeedback(options) {
717
813
  busy = true;
718
814
  sync();
719
815
  status.textContent = "";
816
+ const mine = round;
817
+ if (pending) await pending;
818
+ if (mine !== round) return;
720
819
  try {
721
820
  await options.submit({
722
821
  message,
@@ -740,6 +839,11 @@ function mountFlowFeedback(options) {
740
839
  if (!suppressClick) void open();
741
840
  });
742
841
  bug.addEventListener("pointerdown", onDown);
842
+ if (!options.capture)
843
+ bug.addEventListener("pointerdown", () => void import("modern-screenshot").catch(() => {
844
+ }), {
845
+ once: true
846
+ });
743
847
  bug.addEventListener("pointermove", onMove);
744
848
  bug.addEventListener("pointerup", onUp);
745
849
  bug.addEventListener("pointercancel", onUp);
@@ -759,7 +863,11 @@ function mountFlowFeedback(options) {
759
863
  );
760
864
  });
761
865
  scrim.addEventListener("click", close);
762
- remove.addEventListener("click", () => setShot(null));
866
+ remove.addEventListener("click", () => {
867
+ round += 1;
868
+ pending = null;
869
+ setShot(null);
870
+ });
763
871
  text.addEventListener("input", sync);
764
872
  send.addEventListener("click", () => void submit());
765
873
  sheet.addEventListener("keydown", (event) => {