artarch-feedback-sdk 0.1.0 → 0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # artarch-feedback-sdk
2
2
 
3
- Headless browser SDK for collecting feedback with screenshots and screen recordings. The SDK owns browser capture and asset lifecycle; the host application owns authentication, CDN storage, and feedback APIs.
3
+ Browser SDK for collecting feedback with screenshots and screen recordings. Use the headless core with your own UI, or mount the optional default widget. The SDK owns browser capture and asset lifecycle; the host application owns authentication, CDN storage, and feedback APIs.
4
4
 
5
5
  ## About ArtArch
6
6
 
@@ -24,6 +24,7 @@ and feedback adapters.
24
24
  | Capture lifecycle | Handles permission denial, cancellation, overlapping starts/stops, final media chunks, and release of tracks and listeners. |
25
25
  | Feedback submission | Accepts text alone, text with a screenshot, text with a recording, or both attachments. Uploads finish before the feedback record is submitted. |
26
26
  | Business context | Forwards host-provided `context`, such as platform, project ID, route, or node ID, without collecting it automatically. |
27
+ | Optional default UI | Top-of-page entry, introduction with a persistent opt-out, page frame, recording/screenshot/text toolbar, attachment preview, and feedback form. |
27
28
 
28
29
  Capture requires a secure context (HTTPS or localhost), a user gesture, and the
29
30
  browser's permission picker. The available surfaces and audio sources depend on
@@ -36,10 +37,11 @@ Next.js, Axios, Sentry, or a cloud storage provider.
36
37
 
37
38
  | Owner | Responsibilities |
38
39
  | --- | --- |
39
- | SDK | Browser capture, local media files, capture errors and cancellation, and sequencing upload/submission through `FeedbackTransport`. |
40
+ | SDK core | Browser capture, local media files, capture errors and cancellation, and sequencing upload/submission through `FeedbackTransport`. |
41
+ | SDK widget (optional) | Default dialogs, toolbar, styles, text form, local previews, validation, recording timer, and localized capture/submission states. |
40
42
  | Host `uploadAsset` adapter | Authentication, upload credentials, storage/CDN selection, and uploading the provided file. Returns `{ url, id? }`. |
41
43
  | Host `submitFeedback` adapter | Sending the message, uploaded attachment URLs, and context to a business API. Returns `{ id? }`. |
42
- | Host application | Feedback button and form, previews, validation, user-facing error messages, context selection, retention, and the feedback management backend. |
44
+ | Host application | Transport adapters, context selection, retention, and the feedback management backend. It may customize the default widget or replace it with its own UI using the core API. |
43
45
 
44
46
  A host can build this user flow around the SDK:
45
47
 
@@ -55,14 +57,18 @@ uploaded files left behind when another upload or the final submission fails.
55
57
 
56
58
  ## Current scope
57
59
 
58
- The current deliverable is the core SDK. It does not include a feedback widget,
59
- preview UI, screenshot annotation/redaction, video editing, a feedback backend,
60
+ The SDK includes a headless core and an optional default widget. It does not
61
+ include screenshot annotation/redaction, video editing, a feedback backend,
60
62
  or an administration console. It does not automatically collect console logs,
61
63
  network requests, page URLs, or historical session replays.
62
64
 
63
65
  The npm package name is `artarch-feedback-sdk`; its directory in this repository
64
- is `packages/feedback-sdk`. The SDK is not yet connected to the production
65
- `tool-canvas` feedback UI. Update this status when integrating the package.
66
+ is `packages/feedback-sdk`. Version `0.1.0` contains the core API; version `0.2.0`
67
+ adds the optional `/widget` entry with the default UI and customization options.
68
+ The tool-canvas integration mounts it globally, but its API
69
+ adapters are deliberately unconfigured: submission keeps the draft and displays
70
+ an unavailable message, without uploading or claiming success. Neither this UI
71
+ integration nor its transport is deployed to production.
66
72
 
67
73
  ## Install
68
74
 
@@ -74,6 +80,89 @@ pnpm add artarch-feedback-sdk
74
80
 
75
81
  For local development, run `npm pack` in this package and install the
76
82
  resulting `.tgz` in a consuming project. See the build and test commands below.
83
+ Workspace installation builds the SDK through its `prepare` script. If install
84
+ scripts are disabled, run `pnpm --filter artarch-feedback-sdk build` before
85
+ building or starting tool-canvas; rebuild it after changing SDK source.
86
+
87
+ ## Default widget
88
+
89
+ The optional UI is a separate entry, with no React or CSS framework dependency.
90
+ Import it only when you want the built-in interface. Call it after the document
91
+ is ready (for example, in a React effect), and call `destroy()` on unmount.
92
+
93
+ ```ts
94
+ import { createFeedbackWidget } from 'artarch-feedback-sdk/widget';
95
+
96
+ const widget = createFeedbackWidget({
97
+ transport: { uploadAsset, submitFeedback }, // Your adapters; see below.
98
+ locale: 'zh', // 'en' by default
99
+ context: () => ({ projectId, route: window.location.pathname }),
100
+ recording: { maxDurationMs: 60_000, audio: false },
101
+ });
102
+
103
+ // On application unmount:
104
+ widget.destroy();
105
+ ```
106
+
107
+ The default entry sits at the top center of the page. Opening it shows a page
108
+ frame and a toolbar with Record, Screenshot, and Text only. The introduction
109
+ appears once per widget instance; checking "Don't show this again" persists the
110
+ preference in localStorage. Storage being unavailable does not block feedback.
111
+
112
+ - Text opens a compact form beneath the toolbar. Blank/whitespace-only messages
113
+ cannot be sent; descriptions are limited to 5,000 characters.
114
+ - Screenshot capture hides the widget before opening the browser picker, then
115
+ displays the resulting image in a preview dialog with the description form.
116
+ - Recording keeps the page interactive and shows a timer, Stop, and Close.
117
+ Manual stop, the duration limit, and browser-ended sharing all open a video
118
+ preview. Close cancels and discards capture. The toolbar may appear in a recording.
119
+ - The paperclip accepts one PNG, JPEG, WebP, GIF, MP4, or WebM attachment, up to
120
+ 50 MiB by default. A new attachment replaces the previous one; remove returns
121
+ to text feedback without clearing the message. The core API can still accept
122
+ both a screenshot and a recording in a single submission.
123
+ - Files stay local until Send. Failed submissions retain the message and preview
124
+ for retry. Closing cancels pending work and revokes local preview URLs. Drafts
125
+ are in memory only; closing, navigating away, or reloading discards them.
126
+
127
+ ### Customize or replace the UI
128
+
129
+ ```ts
130
+ const widget = createFeedbackWidget({
131
+ transport: { uploadAsset, submitFeedback },
132
+ trigger: false, // Use your own entry button.
133
+ introduction: { storageKey: 'my-app:feedback-intro' }, // Or false to skip it.
134
+ locale: 'en',
135
+ labels: { trigger: 'Report a problem', introTitle: 'Help us improve' },
136
+ theme: {
137
+ '--feedback-accent': '#e0ff64',
138
+ '--feedback-accent-text': '#171717',
139
+ '--feedback-surface': '#141414',
140
+ '--feedback-top': '12px',
141
+ },
142
+ styles: '.text-panel { border-radius: 16px; }',
143
+ formatSubmitError: () => 'Could not send. Please try again.',
144
+ onSuccess: result => console.log('Feedback accepted:', result.id),
145
+ });
146
+
147
+ document.querySelector('#my-feedback-button')?.addEventListener('click', widget.open);
148
+ widget.setLocale('zh'); // Updates labels without losing the draft.
149
+ ```
150
+
151
+ Shadow DOM isolates the widget from host styles. Override `theme` custom properties
152
+ or use trusted `styles` inside the shadow root; external CSS can also target
153
+ `[data-artarch-feedback]::part(...)` or a class you assign to `widget.element`.
154
+ Public parts are `trigger`,
155
+ `frame`, `toolbar`, `intro`, `text-panel`, `review`, `preview`, `form`, `textarea`,
156
+ `footer`, and `notice`. Other variables include `--feedback-text`,
157
+ `--feedback-muted`, `--feedback-border`, `--feedback-field`, `--feedback-focus`,
158
+ `--feedback-error`, `--feedback-radius`, `--feedback-font`, and
159
+ `--feedback-z-index`. `nonce` supports applications with a CSP style nonce.
160
+ Native dialogs provide modal focus handling; the compact text panel leaves the
161
+ page usable. The layout adapts to narrow viewports and respects reduced motion.
162
+
163
+ For fully custom dialogs or rendering, use `FeedbackClient` from the main entry
164
+ and build your own interface. It does not import or mount the widget. The host
165
+ retains complete control of its upload and submission adapters in either mode.
77
166
 
