@hitslop/slurp-sdk 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -111,7 +111,7 @@ init({ key: 'slurp_pk_…', endpoint: 'https://slurp.YOUR-SUBDOMAIN.workers.dev/
111
111
  mountFeedback({ askEmail: true, data: () => ({ plan: currentPlan() }) });
112
112
  ```
113
113
 
114
- The event is `{ event: 'feedback', data: { message, rating?, email?, ...data() } }` with the usual page context, so in SQL: `SELECT timestamp, data->>'message', data->>'rating' FROM events WHERE event = 'feedback'`. Options: `client` (default: the client from `init`), `label`, `title`, `placeholder`, `ratings` (default true), `askEmail` (default false), `position` (`bottom-right` or `bottom-left`), `accent`, `theme` (`auto` follows the visitor's dark mode; `light` or `dark` match your page), `event`, `data`, `button: false` (open it from your own button with the returned `open()`), `container`. It returns `{ open, close, destroy }`. Text a person types is only ever set as text, never parsed as HTML, and the styles live in a shadow root so they neither affect nor inherit from your page. Browser only.
114
+ The event is `{ event: 'feedback', data: { message, rating?, email?, ...data() } }` with the usual page context, so in SQL: `SELECT timestamp, data->>'message', data->>'rating' FROM events WHERE event = 'feedback'`. Options: `client` (default: the client from `init`), `label`, `title`, `placeholder`, `ratings` (default true), `askEmail` (default false), `position` (`bottom-right` or `bottom-left`), `accent`, `theme` (`auto` follows the visitor's dark mode; `light` or `dark` match your page), `event`, `data`, `button: false` (open it from your own button with the returned `open()`), `container`, `poweredBy` (default true: a small "Powered by Slurp" link under the form, opening slurp.run in a new tab without sending your site's address; `false` hides it). It returns `{ open, close, destroy }`. Text a person types is only ever set as text, never parsed as HTML, and the styles live in a shadow root so they neither affect nor inherit from your page. Browser only.
115
115
 
116
116
  ## Contributing
117
117
 
package/dist/event.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const version = "0.1.1";
1
+ export declare const version = "0.1.2";
2
2
  export declare const encoder: {
3
3
  encode(text: string): Uint8Array;
4
4
  };
package/dist/event.js CHANGED
@@ -1,4 +1,4 @@
1
- export const version = '0.1.1';
1
+ export const version = '0.1.2';
2
2
  /** UTF-8 for runtimes without TextEncoder (React Native before Hermes shipped it). */
3
3
  function utf8Encode(text) {
4
4
  const out = [];
@@ -26,6 +26,8 @@ export interface FeedbackOptions {
26
26
  button?: boolean;
27
27
  /** Where to attach the widget. Default `document.body`. */
28
28
  container?: HTMLElement;
29
+ /** Show the small "Powered by Slurp" link under the form. Default true; set false to hide it. */
30
+ poweredBy?: boolean;
29
31
  }
30
32
  export interface FeedbackHandle {
31
33
  open(): void;
package/dist/feedback.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { track as defaultTrack } from "./index.js";
2
2
  const MAX_MESSAGE = 5000;
3
3
  const HOST_ATTRIBUTE = 'data-slurp-feedback';
4
+ /** The "Powered by" link opens in a new tab without telling slurp.run which site it came from; the utm tag says only that it was this widget. */
5
+ const POWERED_BY_URL = 'https://slurp.run/?utm_source=feedback-widget';
4
6
  const STYLE = `
5
7
  :host { all: initial; }
6
8
  * { box-sizing: border-box; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
@@ -22,17 +24,24 @@ textarea:focus-visible, input:focus-visible, button:focus-visible { outline: 2px
22
24
  .row button { border-radius: 8px; padding: 8px 14px; font-size: 14px; font-weight: 600; cursor: pointer; border: 1px solid #c9ced6; background: #fff; color: #17191c; }
23
25
  .row .send { background: var(--accent); border-color: var(--accent); color: #fff; }
24
26
  .error { margin: 8px 0 0; font-size: 13px; color: #b3001f; }
27
+ .powered { margin: 12px 0 0; text-align: center; font-size: 11px; color: #8a919b; }
28
+ .powered a { color: inherit; text-decoration: none; }
29
+ .powered a:hover, .powered a:focus-visible { color: var(--accent); text-decoration: underline; }
25
30
  .thanks { margin: 6px 0 2px; font-size: 15px; }
26
31
  @media (prefers-color-scheme: dark) {
27
32
  :host([data-theme=auto]) .panel { background: #1f2327; color: #f2f4f6; border-color: #3a4048; }
28
33
  :host([data-theme=auto]) textarea, :host([data-theme=auto]) input[type=email] { background: #15181b; border-color: #3a4048; }
29
34
  :host([data-theme=auto]) .rating button, :host([data-theme=auto]) .row button { background: #15181b; border-color: #3a4048; color: #f2f4f6; }
30
35
  :host([data-theme=auto]) .rating legend { color: #9aa1aa; }
36
+ :host([data-theme=auto]) .row .send { background: var(--accent); border-color: var(--accent); color: #fff; }
37
+ :host([data-theme=auto]) .powered { color: #7d858f; }
31
38
  }
32
39
  :host([data-theme=dark]) .panel { background: #1f2327; color: #f2f4f6; border-color: #3a4048; }
33
40
  :host([data-theme=dark]) textarea, :host([data-theme=dark]) input[type=email] { background: #15181b; border-color: #3a4048; }
34
41
  :host([data-theme=dark]) .rating button, :host([data-theme=dark]) .row button { background: #15181b; border-color: #3a4048; color: #f2f4f6; }
35
42
  :host([data-theme=dark]) .rating legend { color: #9aa1aa; }
43
+ :host([data-theme=dark]) .row .send { background: var(--accent); border-color: var(--accent); color: #fff; }
44
+ :host([data-theme=dark]) .powered { color: #7d858f; }
36
45
  `;
37
46
  function element(tag, attributes = {}, text) {
38
47
  const node = document.createElement(tag);
@@ -50,7 +59,7 @@ function element(tag, attributes = {}, text) {
50
59
  export function mountFeedback(options = {}) {
51
60
  if (typeof document === 'undefined')
52
61
  throw new Error('mountFeedback needs a browser document');
53
- const { label = 'Feedback', title = 'Send feedback', placeholder = 'What is working, and what is not?', ratings = true, askEmail = false, position = 'bottom-right', accent = '#e8002a', event = 'feedback', button = true, theme = 'auto', } = options;
62
+ const { label = 'Feedback', title = 'Send feedback', placeholder = 'What is working, and what is not?', ratings = true, askEmail = false, position = 'bottom-right', accent = '#e8002a', event = 'feedback', button = true, theme = 'auto', poweredBy = true, } = options;
54
63
  const container = options.container ?? document.body;
55
64
  for (const old of container.querySelectorAll(`[${HOST_ATTRIBUTE}]`))
56
65
  old.remove(); // mounting twice replaces, never stacks
@@ -88,6 +97,12 @@ export function mountFeedback(options = {}) {
88
97
  row.append(cancel, send);
89
98
  form.append(...[ratingGroup, message, email, error, row].filter((node) => node !== undefined));
90
99
  panel.append(heading, form);
100
+ let credit;
101
+ if (poweredBy) {
102
+ credit = element('p', { class: 'powered' });
103
+ credit.append('Powered by ', element('a', { href: POWERED_BY_URL, target: '_blank', rel: 'noopener noreferrer' }, 'Slurp'));
104
+ panel.append(credit);
105
+ }
91
106
  root.append(...(button ? [fab, panel] : [panel]));
92
107
  let timer;
93
108
  let thanks;
@@ -158,7 +173,7 @@ export function mountFeedback(options = {}) {
158
173
  }
159
174
  form.setAttribute('hidden', '');
160
175
  thanks = element('p', { class: 'thanks', role: 'status' }, 'Thank you! We read every message.');
161
- panel.appendChild(thanks);
176
+ panel.insertBefore(thanks, credit ?? null); // the thank-you sits above the credit line
162
177
  timer = setTimeout(() => { show(false); reset(); }, 2500);
163
178
  });
164
179
  container.appendChild(host);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hitslop/slurp-sdk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Browser and Node event capture for Slurp: durable queue, byte-identical retries and batching. No runtime dependencies.",
5
5
  "keywords": [
6
6
  "analytics",