d2d-feedbackkit 0.0.0 → 0.1.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 +77 -0
- package/package.json +10 -1
- package/src/index.ts +1 -0
- package/src/solid.tsx +81 -361
- package/src/styles.css +21 -0
- package/src/styles.ts +35 -0
- package/src/types.ts +20 -1
- package/src/web-component.tsx +111 -3
package/README.md
CHANGED
|
@@ -11,3 +11,80 @@ import { createFeedbackLocator } from "d2d-feedbackkit";
|
|
|
11
11
|
import { mountFeedbackLocatorElement } from "d2d-feedbackkit/web-component";
|
|
12
12
|
import feedbackKitBabelPlugin from "d2d-feedbackkit/vite-plugin";
|
|
13
13
|
```
|
|
14
|
+
|
|
15
|
+
## Solid host with your own button
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { createSignal } from "solid-js";
|
|
19
|
+
import { createFeedbackLocator } from "d2d-feedbackkit";
|
|
20
|
+
import { FeedbackLocatorRoot } from "d2d-feedbackkit/widget";
|
|
21
|
+
import "d2d-feedbackkit/styles.css";
|
|
22
|
+
|
|
23
|
+
const locator = createFeedbackLocator({
|
|
24
|
+
appKey: "morgo-app",
|
|
25
|
+
appName: "Morgo App",
|
|
26
|
+
showFloatingButton: false,
|
|
27
|
+
submitFeedback: async (input) => {
|
|
28
|
+
const response = await fetch("/api/feedback", {
|
|
29
|
+
method: "POST",
|
|
30
|
+
body: input.formData,
|
|
31
|
+
});
|
|
32
|
+
return response.json();
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function FeedbackButton() {
|
|
37
|
+
const [open, setOpen] = createSignal(false);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<>
|
|
41
|
+
<button type="button" onClick={() => setOpen(true)}>
|
|
42
|
+
Feedback
|
|
43
|
+
</button>
|
|
44
|
+
<FeedbackLocatorRoot
|
|
45
|
+
locator={locator}
|
|
46
|
+
open={open()}
|
|
47
|
+
onOpenChange={setOpen}
|
|
48
|
+
onSubmitted={() => toast.success("Feedback verstuurd")}
|
|
49
|
+
/>
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Web component host
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { createFeedbackLocator } from "d2d-feedbackkit";
|
|
59
|
+
import { defineFeedbackLocatorElement } from "d2d-feedbackkit/web-component";
|
|
60
|
+
|
|
61
|
+
defineFeedbackLocatorElement({ shadowRoot: true });
|
|
62
|
+
|
|
63
|
+
const locator = createFeedbackLocator({
|
|
64
|
+
appKey: "host-app",
|
|
65
|
+
appName: "Host App",
|
|
66
|
+
showFloatingButton: false,
|
|
67
|
+
submitFeedback: async (input) => {
|
|
68
|
+
const response = await fetch("https://feedbackkit.d2d.cloud/api/submissions", {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "x-api-key": "<feedbackkit-api-key>" },
|
|
71
|
+
body: input.formData,
|
|
72
|
+
});
|
|
73
|
+
return response.json();
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const widget = document.querySelector("d2d-feedbackkit");
|
|
78
|
+
widget.locator = locator;
|
|
79
|
+
widget.showFloatingButton = false;
|
|
80
|
+
|
|
81
|
+
document.querySelector("#feedback-button").addEventListener("click", () => {
|
|
82
|
+
widget.open = true;
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
widget.addEventListener("feedbackkit-submitted", () => {
|
|
86
|
+
showToast("Feedback verstuurd");
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Without the `open` property the widget remains backwards compatible and shows its own floating feedback button by default.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "d2d-feedbackkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SolidJS feedback widget, source locator, and integration helpers.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -37,6 +37,13 @@
|
|
|
37
37
|
"./agent": {
|
|
38
38
|
"types": "./src/agent.ts",
|
|
39
39
|
"default": "./src/agent.ts"
|
|
40
|
+
},
|
|
41
|
+
"./styles": {
|
|
42
|
+
"types": "./src/styles.ts",
|
|
43
|
+
"default": "./src/styles.ts"
|
|
44
|
+
},
|
|
45
|
+
"./styles.css": {
|
|
46
|
+
"default": "./src/styles.css"
|
|
40
47
|
}
|
|
41
48
|
},
|
|
42
49
|
"files": [
|
|
@@ -49,6 +56,8 @@
|
|
|
49
56
|
"src/shortcuts.ts",
|
|
50
57
|
"src/solid.tsx",
|
|
51
58
|
"src/source.ts",
|
|
59
|
+
"src/styles.css",
|
|
60
|
+
"src/styles.ts",
|
|
52
61
|
"src/types.ts",
|
|
53
62
|
"src/vite-plugin.ts",
|
|
54
63
|
"src/web-component.tsx",
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ export function createFeedbackLocator(config: FeedbackLocatorConfig): FeedbackLo
|
|
|
34
34
|
const resolvedConfig = {
|
|
35
35
|
...config,
|
|
36
36
|
hotkey: config.hotkey ?? defaultFeedbackLocatorHotkey,
|
|
37
|
+
showFloatingButton: config.showFloatingButton ?? true,
|
|
37
38
|
contextProviders: config.contextProviders ?? [],
|
|
38
39
|
sourceCollector: config.sourceCollector ?? getFeedbackLocatorSourceForElement,
|
|
39
40
|
};
|
package/src/solid.tsx
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { toBlob } from "html-to-image";
|
|
2
2
|
import {
|
|
3
3
|
ArrowUpRight,
|
|
4
|
-
Bot,
|
|
5
|
-
CheckCircle2,
|
|
6
4
|
ChevronDown,
|
|
7
5
|
FileUp,
|
|
8
6
|
Maximize2,
|
|
@@ -32,24 +30,28 @@ import {
|
|
|
32
30
|
} from "solid-js";
|
|
33
31
|
import type {
|
|
34
32
|
FeedbackLocator,
|
|
35
|
-
FeedbackLocatorAgentSessionResult,
|
|
36
33
|
FeedbackLocatorBounds,
|
|
37
|
-
|
|
34
|
+
FeedbackLocatorCloseReason,
|
|
38
35
|
FeedbackLocatorContextPreview,
|
|
36
|
+
FeedbackLocatorHostedSubmitResult,
|
|
39
37
|
FeedbackLocatorHotkey,
|
|
40
|
-
FeedbackLocatorOpenCodeThreadResult,
|
|
41
38
|
FeedbackLocatorPoint,
|
|
42
39
|
FeedbackLocatorSourceMetadata,
|
|
43
|
-
FeedbackLocatorSubmitResult,
|
|
44
40
|
} from "./types";
|
|
45
41
|
import { createShortcutResolver } from "./shortcuts";
|
|
46
42
|
|
|
47
43
|
type FeedbackLocatorRootProps = {
|
|
48
44
|
locator: FeedbackLocator;
|
|
45
|
+
open?: boolean;
|
|
46
|
+
defaultOpen?: boolean;
|
|
47
|
+
onOpenChange?: (open: boolean) => void;
|
|
48
|
+
onClose?: (reason: FeedbackLocatorCloseReason) => void;
|
|
49
|
+
onSubmitted?: (result: FeedbackLocatorHostedSubmitResult) => void;
|
|
50
|
+
showFloatingButton?: boolean;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
53
|
type FeedbackLocatorMode = "idle" | "selecting" | "annotating" | "submitting";
|
|
52
|
-
type FeedbackLocatorSubmitTarget = "
|
|
54
|
+
type FeedbackLocatorSubmitTarget = "feedback";
|
|
53
55
|
type AnnotationTool = "select" | "pen" | "rect" | "arrow" | "text";
|
|
54
56
|
|
|
55
57
|
type PenAnnotation = {
|
|
@@ -194,6 +196,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
194
196
|
let annotationDragState: AnnotationDragState | null = null;
|
|
195
197
|
|
|
196
198
|
const [mode, setMode] = createSignal<FeedbackLocatorMode>("idle");
|
|
199
|
+
const [uncontrolledOpen, setUncontrolledOpen] = createSignal(Boolean(props.defaultOpen));
|
|
197
200
|
const [hoverRect, setHoverRect] = createSignal<FeedbackLocatorBounds | null>(null);
|
|
198
201
|
const [selected, setSelected] = createSignal<ScreenshotState | null>(null);
|
|
199
202
|
const [annotations, setAnnotations] = createSignal<Annotation[]>([]);
|
|
@@ -218,14 +221,8 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
218
221
|
const [recordingUrl, setRecordingUrl] = createSignal<string | null>(null);
|
|
219
222
|
const [recordingDurationMs, setRecordingDurationMs] = createSignal<number | null>(null);
|
|
220
223
|
const [error, setError] = createSignal<string | null>(null);
|
|
224
|
+
const [successToast, setSuccessToast] = createSignal<string | null>(null);
|
|
221
225
|
const [submitTarget, setSubmitTarget] = createSignal<FeedbackLocatorSubmitTarget | null>(null);
|
|
222
|
-
const [linearResult, setLinearResult] = createSignal<FeedbackLocatorSubmitResult | null>(null);
|
|
223
|
-
const [agentSession, setAgentSession] =
|
|
224
|
-
createSignal<FeedbackLocatorAgentSessionResult | null>(null);
|
|
225
|
-
const [codexThread, setCodexThread] =
|
|
226
|
-
createSignal<FeedbackLocatorCodexThreadResult | null>(null);
|
|
227
|
-
const [openCodeThread, setOpenCodeThread] =
|
|
228
|
-
createSignal<FeedbackLocatorOpenCodeThreadResult | null>(null);
|
|
229
226
|
let mediaRecorder: MediaRecorder | undefined;
|
|
230
227
|
let recordingStream: MediaStream | undefined;
|
|
231
228
|
let recordingStartedAt = 0;
|
|
@@ -240,7 +237,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
240
237
|
|
|
241
238
|
if (matchesHotkey(event, props.locator.config.hotkey!)) {
|
|
242
239
|
event.preventDefault();
|
|
243
|
-
|
|
240
|
+
openWidget();
|
|
244
241
|
}
|
|
245
242
|
};
|
|
246
243
|
|
|
@@ -252,6 +249,18 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
252
249
|
});
|
|
253
250
|
});
|
|
254
251
|
|
|
252
|
+
createEffect(() => {
|
|
253
|
+
const shouldBeOpen = isWidgetOpen();
|
|
254
|
+
|
|
255
|
+
if (shouldBeOpen && mode() === "idle") {
|
|
256
|
+
beginSelection();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!shouldBeOpen && mode() !== "idle") {
|
|
260
|
+
resetState();
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
255
264
|
createEffect(() => {
|
|
256
265
|
if (mode() !== "selecting") {
|
|
257
266
|
return;
|
|
@@ -280,7 +289,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
280
289
|
const handleKeydown = (event: KeyboardEvent) => {
|
|
281
290
|
if (event.key === "Escape") {
|
|
282
291
|
event.preventDefault();
|
|
283
|
-
|
|
292
|
+
requestClose("escape");
|
|
284
293
|
}
|
|
285
294
|
};
|
|
286
295
|
|
|
@@ -387,7 +396,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
if (action === "close") {
|
|
390
|
-
|
|
399
|
+
requestClose("escape");
|
|
391
400
|
}
|
|
392
401
|
};
|
|
393
402
|
|
|
@@ -395,18 +404,38 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
395
404
|
onCleanup(() => document.removeEventListener("keydown", handleKeydown));
|
|
396
405
|
});
|
|
397
406
|
|
|
398
|
-
function
|
|
407
|
+
function isWidgetOpen() {
|
|
408
|
+
return props.open ?? uncontrolledOpen();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function setWidgetOpen(open: boolean) {
|
|
412
|
+
if (props.open === undefined) {
|
|
413
|
+
setUncontrolledOpen(open);
|
|
414
|
+
}
|
|
415
|
+
props.onOpenChange?.(open);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function openWidget() {
|
|
419
|
+
setWidgetOpen(true);
|
|
420
|
+
if (mode() === "idle") {
|
|
421
|
+
beginSelection();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function requestClose(reason: FeedbackLocatorCloseReason) {
|
|
426
|
+
resetState();
|
|
427
|
+
setWidgetOpen(false);
|
|
428
|
+
props.onClose?.(reason);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function beginSelection() {
|
|
399
432
|
setError(null);
|
|
400
433
|
setSubmitTarget(null);
|
|
401
|
-
setLinearResult(null);
|
|
402
|
-
setAgentSession(null);
|
|
403
|
-
setCodexThread(null);
|
|
404
434
|
setSelected(null);
|
|
405
435
|
setAnnotations([]);
|
|
406
436
|
setAnnotationHistory([]);
|
|
407
437
|
setRedoStack([]);
|
|
408
438
|
setDraft(null);
|
|
409
|
-
setTextDraft(null);
|
|
410
439
|
setSelectedAnnotationIndex(null);
|
|
411
440
|
setContextPreview(null);
|
|
412
441
|
annotationDragState = null;
|
|
@@ -417,7 +446,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
417
446
|
setMode("selecting");
|
|
418
447
|
}
|
|
419
448
|
|
|
420
|
-
function
|
|
449
|
+
function resetState() {
|
|
421
450
|
const current = selected();
|
|
422
451
|
if (current) {
|
|
423
452
|
URL.revokeObjectURL(current.originalUrl);
|
|
@@ -440,8 +469,6 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
440
469
|
clearRecording();
|
|
441
470
|
setError(null);
|
|
442
471
|
setSubmitTarget(null);
|
|
443
|
-
setLinearResult(null);
|
|
444
|
-
setAgentSession(null);
|
|
445
472
|
}
|
|
446
473
|
|
|
447
474
|
async function captureElement(element: HTMLElement) {
|
|
@@ -630,68 +657,12 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
630
657
|
}
|
|
631
658
|
|
|
632
659
|
async function submitFeedback() {
|
|
633
|
-
await runSubmission("
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
);
|
|
638
|
-
|
|
639
|
-
try {
|
|
640
|
-
const submitResult = await props.locator.config.submitFeedback(prepared);
|
|
641
|
-
setLinearResult(submitResult);
|
|
642
|
-
openExternalWindow(submitResult.issueUrl, pendingWindow);
|
|
643
|
-
} catch (submitError) {
|
|
644
|
-
closePendingWindow(pendingWindow);
|
|
645
|
-
throw submitError;
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async function startAgentSession() {
|
|
651
|
-
const startAgentSessionAction = props.locator.config.startAgentSession;
|
|
652
|
-
|
|
653
|
-
if (!startAgentSessionAction) {
|
|
654
|
-
setError("Agent sessies zijn nog niet gekoppeld voor deze app.");
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
await runSubmission("agent", async (prepared) => {
|
|
659
|
-
const pendingWindow = openPendingAgentSessionWindow();
|
|
660
|
-
const session = await startAgentSessionAction(prepared);
|
|
661
|
-
setAgentSession(session);
|
|
662
|
-
openAgentSessionWindow(session, pendingWindow);
|
|
663
|
-
});
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
async function startCodexThread() {
|
|
667
|
-
const startCodexThreadAction = props.locator.config.startCodexThread;
|
|
668
|
-
|
|
669
|
-
if (!startCodexThreadAction) {
|
|
670
|
-
setError("Codex threads zijn nog niet gekoppeld voor deze app.");
|
|
671
|
-
return;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
await runSubmission("codex", async (prepared) => {
|
|
675
|
-
const thread = await startCodexThreadAction(prepared);
|
|
676
|
-
setCodexThread(thread);
|
|
677
|
-
|
|
678
|
-
if (thread.linearIssue) {
|
|
679
|
-
openExternalWindow(thread.linearIssue.issueUrl);
|
|
680
|
-
}
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
async function startOpenCodeThread() {
|
|
685
|
-
const startOpenCodeThreadAction = props.locator.config.startOpenCodeThread;
|
|
686
|
-
|
|
687
|
-
if (!startOpenCodeThreadAction) {
|
|
688
|
-
setError("OpenCode sessies zijn nog niet gekoppeld voor deze app.");
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
await runSubmission("opencode", async (prepared) => {
|
|
693
|
-
const thread = await startOpenCodeThreadAction(prepared);
|
|
694
|
-
setOpenCodeThread(thread);
|
|
660
|
+
await runSubmission("feedback", async (prepared) => {
|
|
661
|
+
const submitResult = await props.locator.config.submitFeedback(prepared);
|
|
662
|
+
props.onSubmitted?.(submitResult);
|
|
663
|
+
setSuccessToast("Feedback verstuurd");
|
|
664
|
+
window.setTimeout(() => setSuccessToast(null), 4000);
|
|
665
|
+
requestClose("submitted");
|
|
695
666
|
});
|
|
696
667
|
}
|
|
697
668
|
|
|
@@ -711,8 +682,10 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
711
682
|
}
|
|
712
683
|
|
|
713
684
|
await submit(prepared);
|
|
714
|
-
|
|
715
|
-
|
|
685
|
+
if (mode() === "submitting") {
|
|
686
|
+
applySubmissionAnnotations(prepared);
|
|
687
|
+
setMode("annotating");
|
|
688
|
+
}
|
|
716
689
|
} catch (submitError) {
|
|
717
690
|
setMode("annotating");
|
|
718
691
|
setError(
|
|
@@ -1350,11 +1323,16 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1350
1323
|
|
|
1351
1324
|
return (
|
|
1352
1325
|
<div data-feedback-locator-ui="true">
|
|
1353
|
-
<Show
|
|
1326
|
+
<Show
|
|
1327
|
+
when={
|
|
1328
|
+
mode() === "idle" &&
|
|
1329
|
+
(props.showFloatingButton ?? props.locator.config.showFloatingButton) !== false
|
|
1330
|
+
}
|
|
1331
|
+
>
|
|
1354
1332
|
<button
|
|
1355
1333
|
type="button"
|
|
1356
1334
|
class="fixed bottom-5 right-5 z-[2147483000] inline-flex h-11 items-center gap-2 rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground shadow-md transition hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
1357
|
-
onClick={
|
|
1335
|
+
onClick={openWidget}
|
|
1358
1336
|
title="Feedback toevoegen (Ctrl+Shift+F)"
|
|
1359
1337
|
>
|
|
1360
1338
|
<MessageSquarePlus class="h-4 w-4" />
|
|
@@ -1368,6 +1346,14 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1368
1346
|
</div>
|
|
1369
1347
|
</Show>
|
|
1370
1348
|
|
|
1349
|
+
<Show when={successToast()}>
|
|
1350
|
+
{(message) => (
|
|
1351
|
+
<div class="fixed bottom-20 right-5 z-[2147483000] max-w-sm rounded-md border border-primary/30 bg-background px-4 py-3 text-sm font-medium text-foreground shadow-md">
|
|
1352
|
+
{message()}
|
|
1353
|
+
</div>
|
|
1354
|
+
)}
|
|
1355
|
+
</Show>
|
|
1356
|
+
|
|
1371
1357
|
<Show when={mode() === "selecting"}>
|
|
1372
1358
|
<div class="fixed left-1/2 top-4 z-[2147483000] flex -translate-x-1/2 items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm text-foreground shadow-md">
|
|
1373
1359
|
<MousePointer2 class="h-4 w-4 text-primary" />
|
|
@@ -1375,7 +1361,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1375
1361
|
<button
|
|
1376
1362
|
type="button"
|
|
1377
1363
|
class="ml-2 rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1378
|
-
onClick={
|
|
1364
|
+
onClick={() => requestClose("user")}
|
|
1379
1365
|
title="Annuleren"
|
|
1380
1366
|
>
|
|
1381
1367
|
<X class="h-4 w-4" />
|
|
@@ -1410,7 +1396,7 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1410
1396
|
<button
|
|
1411
1397
|
type="button"
|
|
1412
1398
|
class="rounded-md p-2 text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
1413
|
-
onClick={
|
|
1399
|
+
onClick={() => requestClose("user")}
|
|
1414
1400
|
title="Sluiten"
|
|
1415
1401
|
>
|
|
1416
1402
|
<X class="h-5 w-5" />
|
|
@@ -1592,109 +1578,6 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1592
1578
|
|
|
1593
1579
|
<aside class="flex min-h-0 flex-col bg-background">
|
|
1594
1580
|
<div class="space-y-4 overflow-auto p-4">
|
|
1595
|
-
<Show when={linearResult()}>
|
|
1596
|
-
{(issue) => (
|
|
1597
|
-
<div class="rounded-md border border-primary/40 bg-primary/10 p-3 text-sm">
|
|
1598
|
-
<div class="flex items-center gap-2 font-medium">
|
|
1599
|
-
<CheckCircle2 class="h-4 w-4 text-primary" />
|
|
1600
|
-
Linear issue aangemaakt
|
|
1601
|
-
</div>
|
|
1602
|
-
<a
|
|
1603
|
-
class="mt-2 inline-flex items-center gap-1 text-primary underline-offset-4 hover:underline"
|
|
1604
|
-
href={issue().issueUrl}
|
|
1605
|
-
target="_blank"
|
|
1606
|
-
rel="noreferrer"
|
|
1607
|
-
>
|
|
1608
|
-
{issue().issueIdentifier}
|
|
1609
|
-
<ArrowUpRight class="h-3.5 w-3.5" />
|
|
1610
|
-
</a>
|
|
1611
|
-
</div>
|
|
1612
|
-
)}
|
|
1613
|
-
</Show>
|
|
1614
|
-
|
|
1615
|
-
<Show when={agentSession()}>
|
|
1616
|
-
{(session) => (
|
|
1617
|
-
<div class="rounded-md border border-sky-500/40 bg-sky-500/10 p-3 text-sm">
|
|
1618
|
-
<div class="flex items-center gap-2 font-medium">
|
|
1619
|
-
<CheckCircle2 class="h-4 w-4 text-sky-500" />
|
|
1620
|
-
Agent sessie gestart
|
|
1621
|
-
</div>
|
|
1622
|
-
<Show
|
|
1623
|
-
when={session().sessionUrl}
|
|
1624
|
-
fallback={
|
|
1625
|
-
<div class="mt-2 text-muted-foreground">{session().sessionId}</div>
|
|
1626
|
-
}
|
|
1627
|
-
>
|
|
1628
|
-
{(url) => (
|
|
1629
|
-
<a
|
|
1630
|
-
class="mt-2 inline-flex items-center gap-1 text-sky-600 underline-offset-4 hover:underline"
|
|
1631
|
-
href={url()}
|
|
1632
|
-
target="_blank"
|
|
1633
|
-
rel="noreferrer"
|
|
1634
|
-
>
|
|
1635
|
-
{session().sessionTitle || session().sessionId}
|
|
1636
|
-
<ArrowUpRight class="h-3.5 w-3.5" />
|
|
1637
|
-
</a>
|
|
1638
|
-
)}
|
|
1639
|
-
</Show>
|
|
1640
|
-
</div>
|
|
1641
|
-
)}
|
|
1642
|
-
</Show>
|
|
1643
|
-
|
|
1644
|
-
<Show when={codexThread()}>
|
|
1645
|
-
{(thread) => (
|
|
1646
|
-
<div class="rounded-md border border-violet-500/40 bg-violet-500/10 p-3 text-sm">
|
|
1647
|
-
<div class="flex items-center gap-2 font-medium">
|
|
1648
|
-
<CheckCircle2 class="h-4 w-4 text-violet-500" />
|
|
1649
|
-
Codex thread gestart
|
|
1650
|
-
</div>
|
|
1651
|
-
<div class="mt-2 text-muted-foreground">
|
|
1652
|
-
{thread().threadTitle || thread().threadId}
|
|
1653
|
-
</div>
|
|
1654
|
-
<Show when={thread().linearIssue}>
|
|
1655
|
-
{(issue) => (
|
|
1656
|
-
<a
|
|
1657
|
-
class="mt-2 inline-flex items-center gap-1 text-violet-600 underline-offset-4 hover:underline"
|
|
1658
|
-
href={issue().issueUrl}
|
|
1659
|
-
target="_blank"
|
|
1660
|
-
rel="noreferrer"
|
|
1661
|
-
>
|
|
1662
|
-
Linear: {issue().issueIdentifier}
|
|
1663
|
-
<ArrowUpRight class="h-3.5 w-3.5" />
|
|
1664
|
-
</a>
|
|
1665
|
-
)}
|
|
1666
|
-
</Show>
|
|
1667
|
-
</div>
|
|
1668
|
-
)}
|
|
1669
|
-
</Show>
|
|
1670
|
-
|
|
1671
|
-
<Show when={openCodeThread()}>
|
|
1672
|
-
{(thread) => (
|
|
1673
|
-
<div class="rounded-md border border-emerald-500/40 bg-emerald-500/10 p-3 text-sm">
|
|
1674
|
-
<div class="flex items-center gap-2 font-medium">
|
|
1675
|
-
<CheckCircle2 class="h-4 w-4 text-emerald-500" />
|
|
1676
|
-
OpenCode sessie gestart
|
|
1677
|
-
</div>
|
|
1678
|
-
<div class="mt-2 text-muted-foreground">
|
|
1679
|
-
{thread().sessionTitle || thread().sessionId}
|
|
1680
|
-
</div>
|
|
1681
|
-
<Show when={thread().linearIssue}>
|
|
1682
|
-
{(issue) => (
|
|
1683
|
-
<a
|
|
1684
|
-
class="mt-2 inline-flex items-center gap-1 text-emerald-600 underline-offset-4 hover:underline"
|
|
1685
|
-
href={issue().issueUrl}
|
|
1686
|
-
target="_blank"
|
|
1687
|
-
rel="noreferrer"
|
|
1688
|
-
>
|
|
1689
|
-
Linear: {issue().issueIdentifier}
|
|
1690
|
-
<ArrowUpRight class="h-3.5 w-3.5" />
|
|
1691
|
-
</a>
|
|
1692
|
-
)}
|
|
1693
|
-
</Show>
|
|
1694
|
-
</div>
|
|
1695
|
-
)}
|
|
1696
|
-
</Show>
|
|
1697
|
-
|
|
1698
1581
|
<div>
|
|
1699
1582
|
<label class="mb-2 block text-sm font-medium">Feedback</label>
|
|
1700
1583
|
<textarea
|
|
@@ -1842,11 +1725,11 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1842
1725
|
onClick={submitFeedback}
|
|
1843
1726
|
>
|
|
1844
1727
|
<Show
|
|
1845
|
-
when={submitTarget() === "
|
|
1728
|
+
when={submitTarget() === "feedback"}
|
|
1846
1729
|
fallback={
|
|
1847
1730
|
<>
|
|
1848
1731
|
<Send class="h-4 w-4" />
|
|
1849
|
-
Verstuur
|
|
1732
|
+
Verstuur feedback
|
|
1850
1733
|
</>
|
|
1851
1734
|
}
|
|
1852
1735
|
>
|
|
@@ -1854,92 +1737,6 @@ export const FeedbackLocatorRoot: Component<FeedbackLocatorRootProps> = (props)
|
|
|
1854
1737
|
Versturen...
|
|
1855
1738
|
</Show>
|
|
1856
1739
|
</button>
|
|
1857
|
-
<button
|
|
1858
|
-
type="button"
|
|
1859
|
-
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-background px-4 text-sm font-medium text-foreground transition hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60"
|
|
1860
|
-
disabled={
|
|
1861
|
-
mode() === "submitting" ||
|
|
1862
|
-
!prompt().trim() ||
|
|
1863
|
-
!props.locator.config.startAgentSession
|
|
1864
|
-
}
|
|
1865
|
-
onClick={startAgentSession}
|
|
1866
|
-
title={
|
|
1867
|
-
props.locator.config.startAgentSession
|
|
1868
|
-
? "Start agent met deze feedback"
|
|
1869
|
-
: "Agent sessies zijn nog niet gekoppeld"
|
|
1870
|
-
}
|
|
1871
|
-
>
|
|
1872
|
-
<Show
|
|
1873
|
-
when={submitTarget() === "agent"}
|
|
1874
|
-
fallback={
|
|
1875
|
-
<>
|
|
1876
|
-
<Bot class="h-4 w-4" />
|
|
1877
|
-
Start agent
|
|
1878
|
-
</>
|
|
1879
|
-
}
|
|
1880
|
-
>
|
|
1881
|
-
<LoaderCircle class="h-4 w-4 animate-spin" />
|
|
1882
|
-
Agent starten...
|
|
1883
|
-
</Show>
|
|
1884
|
-
</button>
|
|
1885
|
-
<div class="grid grid-cols-2 gap-2">
|
|
1886
|
-
<button
|
|
1887
|
-
type="button"
|
|
1888
|
-
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-background px-3 text-sm font-medium text-foreground transition hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-60"
|
|
1889
|
-
disabled={
|
|
1890
|
-
mode() === "submitting" ||
|
|
1891
|
-
!prompt().trim() ||
|
|
1892
|
-
!props.locator.config.startCodexThread
|
|
1893
|
-
}
|
|
1894
|
-
onClick={startCodexThread}
|
|
1895
|
-
title={
|
|
1896
|
-
props.locator.config.startCodexThread
|
|
1897
|
-
? "Start Codex thread in plan mode"
|
|
1898
|
-
: "Codex threads zijn nog niet gekoppeld"
|
|
1899
|
-
}
|
|
1900
|
-
>
|
|
1901
|
-
<Show
|
|
1902
|
-
when={submitTarget() === "codex"}
|
|
1903
|
-
fallback={
|
|
1904
|
-
<>
|
|
1905
|
-
<Bot class="h-4 w-4" />
|
|
1906
|
-
Codex
|
|
1907
|
-
</>
|
|
1908
|
-
}
|
|
1909
|
-
>
|
|
1910
|
-
<LoaderCircle class="h-4 w-4 animate-spin" />
|
|
1911
|
-
Codex...
|
|
1912
|
-
</Show>
|
|
1913
|
-
</button>
|
|
1914
|
-
<button
|
|
1915
|
-
type="button"
|
|
1916
|
-
class="inline-flex h-10 w-full items-center justify-center gap-2 rounded-md border bg-background px-3 text-sm font-medium text-foreground transition hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-60"
|
|
1917
|
-
disabled={
|
|
1918
|
-
mode() === "submitting" ||
|
|
1919
|
-
!prompt().trim() ||
|
|
1920
|
-
!props.locator.config.startOpenCodeThread
|
|
1921
|
-
}
|
|
1922
|
-
onClick={startOpenCodeThread}
|
|
1923
|
-
title={
|
|
1924
|
-
props.locator.config.startOpenCodeThread
|
|
1925
|
-
? "Start OpenCode sessie in plan mode"
|
|
1926
|
-
: "OpenCode sessies zijn nog niet gekoppeld"
|
|
1927
|
-
}
|
|
1928
|
-
>
|
|
1929
|
-
<Show
|
|
1930
|
-
when={submitTarget() === "opencode"}
|
|
1931
|
-
fallback={
|
|
1932
|
-
<>
|
|
1933
|
-
<Bot class="h-4 w-4" />
|
|
1934
|
-
OpenCode
|
|
1935
|
-
</>
|
|
1936
|
-
}
|
|
1937
|
-
>
|
|
1938
|
-
<LoaderCircle class="h-4 w-4 animate-spin" />
|
|
1939
|
-
OpenCode...
|
|
1940
|
-
</Show>
|
|
1941
|
-
</button>
|
|
1942
|
-
</div>
|
|
1943
1740
|
</div>
|
|
1944
1741
|
</aside>
|
|
1945
1742
|
</div>
|
|
@@ -2111,83 +1908,6 @@ function formatBytes(size: number) {
|
|
|
2111
1908
|
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
|
2112
1909
|
}
|
|
2113
1910
|
|
|
2114
|
-
function openPendingAgentSessionWindow() {
|
|
2115
|
-
return openPendingExternalWindow(
|
|
2116
|
-
"Feedback agent starten...",
|
|
2117
|
-
"De context wordt voorbereid en de agent chat wordt geopend.",
|
|
2118
|
-
);
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
function openAgentSessionWindow(
|
|
2122
|
-
session: { sessionUrl?: string },
|
|
2123
|
-
pendingWindow?: Window | null,
|
|
2124
|
-
) {
|
|
2125
|
-
if (typeof window === "undefined" || !session.sessionUrl) {
|
|
2126
|
-
return;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
if (pendingWindow && !pendingWindow.closed) {
|
|
2130
|
-
pendingWindow.location.href = session.sessionUrl;
|
|
2131
|
-
return;
|
|
2132
|
-
}
|
|
2133
|
-
|
|
2134
|
-
window.open(session.sessionUrl, "_blank", "noopener,noreferrer");
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
function openPendingExternalWindow(title: string, message: string) {
|
|
2138
|
-
if (typeof window === "undefined") {
|
|
2139
|
-
return null;
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
const pendingWindow = window.open("about:blank", "_blank");
|
|
2143
|
-
|
|
2144
|
-
if (!pendingWindow) {
|
|
2145
|
-
return null;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
try {
|
|
2149
|
-
pendingWindow.opener = null;
|
|
2150
|
-
pendingWindow.document.title = title;
|
|
2151
|
-
pendingWindow.document.body.innerHTML = `<main style="font-family: system-ui, sans-serif; padding: 24px;"><h1 style="font-size: 18px;">${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></main>`;
|
|
2152
|
-
} catch {
|
|
2153
|
-
// Cross-browser popup handling can deny document writes; navigation still works.
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
return pendingWindow;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
function openExternalWindow(url: string | undefined, pendingWindow?: Window | null) {
|
|
2160
|
-
if (typeof window === "undefined" || !url) {
|
|
2161
|
-
return;
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
if (pendingWindow && !pendingWindow.closed) {
|
|
2165
|
-
pendingWindow.location.href = url;
|
|
2166
|
-
return;
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
window.open(url, "_blank", "noopener,noreferrer");
|
|
2170
|
-
}
|
|
2171
|
-
|
|
2172
|
-
function closePendingWindow(pendingWindow?: Window | null) {
|
|
2173
|
-
try {
|
|
2174
|
-
if (pendingWindow && !pendingWindow.closed) {
|
|
2175
|
-
pendingWindow.close();
|
|
2176
|
-
}
|
|
2177
|
-
} catch {
|
|
2178
|
-
// Ignore popup cleanup errors.
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
|
|
2182
|
-
function escapeHtml(value: string) {
|
|
2183
|
-
return value
|
|
2184
|
-
.replace(/&/g, "&")
|
|
2185
|
-
.replace(/</g, "<")
|
|
2186
|
-
.replace(/>/g, ">")
|
|
2187
|
-
.replace(/"/g, """)
|
|
2188
|
-
.replace(/'/g, "'");
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
1911
|
function formatDuration(durationMs: number | null) {
|
|
2192
1912
|
if (!durationMs) {
|
|
2193
1913
|
return "0s";
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
:where([data-feedback-locator-ui="true"]) {
|
|
2
|
+
--background: 0 0% 3%;
|
|
3
|
+
--foreground: 0 0% 96%;
|
|
4
|
+
--muted: 0 0% 9%;
|
|
5
|
+
--muted-foreground: 0 0% 66%;
|
|
6
|
+
--border: 0 0% 18%;
|
|
7
|
+
--primary: 45 96% 53%;
|
|
8
|
+
--primary-foreground: 30 70% 8%;
|
|
9
|
+
--destructive: 0 84% 60%;
|
|
10
|
+
--ring: 45 96% 53%;
|
|
11
|
+
font-family:
|
|
12
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:where([data-feedback-locator-ui="true"], [data-feedback-locator-ui="true"] *) {
|
|
16
|
+
box-sizing: border-box;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:where([data-feedback-locator-ui="true"] button, [data-feedback-locator-ui="true"] textarea, [data-feedback-locator-ui="true"] input) {
|
|
20
|
+
font: inherit;
|
|
21
|
+
}
|
package/src/styles.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const defaultFeedbackLocatorStyles = `
|
|
2
|
+
:host {
|
|
3
|
+
color-scheme: dark;
|
|
4
|
+
--background: 0 0% 3%;
|
|
5
|
+
--foreground: 0 0% 96%;
|
|
6
|
+
--muted: 0 0% 9%;
|
|
7
|
+
--muted-foreground: 0 0% 66%;
|
|
8
|
+
--border: 0 0% 18%;
|
|
9
|
+
--primary: 45 96% 53%;
|
|
10
|
+
--primary-foreground: 30 70% 8%;
|
|
11
|
+
--destructive: 0 84% 60%;
|
|
12
|
+
--ring: 45 96% 53%;
|
|
13
|
+
font-family:
|
|
14
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
[data-feedback-locator-ui="true"],
|
|
18
|
+
[data-feedback-locator-ui="true"] * {
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
[data-feedback-locator-ui="true"] button,
|
|
23
|
+
[data-feedback-locator-ui="true"] textarea,
|
|
24
|
+
[data-feedback-locator-ui="true"] input {
|
|
25
|
+
font: inherit;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
[data-feedback-locator-ui="true"] button {
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[data-feedback-locator-ui="true"] button:disabled {
|
|
33
|
+
cursor: not-allowed;
|
|
34
|
+
}
|
|
35
|
+
`;
|
package/src/types.ts
CHANGED
|
@@ -143,7 +143,24 @@ export type FeedbackLocatorLinearIssueReference = {
|
|
|
143
143
|
issueUrl: string;
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
export type FeedbackLocatorSubmitResult = FeedbackLocatorLinearIssueReference
|
|
146
|
+
export type FeedbackLocatorSubmitResult = Partial<FeedbackLocatorLinearIssueReference> & {
|
|
147
|
+
submissionId?: string;
|
|
148
|
+
appId?: string;
|
|
149
|
+
appKey?: string;
|
|
150
|
+
linearIssue?: FeedbackLocatorLinearIssueReference;
|
|
151
|
+
agentJob?: {
|
|
152
|
+
id: string;
|
|
153
|
+
provider: "codex" | "opencode";
|
|
154
|
+
status: string;
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
export type FeedbackLocatorHostedSubmitResult = FeedbackLocatorSubmitResult;
|
|
158
|
+
export type FeedbackLocatorCloseReason =
|
|
159
|
+
| "user"
|
|
160
|
+
| "submitted"
|
|
161
|
+
| "escape"
|
|
162
|
+
| "outside"
|
|
163
|
+
| "error";
|
|
147
164
|
|
|
148
165
|
export type FeedbackLocatorAgentSessionResult = {
|
|
149
166
|
sessionId: string;
|
|
@@ -191,6 +208,7 @@ export type FeedbackLocatorConfig = {
|
|
|
191
208
|
) => Promise<FeedbackLocatorOpenCodeThreadResult>;
|
|
192
209
|
contextProviders?: FeedbackLocatorContextProvider[];
|
|
193
210
|
hotkey?: FeedbackLocatorHotkey;
|
|
211
|
+
showFloatingButton?: boolean;
|
|
194
212
|
sourceCollector?: FeedbackLocatorSourceCollector;
|
|
195
213
|
};
|
|
196
214
|
|
|
@@ -200,6 +218,7 @@ export type FeedbackLocator = {
|
|
|
200
218
|
FeedbackLocatorConfig,
|
|
201
219
|
| "contextProviders"
|
|
202
220
|
| "hotkey"
|
|
221
|
+
| "showFloatingButton"
|
|
203
222
|
| "sourceCollector"
|
|
204
223
|
| "startAgentSession"
|
|
205
224
|
| "startCodexThread"
|
package/src/web-component.tsx
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { render } from "solid-js/web";
|
|
2
2
|
import { FeedbackLocatorRoot } from "./solid";
|
|
3
|
-
import
|
|
3
|
+
import { defaultFeedbackLocatorStyles } from "./styles";
|
|
4
|
+
import type {
|
|
5
|
+
FeedbackLocator,
|
|
6
|
+
FeedbackLocatorCloseReason,
|
|
7
|
+
FeedbackLocatorHostedSubmitResult,
|
|
8
|
+
} from "./types";
|
|
4
9
|
|
|
5
10
|
export type FeedbackLocatorCustomElementOptions = {
|
|
6
11
|
tagName?: string;
|
|
@@ -10,6 +15,8 @@ export type FeedbackLocatorCustomElementOptions = {
|
|
|
10
15
|
|
|
11
16
|
export type FeedbackLocatorCustomElement = HTMLElement & {
|
|
12
17
|
locator: FeedbackLocator | null;
|
|
18
|
+
open: boolean;
|
|
19
|
+
showFloatingButton: boolean;
|
|
13
20
|
};
|
|
14
21
|
|
|
15
22
|
export const defaultFeedbackLocatorElementName = "d2d-feedbackkit";
|
|
@@ -26,9 +33,15 @@ export function defineFeedbackLocatorElement(
|
|
|
26
33
|
|
|
27
34
|
class FeedbackKitElement extends HTMLElement {
|
|
28
35
|
#locator: FeedbackLocator | null = null;
|
|
36
|
+
#open = false;
|
|
37
|
+
#showFloatingButton = true;
|
|
29
38
|
#dispose: (() => void) | undefined;
|
|
30
39
|
#mount: HTMLElement | undefined;
|
|
31
40
|
|
|
41
|
+
static get observedAttributes() {
|
|
42
|
+
return ["open", "show-floating-button"];
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
get locator() {
|
|
33
46
|
return this.#locator;
|
|
34
47
|
}
|
|
@@ -38,10 +51,51 @@ export function defineFeedbackLocatorElement(
|
|
|
38
51
|
this.#render();
|
|
39
52
|
}
|
|
40
53
|
|
|
54
|
+
get open() {
|
|
55
|
+
return this.#open;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
set open(open: boolean) {
|
|
59
|
+
const nextOpen = Boolean(open);
|
|
60
|
+
if (this.#open === nextOpen) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.#open = nextOpen;
|
|
65
|
+
this.#syncBooleanAttribute("open", nextOpen);
|
|
66
|
+
this.#dispatchOpenChange(nextOpen);
|
|
67
|
+
this.#render();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get showFloatingButton() {
|
|
71
|
+
return this.#showFloatingButton;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
set showFloatingButton(showFloatingButton: boolean) {
|
|
75
|
+
const nextShowFloatingButton = Boolean(showFloatingButton);
|
|
76
|
+
if (this.#showFloatingButton === nextShowFloatingButton) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.#showFloatingButton = nextShowFloatingButton;
|
|
81
|
+
this.#syncBooleanAttribute("show-floating-button", nextShowFloatingButton);
|
|
82
|
+
this.#render();
|
|
83
|
+
}
|
|
84
|
+
|
|
41
85
|
connectedCallback() {
|
|
42
86
|
this.#render();
|
|
43
87
|
}
|
|
44
88
|
|
|
89
|
+
attributeChangedCallback(name: string, _oldValue: string | null, newValue: string | null) {
|
|
90
|
+
if (name === "open") {
|
|
91
|
+
this.open = newValue !== null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (name === "show-floating-button") {
|
|
95
|
+
this.showFloatingButton = newValue !== null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
45
99
|
disconnectedCallback() {
|
|
46
100
|
this.#dispose?.();
|
|
47
101
|
this.#dispose = undefined;
|
|
@@ -57,7 +111,20 @@ export function defineFeedbackLocatorElement(
|
|
|
57
111
|
this.#mount = this.#mount ?? this.#createMount();
|
|
58
112
|
this.#mount.replaceChildren();
|
|
59
113
|
this.#dispose = render(
|
|
60
|
-
() =>
|
|
114
|
+
() => (
|
|
115
|
+
<FeedbackLocatorRoot
|
|
116
|
+
locator={this.#locator!}
|
|
117
|
+
open={this.#open}
|
|
118
|
+
showFloatingButton={this.#showFloatingButton}
|
|
119
|
+
onOpenChange={(open) => {
|
|
120
|
+
this.#open = open;
|
|
121
|
+
this.#syncBooleanAttribute("open", open);
|
|
122
|
+
this.#dispatchOpenChange(open);
|
|
123
|
+
}}
|
|
124
|
+
onClose={(reason) => this.#dispatchClose(reason)}
|
|
125
|
+
onSubmitted={(result) => this.#dispatchSubmitted(result)}
|
|
126
|
+
/>
|
|
127
|
+
),
|
|
61
128
|
this.#mount,
|
|
62
129
|
);
|
|
63
130
|
}
|
|
@@ -73,8 +140,9 @@ export function defineFeedbackLocatorElement(
|
|
|
73
140
|
: options.styles
|
|
74
141
|
? [options.styles]
|
|
75
142
|
: [];
|
|
143
|
+
const allStyles = [defaultFeedbackLocatorStyles, ...styles];
|
|
76
144
|
|
|
77
|
-
for (const styleText of
|
|
145
|
+
for (const styleText of allStyles) {
|
|
78
146
|
const style = document.createElement("style");
|
|
79
147
|
style.textContent = styleText;
|
|
80
148
|
root.append(style);
|
|
@@ -84,6 +152,46 @@ export function defineFeedbackLocatorElement(
|
|
|
84
152
|
root.append(mount);
|
|
85
153
|
return mount;
|
|
86
154
|
}
|
|
155
|
+
|
|
156
|
+
#syncBooleanAttribute(name: string, enabled: boolean) {
|
|
157
|
+
if (enabled && !this.hasAttribute(name)) {
|
|
158
|
+
this.setAttribute(name, "");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!enabled && this.hasAttribute(name)) {
|
|
162
|
+
this.removeAttribute(name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#dispatchOpenChange(open: boolean) {
|
|
167
|
+
this.dispatchEvent(
|
|
168
|
+
new CustomEvent("feedbackkit-open-change", {
|
|
169
|
+
bubbles: true,
|
|
170
|
+
composed: true,
|
|
171
|
+
detail: { open },
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#dispatchClose(reason: FeedbackLocatorCloseReason) {
|
|
177
|
+
this.dispatchEvent(
|
|
178
|
+
new CustomEvent("feedbackkit-close", {
|
|
179
|
+
bubbles: true,
|
|
180
|
+
composed: true,
|
|
181
|
+
detail: { reason },
|
|
182
|
+
}),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#dispatchSubmitted(result: FeedbackLocatorHostedSubmitResult) {
|
|
187
|
+
this.dispatchEvent(
|
|
188
|
+
new CustomEvent("feedbackkit-submitted", {
|
|
189
|
+
bubbles: true,
|
|
190
|
+
composed: true,
|
|
191
|
+
detail: { result },
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
87
195
|
}
|
|
88
196
|
|
|
89
197
|
customElements.define(tagName, FeedbackKitElement);
|