78
167
  ## Host-provided transport
79
168
 
@@ -169,6 +258,7 @@ From the workspace root:
169
258
 
170
259
  ```bash
171
260
  pnpm install --frozen-lockfile
261
+ pnpm --filter artarch-feedback-sdk build
172
262
  pnpm --filter artarch-feedback-sdk exec playwright install chromium
173
263
  pnpm --filter artarch-feedback-sdk test:all
174
264
  ```
@@ -188,9 +278,30 @@ npm run test:all # Both suites
188
278
  outside the repository and checks native Node ESM imports and NodeNext types.
189
279
  - Playwright serves a local fixture on a random port and closes it after testing.
190
280
  It uses native `getDisplayMedia` and `MediaRecorder`, with Chromium test flags
191
- selecting/rejecting the fixture tab. Tests decode PNG pixels at desktop and
281
+ selecting the fixture tab and Permissions Policy producing native capture
282
+ denial. Tests decode PNG pixels at desktop and
192
283
  narrow viewport sizes, play and sample changing video frames, and check cleanup.
193
284
  No production accounts, CDN endpoints or feedback services are contacted.
285
+ - Widget tests cover introduction preferences, text validation, real screenshot
286
+ and video previews, automatic stop, failure/retry, cancellation, late capture
287
+ cleanup, attachment limits, localization, custom styles, and narrow viewports.
288
+ Transport callbacks are test doubles; they do not establish backend integration.
289
+
290
+ The host integration has separate checks:
291
+
292
+ ```bash
293
+ # From the tool-canvas workspace root:
294
+ node --experimental-strip-types --test src/components/Feedback/transport.test.ts
295
+ pnpm build:studio-test
296
+ pnpm exec dotenv -e .env.studio.test -- next start -p 5574
297
+ # In another terminal, against that local app:
298
+ node scripts/verify-feedback-ui.mjs
299
+ ```
300
+
301
+ The page check covers English and Chinese, the global entry, introduction,
302
+ unconfigured submission, and draft retention. It blocks remote requests and
303
+ saves screenshots under this package's `test-results/app-smoke/`. Override
304
+ `FEEDBACK_TEST_BASE_URL` for a different localhost port or base path.
194
305
 
195
306
  Successful screenshots and recordings are attached under `test-results/`.
196
307
  Failed browser tests retain screenshots and traces. Browser binaries are test
