reflet-sdk 0.3.4 → 0.4.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/CHANGELOG.md +7 -0
- package/dist/client-BYuSKJw5.js.map +1 -1
- package/dist/{client-BpEYxx4d.d.ts → client-BomgCsXm.d.ts} +2 -2
- package/dist/{client-BpEYxx4d.d.ts.map → client-BomgCsXm.d.ts.map} +1 -1
- package/dist/feedback.d.ts +11 -15
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +748 -407
- package/dist/feedback.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/{react-context-DZw_HHjj.js → react-context-3_IbhIFB.js} +11 -2
- package/dist/react-context-3_IbhIFB.js.map +1 -0
- package/dist/react.d.ts +22 -33
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +206 -241
- package/dist/react.js.map +1 -1
- package/dist/server.d.ts +5 -5
- package/dist/server.d.ts.map +1 -1
- package/dist/{types-wANRMsGI.d.ts → types-Cak4ZkSy.d.ts} +3 -1
- package/dist/types-Cak4ZkSy.d.ts.map +1 -0
- package/package.json +44 -44
- package/dist/react-context-DZw_HHjj.js.map +0 -1
- package/dist/types-wANRMsGI.d.ts.map +0 -1
package/dist/feedback.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { a as REFLET_Z_INDEX, t as RefletContext } from "./react-context-3_IbhIFB.js";
|
|
2
3
|
import { t as Reflet } from "./client-BYuSKJw5.js";
|
|
3
|
-
import { t as RefletContext } from "./react-context-DZw_HHjj.js";
|
|
4
4
|
import { useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import { Slider } from "@base-ui/react/slider";
|
|
@@ -533,6 +533,7 @@ const MAX_DEPTH = 12;
|
|
|
533
533
|
const MIN_PATH_SEGMENTS = 2;
|
|
534
534
|
const MAX_SELECTOR_LENGTH = 600;
|
|
535
535
|
const MAX_SOURCE_LOCATION_LENGTH = 400;
|
|
536
|
+
const MAX_SELECTION_COMMENT_LENGTH = 2e3;
|
|
536
537
|
const TEST_ATTRIBUTES = [
|
|
537
538
|
"data-testid",
|
|
538
539
|
"data-test-id",
|
|
@@ -809,24 +810,23 @@ function collectPageContext(options) {
|
|
|
809
810
|
}
|
|
810
811
|
//#endregion
|
|
811
812
|
//#region src/feedback/types.ts
|
|
812
|
-
const SDK_VERSION = "0.
|
|
813
|
+
const SDK_VERSION = "0.4.0";
|
|
813
814
|
const DEFAULT_WIDGET_LABELS = {
|
|
814
815
|
annotateHint: "Draw on the screenshot to point at the problem.",
|
|
816
|
+
attachElement: "Attach this element",
|
|
815
817
|
attachmentUploadFailed: "Your feedback was saved, but some screenshots are still pending. Retry to attach them.",
|
|
816
818
|
attachScreenshot: "Attach a screenshot",
|
|
817
819
|
back: "Back",
|
|
818
820
|
cancel: "Cancel",
|
|
819
821
|
captureFailed: "Screenshot unavailable. Try again or send without it.",
|
|
820
822
|
capturing: "Taking screenshot…",
|
|
821
|
-
categoryBug: "Bug",
|
|
822
|
-
categoryIdea: "Idea",
|
|
823
|
-
categoryLabel: "Feedback type",
|
|
824
|
-
categoryQuestion: "Question",
|
|
825
823
|
clearAnnotations: "Clear drawing",
|
|
826
824
|
clearSelection: "Remove selected element",
|
|
827
825
|
descriptionPlaceholder: "What would you like to share?",
|
|
828
826
|
dismissForDays: "Hide for {days} days",
|
|
829
827
|
done: "Done",
|
|
828
|
+
elementNote: "Comment on the picked element",
|
|
829
|
+
elementNotePlaceholder: "What is wrong with this?",
|
|
830
830
|
emailInvalid: "Enter a valid email address.",
|
|
831
831
|
emailLabel: "Email",
|
|
832
832
|
emailPlaceholder: "you@company.com",
|
|
@@ -835,8 +835,9 @@ const DEFAULT_WIDGET_LABELS = {
|
|
|
835
835
|
minimize: "Minimize feedback",
|
|
836
836
|
moreOptions: "More options",
|
|
837
837
|
moveFeedback: "Move feedback",
|
|
838
|
+
pickAnother: "Pick another",
|
|
838
839
|
pickElement: "Point at an element",
|
|
839
|
-
pickElementHint: "
|
|
840
|
+
pickElementHint: "Pick the element you are talking about",
|
|
840
841
|
recapture: "Retake",
|
|
841
842
|
removeScreenshot: "Remove",
|
|
842
843
|
resume: "Resume feedback",
|
|
@@ -861,7 +862,6 @@ const BOX_TOOLS = [
|
|
|
861
862
|
"blur"
|
|
862
863
|
];
|
|
863
864
|
const MIN_GESTURE_LENGTH = 8;
|
|
864
|
-
const ARROW_HEAD_ANGLE = Math.PI / 7;
|
|
865
865
|
const DEFAULT_PATH_TOLERANCE = 2.5;
|
|
866
866
|
function normalizeRect(start, end) {
|
|
867
867
|
return {
|
|
@@ -871,17 +871,6 @@ function normalizeRect(start, end) {
|
|
|
871
871
|
y: Math.min(start.y, end.y)
|
|
872
872
|
};
|
|
873
873
|
}
|
|
874
|
-
/** The two barbs closing the arrow tip, both `size` px away from `end`. */
|
|
875
|
-
function arrowHead(start, end, size) {
|
|
876
|
-
const angle = Math.atan2(end.y - start.y, end.x - start.x);
|
|
877
|
-
if (Math.hypot(end.x - start.x, end.y - start.y) === 0) return [end, end];
|
|
878
|
-
const barb = (offset) => ({
|
|
879
|
-
x: end.x - size * Math.cos(angle + offset),
|
|
880
|
-
y: end.y - size * Math.sin(angle + offset)
|
|
881
|
-
});
|
|
882
|
-
return [barb(-ARROW_HEAD_ANGLE), barb(ARROW_HEAD_ANGLE)];
|
|
883
|
-
}
|
|
884
|
-
/** Drops points a user cannot perceive, keeping the payload small. */
|
|
885
874
|
function simplifyPath(points, tolerance = DEFAULT_PATH_TOLERANCE) {
|
|
886
875
|
if (points.length < 3) return points;
|
|
887
876
|
const [first, ...rest] = points;
|
|
@@ -932,7 +921,6 @@ function toWire(annotation) {
|
|
|
932
921
|
y: start.y
|
|
933
922
|
};
|
|
934
923
|
}
|
|
935
|
-
/** Serializes to the shape the Reflet API stores next to the screenshot. */
|
|
936
924
|
function toWireAnnotations(annotations) {
|
|
937
925
|
return annotations.filter((item) => !isDegenerate(item)).map(toWire);
|
|
938
926
|
}
|
|
@@ -1457,9 +1445,41 @@ function AnnotationToolbar({ editor, labels, drawing }) {
|
|
|
1457
1445
|
});
|
|
1458
1446
|
}
|
|
1459
1447
|
//#endregion
|
|
1448
|
+
//#region src/feedback/core/arrow-geometry.ts
|
|
1449
|
+
function arrowOutline(start, end) {
|
|
1450
|
+
const dx = end.x - start.x;
|
|
1451
|
+
const dy = end.y - start.y;
|
|
1452
|
+
const length = Math.hypot(dx, dy);
|
|
1453
|
+
if (length === 0) return {
|
|
1454
|
+
points: [],
|
|
1455
|
+
strokeWidth: 0
|
|
1456
|
+
};
|
|
1457
|
+
const shaftWidth = Math.min(20, Math.max(6, length * .025), length * .2);
|
|
1458
|
+
const headLength = Math.min(length * .45, shaftWidth * 3);
|
|
1459
|
+
const headHalfWidth = headLength * .55;
|
|
1460
|
+
const shaftLength = length - headLength;
|
|
1461
|
+
const pointOnArrow = (along, across) => ({
|
|
1462
|
+
x: start.x + (dx * along - dy * across) / length,
|
|
1463
|
+
y: start.y + (dy * along + dx * across) / length
|
|
1464
|
+
});
|
|
1465
|
+
return {
|
|
1466
|
+
points: [
|
|
1467
|
+
pointOnArrow(0, 0),
|
|
1468
|
+
pointOnArrow(shaftLength, -shaftWidth / 2),
|
|
1469
|
+
pointOnArrow(shaftLength, -headHalfWidth),
|
|
1470
|
+
{
|
|
1471
|
+
x: end.x,
|
|
1472
|
+
y: end.y
|
|
1473
|
+
},
|
|
1474
|
+
pointOnArrow(shaftLength, headHalfWidth),
|
|
1475
|
+
pointOnArrow(shaftLength, shaftWidth / 2)
|
|
1476
|
+
],
|
|
1477
|
+
strokeWidth: Math.min(4, length * .02)
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
//#endregion
|
|
1460
1481
|
//#region src/feedback/core/annotation-renderer.ts
|
|
1461
1482
|
const STROKE_WIDTH = 3;
|
|
1462
|
-
const ARROW_HEAD_SIZE = 16;
|
|
1463
1483
|
const HIGHLIGHT_ALPHA = .3;
|
|
1464
1484
|
const REDACTION_FILL = "#111827";
|
|
1465
1485
|
const PIXEL_BLOCK = 10;
|
|
@@ -1480,10 +1500,6 @@ function scaleRect(rect, scale) {
|
|
|
1480
1500
|
function isCanvasElement(value) {
|
|
1481
1501
|
return typeof HTMLCanvasElement !== "undefined" && value instanceof HTMLCanvasElement;
|
|
1482
1502
|
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Downsamples the region and paints it back enlarged, so redacted pixels are
|
|
1485
|
-
* unrecoverable. Falls back to an opaque block wherever 2d canvases are absent.
|
|
1486
|
-
*/
|
|
1487
1503
|
function redact(context, rect) {
|
|
1488
1504
|
const source = context.canvas;
|
|
1489
1505
|
const width = Math.max(1, Math.round(rect.width / PIXEL_BLOCK));
|
|
@@ -1503,18 +1519,17 @@ function redact(context, rect) {
|
|
|
1503
1519
|
context.fillStyle = REDACTION_FILL;
|
|
1504
1520
|
context.fillRect(rect.x, rect.y, rect.width, rect.height);
|
|
1505
1521
|
}
|
|
1506
|
-
function drawArrow(context,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
context.stroke();
|
|
1511
|
-
const [left, right] = arrowHead(start, end, ARROW_HEAD_SIZE * scale);
|
|
1522
|
+
function drawArrow(context, annotation, scale) {
|
|
1523
|
+
const { points, strokeWidth } = arrowOutline(annotation.start, annotation.end);
|
|
1524
|
+
const [first, ...rest] = points.map((point) => scalePoint(point, scale));
|
|
1525
|
+
if (!first) return;
|
|
1512
1526
|
context.beginPath();
|
|
1513
|
-
context.moveTo(
|
|
1514
|
-
context.lineTo(
|
|
1515
|
-
context.lineTo(right.x, right.y);
|
|
1527
|
+
context.moveTo(first.x, first.y);
|
|
1528
|
+
for (const point of rest) context.lineTo(point.x, point.y);
|
|
1516
1529
|
context.closePath();
|
|
1530
|
+
context.lineWidth = strokeWidth * scale;
|
|
1517
1531
|
context.fill();
|
|
1532
|
+
context.stroke();
|
|
1518
1533
|
}
|
|
1519
1534
|
function drawPen(context, points, scale) {
|
|
1520
1535
|
const [first, ...rest] = points;
|
|
@@ -1562,7 +1577,7 @@ function drawAnnotation(context, annotation, scale) {
|
|
|
1562
1577
|
drawPen(context, annotation.points ?? [], scale);
|
|
1563
1578
|
break;
|
|
1564
1579
|
case "arrow":
|
|
1565
|
-
drawArrow(context,
|
|
1580
|
+
drawArrow(context, annotation, scale);
|
|
1566
1581
|
break;
|
|
1567
1582
|
case "spotlight":
|
|
1568
1583
|
drawSpotlight(context, rect);
|
|
@@ -1590,10 +1605,6 @@ function loadImage(url) {
|
|
|
1590
1605
|
image.src = url;
|
|
1591
1606
|
});
|
|
1592
1607
|
}
|
|
1593
|
-
/**
|
|
1594
|
-
* Burns the annotations into a copy of the capture so reviewers and agents see
|
|
1595
|
-
* the drawing without needing to replay the vector data.
|
|
1596
|
-
*/
|
|
1597
1608
|
async function renderAnnotatedImage(source, annotations) {
|
|
1598
1609
|
if (annotations.length === 0) return null;
|
|
1599
1610
|
const canvas = document.createElement("canvas");
|
|
@@ -1944,7 +1955,8 @@ function ScreenshotCard({ item, labels, state }) {
|
|
|
1944
1955
|
//#region src/feedback/ui/composer/attachments.tsx
|
|
1945
1956
|
function SelectedElement({ labels, state }) {
|
|
1946
1957
|
if (!state.selection) return null;
|
|
1947
|
-
const
|
|
1958
|
+
const { comment, componentStack, label, selector, sourceLocation } = state.selection;
|
|
1959
|
+
const component = componentStack[0];
|
|
1948
1960
|
return /* @__PURE__ */ jsxs("div", {
|
|
1949
1961
|
className: "selection-chip glass",
|
|
1950
1962
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -1952,17 +1964,24 @@ function SelectedElement({ labels, state }) {
|
|
|
1952
1964
|
className: "selection-label",
|
|
1953
1965
|
disabled: state.isEditingDisabled || state.isCapturing,
|
|
1954
1966
|
onClick: () => state.setStep("picking"),
|
|
1955
|
-
title:
|
|
1967
|
+
title: comment ?? sourceLocation ?? selector,
|
|
1956
1968
|
type: "button",
|
|
1957
|
-
children: [
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1969
|
+
children: [
|
|
1970
|
+
state.elementCapture ? /* @__PURE__ */ jsx("img", {
|
|
1971
|
+
alt: "",
|
|
1972
|
+
height: 32,
|
|
1973
|
+
src: state.elementCapture.objectUrl,
|
|
1974
|
+
width: 32
|
|
1975
|
+
}) : /* @__PURE__ */ jsx(TargetIcon, {}),
|
|
1976
|
+
/* @__PURE__ */ jsx("span", {
|
|
1977
|
+
className: "selection-name",
|
|
1978
|
+
children: component ? `<${component}>` : label
|
|
1979
|
+
}),
|
|
1980
|
+
comment && /* @__PURE__ */ jsx("span", {
|
|
1981
|
+
className: "truncate",
|
|
1982
|
+
children: comment
|
|
1983
|
+
})
|
|
1984
|
+
]
|
|
1966
1985
|
}), /* @__PURE__ */ jsx("button", {
|
|
1967
1986
|
"aria-label": labels.clearSelection,
|
|
1968
1987
|
className: "icon-btn",
|
|
@@ -2092,34 +2111,12 @@ function ComposerOptions({ labels, state }) {
|
|
|
2092
2111
|
}
|
|
2093
2112
|
//#endregion
|
|
2094
2113
|
//#region src/feedback/ui/composer/controls.tsx
|
|
2095
|
-
|
|
2096
|
-
bug: "categoryBug",
|
|
2097
|
-
idea: "categoryIdea",
|
|
2098
|
-
question: "categoryQuestion"
|
|
2099
|
-
};
|
|
2100
|
-
function ComposerControls({ options, labels, state }) {
|
|
2114
|
+
function ComposerControls({ floatingControls, labels, state }) {
|
|
2101
2115
|
return /* @__PURE__ */ jsxs("div", {
|
|
2102
2116
|
className: "composer-toolbar",
|
|
2103
|
-
children: [
|
|
2117
|
+
children: [floatingControls, /* @__PURE__ */ jsxs("div", {
|
|
2104
2118
|
className: "composer-controls glass",
|
|
2105
2119
|
children: [
|
|
2106
|
-
options.categories.length > 1 && /* @__PURE__ */ jsxs("span", {
|
|
2107
|
-
className: "select-control",
|
|
2108
|
-
children: [/* @__PURE__ */ jsx("select", {
|
|
2109
|
-
"aria-label": labels.categoryLabel,
|
|
2110
|
-
className: "category-select",
|
|
2111
|
-
disabled: state.isEditingDisabled,
|
|
2112
|
-
onChange: (event) => {
|
|
2113
|
-
const category = options.categories.find((value) => value === event.target.value);
|
|
2114
|
-
if (category) state.setCategory(category);
|
|
2115
|
-
},
|
|
2116
|
-
value: state.category,
|
|
2117
|
-
children: options.categories.map((value) => /* @__PURE__ */ jsx("option", {
|
|
2118
|
-
value,
|
|
2119
|
-
children: labels[CATEGORY_LABELS[value]]
|
|
2120
|
-
}, value))
|
|
2121
|
-
}), /* @__PURE__ */ jsx(ChevronDownIcon, {})]
|
|
2122
|
-
}),
|
|
2123
2120
|
/* @__PURE__ */ jsx("button", {
|
|
2124
2121
|
"aria-label": labels.attachScreenshot,
|
|
2125
2122
|
className: "icon-btn",
|
|
@@ -2167,7 +2164,7 @@ function MessageInput({ labels, state }) {
|
|
|
2167
2164
|
"aria-busy": state.isSubmitting,
|
|
2168
2165
|
"aria-label": submitLabel,
|
|
2169
2166
|
className: "submit",
|
|
2170
|
-
disabled: state.isSubmitting || state.isCapturing || !state.
|
|
2167
|
+
disabled: state.isSubmitting || state.isCapturing || !state.canSubmit,
|
|
2171
2168
|
title: submitLabel,
|
|
2172
2169
|
type: "submit",
|
|
2173
2170
|
children: state.isSubmitting ? /* @__PURE__ */ jsx("span", { className: "spinner" }) : /* @__PURE__ */ jsx(ArrowIcon, {})
|
|
@@ -2176,7 +2173,7 @@ function MessageInput({ labels, state }) {
|
|
|
2176
2173
|
}
|
|
2177
2174
|
//#endregion
|
|
2178
2175
|
//#region src/feedback/ui/panel.tsx
|
|
2179
|
-
function FeedbackPanel({
|
|
2176
|
+
function FeedbackPanel({ floatingControls, labels, state }) {
|
|
2180
2177
|
const panelRef = useRef(null);
|
|
2181
2178
|
useEffect(() => {
|
|
2182
2179
|
panelRef.current?.focus();
|
|
@@ -2193,7 +2190,7 @@ function FeedbackPanel({ options, labels, state }) {
|
|
|
2193
2190
|
]
|
|
2194
2191
|
}), /* @__PURE__ */ jsx("div", {
|
|
2195
2192
|
className: "composer-toolbar",
|
|
2196
|
-
children:
|
|
2193
|
+
children: floatingControls
|
|
2197
2194
|
})]
|
|
2198
2195
|
});
|
|
2199
2196
|
return /* @__PURE__ */ jsxs("section", {
|
|
@@ -2216,8 +2213,8 @@ function FeedbackPanel({ options, labels, state }) {
|
|
|
2216
2213
|
state
|
|
2217
2214
|
}),
|
|
2218
2215
|
/* @__PURE__ */ jsx(ComposerControls, {
|
|
2216
|
+
floatingControls,
|
|
2219
2217
|
labels,
|
|
2220
|
-
options,
|
|
2221
2218
|
state
|
|
2222
2219
|
}),
|
|
2223
2220
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -2241,8 +2238,86 @@ function FeedbackPanel({ options, labels, state }) {
|
|
|
2241
2238
|
});
|
|
2242
2239
|
}
|
|
2243
2240
|
//#endregion
|
|
2241
|
+
//#region src/feedback/ui/selection-outline.tsx
|
|
2242
|
+
/**
|
|
2243
|
+
* Keeps the picked element ringed while the reporter writes about it, so the
|
|
2244
|
+
* composer parked next to it is visibly about that element.
|
|
2245
|
+
*/
|
|
2246
|
+
function SelectionOutline({ node }) {
|
|
2247
|
+
const boxRef = useRef(null);
|
|
2248
|
+
useEffect(() => {
|
|
2249
|
+
const box = boxRef.current;
|
|
2250
|
+
if (!(box && node)) return;
|
|
2251
|
+
let frame = 0;
|
|
2252
|
+
const draw = () => {
|
|
2253
|
+
frame = 0;
|
|
2254
|
+
const rect = node.getBoundingClientRect();
|
|
2255
|
+
const hidden = rect.width === 0 || rect.height === 0 || rect.bottom < 0 || rect.top > window.innerHeight;
|
|
2256
|
+
box.style.opacity = hidden ? "0" : "1";
|
|
2257
|
+
box.style.width = `${rect.width}px`;
|
|
2258
|
+
box.style.height = `${rect.height}px`;
|
|
2259
|
+
box.style.transform = `translate(${rect.left}px, ${rect.top}px)`;
|
|
2260
|
+
};
|
|
2261
|
+
const schedule = () => {
|
|
2262
|
+
frame ||= requestAnimationFrame(draw);
|
|
2263
|
+
};
|
|
2264
|
+
draw();
|
|
2265
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(draw);
|
|
2266
|
+
observer?.observe(node);
|
|
2267
|
+
window.addEventListener("scroll", schedule, {
|
|
2268
|
+
capture: true,
|
|
2269
|
+
passive: true
|
|
2270
|
+
});
|
|
2271
|
+
window.addEventListener("resize", schedule);
|
|
2272
|
+
return () => {
|
|
2273
|
+
cancelAnimationFrame(frame);
|
|
2274
|
+
observer?.disconnect();
|
|
2275
|
+
window.removeEventListener("scroll", schedule, true);
|
|
2276
|
+
window.removeEventListener("resize", schedule);
|
|
2277
|
+
};
|
|
2278
|
+
}, [node]);
|
|
2279
|
+
if (!node) return null;
|
|
2280
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2281
|
+
"aria-hidden": "true",
|
|
2282
|
+
className: "selection-outline",
|
|
2283
|
+
ref: boxRef
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
//#endregion
|
|
2287
|
+
//#region src/feedback/ui/visible-viewport.ts
|
|
2288
|
+
/**
|
|
2289
|
+
* The part of the page the reporter can actually see, in fixed-position
|
|
2290
|
+
* coordinates. On mobile the keyboard shrinks this without touching
|
|
2291
|
+
* `window.innerHeight`, so anything that must stay reachable measures here.
|
|
2292
|
+
*/
|
|
2293
|
+
function visibleViewport() {
|
|
2294
|
+
const viewport = window.visualViewport;
|
|
2295
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
2296
|
+
const top = viewport?.offsetTop ?? 0;
|
|
2297
|
+
const width = viewport?.width ?? window.innerWidth;
|
|
2298
|
+
const height = viewport?.height ?? window.innerHeight;
|
|
2299
|
+
return {
|
|
2300
|
+
bottom: window.innerHeight - top - height,
|
|
2301
|
+
height,
|
|
2302
|
+
left,
|
|
2303
|
+
right: window.innerWidth - left - width,
|
|
2304
|
+
top,
|
|
2305
|
+
width
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function onViewportChange(update) {
|
|
2309
|
+
window.addEventListener("resize", update);
|
|
2310
|
+
window.visualViewport?.addEventListener("resize", update);
|
|
2311
|
+
window.visualViewport?.addEventListener("scroll", update);
|
|
2312
|
+
return () => {
|
|
2313
|
+
window.removeEventListener("resize", update);
|
|
2314
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
2315
|
+
window.visualViewport?.removeEventListener("scroll", update);
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
//#endregion
|
|
2244
2319
|
//#region src/feedback/ui/floating/use-floating-position.ts
|
|
2245
|
-
const EDGE_GAP = 12;
|
|
2320
|
+
const EDGE_GAP$1 = 12;
|
|
2246
2321
|
const KEYBOARD_STEP = 24;
|
|
2247
2322
|
const KEY_DIRECTIONS = {
|
|
2248
2323
|
ArrowDown: {
|
|
@@ -2262,25 +2337,10 @@ const KEY_DIRECTIONS = {
|
|
|
2262
2337
|
y: -24
|
|
2263
2338
|
}
|
|
2264
2339
|
};
|
|
2265
|
-
function visibleViewport() {
|
|
2266
|
-
const viewport = window.visualViewport;
|
|
2267
|
-
const left = viewport?.offsetLeft ?? 0;
|
|
2268
|
-
const top = viewport?.offsetTop ?? 0;
|
|
2269
|
-
const width = viewport?.width ?? window.innerWidth;
|
|
2270
|
-
const height = viewport?.height ?? window.innerHeight;
|
|
2271
|
-
return {
|
|
2272
|
-
bottom: window.innerHeight - top - height,
|
|
2273
|
-
height,
|
|
2274
|
-
left,
|
|
2275
|
-
right: window.innerWidth - left - width,
|
|
2276
|
-
top,
|
|
2277
|
-
width
|
|
2278
|
-
};
|
|
2279
|
-
}
|
|
2280
2340
|
function clampPosition(point, bounds, viewport = visibleViewport()) {
|
|
2281
2341
|
return {
|
|
2282
|
-
x: Math.max(viewport.left + EDGE_GAP, Math.min(point.x, viewport.left + viewport.width - bounds.width - EDGE_GAP)),
|
|
2283
|
-
y: Math.max(viewport.top + EDGE_GAP, Math.min(point.y, viewport.top + viewport.height - bounds.height - EDGE_GAP))
|
|
2342
|
+
x: Math.max(viewport.left + EDGE_GAP$1, Math.min(point.x, viewport.left + viewport.width - bounds.width - EDGE_GAP$1)),
|
|
2343
|
+
y: Math.max(viewport.top + EDGE_GAP$1, Math.min(point.y, viewport.top + viewport.height - bounds.height - EDGE_GAP$1))
|
|
2284
2344
|
};
|
|
2285
2345
|
}
|
|
2286
2346
|
function useFloatingPosition(corner = "bottom-right") {
|
|
@@ -2309,17 +2369,12 @@ function useFloatingPosition(corner = "bottom-right") {
|
|
|
2309
2369
|
keepInViewport();
|
|
2310
2370
|
}, [keepInViewport]);
|
|
2311
2371
|
useEffect(() => {
|
|
2312
|
-
const resizeViewport = () => setViewport(visibleViewport());
|
|
2313
2372
|
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(keepInViewport);
|
|
2314
2373
|
if (rootRef.current) observer?.observe(rootRef.current);
|
|
2315
|
-
|
|
2316
|
-
window.visualViewport?.addEventListener("resize", resizeViewport);
|
|
2317
|
-
window.visualViewport?.addEventListener("scroll", resizeViewport);
|
|
2374
|
+
const stopListening = onViewportChange(() => setViewport(visibleViewport()));
|
|
2318
2375
|
return () => {
|
|
2319
2376
|
observer?.disconnect();
|
|
2320
|
-
|
|
2321
|
-
window.visualViewport?.removeEventListener("resize", resizeViewport);
|
|
2322
|
-
window.visualViewport?.removeEventListener("scroll", resizeViewport);
|
|
2377
|
+
stopListening();
|
|
2323
2378
|
};
|
|
2324
2379
|
}, [keepInViewport]);
|
|
2325
2380
|
const onPointerDown = (event) => {
|
|
@@ -2369,10 +2424,10 @@ function useFloatingPosition(corner = "bottom-right") {
|
|
|
2369
2424
|
}
|
|
2370
2425
|
//#endregion
|
|
2371
2426
|
//#region src/feedback/ui/floating/floating-widget.tsx
|
|
2372
|
-
function FloatingWidget({ labels,
|
|
2427
|
+
function FloatingWidget({ labels, position, state }) {
|
|
2373
2428
|
const [minimized, setMinimized] = useState(false);
|
|
2374
2429
|
const launcherRef = useRef(null);
|
|
2375
|
-
const floating = useFloatingPosition(
|
|
2430
|
+
const floating = useFloatingPosition(position);
|
|
2376
2431
|
const showPanel = state.isOpen && !minimized;
|
|
2377
2432
|
const minimize = () => {
|
|
2378
2433
|
setMinimized(true);
|
|
@@ -2396,12 +2451,12 @@ function FloatingWidget({ labels, options, state }) {
|
|
|
2396
2451
|
className: "root",
|
|
2397
2452
|
"data-editing": state.isOpen && (state.step === "picking" || state.step === "annotate"),
|
|
2398
2453
|
"data-moved": Boolean(floating.position),
|
|
2399
|
-
"data-position":
|
|
2454
|
+
"data-position": position,
|
|
2400
2455
|
ref: floating.rootRef,
|
|
2401
|
-
children: [
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2456
|
+
children: [
|
|
2457
|
+
/* @__PURE__ */ jsx("style", { children: floating.styles }),
|
|
2458
|
+
showPanel && state.step === "compose" && /* @__PURE__ */ jsx(SelectionOutline, { node: state.selectedNode }),
|
|
2459
|
+
showPanel ? /* @__PURE__ */ jsx(FeedbackPanel, {
|
|
2405
2460
|
floatingControls: /* @__PURE__ */ jsxs("div", {
|
|
2406
2461
|
className: "floating-controls glass",
|
|
2407
2462
|
children: [/* @__PURE__ */ jsx("button", {
|
|
@@ -2419,26 +2474,85 @@ function FloatingWidget({ labels, options, state }) {
|
|
|
2419
2474
|
type: "button",
|
|
2420
2475
|
children: /* @__PURE__ */ jsx(MinusIcon, {})
|
|
2421
2476
|
})]
|
|
2422
|
-
})
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2477
|
+
}),
|
|
2478
|
+
labels,
|
|
2479
|
+
state
|
|
2480
|
+
}) : /* @__PURE__ */ jsx(Launcher, {
|
|
2481
|
+
buttonRef: launcherRef,
|
|
2482
|
+
isOpen: false,
|
|
2483
|
+
label: state.isOpen ? labels.resume : labels.trigger,
|
|
2484
|
+
onClick: () => {
|
|
2485
|
+
setMinimized(false);
|
|
2486
|
+
if (!state.isOpen) state.open();
|
|
2487
|
+
}
|
|
2488
|
+
})
|
|
2489
|
+
]
|
|
2434
2490
|
});
|
|
2435
2491
|
}
|
|
2436
2492
|
//#endregion
|
|
2493
|
+
//#region src/feedback/ui/floating/element-walk.ts
|
|
2494
|
+
const ARROW_SIBLING = {
|
|
2495
|
+
ArrowLeft: "previousElementSibling",
|
|
2496
|
+
ArrowRight: "nextElementSibling"
|
|
2497
|
+
};
|
|
2498
|
+
const AIM_KEYS = [
|
|
2499
|
+
"Tab",
|
|
2500
|
+
"ArrowUp",
|
|
2501
|
+
"ArrowDown",
|
|
2502
|
+
"ArrowLeft",
|
|
2503
|
+
"ArrowRight"
|
|
2504
|
+
];
|
|
2505
|
+
function isWidgetOwned(element) {
|
|
2506
|
+
return element.closest(`[${WIDGET_MARKER}]`) !== null;
|
|
2507
|
+
}
|
|
2508
|
+
function isPickable(element) {
|
|
2509
|
+
if (!element || element === document.documentElement || isWidgetOwned(element)) return false;
|
|
2510
|
+
const rect = element.getBoundingClientRect();
|
|
2511
|
+
return rect.width > 0 && rect.height > 0;
|
|
2512
|
+
}
|
|
2513
|
+
function pickableElements() {
|
|
2514
|
+
return Array.from(document.body.querySelectorAll("*")).filter(isPickable);
|
|
2515
|
+
}
|
|
2516
|
+
function documentStep(from, backwards) {
|
|
2517
|
+
const all = pickableElements();
|
|
2518
|
+
const index = all.indexOf(from);
|
|
2519
|
+
if (all.length === 0) return null;
|
|
2520
|
+
if (index === -1) return all[0] ?? null;
|
|
2521
|
+
return all[(index + (backwards ? -1 : 1) + all.length) % all.length] ?? null;
|
|
2522
|
+
}
|
|
2523
|
+
function climb(from) {
|
|
2524
|
+
let candidate = from.parentElement;
|
|
2525
|
+
while (candidate && !isPickable(candidate)) candidate = candidate.parentElement;
|
|
2526
|
+
return candidate;
|
|
2527
|
+
}
|
|
2528
|
+
function descend(from) {
|
|
2529
|
+
let candidate = from.firstElementChild;
|
|
2530
|
+
while (candidate && !isPickable(candidate)) candidate = candidate.nextElementSibling;
|
|
2531
|
+
return candidate;
|
|
2532
|
+
}
|
|
2533
|
+
function sibling(from, direction) {
|
|
2534
|
+
let candidate = from[direction];
|
|
2535
|
+
while (candidate && !isPickable(candidate)) candidate = candidate[direction];
|
|
2536
|
+
return candidate;
|
|
2537
|
+
}
|
|
2538
|
+
function stepAim(from, key, backwards) {
|
|
2539
|
+
if (!(from && isPickable(from))) return pickableElements()[0] ?? null;
|
|
2540
|
+
if (key === "Tab") return documentStep(from, backwards);
|
|
2541
|
+
if (key === "ArrowUp") return climb(from);
|
|
2542
|
+
if (key === "ArrowDown") return descend(from);
|
|
2543
|
+
const direction = ARROW_SIBLING[key];
|
|
2544
|
+
return direction ? sibling(from, direction) : null;
|
|
2545
|
+
}
|
|
2546
|
+
//#endregion
|
|
2437
2547
|
//#region src/feedback/ui/picker.tsx
|
|
2438
2548
|
const LABEL_HEIGHT = 24;
|
|
2439
2549
|
const LABEL_GAP = 6;
|
|
2440
|
-
|
|
2441
|
-
|
|
2550
|
+
const NOTE_WIDTH = 300;
|
|
2551
|
+
const NOTE_GAP = 10;
|
|
2552
|
+
const EDGE_GAP = 12;
|
|
2553
|
+
/** Events raised by the picker's own note card must reach it, never be swallowed. */
|
|
2554
|
+
function targetsWidget(event) {
|
|
2555
|
+
return event.composedPath().some((node) => node instanceof Element && isWidgetOwned(node));
|
|
2442
2556
|
}
|
|
2443
2557
|
function elementUnder(x, y) {
|
|
2444
2558
|
const element = document.elementFromPoint(x, y);
|
|
@@ -2455,233 +2569,298 @@ function describeTarget(element) {
|
|
|
2455
2569
|
};
|
|
2456
2570
|
}
|
|
2457
2571
|
/**
|
|
2572
|
+
* Pins the note card to the element: below it, flipped above when the visible
|
|
2573
|
+
* viewport — what is left of the screen once a mobile keyboard is up — has no
|
|
2574
|
+
* room there. Measures against the part of the element actually on screen, so
|
|
2575
|
+
* picking a full-height section still leaves the card in view.
|
|
2576
|
+
*/
|
|
2577
|
+
function noteStyle(rect, view) {
|
|
2578
|
+
const left = Math.min(Math.max(view.left + EDGE_GAP, rect.left), Math.max(view.left + EDGE_GAP, view.left + view.width - NOTE_WIDTH - EDGE_GAP));
|
|
2579
|
+
const visibleTop = Math.max(rect.top, view.top + EDGE_GAP);
|
|
2580
|
+
const visibleBottom = Math.min(rect.bottom, view.top + view.height - EDGE_GAP);
|
|
2581
|
+
if (view.top + view.height - visibleBottom > visibleTop - view.top) return {
|
|
2582
|
+
left,
|
|
2583
|
+
top: visibleBottom + NOTE_GAP
|
|
2584
|
+
};
|
|
2585
|
+
return {
|
|
2586
|
+
bottom: window.innerHeight - visibleTop + NOTE_GAP,
|
|
2587
|
+
left
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2458
2591
|
* Points at any element in the page the way a coding agent needs it: the React
|
|
2459
|
-
* component that owns it plus the page region it lives in.
|
|
2592
|
+
* component that owns it plus the page region it lives in. Picking never
|
|
2593
|
+
* commits on its own — it pins a note card to the element, so the reporter
|
|
2594
|
+
* writes about that element right there and confirms. Aiming stays live until
|
|
2595
|
+
* then, which is what makes the flow usable without hover, on touch.
|
|
2460
2596
|
*/
|
|
2461
|
-
function ElementPicker({
|
|
2597
|
+
function ElementPicker({ labels, onCancel, onPick }) {
|
|
2462
2598
|
const [target, setTarget] = useState(null);
|
|
2599
|
+
const [pinned, setPinned] = useState(null);
|
|
2600
|
+
const [note, setNote] = useState("");
|
|
2601
|
+
const noteRef = useRef(null);
|
|
2602
|
+
const pinnedRef = useRef(false);
|
|
2603
|
+
const [view, setView] = useState(visibleViewport);
|
|
2604
|
+
const aimRef = useRef(null);
|
|
2605
|
+
useEffect(() => onViewportChange(() => setView(visibleViewport())), []);
|
|
2606
|
+
useEffect(() => {
|
|
2607
|
+
pinnedRef.current = pinned !== null;
|
|
2608
|
+
if (pinned) noteRef.current?.focus({ preventScroll: true });
|
|
2609
|
+
}, [pinned]);
|
|
2610
|
+
useEffect(() => {
|
|
2611
|
+
aimRef.current = target?.element ?? null;
|
|
2612
|
+
}, [target]);
|
|
2463
2613
|
useEffect(() => {
|
|
2464
2614
|
const swallow = (event) => {
|
|
2615
|
+
if (targetsWidget(event)) return;
|
|
2465
2616
|
event.preventDefault();
|
|
2466
2617
|
event.stopPropagation();
|
|
2467
2618
|
};
|
|
2468
|
-
const
|
|
2469
|
-
const element = elementUnder(
|
|
2470
|
-
if (!element)
|
|
2471
|
-
setTarget(null);
|
|
2472
|
-
return;
|
|
2473
|
-
}
|
|
2619
|
+
const aimAt = (x, y) => {
|
|
2620
|
+
const element = elementUnder(x, y);
|
|
2621
|
+
if (!element) return;
|
|
2474
2622
|
setTarget((current) => current?.element === element ? {
|
|
2475
2623
|
...current,
|
|
2476
2624
|
rect: element.getBoundingClientRect()
|
|
2477
2625
|
} : describeTarget(element));
|
|
2478
2626
|
};
|
|
2479
|
-
const
|
|
2627
|
+
const onMove = (event) => {
|
|
2628
|
+
if (pinnedRef.current || targetsWidget(event)) return;
|
|
2629
|
+
aimAt(event.clientX, event.clientY);
|
|
2630
|
+
};
|
|
2631
|
+
const onPointerDown = (event) => {
|
|
2632
|
+
if (targetsWidget(event)) return;
|
|
2633
|
+
swallow(event);
|
|
2634
|
+
if (!pinnedRef.current) aimAt(event.clientX, event.clientY);
|
|
2635
|
+
};
|
|
2636
|
+
const onPointerUp = (event) => {
|
|
2637
|
+
if (targetsWidget(event)) return;
|
|
2480
2638
|
swallow(event);
|
|
2639
|
+
if (pinnedRef.current) return;
|
|
2481
2640
|
const element = elementUnder(event.clientX, event.clientY);
|
|
2482
|
-
if (element)
|
|
2641
|
+
if (element) setPinned(describeTarget(element));
|
|
2642
|
+
};
|
|
2643
|
+
const moveAim = (key, backwards) => {
|
|
2644
|
+
const next = stepAim(aimRef.current, key, backwards);
|
|
2645
|
+
if (!next) return;
|
|
2646
|
+
next.scrollIntoView({
|
|
2647
|
+
block: "nearest",
|
|
2648
|
+
inline: "nearest"
|
|
2649
|
+
});
|
|
2650
|
+
aimRef.current = next;
|
|
2651
|
+
setTarget(describeTarget(next));
|
|
2652
|
+
};
|
|
2653
|
+
const onEscape = () => {
|
|
2654
|
+
if (pinnedRef.current) {
|
|
2655
|
+
setPinned(null);
|
|
2656
|
+
setNote("");
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
onCancel();
|
|
2483
2660
|
};
|
|
2484
2661
|
const onKeyDown = (event) => {
|
|
2485
2662
|
if (event.key === "Escape") {
|
|
2486
2663
|
swallow(event);
|
|
2487
|
-
|
|
2664
|
+
onEscape();
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
if (pinnedRef.current || targetsWidget(event)) return;
|
|
2668
|
+
if (event.key === "Enter") {
|
|
2669
|
+
swallow(event);
|
|
2670
|
+
if (aimRef.current) setPinned(describeTarget(aimRef.current));
|
|
2671
|
+
return;
|
|
2488
2672
|
}
|
|
2673
|
+
if (!AIM_KEYS.includes(event.key)) return;
|
|
2674
|
+
swallow(event);
|
|
2675
|
+
moveAim(event.key, event.shiftKey);
|
|
2676
|
+
};
|
|
2677
|
+
const onScroll = () => {
|
|
2678
|
+
setTarget((current) => current ? {
|
|
2679
|
+
...current,
|
|
2680
|
+
rect: current.element.getBoundingClientRect()
|
|
2681
|
+
} : current);
|
|
2682
|
+
setPinned((current) => current ? {
|
|
2683
|
+
...current,
|
|
2684
|
+
rect: current.element.getBoundingClientRect()
|
|
2685
|
+
} : current);
|
|
2489
2686
|
};
|
|
2490
2687
|
document.addEventListener("pointermove", onMove, true);
|
|
2491
|
-
document.addEventListener("pointerdown",
|
|
2688
|
+
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2689
|
+
document.addEventListener("pointerup", onPointerUp, true);
|
|
2492
2690
|
document.addEventListener("mousedown", swallow, true);
|
|
2493
2691
|
document.addEventListener("mouseup", swallow, true);
|
|
2494
|
-
document.addEventListener("click",
|
|
2692
|
+
document.addEventListener("click", swallow, true);
|
|
2495
2693
|
document.addEventListener("keydown", onKeyDown, true);
|
|
2694
|
+
window.addEventListener("scroll", onScroll, {
|
|
2695
|
+
capture: true,
|
|
2696
|
+
passive: true
|
|
2697
|
+
});
|
|
2496
2698
|
const previousCursor = document.body.style.cursor;
|
|
2497
2699
|
document.body.style.cursor = "crosshair";
|
|
2498
2700
|
return () => {
|
|
2499
2701
|
document.removeEventListener("pointermove", onMove, true);
|
|
2500
|
-
document.removeEventListener("pointerdown",
|
|
2702
|
+
document.removeEventListener("pointerdown", onPointerDown, true);
|
|
2703
|
+
document.removeEventListener("pointerup", onPointerUp, true);
|
|
2501
2704
|
document.removeEventListener("mousedown", swallow, true);
|
|
2502
2705
|
document.removeEventListener("mouseup", swallow, true);
|
|
2503
|
-
document.removeEventListener("click",
|
|
2706
|
+
document.removeEventListener("click", swallow, true);
|
|
2504
2707
|
document.removeEventListener("keydown", onKeyDown, true);
|
|
2708
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
2505
2709
|
document.body.style.cursor = previousCursor;
|
|
2506
2710
|
};
|
|
2507
|
-
}, [onCancel
|
|
2508
|
-
const rect = target?.rect;
|
|
2711
|
+
}, [onCancel]);
|
|
2712
|
+
const rect = (pinned ?? target)?.rect;
|
|
2509
2713
|
const labelAbove = rect ? rect.top > 30 : true;
|
|
2510
2714
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2511
2715
|
rect && /* @__PURE__ */ jsx("div", {
|
|
2512
2716
|
className: "picker-box",
|
|
2717
|
+
"data-pinned": pinned !== null,
|
|
2513
2718
|
style: {
|
|
2514
2719
|
height: rect.height,
|
|
2515
|
-
|
|
2516
|
-
top: rect.top,
|
|
2720
|
+
translate: `${rect.left}px ${rect.top}px`,
|
|
2517
2721
|
width: rect.width
|
|
2518
2722
|
}
|
|
2519
2723
|
}),
|
|
2520
|
-
rect && target && /* @__PURE__ */ jsxs("div", {
|
|
2724
|
+
rect && target && !pinned && /* @__PURE__ */ jsxs("div", {
|
|
2521
2725
|
className: "picker-label",
|
|
2522
|
-
style: {
|
|
2523
|
-
left: Math.max(4, rect.left),
|
|
2524
|
-
top: labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP
|
|
2525
|
-
},
|
|
2726
|
+
style: { translate: `${Math.max(4, rect.left)}px ${labelAbove ? rect.top - LABEL_HEIGHT - LABEL_GAP : rect.bottom + LABEL_GAP}px` },
|
|
2526
2727
|
children: [/* @__PURE__ */ jsx("strong", { children: target.componentName ? `<${target.componentName}>` : target.label }), target.region && /* @__PURE__ */ jsx("span", { children: target.region })]
|
|
2527
2728
|
}),
|
|
2528
|
-
/* @__PURE__ */ jsxs("
|
|
2529
|
-
className: "picker-
|
|
2729
|
+
pinned ? /* @__PURE__ */ jsxs("form", {
|
|
2730
|
+
className: "picker-note glass",
|
|
2731
|
+
onSubmit: (event) => {
|
|
2732
|
+
event.preventDefault();
|
|
2733
|
+
onPick(pinned.element, note.trim());
|
|
2734
|
+
},
|
|
2735
|
+
style: noteStyle(pinned.rect, view),
|
|
2530
2736
|
children: [
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2737
|
+
/* @__PURE__ */ jsxs("p", {
|
|
2738
|
+
className: "picker-note-target",
|
|
2739
|
+
children: [/* @__PURE__ */ jsx("strong", { children: pinned.componentName ? `<${pinned.componentName}>` : pinned.label }), pinned.region && /* @__PURE__ */ jsx("span", { children: pinned.region })]
|
|
2740
|
+
}),
|
|
2741
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
2742
|
+
"aria-label": labels.elementNote,
|
|
2743
|
+
maxLength: MAX_SELECTION_COMMENT_LENGTH,
|
|
2744
|
+
onChange: (event) => setNote(event.target.value),
|
|
2745
|
+
onKeyDown: (event) => {
|
|
2746
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
2747
|
+
event.preventDefault();
|
|
2748
|
+
onPick(pinned.element, note.trim());
|
|
2749
|
+
}
|
|
2750
|
+
},
|
|
2751
|
+
placeholder: labels.elementNotePlaceholder,
|
|
2752
|
+
ref: noteRef,
|
|
2753
|
+
rows: 2,
|
|
2754
|
+
value: note
|
|
2755
|
+
}),
|
|
2756
|
+
/* @__PURE__ */ jsxs("div", {
|
|
2757
|
+
className: "picker-note-actions",
|
|
2758
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
2759
|
+
className: "picker-cancel",
|
|
2760
|
+
onClick: () => {
|
|
2761
|
+
setPinned(null);
|
|
2762
|
+
setNote("");
|
|
2763
|
+
},
|
|
2764
|
+
type: "button",
|
|
2765
|
+
children: labels.pickAnother
|
|
2766
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2767
|
+
"aria-label": labels.attachElement,
|
|
2768
|
+
className: "submit",
|
|
2769
|
+
title: labels.attachElement,
|
|
2770
|
+
type: "submit",
|
|
2771
|
+
children: /* @__PURE__ */ jsx(ArrowIcon, {})
|
|
2772
|
+
})]
|
|
2773
|
+
})
|
|
2534
2774
|
]
|
|
2775
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
2776
|
+
className: "picker-hint",
|
|
2777
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
2778
|
+
className: "picker-instruction",
|
|
2779
|
+
children: [
|
|
2780
|
+
labels.pickElementHint,
|
|
2781
|
+
" ",
|
|
2782
|
+
/* @__PURE__ */ jsx("kbd", { children: "Tab" }),
|
|
2783
|
+
/* @__PURE__ */ jsx("kbd", { children: "Enter" }),
|
|
2784
|
+
/* @__PURE__ */ jsx("kbd", { children: "Esc" })
|
|
2785
|
+
]
|
|
2786
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
2787
|
+
className: "picker-cancel",
|
|
2788
|
+
onClick: onCancel,
|
|
2789
|
+
type: "button",
|
|
2790
|
+
children: labels.cancel
|
|
2791
|
+
})]
|
|
2535
2792
|
})
|
|
2536
2793
|
] });
|
|
2537
2794
|
}
|
|
2538
2795
|
//#endregion
|
|
2539
|
-
//#region src/feedback/ui/styles.ts
|
|
2540
|
-
const
|
|
2541
|
-
:
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
--rf-radius: 20px;
|
|
2555
|
-
--rf-pill: 999px;
|
|
2556
|
-
--rf-width: 360px;
|
|
2557
|
-
--rf-glass: rgb(255 255 255 / 72%);
|
|
2558
|
-
--rf-glass-highlight: rgb(255 255 255 / 90%);
|
|
2559
|
-
--rf-glass-stroke: rgb(255 255 255 / 70%);
|
|
2560
|
-
--rf-radius-sm: 8px;
|
|
2561
|
-
--rf-shadow-inset: inset 0 0 0 1px var(--rf-border);
|
|
2562
|
-
--rf-shadow-low: 0 0 0 1px rgb(15 15 15 / 4%), 0 1px 2px rgb(15 15 15 / 6%), 0 4px 10px -4px rgb(15 15 15 / 10%);
|
|
2563
|
-
--rf-shadow: 0 0 0 1px rgb(15 15 15 / 4%), 0 2px 5px -1px rgb(15 15 15 / 7%), 0 12px 32px -8px rgb(15 15 15 / 16%);
|
|
2564
|
-
--rf-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
|
2565
|
-
color-scheme: light;
|
|
2566
|
-
all: initial;
|
|
2567
|
-
font-family: var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
2568
|
-
}
|
|
2796
|
+
//#region src/feedback/ui/annotation/styles.ts
|
|
2797
|
+
const ANNOTATOR_STYLES = `
|
|
2798
|
+
.overlay { margin: 0; border: 0; padding: 0; width: 100vw; max-width: none; height: 100dvh; max-height: none; position: fixed; inset: 0; z-index: ${REFLET_Z_INDEX.annotatorOverlay}; display: flex; flex-direction: column; background: transparent; color: var(--rf-text); font: 13px/1.45 ui-sans-serif, system-ui, sans-serif; }
|
|
2799
|
+
.overlay::before { content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none; background: color-mix(in srgb, var(--rf-bg) 94%, transparent); animation: rf-editor-in 260ms var(--rf-ease); transition: opacity 260ms var(--rf-ease); }
|
|
2800
|
+
.overlay::backdrop { background: transparent; }
|
|
2801
|
+
@keyframes rf-editor-in { from { opacity: 0; } }
|
|
2802
|
+
.editor { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; padding: 20px; min-height: 0; container-type: size; }
|
|
2803
|
+
.editor-image { width: min(100cqw, calc(100cqh * var(--rf-capture-ratio))); aspect-ratio: var(--rf-capture-ratio); flex: none; position: relative; }
|
|
2804
|
+
.editor-visual { position: absolute; inset: 0; border-radius: var(--rf-radius); box-shadow: var(--rf-shadow); transform-origin: top left; overflow: hidden; }
|
|
2805
|
+
.editor-visual img, .editor-visual canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; object-position: top center; outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
|
|
2806
|
+
.editor-visual canvas { cursor: crosshair; touch-action: none; }
|
|
2807
|
+
.overlay[data-opening="true"] .editor { pointer-events: none; }
|
|
2808
|
+
.overlay[data-closing="true"] { pointer-events: none; }
|
|
2809
|
+
.overlay[data-closing="true"]::before { opacity: 0; }
|
|
2810
|
+
.overlay[data-closing="true"] .toolbar { opacity: 0; transform: translateY(8px); }
|
|
2569
2811
|
|
|
2570
|
-
:
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2812
|
+
.toolbar { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; transition: opacity 180ms var(--rf-ease), transform 180ms var(--rf-ease); padding: 4px 12px max(16px, env(safe-area-inset-bottom)); }
|
|
2813
|
+
.toolbar .group { display: flex; align-items: center; gap: 2px; padding: 4px; border-radius: var(--rf-pill); }
|
|
2814
|
+
.tool[aria-pressed="true"], .tool[data-selected="true"] { background: var(--rf-primary); color: var(--rf-primary-text); }
|
|
2815
|
+
.color-picker { position: relative; }
|
|
2816
|
+
.color-picker summary { display: flex; justify-content: center; align-items: center; }
|
|
2817
|
+
.color-preview { width: 18px; height: 18px; border-radius: 50%; background: var(--annotation-color); box-shadow: inset 0 0 0 1px var(--rf-border); }
|
|
2818
|
+
.color-popover { --rf-color-width: 200px; position: absolute; bottom: calc(100% + 14px); left: clamp(calc(12px - var(--rf-color-anchor)), 0px, calc(100vw - var(--rf-color-anchor) - var(--rf-color-width) - 12px)); display: flex; align-items: center; gap: 6px; width: var(--rf-color-width); padding: 4px 4px 4px 14px; border-radius: var(--rf-pill); animation: rf-in 180ms var(--rf-ease); }
|
|
2819
|
+
.color-picker[data-placement="below"] .color-popover { top: calc(100% + 14px); bottom: auto; }
|
|
2820
|
+
.color-slider { flex: 1; min-width: 0; }
|
|
2821
|
+
.color-slider-control { display: flex; align-items: center; height: 40px; width: 100%; touch-action: none; user-select: none; cursor: pointer; }
|
|
2822
|
+
.color-slider-track { height: 12px; width: 100%; border-radius: var(--rf-pill); background: linear-gradient(to right, var(--rf-swatch-base), var(--annotation-base-color)); box-shadow: inset 0 0 0 1px var(--rf-border); }
|
|
2823
|
+
.color-slider-thumb { width: 16px; height: 16px; border: 2px solid var(--rf-swatch-base); border-radius: 50%; background: var(--annotation-color); box-shadow: var(--rf-shadow-low); transition: scale 160ms var(--rf-ease); }
|
|
2824
|
+
.color-slider-thumb[data-dragging] { scale: 1.12; }
|
|
2825
|
+
.color-slider-thumb:has(:focus-visible) { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
|
|
2826
|
+
.color-spectrum { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 40px; height: 40px; border-radius: 50%; cursor: pointer; }
|
|
2827
|
+
.color-spectrum::before { content: ""; width: 16px; height: 16px; border-radius: 50%; background: var(--rf-spectrum); box-shadow: inset 0 0 0 1px var(--rf-glass-highlight); }
|
|
2828
|
+
.color-spectrum:hover { background: var(--rf-bg-hover); }
|
|
2829
|
+
.color-spectrum button { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
|
|
2830
|
+
.color-spectrum button:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
|
|
2831
|
+
.color-spectrum input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
|
|
2832
|
+
.done-btn { display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; padding: 0; border-radius: var(--rf-pill); color: var(--rf-primary-text); background: var(--rf-primary); }
|
|
2833
|
+
.drawing-menu { position: relative; }
|
|
2834
|
+
.drawing-popover { position: absolute; z-index: 2; right: 0; bottom: calc(100% + 12px); width: 200px; max-height: calc(50dvh - 68px); overflow-y: auto; padding: 6px; border-radius: var(--rf-radius); }
|
|
2835
|
+
.drawing-menu[data-placement="below"] .drawing-popover { top: calc(100% + 12px); bottom: auto; }
|
|
2836
|
+
.drawing-option { display: flex; align-items: center; width: 100%; min-height: 40px; gap: 10px; padding: 0 10px; border-radius: 12px; text-align: left; }
|
|
2837
|
+
.drawing-option:hover, .drawing-option[aria-pressed="true"] { background: var(--rf-bg-hover); }
|
|
2838
|
+
.drawing-divider { height: 1px; margin: 5px; background: var(--rf-border); }
|
|
2839
|
+
.editor canvas[data-tool="text"] { cursor: text; }
|
|
2840
|
+
.text-annotation-editor { position: fixed; z-index: 3; width: 240px; padding: 8px; border-radius: var(--rf-radius); }
|
|
2841
|
+
.text-annotation-editor:focus-within { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
|
|
2842
|
+
.text-annotation-editor textarea { display: block; width: 100%; min-height: 64px; max-height: 112px; padding: 6px; border: 0; background: transparent; resize: vertical; font-size: 16px; line-height: 24px; }
|
|
2843
|
+
.text-annotation-editor textarea:focus { outline: none; }
|
|
2844
|
+
.text-annotation-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
|
|
2845
|
+
@media (pointer: coarse) {
|
|
2846
|
+
.drawing-option { min-height: 44px; }
|
|
2847
|
+
.done-btn { width: 44px; height: 44px; }
|
|
2848
|
+
.color-popover { --rf-color-width: 232px; padding-left: 16px; }
|
|
2849
|
+
.color-slider-control, .color-spectrum { height: 44px; }
|
|
2850
|
+
.color-spectrum { width: 44px; }
|
|
2851
|
+
.color-slider-thumb, .color-spectrum::before { width: 18px; height: 18px; }
|
|
2852
|
+
.editor { padding: 8px; }
|
|
2853
|
+
.toolbar { gap: 6px; }
|
|
2854
|
+
.toolbar .group { padding: 3px; gap: 0; }
|
|
2588
2855
|
}
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
.root {
|
|
2594
|
-
position: fixed;
|
|
2595
|
-
z-index: 2147483000;
|
|
2596
|
-
display: flex;
|
|
2597
|
-
align-items: flex-end;
|
|
2598
|
-
gap: 8px;
|
|
2599
|
-
font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
|
|
2600
|
-
color: var(--rf-text);
|
|
2856
|
+
@media (pointer: coarse) and (max-width: 360px) {
|
|
2857
|
+
.toolbar { gap: 4px; padding-inline: 2px; }
|
|
2858
|
+
.toolbar .group { padding: 0; }
|
|
2601
2859
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
.root[data-position^="top"] { top: calc(var(--rf-viewport-top, 0px) + max(var(--rf-offset), env(safe-area-inset-top))); align-items: flex-start; }
|
|
2607
|
-
.root[data-position="bottom-right"] .panel { transform-origin: bottom right; }
|
|
2608
|
-
.root[data-position="bottom-left"] .panel { transform-origin: bottom left; }
|
|
2609
|
-
.root[data-position^="top"] .panel { transform-origin: top center; }
|
|
2610
|
-
.root[data-editing="true"] { visibility: hidden; pointer-events: none; }
|
|
2611
|
-
button, select, input, textarea { font: inherit; color: inherit; }
|
|
2612
|
-
button { margin: 0; cursor: pointer; border: 0; background: none; }
|
|
2613
|
-
button:disabled { cursor: not-allowed; opacity: .4; }
|
|
2614
|
-
svg { flex-shrink: 0; }
|
|
2615
|
-
:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
|
|
2616
|
-
.icon-btn, .tool {
|
|
2617
|
-
display: inline-flex; align-items: center; justify-content: center;
|
|
2618
|
-
width: 32px; height: 32px; flex: none; border-radius: var(--rf-pill); color: var(--rf-text-muted);
|
|
2619
|
-
transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease);
|
|
2620
|
-
}
|
|
2621
|
-
.icon-btn:hover, .tool:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
2622
|
-
.launcher {
|
|
2623
|
-
display: inline-flex; align-items: center; gap: 7px; height: 40px; padding: 0 15px;
|
|
2624
|
-
border-radius: var(--rf-pill); color: var(--rf-text);
|
|
2625
|
-
font-weight: 500;
|
|
2626
|
-
transition: transform 200ms var(--rf-ease), box-shadow 200ms var(--rf-ease);
|
|
2627
|
-
}
|
|
2628
|
-
.launcher:hover { transform: translateY(-2px); box-shadow: var(--rf-shadow); }
|
|
2629
|
-
.launcher-icon { display: flex; width: 15px; height: 15px; }
|
|
2630
|
-
.launcher-icon .on-close { display: none; }
|
|
2631
|
-
.floating-controls {
|
|
2632
|
-
display: flex; align-items: center; padding: 3px; flex: none;
|
|
2633
|
-
border-radius: var(--rf-pill);
|
|
2634
|
-
animation: rf-in 220ms var(--rf-ease);
|
|
2635
|
-
}
|
|
2636
|
-
.drag-handle { touch-action: none; cursor: grab; }
|
|
2637
|
-
.drag-handle:active { cursor: grabbing; }
|
|
2638
|
-
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2639
|
-
.ghost-btn { display: flex; align-items: center; gap: 8px; min-height: 32px; padding: 0 8px; border-radius: var(--rf-radius-sm); }
|
|
2640
|
-
.ghost-btn:hover { background: var(--rf-bg-hover); }
|
|
2641
|
-
.error { margin: 0; padding: 10px 14px; border-radius: var(--rf-radius); font-size: 12px; color: var(--rf-danger); }
|
|
2642
|
-
.done { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 24px; text-align: center; }
|
|
2643
|
-
.done svg { color: var(--rf-success); }
|
|
2644
|
-
.done h2 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
2645
|
-
.done p { margin: 0; font-size: 12px; color: var(--rf-text-muted); }
|
|
2646
|
-
.spinner { width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: rf-spin 700ms linear infinite; }
|
|
2647
|
-
.hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
2648
|
-
@keyframes rf-spin { to { transform: rotate(360deg); } }
|
|
2649
|
-
@keyframes rf-in { from { opacity: 0; transform: translateY(5px) scale(.98); } }
|
|
2650
|
-
@keyframes rf-fade { from { opacity: 0; } }
|
|
2651
|
-
@media (pointer: coarse) {
|
|
2652
|
-
.icon-btn, .tool { width: 44px; height: 44px; }
|
|
2653
|
-
.launcher, .ghost-btn { min-height: 44px; }
|
|
2654
|
-
}
|
|
2655
|
-
.glass {
|
|
2656
|
-
position: relative;
|
|
2657
|
-
isolation: isolate;
|
|
2658
|
-
border: 1px solid var(--rf-glass-stroke);
|
|
2659
|
-
box-shadow: inset 0 1px 0 var(--rf-glass-highlight), var(--rf-shadow);
|
|
2660
|
-
}
|
|
2661
|
-
.glass::before {
|
|
2662
|
-
content: "";
|
|
2663
|
-
position: absolute;
|
|
2664
|
-
inset: 0;
|
|
2665
|
-
z-index: -1;
|
|
2666
|
-
border-radius: inherit;
|
|
2667
|
-
background: var(--rf-bg);
|
|
2668
|
-
pointer-events: none;
|
|
2669
|
-
}
|
|
2670
|
-
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
|
|
2671
|
-
.glass::before {
|
|
2672
|
-
background: var(--rf-glass);
|
|
2673
|
-
-webkit-backdrop-filter: blur(20px) saturate(1.6);
|
|
2674
|
-
backdrop-filter: blur(20px) saturate(1.6);
|
|
2675
|
-
}
|
|
2676
|
-
}
|
|
2677
|
-
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
|
|
2678
|
-
.glass::before { background: var(--rf-bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
2679
|
-
}
|
|
2680
|
-
.capture-halo { position: fixed; inset: 0; z-index: 2147482999; pointer-events: none; opacity: 0; transition: opacity 500ms var(--rf-ease); }
|
|
2681
|
-
.capture-halo[data-active="true"] { opacity: 1; transition-duration: 140ms; }
|
|
2682
|
-
.capture-halo::before { content: ""; position: absolute; inset: 0; border-radius: 20px; box-shadow: inset 14px 0 36px -16px var(--rf-accent), inset -14px 0 36px -16px var(--rf-accent), inset 0 0 4px 1px color-mix(in srgb, var(--rf-accent) 35%, transparent); }
|
|
2683
|
-
.capture-halo::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--rf-accent) 20%, transparent), transparent 5%, transparent 95%, color-mix(in srgb, var(--rf-accent) 20%, transparent)); }
|
|
2684
|
-
|
|
2860
|
+
`;
|
|
2861
|
+
//#endregion
|
|
2862
|
+
//#region src/feedback/ui/composer/styles.ts
|
|
2863
|
+
const COMPOSER_STYLES = `
|
|
2685
2864
|
.panel { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 8px; outline: none; animation: rf-in 240ms var(--rf-ease); }
|
|
2686
2865
|
.success-panel { border-radius: var(--rf-radius); }
|
|
2687
2866
|
.composer { position: relative; display: flex; flex-direction: column; gap: 8px; }
|
|
@@ -2689,7 +2868,7 @@ svg { flex-shrink: 0; }
|
|
|
2689
2868
|
.message-row textarea {
|
|
2690
2869
|
flex: 1; width: 0; min-height: 36px; height: 36px; max-height: min(140px, 30dvh);
|
|
2691
2870
|
margin: 0; padding: 8px 10px 8px 12px; border: 0; border-radius: 20px; resize: none;
|
|
2692
|
-
background: transparent; line-height: 20px;
|
|
2871
|
+
background: transparent; line-height: 20px;
|
|
2693
2872
|
}
|
|
2694
2873
|
.panel:focus-within .message-row[data-expanded="true"] textarea { height: 76px; }
|
|
2695
2874
|
.message-row textarea::placeholder { color: var(--rf-text-muted); }
|
|
@@ -2701,11 +2880,6 @@ svg { flex-shrink: 0; }
|
|
|
2701
2880
|
.submit:disabled { background: var(--rf-bg-hover); color: var(--rf-text-muted); opacity: .65; }
|
|
2702
2881
|
.composer-toolbar { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
|
|
2703
2882
|
.composer-controls { display: flex; align-items: center; gap: 2px; padding: 3px; border-radius: var(--rf-pill); }
|
|
2704
|
-
.select-control { position: relative; display: flex; align-items: center; min-width: 0; }
|
|
2705
|
-
.select-control > svg { position: absolute; right: 9px; width: 12px; height: 12px; pointer-events: none; color: var(--rf-text-muted); }
|
|
2706
|
-
.category-select { appearance: none; min-width: 0; max-width: 124px; height: 32px; padding: 0 26px 0 12px; border: 0; border-radius: var(--rf-pill); color: var(--rf-text); background: transparent; cursor: pointer; text-overflow: ellipsis; }
|
|
2707
|
-
.category-select:hover:hover { background: var(--rf-bg-hover); }
|
|
2708
|
-
option { background: var(--rf-bg); color: var(--rf-text); }
|
|
2709
2883
|
.composer-options { position: relative; }
|
|
2710
2884
|
summary { cursor: pointer; list-style: none; }
|
|
2711
2885
|
summary::-webkit-details-marker { display: none; }
|
|
@@ -2714,7 +2888,7 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2714
2888
|
.email-field { display: flex; flex-direction: column; gap: 6px; color: var(--rf-text-muted); font-size: 12px; }
|
|
2715
2889
|
.email-field [role="alert"] { color: var(--rf-danger); }
|
|
2716
2890
|
.email-field input { width: 100%; padding: 9px; border: 1px solid var(--rf-border); border-radius: var(--rf-radius-sm); background: var(--rf-bg-subtle); color: var(--rf-text); }
|
|
2717
|
-
.dismiss-btn { min-height:
|
|
2891
|
+
.dismiss-btn { min-height: 40px; text-align: left; padding: 0 8px; border-radius: var(--rf-radius-sm); color: var(--rf-text-muted); }
|
|
2718
2892
|
.dismiss-btn:hover { background: var(--rf-bg-hover); }
|
|
2719
2893
|
.version-info { font-size: 10px; color: var(--rf-text-muted); padding: 4px 8px 0; border-top: 1px solid var(--rf-border); text-decoration: none; }
|
|
2720
2894
|
.version-info:hover { color: var(--rf-text); }
|
|
@@ -2733,9 +2907,9 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2733
2907
|
.attachments:empty { display: none; }
|
|
2734
2908
|
.screenshot-attachment, .attachment-skeleton { position: relative; flex: none; margin: 0; min-width: 0; width: 128px; height: 78px; padding: 3px; border-radius: var(--rf-radius); animation: rf-in 260ms var(--rf-ease); }
|
|
2735
2909
|
.screenshot-preview { display: block; position: relative; width: 100%; height: 100%; padding: 0; overflow: hidden; border-radius: 16px; }
|
|
2736
|
-
.screenshot-preview img, .screenshot-preview canvas { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top center; }
|
|
2910
|
+
.screenshot-preview img, .screenshot-preview canvas { width: 100%; height: 100%; display: block; object-fit: cover; object-position: top center; outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
|
|
2737
2911
|
.screenshot-preview canvas { position: absolute; inset: 0; pointer-events: none; }
|
|
2738
|
-
.attachment-caption { position: absolute; left: 5px; bottom: 5px; display: flex; align-items: center; gap: 4px; padding: 3px 6px; font-size: 10px; border-radius: var(--rf-pill); color: var(--rf-text); }
|
|
2912
|
+
.attachment-caption { position: absolute; left: 5px; bottom: 5px; display: flex; align-items: center; gap: 4px; padding: 3px 6px; font-size: 10px; font-variant-numeric: tabular-nums; border-radius: var(--rf-pill); color: var(--rf-text); }
|
|
2739
2913
|
.attachment-caption svg { width: 12px; height: 12px; }
|
|
2740
2914
|
.annotation-count { color: var(--rf-text); font-weight: 600; }
|
|
2741
2915
|
.attachment-actions { position: absolute; right: 4px; bottom: -4px; display: flex; gap: 1px; padding: 1px; border-radius: var(--rf-pill); opacity: 0; pointer-events: none; transform: translateY(3px); transition: opacity 150ms var(--rf-ease), transform 150ms var(--rf-ease); }
|
|
@@ -2745,9 +2919,9 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2745
2919
|
.attachment-skeleton::after { content: ""; position: absolute; inset: 0; border-radius: inherit; background: var(--rf-bg-subtle); animation: rf-fade 800ms alternate infinite; }
|
|
2746
2920
|
.selection-chip { display: flex; align-items: center; max-width: 100%; padding: 3px; border-radius: var(--rf-pill); }
|
|
2747
2921
|
.selection-label { display: flex; align-items: center; min-width: 0; gap: 6px; padding: 0 6px; font-size: 11px; }
|
|
2748
|
-
.selection-
|
|
2922
|
+
.selection-name { flex: none; font-weight: 600; color: var(--rf-accent); }
|
|
2923
|
+
.selection-label img { object-fit: contain; border-radius: var(--rf-pill); outline: 1px solid var(--rf-hairline); outline-offset: -1px; }
|
|
2749
2924
|
@media (pointer: coarse) {
|
|
2750
|
-
.category-select { height: 44px; max-width: 96px; }
|
|
2751
2925
|
.message-row { border-radius: 28px; }
|
|
2752
2926
|
.message-row textarea { font-size: 16px; min-height: 44px; height: 44px; padding-top: 12px; }
|
|
2753
2927
|
.submit { width: 44px; height: 44px; }
|
|
@@ -2759,79 +2933,26 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2759
2933
|
.email-field input { font-size: 16px; }
|
|
2760
2934
|
.dismiss-btn, .selection-label { min-height: 44px; }
|
|
2761
2935
|
}
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
@keyframes rf-editor-in { from { background: transparent; } }
|
|
2767
|
-
.editor { flex: 1; position: relative; display: flex; align-items: center; justify-content: center; padding: 20px; min-height: 0; container-type: size; }
|
|
2768
|
-
.editor-image { width: min(100cqw, calc(100cqh * var(--rf-capture-ratio))); aspect-ratio: var(--rf-capture-ratio); flex: none; position: relative; }
|
|
2769
|
-
.editor-visual { position: absolute; inset: 0; border-radius: var(--rf-radius); box-shadow: var(--rf-shadow); transform-origin: top left; overflow: hidden; }
|
|
2770
|
-
.editor-visual img, .editor-visual canvas { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; object-position: top center; }
|
|
2771
|
-
.editor-visual canvas { cursor: crosshair; touch-action: none; }
|
|
2772
|
-
.overlay[data-opening="true"] .editor { pointer-events: none; }
|
|
2773
|
-
.overlay[data-closing="true"] { pointer-events: none; background: transparent; }
|
|
2774
|
-
.overlay[data-closing="true"] .toolbar { opacity: 0; transform: translateY(8px); }
|
|
2775
|
-
|
|
2776
|
-
.toolbar { display: flex; align-items: center; justify-content: center; gap: 8px; flex-wrap: wrap; transition: opacity 180ms var(--rf-ease), transform 180ms var(--rf-ease); padding: 4px 12px max(16px, env(safe-area-inset-bottom)); }
|
|
2777
|
-
.toolbar .group { display: flex; align-items: center; gap: 2px; padding: 4px; border-radius: var(--rf-pill); }
|
|
2778
|
-
.tool[aria-pressed="true"], .tool[data-selected="true"] { background: var(--rf-primary); color: var(--rf-primary-text); }
|
|
2779
|
-
.color-picker { position: relative; }
|
|
2780
|
-
.color-picker summary { display: flex; justify-content: center; align-items: center; }
|
|
2781
|
-
.color-preview { width: 18px; height: 18px; border-radius: 50%; background: var(--annotation-color); box-shadow: inset 0 0 0 1px var(--rf-border); }
|
|
2782
|
-
.color-popover { --rf-color-width: 200px; position: absolute; bottom: calc(100% + 14px); left: clamp(calc(12px - var(--rf-color-anchor)), 0px, calc(100vw - var(--rf-color-anchor) - var(--rf-color-width) - 12px)); display: flex; align-items: center; gap: 6px; width: var(--rf-color-width); padding: 4px 4px 4px 14px; border-radius: var(--rf-pill); animation: rf-in 180ms var(--rf-ease); }
|
|
2783
|
-
.color-picker[data-placement="below"] .color-popover { top: calc(100% + 14px); bottom: auto; }
|
|
2784
|
-
.color-slider { flex: 1; min-width: 0; }
|
|
2785
|
-
.color-slider-control { display: flex; align-items: center; height: 32px; width: 100%; touch-action: none; user-select: none; cursor: pointer; }
|
|
2786
|
-
.color-slider-track { height: 12px; width: 100%; border-radius: var(--rf-pill); background: linear-gradient(to right, white, var(--annotation-base-color)); box-shadow: inset 0 0 0 1px var(--rf-border); }
|
|
2787
|
-
.color-slider-thumb { width: 16px; height: 16px; border: 2px solid white; border-radius: 50%; background: var(--annotation-color); box-shadow: var(--rf-shadow-low); transition: scale 160ms var(--rf-ease); }
|
|
2788
|
-
.color-slider-thumb[data-dragging] { scale: 1.12; }
|
|
2789
|
-
.color-slider-thumb:has(:focus-visible), .color-spectrum:has(:focus-visible) { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
|
|
2790
|
-
.color-spectrum { position: relative; display: flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; }
|
|
2791
|
-
.color-spectrum::before { content: ""; width: 16px; height: 16px; border-radius: 50%; background: conic-gradient(#ff5757, #ffde59, #6bdb78, #4ac9fa, #8b5cf6, #ef73dc, #ff5757); box-shadow: inset 0 0 0 1px var(--rf-glass-highlight); }
|
|
2792
|
-
.color-spectrum:hover { background: var(--rf-bg-hover); }
|
|
2793
|
-
.color-spectrum button { position: absolute; inset: 0; width: 100%; height: 100%; border-radius: inherit; }
|
|
2794
|
-
.color-spectrum button:focus-visible { outline: none; }
|
|
2795
|
-
.color-spectrum input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; pointer-events: none; }
|
|
2796
|
-
.done-btn { display: flex; align-items: center; justify-content: center; width: 34px; height: 34px; padding: 0; border-radius: var(--rf-pill); color: var(--rf-primary-text); background: var(--rf-primary); }
|
|
2797
|
-
.drawing-menu { position: relative; }
|
|
2798
|
-
.drawing-popover { position: absolute; z-index: 2; right: 0; bottom: calc(100% + 12px); width: 200px; max-height: calc(50dvh - 68px); overflow-y: auto; padding: 6px; border-radius: var(--rf-radius); }
|
|
2799
|
-
.drawing-menu[data-placement="below"] .drawing-popover { top: calc(100% + 12px); bottom: auto; }
|
|
2800
|
-
.drawing-option { display: flex; align-items: center; width: 100%; min-height: 36px; gap: 10px; padding: 0 10px; border-radius: 12px; text-align: left; }
|
|
2801
|
-
.drawing-option:hover, .drawing-option[aria-pressed="true"] { background: var(--rf-bg-hover); }
|
|
2802
|
-
.drawing-divider { height: 1px; margin: 5px; background: var(--rf-border); }
|
|
2803
|
-
.editor canvas[data-tool="text"] { cursor: text; }
|
|
2804
|
-
.text-annotation-editor { position: fixed; z-index: 3; width: 240px; padding: 8px; border-radius: var(--rf-radius); }
|
|
2805
|
-
.text-annotation-editor textarea { display: block; width: 100%; min-height: 64px; max-height: 112px; padding: 6px; border: 0; background: transparent; resize: vertical; font-size: 16px; line-height: 24px; }
|
|
2806
|
-
.text-annotation-editor textarea:focus { outline: none; }
|
|
2807
|
-
.text-annotation-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
|
|
2808
|
-
@media (pointer: coarse) {
|
|
2809
|
-
.drawing-option { min-height: 44px; }
|
|
2810
|
-
.done-btn { width: 44px; height: 44px; }
|
|
2811
|
-
.color-popover { --rf-color-width: 232px; padding-left: 16px; }
|
|
2812
|
-
.color-slider-control, .color-spectrum { height: 44px; }
|
|
2813
|
-
.color-spectrum { width: 44px; }
|
|
2814
|
-
.color-slider-thumb, .color-spectrum::before { width: 18px; height: 18px; }
|
|
2815
|
-
.editor { padding: 8px; }
|
|
2816
|
-
.toolbar { gap: 6px; }
|
|
2817
|
-
.toolbar .group { padding: 3px; gap: 0; }
|
|
2818
|
-
}
|
|
2819
|
-
@media (pointer: coarse) and (max-width: 360px) {
|
|
2820
|
-
.toolbar { gap: 4px; padding-inline: 2px; }
|
|
2821
|
-
.toolbar .group { padding: 0; }
|
|
2822
|
-
}
|
|
2823
|
-
|
|
2824
|
-
|
|
2936
|
+
`;
|
|
2937
|
+
//#endregion
|
|
2938
|
+
//#region src/feedback/ui/floating/picker-styles.ts
|
|
2939
|
+
const PICKER_STYLES = `
|
|
2825
2940
|
.picker-box {
|
|
2826
2941
|
position: fixed;
|
|
2942
|
+
top: 0;
|
|
2943
|
+
left: 0;
|
|
2827
2944
|
pointer-events: none;
|
|
2828
2945
|
border-radius: 5px;
|
|
2829
2946
|
background: color-mix(in srgb, var(--rf-accent) 10%, transparent);
|
|
2830
2947
|
box-shadow: 0 0 0 2px var(--rf-accent), 0 0 0 5px color-mix(in srgb, var(--rf-accent) 18%, transparent);
|
|
2831
|
-
|
|
2948
|
+
will-change: translate;
|
|
2949
|
+
transition: translate 130ms var(--rf-ease);
|
|
2832
2950
|
}
|
|
2951
|
+
.picker-box[data-pinned="true"] { transition: none; }
|
|
2833
2952
|
.picker-label {
|
|
2834
2953
|
position: fixed;
|
|
2954
|
+
top: 0;
|
|
2955
|
+
left: 0;
|
|
2835
2956
|
pointer-events: none;
|
|
2836
2957
|
display: inline-flex;
|
|
2837
2958
|
align-items: center;
|
|
@@ -2841,32 +2962,45 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2841
2962
|
padding: 0 8px;
|
|
2842
2963
|
font-size: 11.5px;
|
|
2843
2964
|
font-weight: 500;
|
|
2844
|
-
color:
|
|
2965
|
+
color: var(--rf-accent-text);
|
|
2845
2966
|
background: var(--rf-accent);
|
|
2846
2967
|
border-radius: 6px;
|
|
2847
|
-
box-shadow:
|
|
2968
|
+
box-shadow: var(--rf-shadow-float);
|
|
2848
2969
|
white-space: nowrap;
|
|
2849
2970
|
overflow: hidden;
|
|
2850
|
-
|
|
2971
|
+
will-change: translate;
|
|
2972
|
+
transition: translate 130ms var(--rf-ease);
|
|
2851
2973
|
}
|
|
2852
2974
|
.picker-label strong { flex: none; font-weight: 600; }
|
|
2853
2975
|
.picker-label span { min-width: 0; overflow: hidden; text-overflow: ellipsis; opacity: 0.75; }
|
|
2976
|
+
.selection-outline {
|
|
2977
|
+
position: fixed;
|
|
2978
|
+
top: 0;
|
|
2979
|
+
left: 0;
|
|
2980
|
+
pointer-events: none;
|
|
2981
|
+
border-radius: 5px;
|
|
2982
|
+
background: color-mix(in srgb, var(--rf-accent) 7%, transparent);
|
|
2983
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--rf-accent) 70%, transparent), 0 0 0 5px color-mix(in srgb, var(--rf-accent) 14%, transparent);
|
|
2984
|
+
transition: opacity 160ms var(--rf-ease);
|
|
2985
|
+
}
|
|
2854
2986
|
.picker-hint {
|
|
2855
2987
|
position: fixed;
|
|
2856
2988
|
left: 50%;
|
|
2857
|
-
bottom: 24px;
|
|
2989
|
+
bottom: calc(24px + env(safe-area-inset-bottom));
|
|
2858
2990
|
transform: translateX(-50%);
|
|
2859
2991
|
display: flex;
|
|
2860
2992
|
align-items: center;
|
|
2861
2993
|
gap: 6px;
|
|
2862
|
-
|
|
2994
|
+
max-width: calc(100vw - 24px);
|
|
2995
|
+
padding: 5px 6px 5px 12px;
|
|
2863
2996
|
font-size: 12.5px;
|
|
2864
2997
|
color: var(--rf-text);
|
|
2865
2998
|
background: var(--rf-bg);
|
|
2866
|
-
border-radius:
|
|
2999
|
+
border-radius: var(--rf-pill);
|
|
2867
3000
|
box-shadow: var(--rf-shadow);
|
|
2868
3001
|
animation: rf-fade 240ms var(--rf-ease);
|
|
2869
3002
|
}
|
|
3003
|
+
.picker-instruction { display: flex; align-items: center; gap: 6px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2870
3004
|
.picker-hint kbd {
|
|
2871
3005
|
padding: 1px 5px;
|
|
2872
3006
|
font: inherit;
|
|
@@ -2876,10 +3010,227 @@ summary::-webkit-details-marker { display: none; }
|
|
|
2876
3010
|
background: var(--rf-bg-subtle);
|
|
2877
3011
|
box-shadow: var(--rf-shadow-inset);
|
|
2878
3012
|
}
|
|
3013
|
+
.picker-cancel {
|
|
3014
|
+
display: inline-flex;
|
|
3015
|
+
align-items: center;
|
|
3016
|
+
height: 40px;
|
|
3017
|
+
padding: 0 14px;
|
|
3018
|
+
border-radius: var(--rf-pill);
|
|
3019
|
+
font-size: 12.5px;
|
|
3020
|
+
font-weight: 500;
|
|
3021
|
+
white-space: nowrap;
|
|
3022
|
+
color: var(--rf-text-muted);
|
|
3023
|
+
}
|
|
3024
|
+
.picker-cancel:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
3025
|
+
.picker-note {
|
|
3026
|
+
position: fixed;
|
|
3027
|
+
z-index: ${REFLET_Z_INDEX.annotatorOverlay};
|
|
3028
|
+
display: flex;
|
|
3029
|
+
flex-direction: column;
|
|
3030
|
+
gap: 6px;
|
|
3031
|
+
width: 300px;
|
|
3032
|
+
max-width: calc(100vw - 24px);
|
|
3033
|
+
padding: 6px 6px 6px 8px;
|
|
3034
|
+
border-radius: 18px;
|
|
3035
|
+
animation: rf-in 180ms var(--rf-ease);
|
|
3036
|
+
}
|
|
3037
|
+
.picker-note:focus-within { outline: 2px solid var(--rf-accent); outline-offset: 2px; }
|
|
3038
|
+
.picker-note-target {
|
|
3039
|
+
display: flex;
|
|
3040
|
+
align-items: center;
|
|
3041
|
+
gap: 6px;
|
|
3042
|
+
margin: 0;
|
|
3043
|
+
padding: 2px 4px 0;
|
|
3044
|
+
font-size: 11px;
|
|
3045
|
+
color: var(--rf-text-muted);
|
|
3046
|
+
white-space: nowrap;
|
|
3047
|
+
}
|
|
3048
|
+
.picker-note-target strong { flex: none; font-weight: 600; color: var(--rf-accent); }
|
|
3049
|
+
.picker-note-target span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
3050
|
+
.picker-note textarea {
|
|
3051
|
+
width: 100%;
|
|
3052
|
+
min-height: 46px;
|
|
3053
|
+
max-height: min(160px, 30dvh);
|
|
3054
|
+
margin: 0;
|
|
3055
|
+
padding: 7px 6px 0;
|
|
3056
|
+
border: 0;
|
|
3057
|
+
resize: none;
|
|
3058
|
+
background: transparent;
|
|
3059
|
+
color: var(--rf-text);
|
|
3060
|
+
font-size: 13px;
|
|
3061
|
+
line-height: 19px;
|
|
3062
|
+
}
|
|
3063
|
+
.picker-note textarea:focus { outline: none; }
|
|
3064
|
+
.picker-note textarea::placeholder { color: var(--rf-text-muted); }
|
|
3065
|
+
.picker-note-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
3066
|
+
@media (pointer: coarse) {
|
|
3067
|
+
.picker-hint { padding: 6px 8px 6px 14px; }
|
|
3068
|
+
.picker-hint kbd { display: none; }
|
|
3069
|
+
.picker-cancel { height: 44px; padding: 0 18px; font-size: 14px; }
|
|
3070
|
+
.picker-note textarea { font-size: 16px; min-height: 56px; }
|
|
3071
|
+
}
|
|
2879
3072
|
|
|
3073
|
+
`;
|
|
3074
|
+
//#endregion
|
|
3075
|
+
//#region src/feedback/ui/styles.ts
|
|
3076
|
+
const WIDGET_STYLES = `
|
|
3077
|
+
:host {
|
|
3078
|
+
--rf-offset: 20px;
|
|
3079
|
+
--rf-bg: #ffffff;
|
|
3080
|
+
--rf-bg-subtle: rgb(55 53 47 / 4%);
|
|
3081
|
+
--rf-bg-hover: rgb(55 53 47 / 7%);
|
|
3082
|
+
--rf-border: rgb(55 53 47 / 9%);
|
|
3083
|
+
--rf-hairline: rgb(15 15 15 / 10%);
|
|
3084
|
+
--rf-text: #37352f;
|
|
3085
|
+
--rf-text-muted: rgb(55 53 47 / 68%);
|
|
3086
|
+
--rf-primary: #37352f;
|
|
3087
|
+
--rf-primary-text: #ffffff;
|
|
3088
|
+
--rf-accent: #2383e2;
|
|
3089
|
+
--rf-accent-text: #ffffff;
|
|
3090
|
+
--rf-danger: #d44c47;
|
|
3091
|
+
--rf-success: #448361;
|
|
3092
|
+
--rf-swatch-base: #ffffff;
|
|
3093
|
+
--rf-spectrum: conic-gradient(#ff5757, #ffde59, #6bdb78, #4ac9fa, #8b5cf6, #ef73dc, #ff5757);
|
|
3094
|
+
--rf-radius: 20px;
|
|
3095
|
+
--rf-pill: 999px;
|
|
3096
|
+
--rf-width: 360px;
|
|
3097
|
+
--rf-glass: rgb(255 255 255 / 72%);
|
|
3098
|
+
--rf-glass-highlight: rgb(255 255 255 / 90%);
|
|
3099
|
+
--rf-glass-stroke: rgb(255 255 255 / 70%);
|
|
3100
|
+
--rf-radius-sm: 8px;
|
|
3101
|
+
--rf-shadow-inset: inset 0 0 0 1px var(--rf-border);
|
|
3102
|
+
--rf-shadow-low: 0 0 0 1px rgb(15 15 15 / 4%), 0 1px 2px rgb(15 15 15 / 6%), 0 4px 10px -4px rgb(15 15 15 / 10%);
|
|
3103
|
+
--rf-shadow: 0 0 0 1px rgb(15 15 15 / 4%), 0 2px 5px -1px rgb(15 15 15 / 7%), 0 12px 32px -8px rgb(15 15 15 / 16%);
|
|
3104
|
+
--rf-shadow-float: 0 4px 12px -3px rgb(15 15 15 / 35%);
|
|
3105
|
+
--rf-ease: cubic-bezier(0.16, 1, 0.3, 1);
|
|
3106
|
+
color-scheme: light;
|
|
3107
|
+
all: initial;
|
|
3108
|
+
font-family: var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
|
3109
|
+
}
|
|
2880
3110
|
|
|
3111
|
+
:host([data-theme="dark"]) {
|
|
3112
|
+
--rf-bg: #202020;
|
|
3113
|
+
--rf-bg-subtle: rgb(255 255 255 / 5%);
|
|
3114
|
+
--rf-bg-hover: rgb(255 255 255 / 9%);
|
|
3115
|
+
--rf-border: rgb(255 255 255 / 9%);
|
|
3116
|
+
--rf-hairline: rgb(255 255 255 / 12%);
|
|
3117
|
+
--rf-text: #ebeae8;
|
|
3118
|
+
--rf-text-muted: rgb(255 255 255 / 68%);
|
|
3119
|
+
--rf-glass: rgb(32 32 32 / 78%);
|
|
3120
|
+
--rf-glass-highlight: rgb(255 255 255 / 14%);
|
|
3121
|
+
--rf-glass-stroke: rgb(255 255 255 / 12%);
|
|
3122
|
+
--rf-primary: #f1efec;
|
|
3123
|
+
--rf-primary-text: #1c1c1b;
|
|
3124
|
+
--rf-accent: #529cca;
|
|
3125
|
+
--rf-danger: #eb5757;
|
|
3126
|
+
--rf-success: #4dab74;
|
|
3127
|
+
--rf-shadow-low: 0 0 0 1px rgb(255 255 255 / 6%), 0 1px 2px rgb(0 0 0 / 30%), 0 4px 10px -4px rgb(0 0 0 / 40%);
|
|
3128
|
+
--rf-shadow: 0 0 0 1px rgb(255 255 255 / 7%), 0 2px 5px -1px rgb(0 0 0 / 35%), 0 14px 36px -10px rgb(0 0 0 / 55%);
|
|
3129
|
+
--rf-shadow-float: 0 4px 14px -3px rgb(0 0 0 / 55%);
|
|
3130
|
+
color-scheme: dark;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
3134
|
+
|
|
3135
|
+
|
|
3136
|
+
.root {
|
|
3137
|
+
position: fixed;
|
|
3138
|
+
z-index: ${REFLET_Z_INDEX.widgetRoot};
|
|
3139
|
+
display: flex;
|
|
3140
|
+
align-items: flex-end;
|
|
3141
|
+
gap: 8px;
|
|
3142
|
+
font: 13px/1.45 var(--rf-font, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
|
|
3143
|
+
color: var(--rf-text);
|
|
3144
|
+
}
|
|
3145
|
+
.root:has(.panel) { width: min(var(--rf-width), calc(var(--rf-viewport-width, 100vw) - 24px)); }
|
|
3146
|
+
.root[data-position$="right"] { right: calc(var(--rf-viewport-right, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
|
|
3147
|
+
.root[data-position$="left"] { left: calc(var(--rf-viewport-left, 0px) + max(12px, min(var(--rf-offset), calc((var(--rf-viewport-width, 100vw) - var(--rf-width)) / 2)))); }
|
|
3148
|
+
.root[data-position^="bottom"] { bottom: calc(var(--rf-viewport-bottom, 0px) + max(var(--rf-offset), env(safe-area-inset-bottom))); }
|
|
3149
|
+
.root[data-position^="top"] { top: calc(var(--rf-viewport-top, 0px) + max(var(--rf-offset), env(safe-area-inset-top))); align-items: flex-start; }
|
|
3150
|
+
.root[data-position="bottom-right"] .panel { transform-origin: bottom right; }
|
|
3151
|
+
.root[data-position="bottom-left"] .panel { transform-origin: bottom left; }
|
|
3152
|
+
.root[data-position^="top"] .panel { transform-origin: top center; }
|
|
3153
|
+
.root[data-editing="true"] { visibility: hidden; pointer-events: none; }
|
|
3154
|
+
button, select, input, textarea { font: inherit; color: inherit; }
|
|
3155
|
+
button { margin: 0; cursor: pointer; border: 0; background: none; transition: scale 120ms var(--rf-ease); }
|
|
3156
|
+
button:disabled { cursor: not-allowed; opacity: .4; }
|
|
3157
|
+
button:active:not(:disabled) { scale: .97; }
|
|
3158
|
+
svg { flex-shrink: 0; }
|
|
3159
|
+
:focus-visible { outline: 2px solid var(--rf-accent); outline-offset: 3px; }
|
|
3160
|
+
.icon-btn, .tool {
|
|
3161
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
3162
|
+
width: 40px; height: 40px; flex: none; border-radius: var(--rf-pill); color: var(--rf-text-muted);
|
|
3163
|
+
transition: background 160ms var(--rf-ease), color 160ms var(--rf-ease), scale 120ms var(--rf-ease);
|
|
3164
|
+
}
|
|
3165
|
+
.icon-btn:hover, .tool:hover { background: var(--rf-bg-hover); color: var(--rf-text); }
|
|
3166
|
+
.launcher {
|
|
3167
|
+
display: inline-flex; align-items: center; gap: 7px; height: 40px; padding: 0 15px;
|
|
3168
|
+
border-radius: var(--rf-pill); color: var(--rf-text);
|
|
3169
|
+
font-weight: 500;
|
|
3170
|
+
transition: transform 200ms var(--rf-ease), box-shadow 200ms var(--rf-ease), scale 120ms var(--rf-ease);
|
|
3171
|
+
}
|
|
3172
|
+
.launcher:hover { transform: translateY(-2px); box-shadow: var(--rf-shadow); }
|
|
3173
|
+
.launcher-icon { display: flex; width: 15px; height: 15px; }
|
|
3174
|
+
.launcher-icon .on-close { display: none; }
|
|
3175
|
+
.floating-controls {
|
|
3176
|
+
display: flex; align-items: center; padding: 3px; flex: none;
|
|
3177
|
+
border-radius: var(--rf-pill);
|
|
3178
|
+
animation: rf-in 220ms var(--rf-ease);
|
|
3179
|
+
}
|
|
3180
|
+
.drag-handle { touch-action: none; cursor: grab; }
|
|
3181
|
+
.drag-handle:active { cursor: grabbing; }
|
|
3182
|
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
3183
|
+
.ghost-btn { display: flex; align-items: center; gap: 8px; min-height: 40px; padding: 0 8px; border-radius: var(--rf-radius-sm); }
|
|
3184
|
+
.ghost-btn:hover { background: var(--rf-bg-hover); }
|
|
3185
|
+
.error { margin: 0; padding: 10px 14px; border-radius: var(--rf-radius); font-size: 12px; color: var(--rf-danger); }
|
|
3186
|
+
.done { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 24px; text-align: center; }
|
|
3187
|
+
.done svg { color: var(--rf-success); }
|
|
3188
|
+
.done h2 { margin: 0; font-size: 14px; font-weight: 600; }
|
|
3189
|
+
.done p { margin: 0; font-size: 12px; color: var(--rf-text-muted); }
|
|
3190
|
+
.spinner { width: 14px; height: 14px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: rf-spin 700ms linear infinite; }
|
|
3191
|
+
.hp { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
3192
|
+
@keyframes rf-spin { to { transform: rotate(360deg); } }
|
|
3193
|
+
@keyframes rf-in { from { opacity: 0; transform: translateY(5px) scale(.98); } }
|
|
3194
|
+
@keyframes rf-fade { from { opacity: 0; } }
|
|
3195
|
+
@media (pointer: coarse) {
|
|
3196
|
+
.icon-btn, .tool { width: 44px; height: 44px; }
|
|
3197
|
+
.launcher, .ghost-btn { min-height: 44px; }
|
|
3198
|
+
}
|
|
3199
|
+
.glass {
|
|
3200
|
+
position: relative;
|
|
3201
|
+
isolation: isolate;
|
|
3202
|
+
border: 1px solid var(--rf-glass-stroke);
|
|
3203
|
+
box-shadow: inset 0 1px 0 var(--rf-glass-highlight), var(--rf-shadow);
|
|
3204
|
+
}
|
|
3205
|
+
.glass::before {
|
|
3206
|
+
content: "";
|
|
3207
|
+
position: absolute;
|
|
3208
|
+
inset: 0;
|
|
3209
|
+
z-index: -1;
|
|
3210
|
+
border-radius: inherit;
|
|
3211
|
+
background: var(--rf-bg);
|
|
3212
|
+
pointer-events: none;
|
|
3213
|
+
}
|
|
3214
|
+
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
|
|
3215
|
+
.glass::before {
|
|
3216
|
+
background: var(--rf-glass);
|
|
3217
|
+
-webkit-backdrop-filter: blur(20px) saturate(1.6);
|
|
3218
|
+
backdrop-filter: blur(20px) saturate(1.6);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
|
|
3222
|
+
.glass::before { background: var(--rf-bg); backdrop-filter: none; -webkit-backdrop-filter: none; }
|
|
3223
|
+
}
|
|
3224
|
+
.capture-halo { position: fixed; inset: 0; z-index: ${REFLET_Z_INDEX.captureHalo}; pointer-events: none; opacity: 0; transition: opacity 500ms var(--rf-ease); }
|
|
3225
|
+
.capture-halo[data-active="true"] { opacity: 1; transition-duration: 140ms; }
|
|
3226
|
+
.capture-halo::before { content: ""; position: absolute; inset: 0; border-radius: 20px; box-shadow: inset 14px 0 36px -16px var(--rf-accent), inset -14px 0 36px -16px var(--rf-accent), inset 0 0 4px 1px color-mix(in srgb, var(--rf-accent) 35%, transparent); }
|
|
3227
|
+
.capture-halo::after { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, color-mix(in srgb, var(--rf-accent) 20%, transparent), transparent 5%, transparent 95%, color-mix(in srgb, var(--rf-accent) 20%, transparent)); }
|
|
3228
|
+
${COMPOSER_STYLES}
|
|
3229
|
+
${ANNOTATOR_STYLES}
|
|
3230
|
+
${PICKER_STYLES}
|
|
2881
3231
|
@media (prefers-reduced-motion: reduce) {
|
|
2882
3232
|
*, *::before, *::after { animation: none !important; transition: none !important; }
|
|
3233
|
+
button:active:not(:disabled) { scale: 1; }
|
|
2883
3234
|
}
|
|
2884
3235
|
`;
|
|
2885
3236
|
//#endregion
|
|
@@ -3186,10 +3537,9 @@ function deriveTitle(message) {
|
|
|
3186
3537
|
return `${(lastSpace > 0 ? clipped.slice(0, lastSpace) : clipped).trimEnd()}…`;
|
|
3187
3538
|
}
|
|
3188
3539
|
function buildDescription(params) {
|
|
3189
|
-
const {
|
|
3190
|
-
const label = category.charAt(0).toUpperCase() + category.slice(1);
|
|
3540
|
+
const { email, isAnonymous, message } = params;
|
|
3191
3541
|
const contact = isAnonymous && email ? `\n\n---\nContact: ${email}` : "";
|
|
3192
|
-
return
|
|
3542
|
+
return `${message.trim()}${contact}`;
|
|
3193
3543
|
}
|
|
3194
3544
|
async function attachPreparedScreenshots(transport, result) {
|
|
3195
3545
|
const results = await Promise.allSettled(result.pendingScreenshots.map((screenshot) => transport.saveScreenshot({
|
|
@@ -3421,10 +3771,6 @@ function useScreenshotDrafts(captureFailed, onError) {
|
|
|
3421
3771
|
}
|
|
3422
3772
|
//#endregion
|
|
3423
3773
|
//#region src/feedback/ui/state/use-widget-config.ts
|
|
3424
|
-
function defaultCategory(props) {
|
|
3425
|
-
const preferred = props.defaultCategory ?? "bug";
|
|
3426
|
-
return props.categories?.find((category) => category === preferred) ?? props.categories?.[0] ?? preferred;
|
|
3427
|
-
}
|
|
3428
3774
|
function dismissalDuration(days) {
|
|
3429
3775
|
return days !== void 0 && Number.isFinite(days) && days > 0 ? days : null;
|
|
3430
3776
|
}
|
|
@@ -3446,7 +3792,6 @@ function useWidgetConfig(props) {
|
|
|
3446
3792
|
user,
|
|
3447
3793
|
userToken
|
|
3448
3794
|
]),
|
|
3449
|
-
defaultCategory: defaultCategory(props),
|
|
3450
3795
|
dismissalKey: `reflet-feedback-dismissed:${publicKey ?? "default"}:${user?.id ?? "anonymous"}`,
|
|
3451
3796
|
dismissForDays: dismissalDuration(props.dismissForDays),
|
|
3452
3797
|
isAnonymous: !(user || userToken)
|
|
@@ -3533,16 +3878,16 @@ function matchesHotkey(event, hotkey, platform = typeof navigator === "undefined
|
|
|
3533
3878
|
return event.altKey === expected.alt && event.ctrlKey === expected.ctrl && event.metaKey === expected.meta && event.shiftKey === expected.shift;
|
|
3534
3879
|
}
|
|
3535
3880
|
function useWidgetState(props) {
|
|
3536
|
-
const { client,
|
|
3881
|
+
const { client, dismissalKey, dismissForDays, isAnonymous } = useWidgetConfig(props);
|
|
3537
3882
|
const [isOpen, setIsOpen] = useState(false);
|
|
3538
3883
|
const [annotationTrigger, setAnnotationTrigger] = useState(null);
|
|
3539
3884
|
const [step, setStep] = useState("compose");
|
|
3540
|
-
const [category, setCategory] = useState(defaultCategory);
|
|
3541
3885
|
const [message, setMessage] = useState("");
|
|
3542
3886
|
const [email, setEmail] = useState("");
|
|
3543
3887
|
const [honeypot, setHoneypot] = useState("");
|
|
3544
3888
|
const [elementCapture, replaceElementCapture] = useOwnedCapture();
|
|
3545
3889
|
const [selection, setSelection] = useState(null);
|
|
3890
|
+
const [selectedNode, setSelectedNode] = useState(null);
|
|
3546
3891
|
const [isElementCapturing, setIsElementCapturing] = useState(false);
|
|
3547
3892
|
const successTimer = useRef(null);
|
|
3548
3893
|
const [error, setError] = useState(null);
|
|
@@ -3594,14 +3939,13 @@ function useWidgetState(props) {
|
|
|
3594
3939
|
resetScreenshots();
|
|
3595
3940
|
replaceElementCapture(null);
|
|
3596
3941
|
setSelection(null);
|
|
3942
|
+
setSelectedNode(null);
|
|
3597
3943
|
setMessage("");
|
|
3598
3944
|
setEmail("");
|
|
3599
3945
|
setHoneypot("");
|
|
3600
3946
|
setError(null);
|
|
3601
3947
|
setStep("compose");
|
|
3602
|
-
setCategory(defaultCategory);
|
|
3603
3948
|
}, [
|
|
3604
|
-
defaultCategory,
|
|
3605
3949
|
resetSubmission,
|
|
3606
3950
|
resetScreenshots,
|
|
3607
3951
|
replaceElementCapture
|
|
@@ -3635,11 +3979,15 @@ function useWidgetState(props) {
|
|
|
3635
3979
|
props.onOpen,
|
|
3636
3980
|
refreshAutomatic
|
|
3637
3981
|
]);
|
|
3638
|
-
const selectElement = useCallback(async (element) => {
|
|
3982
|
+
const selectElement = useCallback(async (element, note = "") => {
|
|
3639
3983
|
cancelPendingCapture();
|
|
3640
3984
|
setIsElementCapturing(true);
|
|
3641
|
-
const picked =
|
|
3985
|
+
const picked = note ? {
|
|
3986
|
+
...buildElementSelection(element),
|
|
3987
|
+
comment: note.slice(0, MAX_SELECTION_COMMENT_LENGTH)
|
|
3988
|
+
} : buildElementSelection(element);
|
|
3642
3989
|
setSelection(picked);
|
|
3990
|
+
setSelectedNode(element);
|
|
3643
3991
|
setStep("compose");
|
|
3644
3992
|
const pick = ++pickRef.current;
|
|
3645
3993
|
const context = collectPageContext({ sdkVersion: SDK_VERSION });
|
|
@@ -3669,18 +4017,19 @@ function useWidgetState(props) {
|
|
|
3669
4017
|
pickRef.current++;
|
|
3670
4018
|
replaceElementCapture(null);
|
|
3671
4019
|
setSelection(null);
|
|
4020
|
+
setSelectedNode(null);
|
|
3672
4021
|
setIsElementCapturing(false);
|
|
3673
4022
|
clearSelectionAnnotations();
|
|
3674
4023
|
}, [clearSelectionAnnotations, replaceElementCapture]);
|
|
4024
|
+
const reportedMessage = message.trim() || selection?.comment || "";
|
|
3675
4025
|
const submit = () => {
|
|
3676
4026
|
if (honeypot) {
|
|
3677
4027
|
setStep("success");
|
|
3678
4028
|
return;
|
|
3679
4029
|
}
|
|
3680
4030
|
return submission.submit({
|
|
3681
|
-
category,
|
|
3682
4031
|
context: {
|
|
3683
|
-
...activeScreenshot?.context ?? collectPageContext({ sdkVersion: "0.
|
|
4032
|
+
...activeScreenshot?.context ?? collectPageContext({ sdkVersion: "0.4.0" }),
|
|
3684
4033
|
consoleEvents: consoleRef.current?.events(),
|
|
3685
4034
|
metadata: props.metadata,
|
|
3686
4035
|
selection: selection ?? void 0
|
|
@@ -3688,7 +4037,7 @@ function useWidgetState(props) {
|
|
|
3688
4037
|
element: elementCapture,
|
|
3689
4038
|
email,
|
|
3690
4039
|
isAnonymous,
|
|
3691
|
-
message,
|
|
4040
|
+
message: reportedMessage,
|
|
3692
4041
|
screenshots: gallery.screenshots
|
|
3693
4042
|
});
|
|
3694
4043
|
};
|
|
@@ -3702,8 +4051,8 @@ function useWidgetState(props) {
|
|
|
3702
4051
|
annotations,
|
|
3703
4052
|
annotationTrigger,
|
|
3704
4053
|
canDismiss: dismissForDays !== null,
|
|
4054
|
+
canSubmit: reportedMessage.length > 0,
|
|
3705
4055
|
capture,
|
|
3706
|
-
category,
|
|
3707
4056
|
clearSelection,
|
|
3708
4057
|
close,
|
|
3709
4058
|
dismiss,
|
|
@@ -3726,9 +4075,9 @@ function useWidgetState(props) {
|
|
|
3726
4075
|
retakeCapture: gallery.retakeCapture,
|
|
3727
4076
|
screenshots: gallery.screenshots,
|
|
3728
4077
|
selectElement,
|
|
4078
|
+
selectedNode,
|
|
3729
4079
|
selection,
|
|
3730
4080
|
setAnnotations: gallery.setAnnotations,
|
|
3731
|
-
setCategory,
|
|
3732
4081
|
setEmail,
|
|
3733
4082
|
setHoneypot,
|
|
3734
4083
|
setMessage,
|
|
@@ -3740,13 +4089,8 @@ function useWidgetState(props) {
|
|
|
3740
4089
|
}
|
|
3741
4090
|
//#endregion
|
|
3742
4091
|
//#region src/feedback/widget.tsx
|
|
3743
|
-
const DEFAULT_CATEGORIES = [
|
|
3744
|
-
"bug",
|
|
3745
|
-
"idea",
|
|
3746
|
-
"question"
|
|
3747
|
-
];
|
|
3748
4092
|
function RefletFeedback(props) {
|
|
3749
|
-
const {
|
|
4093
|
+
const { enabled = true, hotkey = null, offset = 20, position = "bottom-right", theme = "auto" } = props;
|
|
3750
4094
|
const labels = {
|
|
3751
4095
|
...DEFAULT_WIDGET_LABELS,
|
|
3752
4096
|
...props.labels
|
|
@@ -3784,10 +4128,7 @@ function RefletFeedback(props) {
|
|
|
3784
4128
|
}),
|
|
3785
4129
|
/* @__PURE__ */ jsx(FloatingWidget, {
|
|
3786
4130
|
labels,
|
|
3787
|
-
|
|
3788
|
-
categories,
|
|
3789
|
-
position
|
|
3790
|
-
},
|
|
4131
|
+
position,
|
|
3791
4132
|
state
|
|
3792
4133
|
}),
|
|
3793
4134
|
isOpen && state.step === "annotate" && state.activeScreenshot && /* @__PURE__ */ jsx(Annotator, {
|
|
@@ -3805,7 +4146,7 @@ function RefletFeedback(props) {
|
|
|
3805
4146
|
labels
|
|
3806
4147
|
}),
|
|
3807
4148
|
isOpen && state.step === "picking" && /* @__PURE__ */ jsx(ElementPicker, {
|
|
3808
|
-
|
|
4149
|
+
labels,
|
|
3809
4150
|
onCancel: () => state.setStep("compose"),
|
|
3810
4151
|
onPick: state.selectElement
|
|
3811
4152
|
})
|