@dryui/feedback 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/annotation-marker.svelte +163 -0
- package/dist/components/annotation-marker.svelte.d.ts +11 -0
- package/dist/components/annotation-popup.svelte +669 -0
- package/dist/components/annotation-popup.svelte.d.ts +42 -0
- package/dist/components/highlight-overlay.svelte +48 -0
- package/dist/components/highlight-overlay.svelte.d.ts +8 -0
- package/dist/components/settings-panel.svelte +446 -0
- package/dist/components/settings-panel.svelte.d.ts +24 -0
- package/dist/components/toolbar.svelte +1111 -0
- package/dist/components/toolbar.svelte.d.ts +46 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +37 -0
- package/dist/feedback.svelte +2879 -0
- package/dist/feedback.svelte.d.ts +4 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +7 -0
- package/dist/layout-mode/catalog.d.ts +16 -0
- package/dist/layout-mode/catalog.js +81 -0
- package/dist/layout-mode/component-actions.svelte +84 -0
- package/dist/layout-mode/component-actions.svelte.d.ts +18 -0
- package/dist/layout-mode/component-picker.svelte +73 -0
- package/dist/layout-mode/component-picker.svelte.d.ts +10 -0
- package/dist/layout-mode/design-mode.svelte +1115 -0
- package/dist/layout-mode/design-mode.svelte.d.ts +24 -0
- package/dist/layout-mode/design-palette.svelte +396 -0
- package/dist/layout-mode/design-palette.svelte.d.ts +20 -0
- package/dist/layout-mode/element-heuristics.d.ts +5 -0
- package/dist/layout-mode/element-heuristics.js +51 -0
- package/dist/layout-mode/freeze.d.ts +6 -0
- package/dist/layout-mode/freeze.js +163 -0
- package/dist/layout-mode/generated-library.d.ts +940 -0
- package/dist/layout-mode/generated-library.js +1445 -0
- package/dist/layout-mode/geometry.d.ts +38 -0
- package/dist/layout-mode/geometry.js +133 -0
- package/dist/layout-mode/history.d.ts +10 -0
- package/dist/layout-mode/history.js +45 -0
- package/dist/layout-mode/index.d.ts +23 -0
- package/dist/layout-mode/index.js +18 -0
- package/dist/layout-mode/live-mount.d.ts +20 -0
- package/dist/layout-mode/live-mount.js +70 -0
- package/dist/layout-mode/output.d.ts +26 -0
- package/dist/layout-mode/output.js +550 -0
- package/dist/layout-mode/placement-skeleton.d.ts +9 -0
- package/dist/layout-mode/placement-skeleton.js +535 -0
- package/dist/layout-mode/rearrange-overlay.svelte +1293 -0
- package/dist/layout-mode/rearrange-overlay.svelte.d.ts +18 -0
- package/dist/layout-mode/responsive-bar.svelte +39 -0
- package/dist/layout-mode/responsive-bar.svelte.d.ts +8 -0
- package/dist/layout-mode/route-creator.svelte +70 -0
- package/dist/layout-mode/route-creator.svelte.d.ts +8 -0
- package/dist/layout-mode/section-detection.d.ts +6 -0
- package/dist/layout-mode/section-detection.js +214 -0
- package/dist/layout-mode/spatial.d.ts +42 -0
- package/dist/layout-mode/spatial.js +156 -0
- package/dist/layout-mode/types.d.ts +144 -0
- package/dist/layout-mode/types.js +84 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.js +1 -0
- package/dist/utils/dryui-detection.d.ts +1 -0
- package/dist/utils/dryui-detection.js +219 -0
- package/dist/utils/element-id.d.ts +12 -0
- package/dist/utils/element-id.js +333 -0
- package/dist/utils/freeze.d.ts +7 -0
- package/dist/utils/freeze.js +168 -0
- package/dist/utils/output.d.ts +15 -0
- package/dist/utils/output.js +245 -0
- package/dist/utils/selection.d.ts +22 -0
- package/dist/utils/selection.js +58 -0
- package/dist/utils/shadow-dom.d.ts +4 -0
- package/dist/utils/shadow-dom.js +39 -0
- package/dist/utils/storage.d.ts +30 -0
- package/dist/utils/storage.js +206 -0
- package/dist/utils/svelte-detection.d.ts +8 -0
- package/dist/utils/svelte-detection.js +86 -0
- package/dist/utils/svelte-meta.d.ts +6 -0
- package/dist/utils/svelte-meta.js +69 -0
- package/dist/utils/sync.d.ts +18 -0
- package/dist/utils/sync.js +62 -0
- package/package.json +65 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { generateDesignOutput as generateLayoutDesignOutput, generateRearrangeOutput as generateLayoutRearrangeOutput, } from '../layout-mode/output.js';
|
|
2
|
+
export const OUTPUT_DETAIL_OPTIONS = [
|
|
3
|
+
{ value: 'compact', label: 'Compact' },
|
|
4
|
+
{ value: 'standard', label: 'Standard' },
|
|
5
|
+
{ value: 'detailed', label: 'Detailed' },
|
|
6
|
+
{ value: 'forensic', label: 'Forensic' },
|
|
7
|
+
];
|
|
8
|
+
function viewportSummary() {
|
|
9
|
+
if (typeof window === 'undefined')
|
|
10
|
+
return 'unknown';
|
|
11
|
+
return `${window.innerWidth}x${window.innerHeight}`;
|
|
12
|
+
}
|
|
13
|
+
function browserLocationHref() {
|
|
14
|
+
if (typeof window !== 'undefined' && window.location?.href) {
|
|
15
|
+
return window.location.href;
|
|
16
|
+
}
|
|
17
|
+
if (typeof location !== 'undefined' && location.href) {
|
|
18
|
+
return location.href;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function formatRect(rect) {
|
|
23
|
+
return `${Math.round(rect.width)}x${Math.round(rect.height)} at (${Math.round(rect.x)}, ${Math.round(rect.y)})`;
|
|
24
|
+
}
|
|
25
|
+
function normalizeWhitespace(value) {
|
|
26
|
+
return value.replace(/\s+/g, ' ').trim();
|
|
27
|
+
}
|
|
28
|
+
function uniqueEntries(entries) {
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const result = [];
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
const normalized = normalizeWhitespace(entry);
|
|
33
|
+
if (!normalized)
|
|
34
|
+
continue;
|
|
35
|
+
const key = normalized.toLowerCase();
|
|
36
|
+
if (seen.has(key))
|
|
37
|
+
continue;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
result.push(normalized);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function splitEntries(value, separator) {
|
|
44
|
+
return uniqueEntries(value.split(separator));
|
|
45
|
+
}
|
|
46
|
+
function normalizeKeyValueEntry(value) {
|
|
47
|
+
const normalized = normalizeWhitespace(value.replace(/[;,]+$/, ''));
|
|
48
|
+
const match = normalized.match(/^([\w-]+)\s*([=:])\s*(.+)$/);
|
|
49
|
+
if (!match) {
|
|
50
|
+
return normalized;
|
|
51
|
+
}
|
|
52
|
+
const [, key, , rawValue] = match;
|
|
53
|
+
return `${key}: ${normalizeWhitespace(rawValue ?? '')}`;
|
|
54
|
+
}
|
|
55
|
+
function splitAccessibilityEntries(value) {
|
|
56
|
+
const normalized = value.trim();
|
|
57
|
+
let entries = splitEntries(normalized, /[\n;]+/);
|
|
58
|
+
if (entries.length <= 1) {
|
|
59
|
+
entries = splitEntries(normalized, /,\s*(?=[a-z][\w-]*\s*[=:])/i);
|
|
60
|
+
}
|
|
61
|
+
if (entries.length <= 1 && normalized.includes(',')) {
|
|
62
|
+
entries = splitEntries(normalized, /,\s*/);
|
|
63
|
+
}
|
|
64
|
+
return uniqueEntries(entries.map(normalizeKeyValueEntry));
|
|
65
|
+
}
|
|
66
|
+
function splitNearbyElements(value) {
|
|
67
|
+
return splitEntries(value, /[\n;,]+/);
|
|
68
|
+
}
|
|
69
|
+
function splitComputedStyles(value) {
|
|
70
|
+
const normalized = value.replace(/[{}]/g, ' ');
|
|
71
|
+
return uniqueEntries(normalized
|
|
72
|
+
.split(/[\n;]+/)
|
|
73
|
+
.map((entry) => normalizeKeyValueEntry(entry)));
|
|
74
|
+
}
|
|
75
|
+
function formatListSection(label, entries) {
|
|
76
|
+
if (entries.length === 0) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
if (entries.length === 1) {
|
|
80
|
+
return [`**${label}:** ${entries[0]}`];
|
|
81
|
+
}
|
|
82
|
+
return [`**${label} (${entries.length}):**`, ...entries.map((entry) => `- ${entry}`)];
|
|
83
|
+
}
|
|
84
|
+
function formatFullPathSection(fullPath) {
|
|
85
|
+
const normalizedPath = normalizeWhitespace(fullPath);
|
|
86
|
+
if (!normalizedPath) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const segments = fullPath.includes('>')
|
|
90
|
+
? fullPath.split(/\s*>\s*/).map(normalizeWhitespace).filter(Boolean)
|
|
91
|
+
: [];
|
|
92
|
+
if (segments.length <= 1) {
|
|
93
|
+
return [`**Full path:** ${normalizedPath}`];
|
|
94
|
+
}
|
|
95
|
+
return [
|
|
96
|
+
`**Full path:** ${segments.join(' > ')}`,
|
|
97
|
+
`**Path depth:** ${segments.length}`,
|
|
98
|
+
`**Immediate parent:** ${segments.at(-2)}`,
|
|
99
|
+
`**Target node:** ${segments.at(-1)}`,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
function formatFeedbackHeader(pathname, detail) {
|
|
103
|
+
let header = `## Page Feedback: ${pathname}\n`;
|
|
104
|
+
if (detail === 'forensic') {
|
|
105
|
+
header += `\n**Environment:**\n`;
|
|
106
|
+
header += `- Viewport: ${viewportSummary()}\n`;
|
|
107
|
+
const href = browserLocationHref();
|
|
108
|
+
if (href) {
|
|
109
|
+
header += `- URL: ${href}\n`;
|
|
110
|
+
}
|
|
111
|
+
if (typeof navigator !== 'undefined') {
|
|
112
|
+
header += `- User Agent: ${navigator.userAgent}\n`;
|
|
113
|
+
}
|
|
114
|
+
if (typeof window !== 'undefined') {
|
|
115
|
+
header += `- Timestamp: ${new Date().toISOString()}\n`;
|
|
116
|
+
header += `- Device Pixel Ratio: ${window.devicePixelRatio}\n`;
|
|
117
|
+
}
|
|
118
|
+
header += `\n`;
|
|
119
|
+
return header;
|
|
120
|
+
}
|
|
121
|
+
if (detail !== 'compact') {
|
|
122
|
+
header += `**Viewport:** ${viewportSummary()}\n`;
|
|
123
|
+
}
|
|
124
|
+
header += `\n`;
|
|
125
|
+
return header;
|
|
126
|
+
}
|
|
127
|
+
export function generateFeedbackOutput(annotations, pathname, detail) {
|
|
128
|
+
if (annotations.length === 0) {
|
|
129
|
+
return '';
|
|
130
|
+
}
|
|
131
|
+
if (detail === 'compact') {
|
|
132
|
+
return (formatFeedbackHeader(pathname, detail) +
|
|
133
|
+
annotations
|
|
134
|
+
.map((annotation, index) => {
|
|
135
|
+
const selectedText = annotation.selectedText
|
|
136
|
+
? ` (re: "${annotation.selectedText.slice(0, 30)}${annotation.selectedText.length > 30 ? '...' : ''}")`
|
|
137
|
+
: '';
|
|
138
|
+
const sourceFile = annotation.sourceFile ? ` (${annotation.sourceFile})` : '';
|
|
139
|
+
return `${index + 1}. ${annotation.element}${sourceFile}: ${annotation.comment}${selectedText}`;
|
|
140
|
+
})
|
|
141
|
+
.join('\n'));
|
|
142
|
+
}
|
|
143
|
+
const sections = annotations.map((annotation, index) => {
|
|
144
|
+
const lines = [];
|
|
145
|
+
lines.push(`### ${index + 1}. ${annotation.element}`);
|
|
146
|
+
lines.push(`**Location:** ${annotation.elementPath}`);
|
|
147
|
+
if (annotation.sourceFile)
|
|
148
|
+
lines.push(`**Source:** ${annotation.sourceFile}`);
|
|
149
|
+
if (annotation.svelteComponent)
|
|
150
|
+
lines.push(`**Svelte:** ${annotation.svelteComponent}`);
|
|
151
|
+
if (annotation.reactComponents)
|
|
152
|
+
lines.push(`**React:** ${annotation.reactComponents}`);
|
|
153
|
+
if (annotation.dryuiComponent)
|
|
154
|
+
lines.push(`**DryUI:** ${annotation.dryuiComponent}`);
|
|
155
|
+
if (detail === 'detailed' || detail === 'forensic') {
|
|
156
|
+
if (annotation.cssClasses)
|
|
157
|
+
lines.push(`**Classes:** ${annotation.cssClasses}`);
|
|
158
|
+
if (annotation.boundingBox)
|
|
159
|
+
lines.push(`**Bounding box:** ${formatRect(annotation.boundingBox)}`);
|
|
160
|
+
if (annotation.selectedText)
|
|
161
|
+
lines.push(`**Selected text:** ${annotation.selectedText}`);
|
|
162
|
+
if (annotation.nearbyText && !annotation.selectedText) {
|
|
163
|
+
lines.push(`**Context:** ${annotation.nearbyText.slice(0, 140)}`);
|
|
164
|
+
}
|
|
165
|
+
if (annotation.isMultiSelect)
|
|
166
|
+
lines.push(`**Selection:** Multi-select`);
|
|
167
|
+
if (annotation.elementBoundingBoxes?.length) {
|
|
168
|
+
lines.push(`**Selection targets:** ${annotation.elementBoundingBoxes.length} elements`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (detail === 'forensic') {
|
|
172
|
+
if (annotation.accessibility) {
|
|
173
|
+
lines.push(...formatListSection('Accessibility', splitAccessibilityEntries(annotation.accessibility)));
|
|
174
|
+
}
|
|
175
|
+
if (annotation.nearbyElements) {
|
|
176
|
+
lines.push(...formatListSection('Nearby elements', splitNearbyElements(annotation.nearbyElements)));
|
|
177
|
+
}
|
|
178
|
+
if (annotation.nearbyText)
|
|
179
|
+
lines.push(`**Nearby text:** ${annotation.nearbyText}`);
|
|
180
|
+
if (annotation.computedStyles) {
|
|
181
|
+
lines.push(...formatListSection('Computed styles', splitComputedStyles(annotation.computedStyles)));
|
|
182
|
+
}
|
|
183
|
+
if (annotation.fullPath) {
|
|
184
|
+
lines.push(...formatFullPathSection(annotation.fullPath));
|
|
185
|
+
}
|
|
186
|
+
lines.push(`**Annotation at:** ${annotation.x.toFixed(1)}% from left, ${Math.round(annotation.y)}px from top`);
|
|
187
|
+
if (annotation.thread?.length) {
|
|
188
|
+
lines.push(`**Thread messages:** ${annotation.thread.length}`);
|
|
189
|
+
}
|
|
190
|
+
if (annotation.status)
|
|
191
|
+
lines.push(`**Status:** ${annotation.status}`);
|
|
192
|
+
if (annotation.resolvedBy)
|
|
193
|
+
lines.push(`**Resolved by:** ${annotation.resolvedBy}`);
|
|
194
|
+
if (annotation.resolvedAt)
|
|
195
|
+
lines.push(`**Resolved at:** ${annotation.resolvedAt}`);
|
|
196
|
+
if (annotation.resolutionNote)
|
|
197
|
+
lines.push(`**Resolution note:** ${annotation.resolutionNote}`);
|
|
198
|
+
if (annotation.intent)
|
|
199
|
+
lines.push(`**Intent:** ${annotation.intent}`);
|
|
200
|
+
if (annotation.severity)
|
|
201
|
+
lines.push(`**Severity:** ${annotation.severity}`);
|
|
202
|
+
}
|
|
203
|
+
lines.push(`**Feedback:** ${annotation.comment}`);
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
});
|
|
206
|
+
return formatFeedbackHeader(pathname, detail) + sections.join('\n\n---\n\n');
|
|
207
|
+
}
|
|
208
|
+
function getViewportSize() {
|
|
209
|
+
if (typeof window === 'undefined') {
|
|
210
|
+
return { width: 0, height: 0 };
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
width: window.innerWidth,
|
|
214
|
+
height: window.innerHeight,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
export function generateDesignOutput(placements, detail, options = {}) {
|
|
218
|
+
return generateLayoutDesignOutput(placements, getViewportSize(), options, detail);
|
|
219
|
+
}
|
|
220
|
+
export function generateRearrangeOutput(state, detail) {
|
|
221
|
+
return generateLayoutRearrangeOutput(state, detail, getViewportSize());
|
|
222
|
+
}
|
|
223
|
+
export function generateOutput(annotations, pathname, detail, options = {}) {
|
|
224
|
+
const sections = [];
|
|
225
|
+
if (annotations.length > 0) {
|
|
226
|
+
sections.push(generateFeedbackOutput(annotations, pathname, detail));
|
|
227
|
+
}
|
|
228
|
+
const designOutput = generateDesignOutput(options.designPlacements ?? [], detail, {
|
|
229
|
+
blankCanvas: options.blankCanvas,
|
|
230
|
+
wireframePurpose: options.wireframePurpose,
|
|
231
|
+
});
|
|
232
|
+
if (designOutput) {
|
|
233
|
+
sections.push(designOutput);
|
|
234
|
+
}
|
|
235
|
+
if (options.rearrangeState) {
|
|
236
|
+
const rearrangeOutput = generateRearrangeOutput(options.rearrangeState, detail);
|
|
237
|
+
if (rearrangeOutput) {
|
|
238
|
+
sections.push(rearrangeOutput);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (sections.length === 0) {
|
|
242
|
+
return '';
|
|
243
|
+
}
|
|
244
|
+
return sections.join('\n\n');
|
|
245
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Rect } from '../types.js';
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
export interface RectLike {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function normalizeText(text: string): string;
|
|
13
|
+
export declare function toRect(rect: RectLike): Rect;
|
|
14
|
+
export declare function rectFromPoints(start: Point, end: Point): Rect;
|
|
15
|
+
export declare function unionRects(rects: readonly RectLike[]): Rect | null;
|
|
16
|
+
export declare function hasMeaningfulArea(rect: Rect, threshold: number): boolean;
|
|
17
|
+
export declare function intersectsRect(a: Rect, b: RectLike): boolean;
|
|
18
|
+
export declare function getPopupPosition(rect: Rect, viewportWidth: number, popupWidth: number, viewportPadding?: number): {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
};
|
|
22
|
+
export declare function uniqueLabels(labels: string[]): string[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const DEFAULT_VIEWPORT_PADDING = 16;
|
|
2
|
+
export function normalizeText(text) {
|
|
3
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
4
|
+
}
|
|
5
|
+
export function toRect(rect) {
|
|
6
|
+
return {
|
|
7
|
+
x: rect.x,
|
|
8
|
+
y: rect.y,
|
|
9
|
+
width: rect.width,
|
|
10
|
+
height: rect.height,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function rectFromPoints(start, end) {
|
|
14
|
+
const x = Math.min(start.x, end.x);
|
|
15
|
+
const y = Math.min(start.y, end.y);
|
|
16
|
+
return {
|
|
17
|
+
x,
|
|
18
|
+
y,
|
|
19
|
+
width: Math.abs(end.x - start.x),
|
|
20
|
+
height: Math.abs(end.y - start.y),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function unionRects(rects) {
|
|
24
|
+
const [first, ...rest] = rects;
|
|
25
|
+
if (!first)
|
|
26
|
+
return null;
|
|
27
|
+
let left = first.x;
|
|
28
|
+
let top = first.y;
|
|
29
|
+
let right = first.x + first.width;
|
|
30
|
+
let bottom = first.y + first.height;
|
|
31
|
+
for (const rect of rest) {
|
|
32
|
+
left = Math.min(left, rect.x);
|
|
33
|
+
top = Math.min(top, rect.y);
|
|
34
|
+
right = Math.max(right, rect.x + rect.width);
|
|
35
|
+
bottom = Math.max(bottom, rect.y + rect.height);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
x: left,
|
|
39
|
+
y: top,
|
|
40
|
+
width: right - left,
|
|
41
|
+
height: bottom - top,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function hasMeaningfulArea(rect, threshold) {
|
|
45
|
+
return rect.width >= threshold || rect.height >= threshold;
|
|
46
|
+
}
|
|
47
|
+
export function intersectsRect(a, b) {
|
|
48
|
+
return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
49
|
+
}
|
|
50
|
+
export function getPopupPosition(rect, viewportWidth, popupWidth, viewportPadding = DEFAULT_VIEWPORT_PADDING) {
|
|
51
|
+
return {
|
|
52
|
+
x: Math.min(rect.x + rect.width + 8, Math.max(viewportPadding, viewportWidth - popupWidth)),
|
|
53
|
+
y: Math.max(viewportPadding, rect.y),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function uniqueLabels(labels) {
|
|
57
|
+
return labels.filter((label, index, all) => label.length > 0 && all.indexOf(label) === index);
|
|
58
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function deepElementFromPoint(x: number, y: number): Element | null;
|
|
2
|
+
export declare function isInShadowDOM(el: Element): boolean;
|
|
3
|
+
export declare function getShadowHost(el: Element): Element | null;
|
|
4
|
+
export declare function closestCrossingShadow(el: Element, selector: string): Element | null;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function deepElementFromPoint(x, y) {
|
|
2
|
+
let el = document.elementFromPoint(x, y);
|
|
3
|
+
if (!el)
|
|
4
|
+
return null;
|
|
5
|
+
while (el.shadowRoot) {
|
|
6
|
+
const inner = el.shadowRoot.elementFromPoint(x, y);
|
|
7
|
+
if (!inner || inner === el)
|
|
8
|
+
break;
|
|
9
|
+
el = inner;
|
|
10
|
+
}
|
|
11
|
+
return el;
|
|
12
|
+
}
|
|
13
|
+
export function isInShadowDOM(el) {
|
|
14
|
+
const root = el.getRootNode();
|
|
15
|
+
return root instanceof ShadowRoot;
|
|
16
|
+
}
|
|
17
|
+
export function getShadowHost(el) {
|
|
18
|
+
const root = el.getRootNode();
|
|
19
|
+
if (root instanceof ShadowRoot) {
|
|
20
|
+
return root.host;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function closestCrossingShadow(el, selector) {
|
|
25
|
+
let current = el;
|
|
26
|
+
while (current) {
|
|
27
|
+
const match = current.closest(selector);
|
|
28
|
+
if (match)
|
|
29
|
+
return match;
|
|
30
|
+
const root = current.getRootNode();
|
|
31
|
+
if (root instanceof ShadowRoot) {
|
|
32
|
+
current = root.host;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Annotation, DesignPlacement, FeedbackSettings, RearrangeState, WireframeState } from '../types.js';
|
|
2
|
+
export declare function getStorageKey(pathname: string): string;
|
|
3
|
+
export declare function loadAnnotations<T = Annotation>(pathname: string): T[];
|
|
4
|
+
export declare function loadAllAnnotations<T = Annotation>(): Map<string, T[]>;
|
|
5
|
+
export declare function saveAnnotations<T = Annotation>(pathname: string, annotations: T[]): void;
|
|
6
|
+
export declare function saveAnnotationsWithSyncMarker(pathname: string, annotations: Annotation[], sessionId: string): void;
|
|
7
|
+
export declare function getUnsyncedAnnotations(pathname: string, sessionId?: string): Annotation[];
|
|
8
|
+
export declare function clearSyncMarkers(pathname: string): void;
|
|
9
|
+
export declare function clearAnnotations(pathname: string): void;
|
|
10
|
+
export declare function getDesignStorageKey(pathname: string): string;
|
|
11
|
+
export declare function loadDesignPlacements(pathname: string): DesignPlacement[];
|
|
12
|
+
export declare function saveDesignPlacements(pathname: string, placements: DesignPlacement[]): void;
|
|
13
|
+
export declare function clearDesignPlacements(pathname: string): void;
|
|
14
|
+
export declare function getRearrangeStorageKey(pathname: string): string;
|
|
15
|
+
export declare function loadRearrangeState(pathname: string): RearrangeState | null;
|
|
16
|
+
export declare function saveRearrangeState(pathname: string, state: RearrangeState): void;
|
|
17
|
+
export declare function clearRearrangeState(pathname: string): void;
|
|
18
|
+
export declare function getWireframeStorageKey(pathname: string): string;
|
|
19
|
+
export declare function loadWireframeState(pathname: string): WireframeState | null;
|
|
20
|
+
export declare function saveWireframeState(pathname: string, state: WireframeState): void;
|
|
21
|
+
export declare function clearWireframeState(pathname: string): void;
|
|
22
|
+
export declare function getSessionStorageKey(pathname: string): string;
|
|
23
|
+
export declare function loadSessionId(pathname: string): string | null;
|
|
24
|
+
export declare function saveSessionId(pathname: string, sessionId: string): void;
|
|
25
|
+
export declare function clearSessionId(pathname: string): void;
|
|
26
|
+
export declare function loadToolbarHidden(): boolean;
|
|
27
|
+
export declare function saveToolbarHidden(hidden: boolean): void;
|
|
28
|
+
export declare function loadSettings(): FeedbackSettings;
|
|
29
|
+
export declare function saveSettings(settings: FeedbackSettings): void;
|
|
30
|
+
export declare function clearSettings(): void;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { DEFAULT_SETTINGS, normalizeAnnotationColor } from '../constants.js';
|
|
2
|
+
const ANNOTATION_PREFIX = 'dryui-feedback-';
|
|
3
|
+
const DESIGN_PREFIX = 'dryui-feedback-design-';
|
|
4
|
+
const REARRANGE_PREFIX = 'dryui-feedback-rearrange-';
|
|
5
|
+
const WIREFRAME_PREFIX = 'dryui-feedback-wireframe-';
|
|
6
|
+
const SESSION_PREFIX = 'dryui-feedback-session-';
|
|
7
|
+
const SETTINGS_KEY = 'dryui-feedback-settings';
|
|
8
|
+
const TOOLBAR_HIDDEN_SESSION_KEY = `${SESSION_PREFIX}toolbar-hidden`;
|
|
9
|
+
const RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
10
|
+
function hasStorage() {
|
|
11
|
+
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';
|
|
12
|
+
}
|
|
13
|
+
function hasSessionStorage() {
|
|
14
|
+
return typeof window !== 'undefined' && typeof window.sessionStorage !== 'undefined';
|
|
15
|
+
}
|
|
16
|
+
function filterRetained(items) {
|
|
17
|
+
const cutoff = Date.now() - RETENTION_MS;
|
|
18
|
+
return items.filter((item) => !item.timestamp || item.timestamp > cutoff);
|
|
19
|
+
}
|
|
20
|
+
function normalizeAnnotationColors(items) {
|
|
21
|
+
return items.map((item) => {
|
|
22
|
+
if (!item || typeof item !== 'object' || !('color' in item))
|
|
23
|
+
return item;
|
|
24
|
+
const color = item.color;
|
|
25
|
+
if (typeof color !== 'string')
|
|
26
|
+
return item;
|
|
27
|
+
return {
|
|
28
|
+
...item,
|
|
29
|
+
color: normalizeAnnotationColor(color),
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function loadJson(key, fallback) {
|
|
34
|
+
if (!hasStorage())
|
|
35
|
+
return fallback;
|
|
36
|
+
try {
|
|
37
|
+
const raw = localStorage.getItem(key);
|
|
38
|
+
if (!raw)
|
|
39
|
+
return fallback;
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function saveJson(key, value) {
|
|
47
|
+
if (!hasStorage())
|
|
48
|
+
return;
|
|
49
|
+
try {
|
|
50
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// localStorage can be unavailable or full
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function removeStorageKey(key) {
|
|
57
|
+
if (!hasStorage())
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
localStorage.removeItem(key);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// ignore write failures
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function getStorageKey(pathname) {
|
|
67
|
+
return `${ANNOTATION_PREFIX}${pathname}`;
|
|
68
|
+
}
|
|
69
|
+
export function loadAnnotations(pathname) {
|
|
70
|
+
const annotations = loadJson(getStorageKey(pathname), []);
|
|
71
|
+
return normalizeAnnotationColors(filterRetained(annotations));
|
|
72
|
+
}
|
|
73
|
+
export function loadAllAnnotations() {
|
|
74
|
+
const result = new Map();
|
|
75
|
+
if (!hasStorage())
|
|
76
|
+
return result;
|
|
77
|
+
const cutoff = Date.now() - RETENTION_MS;
|
|
78
|
+
try {
|
|
79
|
+
for (let index = 0; index < localStorage.length; index += 1) {
|
|
80
|
+
const key = localStorage.key(index);
|
|
81
|
+
if (!key?.startsWith(ANNOTATION_PREFIX))
|
|
82
|
+
continue;
|
|
83
|
+
const pathname = key.slice(ANNOTATION_PREFIX.length);
|
|
84
|
+
const annotations = normalizeAnnotationColors(loadJson(key, []).filter((annotation) => !annotation.timestamp || annotation.timestamp > cutoff));
|
|
85
|
+
if (annotations.length > 0) {
|
|
86
|
+
result.set(pathname, annotations);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
export function saveAnnotations(pathname, annotations) {
|
|
96
|
+
saveJson(getStorageKey(pathname), annotations);
|
|
97
|
+
}
|
|
98
|
+
export function saveAnnotationsWithSyncMarker(pathname, annotations, sessionId) {
|
|
99
|
+
saveAnnotations(pathname, annotations.map((annotation) => ({
|
|
100
|
+
...annotation,
|
|
101
|
+
_syncedTo: sessionId,
|
|
102
|
+
})));
|
|
103
|
+
}
|
|
104
|
+
export function getUnsyncedAnnotations(pathname, sessionId) {
|
|
105
|
+
return loadAnnotations(pathname).filter((annotation) => {
|
|
106
|
+
if (!annotation._syncedTo)
|
|
107
|
+
return true;
|
|
108
|
+
return sessionId ? annotation._syncedTo !== sessionId : false;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
export function clearSyncMarkers(pathname) {
|
|
112
|
+
const annotations = loadAnnotations(pathname).map((annotation) => {
|
|
113
|
+
const { _syncedTo, ...rest } = annotation;
|
|
114
|
+
return rest;
|
|
115
|
+
});
|
|
116
|
+
saveAnnotations(pathname, annotations);
|
|
117
|
+
}
|
|
118
|
+
export function clearAnnotations(pathname) {
|
|
119
|
+
removeStorageKey(getStorageKey(pathname));
|
|
120
|
+
}
|
|
121
|
+
export function getDesignStorageKey(pathname) {
|
|
122
|
+
return `${DESIGN_PREFIX}${pathname}`;
|
|
123
|
+
}
|
|
124
|
+
export function loadDesignPlacements(pathname) {
|
|
125
|
+
return loadJson(getDesignStorageKey(pathname), []);
|
|
126
|
+
}
|
|
127
|
+
export function saveDesignPlacements(pathname, placements) {
|
|
128
|
+
saveJson(getDesignStorageKey(pathname), placements);
|
|
129
|
+
}
|
|
130
|
+
export function clearDesignPlacements(pathname) {
|
|
131
|
+
removeStorageKey(getDesignStorageKey(pathname));
|
|
132
|
+
}
|
|
133
|
+
export function getRearrangeStorageKey(pathname) {
|
|
134
|
+
return `${REARRANGE_PREFIX}${pathname}`;
|
|
135
|
+
}
|
|
136
|
+
export function loadRearrangeState(pathname) {
|
|
137
|
+
return loadJson(getRearrangeStorageKey(pathname), null);
|
|
138
|
+
}
|
|
139
|
+
export function saveRearrangeState(pathname, state) {
|
|
140
|
+
saveJson(getRearrangeStorageKey(pathname), state);
|
|
141
|
+
}
|
|
142
|
+
export function clearRearrangeState(pathname) {
|
|
143
|
+
removeStorageKey(getRearrangeStorageKey(pathname));
|
|
144
|
+
}
|
|
145
|
+
export function getWireframeStorageKey(pathname) {
|
|
146
|
+
return `${WIREFRAME_PREFIX}${pathname}`;
|
|
147
|
+
}
|
|
148
|
+
export function loadWireframeState(pathname) {
|
|
149
|
+
return loadJson(getWireframeStorageKey(pathname), null);
|
|
150
|
+
}
|
|
151
|
+
export function saveWireframeState(pathname, state) {
|
|
152
|
+
saveJson(getWireframeStorageKey(pathname), state);
|
|
153
|
+
}
|
|
154
|
+
export function clearWireframeState(pathname) {
|
|
155
|
+
removeStorageKey(getWireframeStorageKey(pathname));
|
|
156
|
+
}
|
|
157
|
+
export function getSessionStorageKey(pathname) {
|
|
158
|
+
return `${SESSION_PREFIX}${pathname}`;
|
|
159
|
+
}
|
|
160
|
+
export function loadSessionId(pathname) {
|
|
161
|
+
return loadJson(getSessionStorageKey(pathname), null);
|
|
162
|
+
}
|
|
163
|
+
export function saveSessionId(pathname, sessionId) {
|
|
164
|
+
saveJson(getSessionStorageKey(pathname), sessionId);
|
|
165
|
+
}
|
|
166
|
+
export function clearSessionId(pathname) {
|
|
167
|
+
removeStorageKey(getSessionStorageKey(pathname));
|
|
168
|
+
}
|
|
169
|
+
export function loadToolbarHidden() {
|
|
170
|
+
if (!hasSessionStorage())
|
|
171
|
+
return false;
|
|
172
|
+
try {
|
|
173
|
+
return sessionStorage.getItem(TOOLBAR_HIDDEN_SESSION_KEY) === '1';
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
export function saveToolbarHidden(hidden) {
|
|
180
|
+
if (!hasSessionStorage())
|
|
181
|
+
return;
|
|
182
|
+
try {
|
|
183
|
+
if (hidden) {
|
|
184
|
+
sessionStorage.setItem(TOOLBAR_HIDDEN_SESSION_KEY, '1');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
sessionStorage.removeItem(TOOLBAR_HIDDEN_SESSION_KEY);
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
// sessionStorage can be unavailable
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function loadSettings() {
|
|
194
|
+
const settings = loadJson(SETTINGS_KEY, {});
|
|
195
|
+
return {
|
|
196
|
+
...DEFAULT_SETTINGS,
|
|
197
|
+
...settings,
|
|
198
|
+
annotationColor: normalizeAnnotationColor(settings.annotationColor),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
export function saveSettings(settings) {
|
|
202
|
+
saveJson(SETTINGS_KEY, settings);
|
|
203
|
+
}
|
|
204
|
+
export function clearSettings() {
|
|
205
|
+
removeStorageKey(SETTINGS_KEY);
|
|
206
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface SvelteDetectionResult {
|
|
2
|
+
readonly sourceFile?: string;
|
|
3
|
+
readonly svelteComponent?: string;
|
|
4
|
+
readonly componentStack: readonly string[];
|
|
5
|
+
readonly sourceFiles: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function detectSvelteMetadata(target: EventTarget | null): SvelteDetectionResult;
|
|
8
|
+
export declare function hasSvelteMetadata(target: EventTarget | null): boolean;
|