@@ -0,0 +1,34 @@
1
+ export interface FeedbackWidgetLabels {
2
+ trigger: string;
3
+ toolbar: string;
4
+ record: string;
5
+ screenshot: string;
6
+ text: string;
7
+ close: string;
8
+ introTitle: string;
9
+ introBody: string;
10
+ dontShowAgain: string;
11
+ acknowledge: string;
12
+ description: string;
13
+ placeholder: string;
14
+ attach: string;
15
+ removeAttachment: string;
16
+ cancel: string;
17
+ send: string;
18
+ sending: string;
19
+ stop: string;
20
+ requesting: string;
21
+ finalizing: string;
22
+ recording: string;
23
+ screenshotPreview: string;
24
+ recordingPreview: string;
25
+ success: string;
26
+ submitFailed: string;
27
+ captureFailed: string;
28
+ permissionDenied: string;
29
+ unsupported: string;
30
+ invalidAttachment: string;
31
+ attachmentTooLarge: string;
32
+ }
33
+ export declare function resolveWidgetLabels(locale: 'en' | 'zh', overrides?: Partial<FeedbackWidgetLabels>): FeedbackWidgetLabels;
34
+ //# sourceMappingURL=widget-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-labels.d.ts","sourceRoot":"","sources":["../src/widget-labels.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AA2CD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,oBAAoB,CAExH"}
@@ -0,0 +1,43 @@
1
+ const en = {
2
+ trigger: 'Send feedback', toolbar: 'Feedback tools', record: 'Record',
3
+ screenshot: 'Screenshot', text: 'Text only', close: 'Close feedback',
4
+ introTitle: 'Thanks for helping us improve!',
5
+ introBody: 'Use the toolbar at the top of the page to record your screen, take a screenshot, or just send a message. Visual context helps us understand what happened. We may not reply to every report, but our team reads them all.',
6
+ dontShowAgain: "Don't show this again", acknowledge: 'Got it',
7
+ description: 'Describe your feedback',
8
+ placeholder: 'Describe what happened and what you expected to see.',
9
+ attach: 'Attach an image or video', removeAttachment: 'Remove attachment',
10
+ cancel: 'Cancel', send: 'Send feedback', sending: 'Sending…',
11
+ stop: 'Stop recording', requesting: 'Choose a screen to share…',
12
+ finalizing: 'Preparing recording…', recording: 'Recording',
13
+ screenshotPreview: 'Screenshot preview', recordingPreview: 'Recording preview',
14
+ success: 'Thank you! Your feedback has been sent.',
15
+ submitFailed: "Couldn't send your feedback. Your draft is still here; please try again.",
16
+ captureFailed: "Couldn't capture your screen. Please try again or send text feedback.",
17
+ permissionDenied: 'Screen sharing was not allowed. Please try again or send text feedback.',
18
+ unsupported: 'Screen capture is unavailable in this browser. You can still send text or attach a file.',
19
+ invalidAttachment: 'Choose a PNG, JPEG, WebP, GIF, MP4, or WebM file.',
20
+ attachmentTooLarge: 'This attachment is too large. Choose a smaller file or make a shorter recording.',
21
+ };
22
+ const zh = {
23
+ trigger: '发送反馈', toolbar: '反馈工具栏', record: '录屏', screenshot: '截图',
24
+ text: '纯文字', close: '关闭反馈', introTitle: '感谢您帮助我们改进!',
25
+ introBody: '请使用页面顶部中央的工具栏。您可以录屏、截图,也可以只发送文字,其中录屏最能帮助我们了解问题细节。我们不会逐条回复,但每一条团队都会看到。',
26
+ dontShowAgain: '以后不再提示', acknowledge: '好的', description: '反馈描述',
27
+ placeholder: '请描述发生了什么,以及您原本期望看到什么。',
28
+ attach: '添加图片或视频附件', removeAttachment: '移除附件', cancel: '取消',
29
+ send: '发送反馈', sending: '正在发送…', stop: '停止录屏',
30
+ requesting: '请选择要共享的屏幕…', finalizing: '正在生成录屏…', recording: '录屏中',
31
+ screenshotPreview: '截图预览', recordingPreview: '录屏预览',
32
+ success: '感谢您的反馈,已发送给团队。',
33
+ submitFailed: '反馈发送失败,内容已保留,请重试。',
34
+ captureFailed: '屏幕捕获失败,请重试或使用纯文字反馈。',
35
+ permissionDenied: '未获得屏幕共享权限,请重试或使用纯文字反馈。',
36
+ unsupported: '当前浏览器不支持屏幕捕获,您仍可发送文字或添加附件。',
37
+ invalidAttachment: '请选择 PNG、JPEG、WebP、GIF、MP4 或 WebM 文件。',
38
+ attachmentTooLarge: '附件过大,请选择更小的文件或缩短录屏时间。',
39
+ };
40
+ export function resolveWidgetLabels(locale, overrides) {
41
+ return { ...(locale === 'zh' ? zh : en), ...overrides };
42
+ }
43
+ //# sourceMappingURL=widget-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-labels.js","sourceRoot":"","sources":["../src/widget-labels.ts"],"names":[],"mappings":"AAiCA,MAAM,EAAE,GAAyB;IAC/B,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,QAAQ;IACrE,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,gBAAgB;IACpE,UAAU,EAAE,gCAAgC;IAC5C,SAAS,EAAE,2NAA2N;IACtO,aAAa,EAAE,uBAAuB,EAAE,WAAW,EAAE,QAAQ;IAC7D,WAAW,EAAE,wBAAwB;IACrC,WAAW,EAAE,sDAAsD;IACnE,MAAM,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,mBAAmB;IACzE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU;IAC5D,IAAI,EAAE,gBAAgB,EAAE,UAAU,EAAE,2BAA2B;IAC/D,UAAU,EAAE,sBAAsB,EAAE,SAAS,EAAE,WAAW;IAC1D,iBAAiB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,mBAAmB;IAC9E,OAAO,EAAE,yCAAyC;IAClD,YAAY,EAAE,0EAA0E;IACxF,aAAa,EAAE,uEAAuE;IACtF,gBAAgB,EAAE,yEAAyE;IAC3F,WAAW,EAAE,0FAA0F;IACvG,iBAAiB,EAAE,mDAAmD;IACtE,kBAAkB,EAAE,kFAAkF;CACvG,CAAC;AAEF,MAAM,EAAE,GAAyB;IAC/B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI;IACjE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY;IACpD,SAAS,EAAE,uEAAuE;IAClF,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM;IAC/D,WAAW,EAAE,uBAAuB;IACpC,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI;IAC3D,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM;IAC5C,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK;IACjE,iBAAiB,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM;IACnD,OAAO,EAAE,gBAAgB;IACzB,YAAY,EAAE,mBAAmB;IACjC,aAAa,EAAE,qBAAqB;IACpC,gBAAgB,EAAE,wBAAwB;IAC1C,WAAW,EAAE,4BAA4B;IACzC,iBAAiB,EAAE,sCAAsC;IACzD,kBAAkB,EAAE,uBAAuB;CAC5C,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,MAAmB,EAAE,SAAyC;IAChG,OAAO,EAAE,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const widgetStyles = "\n:host {\n --feedback-surface: #0b0b0b;\n --feedback-field: #101010;\n --feedback-border: #303030;\n --feedback-text: #f4f4f4;\n --feedback-muted: #a0a0a0;\n --feedback-accent: #ededed;\n --feedback-accent-text: #161616;\n --feedback-focus: #b6b6b6;\n --feedback-error: #ff9999;\n --feedback-radius: 12px;\n --feedback-top: 8px;\n --feedback-z-index: 10000;\n font-family: var(--feedback-font, ui-sans-serif, system-ui, sans-serif);\n font-size: 14px;\n line-height: 1.6;\n color: var(--feedback-text);\n color-scheme: dark;\n}\n*, *::before, *::after { box-sizing: border-box; }\n[hidden] { display: none !important; }\nbutton, textarea, input { font: inherit; }\nbutton { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 34px; padding: 6px 12px; border: 1px solid var(--feedback-border); border-radius: 7px; background: transparent; color: inherit; cursor: pointer; white-space: nowrap; transition: background 120ms, color 120ms; }\nbutton:hover:not(:disabled) { background: #ffffff12; }\nbutton:disabled { opacity: .4; cursor: not-allowed; }\nbutton:focus-visible, input:focus-visible { outline: 2px solid var(--feedback-focus); outline-offset: 3px; }\nsvg { width: 16px; height: 16px; flex-shrink: 0; }\n.primary, button[aria-pressed=\"true\"] { background: var(--feedback-accent); color: var(--feedback-accent-text); border-color: transparent; }\n.primary:hover:not(:disabled), button[aria-pressed=\"true\"]:hover:not(:disabled) { background: var(--feedback-accent); filter: brightness(.92); }\n.icon-button { width: 34px; padding: 6px; flex-shrink: 0; border-color: transparent; }\n.entry, .toolbar, .text-panel, .notice { position: fixed; left: 50%; transform: translateX(-50%); z-index: var(--feedback-z-index); }\n.entry { top: var(--feedback-top); min-height: 32px; padding: 5px 12px; border-radius: 9px; background: var(--feedback-surface); box-shadow: 0 2px 10px #0002; font-size: 12px; }\n.frame { position: fixed; inset: 3px; border: 2px solid var(--feedback-accent); border-radius: 14px; pointer-events: none; z-index: calc(var(--feedback-z-index) - 1); box-shadow: 0 0 0 4px #0006; }\n.toolbar { top: var(--feedback-top); display: flex; align-items: center; gap: 8px; max-width: calc(100vw - 24px); padding: 7px; border: 1px solid var(--feedback-border); border-radius: 13px; background: var(--feedback-surface); box-shadow: 0 10px 35px #0004; }\n.modes { display: flex; align-items: center; gap: 2px; padding: 3px; border: 1px solid var(--feedback-border); border-radius: 8px; background: #ffffff08; }\n.modes button { border: 0; font-size: 12px; min-height: 30px; padding: 5px 9px; }\n.toolbar > .icon-button { border-left: 1px solid var(--feedback-border); border-radius: 0; }\n.recording-controls { display: flex; align-items: center; gap: 10px; padding-left: 5px; }\n.timer { font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 13px; }\n.recording-dot { width: 7px; height: 7px; border-radius: 50%; background: #ff6464; animation: feedback-pulse 1.5s infinite; }\n.text-panel { top: calc(var(--feedback-top) + 64px); width: min(448px, calc(100vw - 24px)); padding: 10px; border: 1px solid var(--feedback-border); border-radius: 16px; background: var(--feedback-surface); box-shadow: 0 16px 60px #0005; }\ndialog { color: var(--feedback-text); background: var(--feedback-surface); border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); padding: 24px; box-shadow: 0 24px 100px #0008; max-width: calc(100vw - 32px); max-height: calc(100dvh - 40px); overflow: auto; font: inherit; }\ndialog::backdrop { background: #0008; }\n.intro { width: 448px; margin: 0 auto; top: calc(var(--feedback-top) + 64px); }\n.intro::backdrop { background: #0004; }\nh2 { margin: 0; font-size: 18px; line-height: 1.4; font-weight: 600; letter-spacing: -.02em; }\n.intro p { margin: 8px 0 20px; color: var(--feedback-muted); font-size: 14px; line-height: 1.75; }\n.intro-actions { display: flex; justify-content: space-between; align-items: center; gap: 12px; }\n.intro label { display: flex; gap: 8px; align-items: center; font-size: 13px; cursor: pointer; }\n.intro input { width: 16px; height: 16px; margin: 0; accent-color: var(--feedback-accent); }\n.review { width: 768px; }\n.review-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }\n.preview { position: relative; border: 1px solid var(--feedback-border); border-radius: 8px; overflow: hidden; background: #060606; margin-bottom: 16px; }\n.preview img, .preview video { display: block; width: 100%; max-height: min(50dvh, 430px); object-fit: contain; }\n.remove { position: absolute; right: 8px; top: 8px; background: #0b0b0be6; color: #fff; border: 1px solid #ffffff30; border-radius: 8px; }\n.filename { display: block; padding: 5px 10px; border-top: 1px solid var(--feedback-border); color: var(--feedback-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\ntextarea { display: block; width: 100%; min-height: 132px; max-height: 40dvh; resize: vertical; padding: 12px; background: var(--feedback-field); color: var(--feedback-text); border: 1px solid var(--feedback-border); border-radius: 10px; font-size: 14px; line-height: 1.65; }\ntextarea::placeholder { color: var(--feedback-muted); opacity: 1; }\ntextarea:focus { outline: 2px solid var(--feedback-focus); outline-offset: 2px; }\n.review textarea { min-height: 64px; border-radius: 7px; }\n.footer { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 14px; }\n.footer .attach { margin-right: auto; }\n.text-panel .footer { margin-top: 10px; }\n.error { color: var(--feedback-error); font-size: 13px; margin: 10px 2px 0; }\n.notice { top: calc(var(--feedback-top) + 68px); max-width: min(460px, calc(100vw - 24px)); display: flex; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--feedback-border); border-radius: 10px; background: var(--feedback-surface); box-shadow: 0 12px 36px #0005; }\n.notice[data-error=\"true\"] { color: var(--feedback-error); }\n.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }\n@keyframes feedback-pulse { 50% { opacity: .4; } }\n@media (max-width: 480px) {\n .toolbar { gap: 4px; }\n .modes button { padding: 5px 7px; }\n dialog { padding: 18px; max-width: calc(100vw - 24px); }\n .review { max-height: calc(100dvh - 24px); }\n .preview img, .preview video { max-height: 38dvh; }\n .footer button { padding-left: 10px; padding-right: 10px; }\n}\n@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }\n";
2
+ //# sourceMappingURL=widget-styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-styles.d.ts","sourceRoot":"","sources":["../src/widget-styles.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,6oNA8ExB,CAAC"}
@@ -0,0 +1,80 @@
1
+ export const widgetStyles = `
2
+ :host {
3
+ --feedback-surface: #0b0b0b;
4
+ --feedback-field: #101010;
5
+ --feedback-border: #303030;
6
+ --feedback-text: #f4f4f4;
7
+ --feedback-muted: #a0a0a0;
8
+ --feedback-accent: #ededed;
9
+ --feedback-accent-text: #161616;
10
+ --feedback-focus: #b6b6b6;
11
+ --feedback-error: #ff9999;
12
+ --feedback-radius: 12px;
13
+ --feedback-top: 8px;
14
+ --feedback-z-index: 10000;
15
+ font-family: var(--feedback-font, ui-sans-serif, system-ui, sans-serif);
16
+ font-size: 14px;
17
+ line-height: 1.6;
18
+ color: var(--feedback-text);
19
+ color-scheme: dark;
20
+ }
21
+ *, *::before, *::after { box-sizing: border-box; }
22
+ [hidden] { display: none !important; }
23
+ button, textarea, input { font: inherit; }
24
+ button { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 34px; padding: 6px 12px; border: 1px solid var(--feedback-border); border-radius: 7px; background: transparent; color: inherit; cursor: pointer; white-space: nowrap; transition: background 120ms, color 120ms; }
25
+ button:hover:not(:disabled) { background: #ffffff12; }
26
+ button:disabled { opacity: .4; cursor: not-allowed; }
27
+ button:focus-visible, input:focus-visible { outline: 2px solid var(--feedback-focus); outline-offset: 3px; }
28
+ svg { width: 16px; height: 16px; flex-shrink: 0; }
29
+ .primary, button[aria-pressed="true"] { background: var(--feedback-accent); color: var(--feedback-accent-text); border-color: transparent; }
30
+ .primary:hover:not(:disabled), button[aria-pressed="true"]:hover:not(:disabled) { background: var(--feedback-accent); filter: brightness(.92); }
31
+ .icon-button { width: 34px; padding: 6px; flex-shrink: 0; border-color: transparent; }
32
+ .entry, .toolbar, .text-panel, .notice { position: fixed; left: 50%; transform: translateX(-50%); z-index: var(--feedback-z-index); }
33
+ .entry { top: var(--feedback-top); min-height: 32px; padding: 5px 12px; border-radius: 9px; background: var(--feedback-surface); box-shadow: 0 2px 10px #0002; font-size: 12px; }
34
+ .frame { position: fixed; inset: 3px; border: 2px solid var(--feedback-accent); border-radius: 14px; pointer-events: none; z-index: calc(var(--feedback-z-index) - 1); box-shadow: 0 0 0 4px #0006; }
35
+ .toolbar { top: var(--feedback-top); display: flex; align-items: center; gap: 8px; max-width: calc(100vw - 24px); padding: 7px; border: 1px solid var(--feedback-border); border-radius: 13px; background: var(--feedback-surface); box-shadow: 0 10px 35px #0004; }
36
+ .modes { display: flex; align-items: center; gap: 2px; padding: 3px; border: 1px solid var(--feedback-border); border-radius: 8px; background: #ffffff08; }
37
+ .modes button { border: 0; font-size: 12px; min-height: 30px; padding: 5px 9px; }
38
+ .toolbar > .icon-button { border-left: 1px solid var(--feedback-border); border-radius: 0; }
39
+ .recording-controls { display: flex; align-items: center; gap: 10px; padding-left: 5px; }
40
+ .timer { font-variant-numeric: tabular-nums; white-space: nowrap; font-size: 13px; }
41
+ .recording-dot { width: 7px; height: 7px; border-radius: 50%; background: #ff6464; animation: feedback-pulse 1.5s infinite; }
42
+ .text-panel { top: calc(var(--feedback-top) + 64px); width: min(448px, calc(100vw - 24px)); padding: 10px; border: 1px solid var(--feedback-border); border-radius: 16px; background: var(--feedback-surface); box-shadow: 0 16px 60px #0005; }
43
+ dialog { color: var(--feedback-text); background: var(--feedback-surface); border: 1px solid var(--feedback-border); border-radius: var(--feedback-radius); padding: 24px; box-shadow: 0 24px 100px #0008; max-width: calc(100vw - 32px); max-height: calc(100dvh - 40px); overflow: auto; font: inherit; }
44
+ dialog::backdrop { background: #0008; }
45
+ .intro { width: 448px; margin: 0 auto; top: calc(var(--feedback-top) + 64px); }
46
+ .intro::backdrop { background: #0004; }
47
+ h2 { margin: 0; font-size: 18px; line-height: 1.4; font-weight: 600; letter-spacing: -.02em; }
48
+ .intro p { margin: 8px 0 20px; color: var(--feedback-muted); font-size: 14px; line-height: 1.75; }
49
+ .intro-actions { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
50
+ .intro label { display: flex; gap: 8px; align-items: center; font-size: 13px; cursor: pointer; }
51
+ .intro input { width: 16px; height: 16px; margin: 0; accent-color: var(--feedback-accent); }
52
+ .review { width: 768px; }
53
+ .review-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
54
+ .preview { position: relative; border: 1px solid var(--feedback-border); border-radius: 8px; overflow: hidden; background: #060606; margin-bottom: 16px; }
55
+ .preview img, .preview video { display: block; width: 100%; max-height: min(50dvh, 430px); object-fit: contain; }
56
+ .remove { position: absolute; right: 8px; top: 8px; background: #0b0b0be6; color: #fff; border: 1px solid #ffffff30; border-radius: 8px; }
57
+ .filename { display: block; padding: 5px 10px; border-top: 1px solid var(--feedback-border); color: var(--feedback-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
58
+ textarea { display: block; width: 100%; min-height: 132px; max-height: 40dvh; resize: vertical; padding: 12px; background: var(--feedback-field); color: var(--feedback-text); border: 1px solid var(--feedback-border); border-radius: 10px; font-size: 14px; line-height: 1.65; }
59
+ textarea::placeholder { color: var(--feedback-muted); opacity: 1; }
60
+ textarea:focus { outline: 2px solid var(--feedback-focus); outline-offset: 2px; }
61
+ .review textarea { min-height: 64px; border-radius: 7px; }
62
+ .footer { display: flex; align-items: center; justify-content: flex-end; gap: 8px; margin-top: 14px; }
63
+ .footer .attach { margin-right: auto; }
64
+ .text-panel .footer { margin-top: 10px; }
65
+ .error { color: var(--feedback-error); font-size: 13px; margin: 10px 2px 0; }
66
+ .notice { top: calc(var(--feedback-top) + 68px); max-width: min(460px, calc(100vw - 24px)); display: flex; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--feedback-border); border-radius: 10px; background: var(--feedback-surface); box-shadow: 0 12px 36px #0005; }
67
+ .notice[data-error="true"] { color: var(--feedback-error); }
68
+ .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
69
+ @keyframes feedback-pulse { 50% { opacity: .4; } }
70
+ @media (max-width: 480px) {
71
+ .toolbar { gap: 4px; }
72
+ .modes button { padding: 5px 7px; }
73
+ dialog { padding: 18px; max-width: calc(100vw - 24px); }
74
+ .review { max-height: calc(100dvh - 24px); }
75
+ .preview img, .preview video { max-height: 38dvh; }
76
+ .footer button { padding-left: 10px; padding-right: 10px; }
77
+ }
78
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; } }
79
+ `;
80
+ //# sourceMappingURL=widget-styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-styles.js","sourceRoot":"","sources":["../src/widget-styles.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8E3B,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { FeedbackTransport, RecordingOptions, ScreenshotOptions } from './types.js';
2
+ import type { FeedbackWidgetLabels } from './widget-labels.js';
3
+ export interface FeedbackWidgetOptions {
4
+ transport: FeedbackTransport;
5
+ /** Read when the user submits; the SDK does not collect page or user data. */
6
+ context?: Record<string, unknown> | (() => Record<string, unknown>);
7
+ locale?: 'en' | 'zh';
8
+ labels?: Partial<FeedbackWidgetLabels>;
9
+ /** false hides the built-in entry button. Call open() from your own button. */
10
+ trigger?: boolean;
11
+ /** Defaults to document.body. Use a container in the main document. */
12
+ container?: HTMLElement;
13
+ /** false skips the introduction. The string is its localStorage preference key. */
14
+ introduction?: false | {
15
+ storageKey?: string;
16
+ };
17
+ screenshot?: Omit<ScreenshotOptions, 'signal'>;
18
+ /** Defaults to 60 seconds, without audio. */
19
+ recording?: Omit<RecordingOptions, 'signal'>;
20
+ /** Maximum size of a captured or attached file, in bytes. Defaults to 50 MiB. */
21
+ maxAttachmentBytes?: number;
22
+ /** CSS custom properties applied to the widget host, e.g. --feedback-surface. */
23
+ theme?: Record<`--feedback-${string}`, string>;
24
+ /** Trusted CSS appended inside the Shadow DOM; never pass user input here. */
25
+ styles?: string;
26
+ /** CSP nonce for the widget's style element. */
27
+ nonce?: string;
28
+ onSuccess?: (result: {
29
+ id?: string;
30
+ }) => void;
31
+ onClose?: () => void;
32
+ /** Map transport errors to a safe, localized message. Raw errors are not displayed. */
33
+ formatSubmitError?: (error: unknown) => string;
34
+ }
35
+ export interface FeedbackWidget {
36
+ readonly element: HTMLElement;
37
+ open(): void;
38
+ /** Discard the draft and cancel any pending capture or submission. */
39
+ close(): void;
40
+ /** Removes all UI/listeners, revokes previews and releases capture. Safe to repeat. */
41
+ destroy(): void;
42
+ setLocale(locale: 'en' | 'zh', labels?: Partial<FeedbackWidgetLabels>): void;
43
+ }
44
+ //# sourceMappingURL=widget-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-types.d.ts","sourceRoot":"","sources":["../src/widget-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACzF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAE/D,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACvC,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,mFAAmF;IACnF,YAAY,CAAC,EAAE,KAAK,GAAG;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,CAAC;IAC/C,6CAA6C;IAC7C,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;IAC7C,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;IAC/C,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CAChD;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,IAAI,IAAI,IAAI,CAAC;IACb,sEAAsE;IACtE,KAAK,IAAI,IAAI,CAAC;IACd,uFAAuF;IACvF,OAAO,IAAI,IAAI,CAAC;IAChB,SAAS,CAAC,MAAM,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,GAAG,IAAI,CAAC;CAC9E"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=widget-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget-types.js","sourceRoot":"","sources":["../src/widget-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { FeedbackWidget, FeedbackWidgetOptions } from './widget-types.js';
2
+ export type { FeedbackWidget, FeedbackWidgetOptions } from './widget-types.js';
3
+ export type { FeedbackWidgetLabels } from './widget-labels.js';
4
+ /** Optional, framework-independent UI. Importing this module is safe during SSR. */
5
+ export declare function createFeedbackWidget(options: FeedbackWidgetOptions): FeedbackWidget;
6
+ //# sourceMappingURL=widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.d.ts","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE/E,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAa/D,oFAAoF;AACpF,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CA4ZnF"}
package/dist/widget.js ADDED
@@ -0,0 +1,481 @@
1
+ import { FeedbackClient } from './client.js';
2
+ import { FeedbackCaptureError } from './errors.js';
3
+ import { resolveWidgetLabels } from './widget-labels.js';
4
+ import { widgetStyles } from './widget-styles.js';
5
+ const icons = {
6
+ record: '<rect x="3" y="6" width="12" height="12" rx="2"/><path d="m15 10 6-3v10l-6-3"/>',
7
+ screenshot: '<path d="m8 5 1-2h6l1 2h4v15H4V5z"/><circle cx="12" cy="12" r="4"/>',
8
+ text: '<path d="M14 2H5v20h14V7zM14 2v6h5M8 12h8M8 16h8"/>',
9
+ close: '<path d="m6 6 12 12M6 18 18 6"/>',
10
+ send: '<path d="m22 2-7 20-4-9-9-4 20-7ZM22 2 11 13"/>',
11
+ attach: '<path d="m20 11-8 8a5 5 0 0 1-7-7l9-9a3.5 3.5 0 0 1 5 5l-9 9a2 2 0 0 1-3-3l8-8"/>',
12
+ stop: '<rect x="5" y="5" width="14" height="14" rx="2"/>',
13
+ feedback: '<path d="M21 11a8 8 0 0 1-8 8H7l-5 3 2-6a8 8 0 0 1-1-5 9 9 0 0 1 18 0Z"/><path d="M8 10h8M8 14h5"/>',
14
+ };
15
+ /** Optional, framework-independent UI. Importing this module is safe during SSR. */
16
+ export function createFeedbackWidget(options) {
17
+ if (typeof document === 'undefined' || !document.body) {
18
+ throw new Error('Mount the feedback widget after the browser document is ready.');
19
+ }
20
+ const maxBytes = options.maxAttachmentBytes ?? 50 * 1024 * 1024;
21
+ if (!Number.isFinite(maxBytes) || maxBytes <= 0)
22
+ throw new Error('maxAttachmentBytes must be positive.');
23
+ const client = new FeedbackClient(options.transport);
24
+ const host = document.createElement('div');
25
+ host.dataset.artarchFeedback = '';
26
+ for (const [key, value] of Object.entries(options.theme ?? {}))
27
+ host.style.setProperty(key, value);
28
+ const root = host.attachShadow({ mode: 'open' });
29
+ const style = document.createElement('style');
30
+ if (options.nonce)
31
+ style.nonce = options.nonce;
32
+ style.textContent = widgetStyles + '\n' + (options.styles ?? '');
33
+ root.append(style);
34
+ const content = document.createElement('div');
35
+ // Only this static SDK-owned template uses HTML; all external text uses textContent.
36
+ content.innerHTML = `
37
+ <button class="entry" part="trigger" data-action="open"><svg data-icon="feedback"></svg><span data-label="trigger"></span></button>
38
+ <div class="frame" part="frame" hidden aria-hidden="true"></div>
39
+ <div class="toolbar" part="toolbar" role="toolbar" data-aria="toolbar" hidden>
40
+ <div class="modes">
41
+ <button data-action="record" aria-pressed="false"><svg data-icon="record"></svg><span data-label="record"></span></button>
42
+ <button data-action="screenshot" aria-pressed="false"><svg data-icon="screenshot"></svg><span data-label="screenshot"></span></button>
43
+ <button data-action="text" aria-pressed="false"><svg data-icon="text"></svg><span data-label="text"></span></button>
44
+ </div>
45
+ <div class="recording-controls" hidden>
46
+ <span class="recording-dot" aria-hidden="true"></span><span class="timer" role="status"></span>
47
+ <button data-action="stop" class="primary"><svg data-icon="stop"></svg><span data-label="stop"></span></button>
48
+ </div>
49
+ <button class="icon-button" data-action="close" data-aria="close"><svg data-icon="close"></svg></button>
50
+ </div>
51
+ <dialog class="intro" part="intro" aria-labelledby="feedback-intro-title" aria-describedby="feedback-intro-body">
52
+ <h2 id="feedback-intro-title" data-label="introTitle"></h2>
53
+ <p id="feedback-intro-body" data-label="introBody"></p>
54
+ <div class="intro-actions">
55
+ <label><input type="checkbox"><span data-label="dontShowAgain"></span></label>
56
+ <button class="primary" data-action="acknowledge" autofocus data-label="acknowledge"></button>
57
+ </div>
58
+ </dialog>
59
+ <section class="text-panel" part="text-panel" data-aria="description" hidden></section>
60
+ <dialog class="review" part="review" aria-labelledby="feedback-review-title">
61
+ <header class="review-header"><h2 id="feedback-review-title" data-label="description"></h2>
62
+ <button class="icon-button" data-action="close" data-aria="close"><svg data-icon="close"></svg></button>
63
+ </header>
64
+ <div class="preview" part="preview">
65
+ <img data-aria="screenshotPreview" hidden>
66
+ <video controls playsinline data-aria="recordingPreview" hidden></video>
67
+ <button class="icon-button remove" data-action="remove" data-aria="removeAttachment"><svg data-icon="close"></svg></button>
68
+ <span class="filename"></span>
69
+ </div>
70
+ </dialog>
71
+ <div class="notice" part="notice" role="status" hidden><span></span><button class="icon-button" data-action="dismiss" data-aria="close"><svg data-icon="close"></svg></button></div>
72
+ `;
73
+ root.append(content);
74
+ const form = document.createElement('form');
75
+ form.setAttribute('part', 'form');
76
+ form.innerHTML = `
77
+ <label class="sr-only" for="feedback-message" data-label="description"></label>
78
+ <textarea id="feedback-message" name="message" part="textarea" maxlength="5000" required></textarea>
79
+ <p class="error" role="alert" hidden></p>
80
+ <div class="footer" part="footer">
81
+ <button type="button" class="icon-button attach" data-action="attach" data-aria="attach"><svg data-icon="attach"></svg></button>
82
+ <input class="file-input" type="file" accept="image/png,image/jpeg,image/webp,image/gif,video/mp4,video/webm" hidden>
83
+ <button type="button" data-action="close" data-label="cancel"></button>
84
+ <button type="submit" class="primary send"><svg data-icon="send"></svg><span data-label="send"></span></button>
85
+ </div>
86
+ `;
87
+ const query = (selector) => root.querySelector(selector);
88
+ const entry = query('.entry');
89
+ const toolbar = query('.toolbar');
90
+ const frame = query('.frame');
91
+ const intro = query('.intro');
92
+ const review = query('.review');
93
+ const panel = query('.text-panel');
94
+ panel.append(form);
95
+ const textarea = query('textarea');
96
+ const fileInput = query('.file-input');
97
+ const submitButton = query('.send');
98
+ const errorMessage = query('.error');
99
+ const notice = query('.notice');
100
+ const previewImage = query('.preview img');
101
+ const previewVideo = query('.preview video');
102
+ const timer = query('.timer');
103
+ const modes = query('.modes');
104
+ const recordingControls = query('.recording-controls');
105
+ const stopButton = query('[data-action="stop"]');
106
+ for (const svg of root.querySelectorAll('svg')) {
107
+ svg.setAttribute('viewBox', '0 0 24 24');
108
+ svg.setAttribute('fill', 'none');
109
+ svg.setAttribute('stroke', 'currentColor');
110
+ svg.setAttribute('stroke-width', '1.6');
111
+ svg.setAttribute('stroke-linecap', 'round');
112
+ svg.setAttribute('stroke-linejoin', 'round');
113
+ svg.setAttribute('aria-hidden', 'true');
114
+ svg.innerHTML = icons[svg.dataset.icon] ?? '';
115
+ }
116
+ for (const button of root.querySelectorAll('button:not(.send)'))
117
+ button.type = 'button';
118
+ let labels = resolveWidgetLabels(options.locale ?? 'en', options.labels);
119
+ let active = false;
120
+ let destroyed = false;
121
+ let capture;
122
+ let submission;
123
+ let attachment;
124
+ let previewUrl;
125
+ let interval;
126
+ let noticeTimeout;
127
+ let previousFocus;
128
+ let introSeen = false;
129
+ const storageKey = options.introduction === false ? ''
130
+ : options.introduction?.storageKey ?? 'artarch-feedback-sdk:hide-introduction';
131
+ function setLocale(locale, overrides = options.labels) {
132
+ labels = resolveWidgetLabels(locale, overrides);
133
+ host.lang = locale;
134
+ for (const node of root.querySelectorAll('[data-label]')) {
135
+ node.textContent = labels[node.dataset.label];
136
+ }
137
+ for (const node of root.querySelectorAll('[data-aria]')) {
138
+ node.setAttribute('aria-label', labels[node.dataset.aria]);
139
+ }
140
+ previewImage.alt = labels.screenshotPreview;
141
+ textarea.placeholder = labels.placeholder;
142
+ updateSubmit();
143
+ }
144
+ function updateSubmit() {
145
+ submitButton.disabled = Boolean(submission) || !textarea.value.trim();
146
+ submitButton.querySelector('span').textContent = submission ? labels.sending : labels.send;
147
+ textarea.disabled = Boolean(submission);
148
+ query('[data-action="attach"]').disabled = Boolean(submission);
149
+ query('[data-action="remove"]').disabled = Boolean(submission);
150
+ form.setAttribute('aria-busy', String(Boolean(submission)));
151
+ }
152
+ function showNotice(message, isError = false) {
153
+ clearTimeout(noticeTimeout);
154
+ notice.querySelector('span').textContent = message;
155
+ notice.dataset.error = String(isError);
156
+ notice.hidden = false;
157
+ if (!isError)
158
+ noticeTimeout = setTimeout(() => { notice.hidden = true; }, 5000);
159
+ }
160
+ function clearAttachment() {
161
+ previewVideo.pause();
162
+ previewVideo.removeAttribute('src');
163
+ previewVideo.load();
164
+ previewImage.removeAttribute('src');
165
+ previewImage.hidden = previewVideo.hidden = true;
166
+ if (previewUrl)
167
+ URL.revokeObjectURL(previewUrl);
168
+ previewUrl = undefined;
169
+ attachment = undefined;
170
+ fileInput.value = '';
171
+ }
172
+ function chooseMode(mode) {
173
+ for (const button of modes.querySelectorAll('button')) {
174
+ button.setAttribute('aria-pressed', String(button.dataset.action === mode));
175
+ }
176
+ }
177
+ function showText() {
178
+ if (destroyed || capture || submission)
179
+ return;
180
+ notice.hidden = true;
181
+ review.close();
182
+ clearAttachment();
183
+ panel.append(form);
184
+ panel.hidden = false;
185
+ chooseMode('text');
186
+ textarea.focus();
187
+ }
188
+ function showAttachment(asset) {
189
+ if (asset.blob.size > maxBytes) {
190
+ if (!review.open) {
191
+ panel.hidden = false;
192
+ panel.append(form);
193
+ }
194
+ errorMessage.textContent = labels.attachmentTooLarge;
195
+ errorMessage.hidden = false;
196
+ return;
197
+ }
198
+ clearAttachment();
199
+ attachment = asset;
200
+ previewUrl = URL.createObjectURL(asset.blob);
201
+ const media = asset.kind === 'screenshot' ? previewImage : previewVideo;
202
+ media.src = previewUrl;
203
+ media.hidden = false;
204
+ query('.filename').textContent = asset.filename;
205
+ panel.hidden = true;
206
+ review.append(form);
207
+ if (!review.open)
208
+ review.showModal();
209
+ textarea.focus();
210
+ }
211
+ function resetCaptureControls() {
212
+ clearInterval(interval);
213
+ interval = undefined;
214
+ modes.hidden = false;
215
+ recordingControls.hidden = true;
216
+ host.style.visibility = '';
217
+ }
218
+ function close() {
219
+ if (destroyed)
220
+ return;
221
+ const wasActive = active;
222
+ active = false;
223
+ capture?.abort();
224
+ submission?.abort();
225
+ capture = submission = undefined;
226
+ resetCaptureControls();
227
+ intro.close();
228
+ review.close();
229
+ panel.hidden = toolbar.hidden = frame.hidden = notice.hidden = true;
230
+ clearTimeout(noticeTimeout);
231
+ clearAttachment();
232
+ panel.append(form);
233
+ textarea.value = '';
234
+ errorMessage.hidden = true;
235
+ entry.hidden = options.trigger === false;
236
+ updateSubmit();
237
+ if (wasActive) {
238
+ (previousFocus?.isConnected ? previousFocus : entry).focus();
239
+ options.onClose?.();
240
+ }
241
+ }
242
+ function open() {
243
+ if (destroyed || active)
244
+ return;
245
+ const focused = root.activeElement ?? document.activeElement;
246
+ previousFocus = focused instanceof HTMLElement ? focused : undefined;
247
+ active = true;
248
+ entry.hidden = notice.hidden = true;
249
+ toolbar.hidden = frame.hidden = false;
250
+ let skip = options.introduction === false || introSeen;
251
+ try {
252
+ skip || (skip = localStorage.getItem(storageKey) === 'true');
253
+ }
254
+ catch { /* Storage is optional. */ }
255
+ if (!skip)
256
+ intro.showModal();
257
+ else
258
+ query('[data-action="screenshot"]').focus();
259
+ }
260
+ function captureError(error) {
261
+ const code = error instanceof FeedbackCaptureError ? error.code : '';
262
+ if (code === 'cancelled')
263
+ return;
264
+ showNotice(code === 'unsupported' ? labels.unsupported
265
+ : code === 'permission-denied' ? labels.permissionDenied : labels.captureFailed, true);
266
+ }
267
+ async function takeScreenshot() {
268
+ if (capture || submission)
269
+ return;
270
+ const controller = new AbortController();
271
+ capture = controller;
272
+ notice.hidden = panel.hidden = true;
273
+ errorMessage.hidden = true;
274
+ chooseMode('screenshot');
275
+ // Hide before the native call, without awaiting and losing the user gesture.
276
+ host.style.visibility = 'hidden';
277
+ try {
278
+ const asset = await client.captureScreenshot({ ...options.screenshot, signal: controller.signal });
279
+ if (destroyed || capture !== controller)
280
+ return;
281
+ host.style.visibility = '';
282
+ capture = undefined;
283
+ showAttachment(asset);
284
+ }
285
+ catch (error) {
286
+ if (!destroyed && capture === controller)
287
+ captureError(error);
288
+ }
289
+ finally {
290
+ if (capture === controller)
291
+ capture = undefined;
292
+ if (!destroyed && !capture)
293
+ host.style.visibility = '';
294
+ }
295
+ }
296
+ async function record() {
297
+ if (capture || submission)
298
+ return;
299
+ const controller = new AbortController();
300
+ capture = controller;
301
+ notice.hidden = panel.hidden = true;
302
+ errorMessage.hidden = true;
303
+ chooseMode('record');
304
+ modes.hidden = true;
305
+ recordingControls.hidden = false;
306
+ stopButton.disabled = true;
307
+ timer.textContent = labels.requesting;
308
+ try {
309
+ await client.startRecording({ maxDurationMs: 60000, ...options.recording, signal: controller.signal });
310
+ if (destroyed || capture !== controller)
311
+ return;
312
+ stopButton.disabled = false;
313
+ const started = Date.now();
314
+ const tick = () => {
315
+ const seconds = Math.floor((Date.now() - started) / 1000);
316
+ timer.textContent = `${labels.recording} ${Math.floor(seconds / 60).toString().padStart(2, '0')}:${(seconds % 60).toString().padStart(2, '0')}`;
317
+ };
318
+ tick();
319
+ interval = setInterval(tick, 500);
320
+ const asset = await client.recorder.finished;
321
+ if (destroyed || capture !== controller)
322
+ return;
323
+ capture = undefined;
324
+ resetCaptureControls();
325
+ showAttachment(asset);
326
+ }
327
+ catch (error) {
328
+ if (!destroyed && capture === controller)
329
+ captureError(error);
330
+ }
331
+ finally {
332
+ if (capture === controller) {
333
+ capture = undefined;
334
+ resetCaptureControls();
335
+ }
336
+ }
337
+ }
338
+ async function submit(event) {
339
+ event.preventDefault();
340
+ if (capture || submission || !textarea.value.trim())
341
+ return;
342
+ const controller = new AbortController();
343
+ submission = controller;
344
+ errorMessage.hidden = true;
345
+ updateSubmit();
346
+ let result;
347
+ try {
348
+ result = await client.submit({
349
+ message: textarea.value.trim(),
350
+ ...(attachment?.kind === 'screenshot' ? { screenshot: attachment } : { recording: attachment }),
351
+ context: typeof options.context === 'function' ? options.context() : options.context,
352
+ signal: controller.signal,
353
+ });
354
+ }
355
+ catch (error) {
356
+ if (!destroyed && submission === controller) {
357
+ let message = labels.submitFailed;
358
+ try {
359
+ message = options.formatSubmitError?.(error) || message;
360
+ }
361
+ catch { /* Keep the draft usable if host formatting fails. */ }
362
+ errorMessage.textContent = message;
363
+ errorMessage.hidden = false;
364
+ }
365
+ return;
366
+ }
367
+ finally {
368
+ if (submission === controller) {
369
+ submission = undefined;
370
+ updateSubmit();
371
+ }
372
+ }
373
+ if (destroyed || controller.signal.aborted)
374
+ return;
375
+ close();
376
+ showNotice(labels.success);
377
+ options.onSuccess?.(result);
378
+ }
379
+ function onClick(event) {
380
+ const target = event.target instanceof Element ? event.target.closest('button[data-action]') : null;
381
+ if (!target || target.disabled)
382
+ return;
383
+ switch (target.dataset.action) {
384
+ case 'open':
385
+ open();
386
+ break;
387
+ case 'close':
388
+ close();
389
+ break;
390
+ case 'dismiss':
391
+ notice.hidden = true;
392
+ break;
393
+ case 'acknowledge':
394
+ introSeen = true;
395
+ if (query('.intro input').checked) {
396
+ try {
397
+ localStorage.setItem(storageKey, 'true');
398
+ }
399
+ catch { /* Keep working without storage. */ }
400
+ }
401
+ intro.close();
402
+ query('[data-action="screenshot"]').focus();
403
+ break;
404
+ case 'text':
405
+ case 'remove':
406
+ showText();
407
+ break;
408
+ case 'screenshot':
409
+ void takeScreenshot();
410
+ break;
411
+ case 'record':
412
+ void record();
413
+ break;
414
+ case 'stop':
415
+ stopButton.disabled = true;
416
+ clearInterval(interval);
417
+ timer.textContent = labels.finalizing;
418
+ // The finished promise in record() owns both manual and automatic completion.
419
+ void client.stopRecording().catch(() => { });
420
+ break;
421
+ case 'attach':
422
+ if (!submission)
423
+ fileInput.click();
424
+ break;
425
+ }
426
+ }
427
+ function attachFile() {
428
+ if (submission || capture)
429
+ return;
430
+ const file = fileInput.files?.[0];
431
+ if (!file)
432
+ return;
433
+ fileInput.value = '';
434
+ errorMessage.hidden = true;
435
+ if (!/^(image\/(png|jpeg|webp|gif)|video\/(mp4|webm))$/.test(file.type)) {
436
+ errorMessage.textContent = labels.invalidAttachment;
437
+ errorMessage.hidden = false;
438
+ return;
439
+ }
440
+ showAttachment({ kind: file.type.startsWith('image/') ? 'screenshot' : 'recording',
441
+ blob: file, contentType: file.type, filename: file.name });
442
+ }
443
+ const onCancel = (event) => { event.preventDefault(); close(); };
444
+ const onKey = (event) => {
445
+ if (event.key === 'Escape' && active && !intro.open && !review.open) {
446
+ event.preventDefault();
447
+ close();
448
+ }
449
+ };
450
+ root.addEventListener('click', onClick);
451
+ root.addEventListener('keydown', event => {
452
+ // Keep text entry and shortcuts out of the host editor's bubble listeners.
453
+ event.stopPropagation();
454
+ onKey(event);
455
+ });
456
+ intro.addEventListener('cancel', onCancel);
457
+ review.addEventListener('cancel', onCancel);
458
+ document.addEventListener('keydown', onKey);
459
+ textarea.addEventListener('input', updateSubmit);
460
+ fileInput.addEventListener('change', attachFile);
461
+ form.addEventListener('submit', submit);
462
+ entry.hidden = options.trigger === false;
463
+ setLocale(options.locale ?? 'en');
464
+ (options.container ?? document.body).append(host);
465
+ return {
466
+ element: host, open, close, setLocale,
467
+ destroy() {
468
+ if (destroyed)
469
+ return;
470
+ try {
471
+ close();
472
+ }
473
+ finally {
474
+ destroyed = true;
475
+ document.removeEventListener('keydown', onKey);
476
+ host.remove();
477
+ }
478
+ },
479
+ };
480
+ }
481
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"widget.js","sourceRoot":"","sources":["../src/widget.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "artarch-feedback-sdk",
3
- "version": "0.1.0",
4
- "description": "Headless browser feedback capture SDK for screenshots and screen recordings.",
3
+ "version": "0.2.0",
4
+ "description": "Browser feedback SDK with screenshots, screen recordings, a headless core, and an optional customizable widget.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -18,10 +18,16 @@
18
18
  "types": "./dist/index.d.ts",
19
19
  "import": "./dist/index.js",
20
20
  "default": "./dist/index.js"
21
+ },
22
+ "./widget": {
23
+ "types": "./dist/widget.d.ts",
24
+ "import": "./dist/widget.js",
25
+ "default": "./dist/widget.js"
21
26
  }
22
27
  },
23
28
  "scripts": {
24
29
  "build": "tsc -p tsconfig.build.json",
30
+ "prepare": "npm run build",
25
31
  "test": "npm run build && node --test --test-timeout=15000 tests/*.test.mjs",
26
32
  "test:browser": "npm run build && playwright test",
27
33
  "test:all": "npm test && npm run test:browser",