@artymclabin/qa-review 0.3.8 → 0.3.9
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.
|
@@ -4,7 +4,31 @@ import { type QAJourneyConfig } from "./journey.js";
|
|
|
4
4
|
/** Was the pointer gesture a click (vs a drag)? Exported for tests. */
|
|
5
5
|
export declare function isClickGesture(dx: number, dy: number): boolean;
|
|
6
6
|
/** Note-reference text for a picked element: « label » (truncated) or tag. */
|
|
7
|
-
export
|
|
7
|
+
export interface PickedElementDescriptor {
|
|
8
|
+
textContent?: string | null;
|
|
9
|
+
tagName: string;
|
|
10
|
+
/** aria-label, or the aria-labelledby target's text. */
|
|
11
|
+
ariaLabel?: string | null;
|
|
12
|
+
/** Text of an associated <label>, whether wrapping or `for=`-linked. */
|
|
13
|
+
labelText?: string | null;
|
|
14
|
+
placeholder?: string | null;
|
|
15
|
+
alt?: string | null;
|
|
16
|
+
title?: string | null;
|
|
17
|
+
/** Current value, for a control the reviewer has already filled in. */
|
|
18
|
+
value?: string | null;
|
|
19
|
+
name?: string | null;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 🚨 A form control has no `textContent`, so reading only that made every input,
|
|
23
|
+
* textarea and select come out as its bare tag name: a reviewer picking seven link
|
|
24
|
+
* fields got seven « input » refs with no way to say which row they meant. The
|
|
25
|
+
* order below is "what a human would call it" - its own text, then the accessible
|
|
26
|
+
* name, then the label a designer wrote, then what it holds - and the tag name only
|
|
27
|
+
* when the element genuinely has no name at all.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pickedElementRef(el: PickedElementDescriptor): string;
|
|
30
|
+
/** Read a live DOM element into the descriptor above. */
|
|
31
|
+
export declare function describePickedElement(el: Element): PickedElementDescriptor;
|
|
8
32
|
/**
|
|
9
33
|
* Element-pick mode semantics (0.3.1): LEFT click picks and exits the mode;
|
|
10
34
|
* RIGHT click picks and STAYS for multi-select (context menu suppressed).
|
|
@@ -52,11 +52,63 @@ const CLICK_DRAG_THRESHOLD_PX = 6;
|
|
|
52
52
|
export function isClickGesture(dx, dy) {
|
|
53
53
|
return Math.hypot(dx, dy) < CLICK_DRAG_THRESHOLD_PX;
|
|
54
54
|
}
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* 🚨 A form control has no `textContent`, so reading only that made every input,
|
|
57
|
+
* textarea and select come out as its bare tag name: a reviewer picking seven link
|
|
58
|
+
* fields got seven « input » refs with no way to say which row they meant. The
|
|
59
|
+
* order below is "what a human would call it" - its own text, then the accessible
|
|
60
|
+
* name, then the label a designer wrote, then what it holds - and the tag name only
|
|
61
|
+
* when the element genuinely has no name at all.
|
|
62
|
+
*/
|
|
63
|
+
export function pickedElementRef(el) {
|
|
64
|
+
const clean = (v) => (v || "").replace(/\s+/g, " ").trim();
|
|
65
|
+
const label = [
|
|
66
|
+
clean(el.textContent),
|
|
67
|
+
clean(el.ariaLabel),
|
|
68
|
+
clean(el.labelText),
|
|
69
|
+
clean(el.placeholder),
|
|
70
|
+
clean(el.alt),
|
|
71
|
+
clean(el.title),
|
|
72
|
+
clean(el.value),
|
|
73
|
+
clean(el.name),
|
|
74
|
+
]
|
|
75
|
+
.find((c) => c.length > 0)
|
|
76
|
+
?.slice(0, 48) || el.tagName.toLowerCase();
|
|
58
77
|
return ` «${label}» `;
|
|
59
78
|
}
|
|
79
|
+
/** Read a live DOM element into the descriptor above. */
|
|
80
|
+
export function describePickedElement(el) {
|
|
81
|
+
const labelledBy = el.getAttribute("aria-labelledby");
|
|
82
|
+
const labelledByText = labelledBy
|
|
83
|
+
? labelledBy
|
|
84
|
+
.split(/\s+/)
|
|
85
|
+
.map((id) => el.ownerDocument?.getElementById(id)?.textContent ?? "")
|
|
86
|
+
.join(" ")
|
|
87
|
+
: null;
|
|
88
|
+
// A control's label is either an ancestor <label> or one pointing at its id.
|
|
89
|
+
let labelText = null;
|
|
90
|
+
const id = el.getAttribute("id");
|
|
91
|
+
if (id) {
|
|
92
|
+
const escaped = id.replace(/["\\]/g, "\\$&");
|
|
93
|
+
labelText = el.ownerDocument?.querySelector(`label[for="${escaped}"]`)?.textContent ?? null;
|
|
94
|
+
}
|
|
95
|
+
if (!labelText)
|
|
96
|
+
labelText = el.closest("label")?.textContent ?? null;
|
|
97
|
+
const isControl = ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName);
|
|
98
|
+
return {
|
|
99
|
+
// A control's own textContent is empty anyway; skipping it explicitly keeps the
|
|
100
|
+
// intent readable rather than relying on that emptiness.
|
|
101
|
+
textContent: isControl ? null : el.textContent,
|
|
102
|
+
tagName: el.tagName,
|
|
103
|
+
ariaLabel: el.getAttribute("aria-label") || labelledByText,
|
|
104
|
+
labelText,
|
|
105
|
+
placeholder: el.getAttribute("placeholder"),
|
|
106
|
+
alt: el.getAttribute("alt"),
|
|
107
|
+
title: el.getAttribute("title"),
|
|
108
|
+
value: isControl ? (el.value ?? null) : null,
|
|
109
|
+
name: el.getAttribute("name"),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
60
112
|
/**
|
|
61
113
|
* Element-pick mode semantics (0.3.1): LEFT click picks and exits the mode;
|
|
62
114
|
* RIGHT click picks and STAYS for multi-select (context menu suppressed).
|
|
@@ -378,7 +430,7 @@ export function QAReviewOverlay({ items, target, gateParam, stateUrl, submitUrl,
|
|
|
378
430
|
return; // ignore the panel itself
|
|
379
431
|
e.preventDefault();
|
|
380
432
|
e.stopPropagation();
|
|
381
|
-
const ref = pickedElementRef(el
|
|
433
|
+
const ref = pickedElementRef(describePickedElement(el));
|
|
382
434
|
const ta = noteRef.current;
|
|
383
435
|
if (ta) {
|
|
384
436
|
const s = ta.selectionStart ?? note.length;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artymclabin/qa-review",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Interactive on-page QA review overlay (spotlight walkthrough, approve/reject verdict ledger) with a framework-agnostic server handler factory and a self-provisioning Postgres store.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|