@dorsk/yubisashi 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,66 +1,98 @@
1
1
  # yubisashi 指差し
2
2
 
3
- Point at your running app, say what's wrong, and the coding agent working on it gets the
4
- element's source `file:line`, the components that rendered it, and your comment. It answers,
5
- edits, and hot-reload shows you the result. The agent's conversation streams in a panel next to
6
- the app.
3
+ Point at elements of your running app from a review panel and hand the coding agent the exact
4
+ source `file:line`, the component chain, the selector and your comment.
7
5
 
8
- ![pick mode](docs/pick.png)
6
+ The review panel lives in [cctui](https://github.com/DorskFR/cctui) as the opt-in **yubisashi**
7
+ plugin. This package is the app side: a framework-free picker that runs inside the framed app,
8
+ and a Vite plugin that injects it in dev.
9
9
 
10
- ## How it works
10
+ ## Use
11
+
12
+ ```sh
13
+ npm install --save-dev @dorsk/yubisashi
14
+ ```
15
+
16
+ ```ts
17
+ // vite.config.ts
18
+ import { yubisashi } from '@dorsk/yubisashi/vite';
11
19
 
12
- `yubi up` puts a small proxy in front of your dev server. Everything passes through to the app,
13
- including HMR websockets. Frame-blocking headers are stripped, so the app can be shown inside
14
- the review shell at `/__yubi/`:
20
+ export default defineConfig({
21
+ plugins: [sveltekit(), yubisashi()],
22
+ });
23
+ ```
15
24
 
16
- - **Comment**: press `C` or the ⌖ button, then click an element. Shift-click adds more
17
- elements; `Esc` cancels. A glass layer takes the clicks, so the app doesn't react, and
18
- disabled controls can be picked too.
19
- - **Source location** comes from Svelte 5's dev-mode `__svelte_meta`, which gives exact
20
- `file:line:col` plus the component chain. For other frameworks you still get the selector,
21
- text and HTML.
22
- - **Conversation tab**: follows the agent's Claude Code transcript live
23
- (`~/.claude/projects/*/<session>.jsonl`).
24
- - **Comments tab**: one thread per comment, with status and a pin on the element.
25
+ ```sh
26
+ YUBI_PARENT_ORIGIN=https://cctui.example.com npm run dev
27
+ ```
25
28
 
26
- Your comments reach the agent through `yubi wait`. The agent runs it as a background command,
27
- it blocks until you send something, then exits with your messages. Claude Code wakes the agent
28
- when a background command exits, so it needs no MCP server, no special launch flags, and no
29
- polling. The same works in any Claude Code session, including cctui ones.
29
+ Then in cctui: **Settings → Plugins → yubisashi**, open the **Review** pane and point it at the
30
+ dev server. Press `C` or the ⌖ button, click an element (shift-click adds more, `Esc` backs
31
+ out) and write the comment.
30
32
 
31
- ![comments](docs/comments.png)
33
+ The plugin only runs under `vite dev` (`apply: 'serve'`) and does nothing at all unless
34
+ `YUBI_PARENT_ORIGIN` (comma-separated origins) or the `parentOrigin` option is set. When active,
35
+ it:
32
36
 
33
- ## Use
37
+ - injects `<script type="module" src="/@id/__x00__virtual:yubisashi">` into every HTML
38
+ response, which calls `createPicker({ parentOrigin })` only when the page is inside an iframe;
39
+ - drops `X-Frame-Options` and rewrites CSP `frame-ancestors` to the configured origins so the
40
+ panel may frame the app.
34
41
 
35
- ```sh
36
- # from the app directory, as a background command in the agent's session
37
- yubi up --target http://localhost:5173 -- npm run dev
38
- yubi url # the link to open (carries the access token)
39
- yubi wait # background: exits with the user's next messages
40
- yubi reply 3 "…" # answer in thread #3
41
- yubi resolve 3 "…" # close it with what changed
42
- yubi dismiss 3 "…" # close it without acting
43
- yubi list [--all]
42
+ An `https` parent cannot frame a plain `http` page unless it is on `localhost`, so run the dev
43
+ server over https (`server.https`) when it lives anywhere else.
44
+
45
+ Source locations come from Svelte 5's dev-mode `__svelte_meta`. Other frameworks still get
46
+ selector, text, attributes and HTML.
47
+
48
+ ## Picker on its own
49
+
50
+ ```ts
51
+ import { createPicker } from '@dorsk/yubisashi/picker';
52
+
53
+ if (window.parent !== window)
54
+ createPicker({ parentOrigin: ['https://cctui.example.com', 'http://localhost:5555'] });
44
55
  ```
45
56
 
46
- `skill/SKILL.md` teaches an agent this loop. Link it into `~/.claude/skills/yubisashi` so every
47
- session knows it.
57
+ The picker talks to the parent window through `postMessage` only. It accepts messages solely
58
+ from `window.parent` at an allowed origin; with several origins it pins itself to the one that
59
+ frames the document (`ancestorOrigins` / `referrer`), otherwise to the first allowed origin
60
+ that sends a valid message.
48
61
 
49
- State lives in `.yubisashi/` in the app directory. It's added to `.git/info/exclude`
50
- automatically and holds the comments, the token and the server address.
62
+ ## Protocol
51
63
 
52
- ## Security
64
+ Every message, both ways, carries `yubi: 1`; anything without it is ignored.
53
65
 
54
- The API lets anyone who can reach it inject prompts into your agent's session. It binds to
55
- 127.0.0.1 by default, and every API call needs the random token in `.yubisashi/token`, which
56
- also stops other sites in your browser from posting to it. Think twice before `--host 0.0.0.0`.
66
+ Parent → app:
57
67
 
58
- ## Develop
68
+ | type | fields |
69
+ | ------------ | -------------------------- |
70
+ | `pick:start` | |
71
+ | `pick:stop` | |
72
+ | `pick:clear` | `id?: number` (pin the selection to comment #id) |
73
+ | `pins:set` | `pins: { id, selector }[]` |
59
74
 
60
- Node ≥ 22.18 runs the TypeScript sources directly; there is no build step.
75
+ App → parent:
76
+
77
+ | type | fields |
78
+ | --------------- | ----------------------------------------------- |
79
+ | `route` | `route: string` |
80
+ | `pick:state` | `picking: boolean` |
81
+ | `pick:hover` | `target: Target \| null` |
82
+ | `pick:selected` | `targets: Target[]`, `route`, `viewport` |
83
+ | `pick:cancel` | |
84
+ | `pin:open` | `id: number` |
85
+
86
+ `Target`: `tag`, `selector`, `text`, `html`, `attrs`, `rect`, `source?: { file, line, column }`,
87
+ `stack: Frame[]` (the components that rendered it). Types and the `isParentMessage` /
88
+ `isChildMessage` guards are exported from `@dorsk/yubisashi/picker`.
89
+
90
+ ## Develop
61
91
 
62
92
  ```sh
63
93
  npm install
64
- npm test # unit, proxy/API integration, and a Playwright end-to-end run
65
- npm run check # tsc + biome
94
+ npx playwright install chromium
95
+ npm run lint # biome
96
+ npm run check # tsc
97
+ npm test # builds dist, then Playwright tests for the picker and the Vite plugin (plain Vite + SvelteKit)
66
98
  ```
@@ -0,0 +1,2 @@
1
+ export { describe, label, selectorOf } from './inspect.ts';
2
+ export { type ChildMessage, createPicker, type Frame, isChildMessage, isParentMessage, type ParentMessage, type PickerHandle, type PickerOptions, type Pin, type Target, type Unmarked, type Viewport, YUBI, } from './picker.ts';
@@ -0,0 +1,3 @@
1
+ export { describe, label, selectorOf } from "./inspect.js";
2
+ export { createPicker, isChildMessage, isParentMessage, YUBI, } from "./picker.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/picker/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAEN,YAAY,EAEZ,cAAc,EACd,eAAe,EAQf,IAAI,GACJ,MAAM,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { Target } from './protocol.ts';
2
+ export declare const shortFile: (file?: string) => string;
3
+ export declare function selectorOf(el: Element): string;
4
+ export declare function describe(el: Element): Target;
5
+ export declare const label: (info: Target) => string;
@@ -0,0 +1,87 @@
1
+ export const shortFile = (file) => (file ? (file.split('/').at(-1) ?? '') : '');
2
+ function svelteMeta(el) {
3
+ for (let node = el; node; node = node.parentElement) {
4
+ const meta = node.__svelte_meta;
5
+ if (meta)
6
+ return meta;
7
+ }
8
+ return null;
9
+ }
10
+ function stackOf(meta) {
11
+ const frames = [];
12
+ for (let p = meta?.parent; p && frames.length < 12; p = p.parent) {
13
+ if (p.type !== 'component' || p.file?.includes('.svelte-kit/generated'))
14
+ continue;
15
+ frames.push({
16
+ type: p.type,
17
+ name: p.componentTag ? `<${p.componentTag}>` : undefined,
18
+ file: p.file,
19
+ line: p.line,
20
+ column: p.column,
21
+ });
22
+ }
23
+ return frames;
24
+ }
25
+ const SCOPED_CLASS = /^(svelte|s)-[\w-]{5,}$/;
26
+ const HOOK_ATTRS = ['data-testid', 'data-verb', 'data-test'];
27
+ const KEPT_ATTRS = ['role', 'aria-label', 'href', 'name', 'type', 'title', 'alt'];
28
+ export function selectorOf(el) {
29
+ const doc = el.ownerDocument;
30
+ const parts = [];
31
+ for (let node = el; node && node.nodeType === 1 && node !== doc.documentElement; node = node.parentElement) {
32
+ for (const attr of HOOK_ATTRS) {
33
+ const value = node.getAttribute(attr);
34
+ if (value) {
35
+ parts.unshift(`[${attr}="${CSS.escape(value)}"]`);
36
+ return parts.join(' > ');
37
+ }
38
+ }
39
+ if (node.id && !/\d{3,}/.test(node.id)) {
40
+ parts.unshift(`#${CSS.escape(node.id)}`);
41
+ return parts.join(' > ');
42
+ }
43
+ let part = node.localName;
44
+ const classes = [...node.classList].filter((c) => !SCOPED_CLASS.test(c)).slice(0, 2);
45
+ if (classes.length)
46
+ part += classes.map((c) => `.${CSS.escape(c)}`).join('');
47
+ const siblings = node.parentElement
48
+ ? [...node.parentElement.children].filter((s) => s.localName === node.localName)
49
+ : [];
50
+ if (siblings.length > 1)
51
+ part += `:nth-of-type(${siblings.indexOf(node) + 1})`;
52
+ parts.unshift(part);
53
+ if (parts.length >= 6)
54
+ break;
55
+ }
56
+ return parts.join(' > ');
57
+ }
58
+ export function describe(el) {
59
+ const meta = svelteMeta(el);
60
+ const attrs = {};
61
+ for (const a of el.attributes) {
62
+ if (a.name.startsWith('data-') || KEPT_ATTRS.includes(a.name))
63
+ attrs[a.name] = a.value.slice(0, 200);
64
+ }
65
+ const r = el.getBoundingClientRect();
66
+ const html = el.outerHTML.replace(/\s+/g, ' ');
67
+ const text = el.innerText ?? el.textContent ?? '';
68
+ return {
69
+ tag: el.localName,
70
+ selector: selectorOf(el),
71
+ text: text.replace(/\s+/g, ' ').trim().slice(0, 100),
72
+ html: html.length > 500 ? `${html.slice(0, 500)}…` : html,
73
+ attrs,
74
+ rect: {
75
+ x: Math.round(r.x),
76
+ y: Math.round(r.y),
77
+ width: Math.round(r.width),
78
+ height: Math.round(r.height),
79
+ },
80
+ source: meta
81
+ ? { file: meta.loc.file, line: meta.loc.line, column: meta.loc.column ?? 0 }
82
+ : undefined,
83
+ stack: stackOf(meta),
84
+ };
85
+ }
86
+ export const label = (info) => `<${info.tag}> ${info.source ? `${shortFile(info.source.file)}:${info.source.line}` : info.selector}`;
87
+ //# sourceMappingURL=inspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/picker/inspect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type ChildMessage, type Frame, type ParentMessage, type Pin, type Target, type Unmarked, type Viewport } from './protocol.ts';
2
+ export { isChildMessage, isParentMessage, YUBI } from './protocol.ts';
3
+ export type { ChildMessage, Frame, ParentMessage, Pin, Target, Unmarked, Viewport };
4
+ export type PickerOptions = {
5
+ /**
6
+ * Origin(s) allowed to host the review panel; messages from anywhere else are ignored.
7
+ * With several, the one framing the document (ancestorOrigins / referrer) is used; when that
8
+ * is unknown, messages go to every allowed origin until one has sent a valid message.
9
+ */
10
+ parentOrigin: string | string[];
11
+ parent?: Window;
12
+ doc?: Document;
13
+ /** Milliseconds between route checks. */
14
+ routeEveryMs?: number;
15
+ };
16
+ export type PickerHandle = {
17
+ start(): void;
18
+ stop(): void;
19
+ clear(id?: number): void;
20
+ setPins(pins: Pin[]): void;
21
+ readonly picking: boolean;
22
+ readonly selection: readonly Target[];
23
+ destroy(): void;
24
+ };
25
+ /**
26
+ * Runs inside the app document: covers it with a glass pane while picking, draws hover/selection
27
+ * boxes and comment pins, and talks to the review panel through `postMessage` only.
28
+ */
29
+ export declare function createPicker(options: PickerOptions): PickerHandle;
@@ -0,0 +1,265 @@
1
+ import { describe, label } from "./inspect.js";
2
+ import { isParentMessage, YUBI, } from "./protocol.js";
3
+ export { isChildMessage, isParentMessage, YUBI } from "./protocol.js";
4
+ const TYPING = 'input, textarea, [contenteditable=""], [contenteditable="true"]';
5
+ const ACCENT = '#f0883e';
6
+ const INK = '#111';
7
+ const MONO = 'ui-monospace, "JetBrains Mono", "SF Mono", monospace';
8
+ const SANS = 'ui-sans-serif, system-ui, sans-serif';
9
+ const routeOf = (win) => win.location.pathname + win.location.search + win.location.hash;
10
+ function framingOrigin(doc) {
11
+ const ancestors = doc.location.ancestorOrigins;
12
+ if (ancestors?.length)
13
+ return ancestors[0];
14
+ try {
15
+ return doc.referrer ? new URL(doc.referrer).origin : undefined;
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ /**
22
+ * Runs inside the app document: covers it with a glass pane while picking, draws hover/selection
23
+ * boxes and comment pins, and talks to the review panel through `postMessage` only.
24
+ */
25
+ export function createPicker(options) {
26
+ const doc = options.doc ?? document;
27
+ const win = doc.defaultView;
28
+ const parent = options.parent ?? win.parent;
29
+ const allowed = [options.parentOrigin].flat().map((o) => new URL(o).origin);
30
+ let origin = allowed.find((o) => o === framingOrigin(doc));
31
+ const root = doc.createElement('div');
32
+ root.setAttribute('data-yubi', 'root');
33
+ root.style.cssText = 'position:fixed;inset:0;z-index:2147483646;pointer-events:none;';
34
+ const overlay = doc.createElement('div');
35
+ overlay.setAttribute('data-yubi', 'overlay');
36
+ overlay.style.cssText = 'position:absolute;inset:0;overflow:hidden;pointer-events:none;';
37
+ const glass = doc.createElement('div');
38
+ glass.setAttribute('data-yubi', 'glass');
39
+ glass.style.cssText =
40
+ 'position:absolute;inset:0;cursor:crosshair;pointer-events:auto;display:none;';
41
+ root.append(overlay, glass);
42
+ doc.documentElement.append(root);
43
+ let picking = false;
44
+ let hover = null;
45
+ let selection = [];
46
+ let pins = [];
47
+ const refs = new Map();
48
+ let route = routeOf(win);
49
+ let raf = 0;
50
+ const send = (message) => {
51
+ const data = { ...message, yubi: YUBI };
52
+ for (const o of origin ? [origin] : allowed)
53
+ parent.postMessage(data, o);
54
+ };
55
+ const viewport = () => ({ width: win.innerWidth, height: win.innerHeight });
56
+ const elementAt = (x, y) => doc.elementsFromPoint(x, y).find((el) => !root.contains(el)) ?? null;
57
+ function setPicking(on) {
58
+ if (picking === on)
59
+ return;
60
+ picking = on;
61
+ glass.style.display = on ? 'block' : 'none';
62
+ if (!on)
63
+ setHover(null);
64
+ send({ type: 'pick:state', picking });
65
+ }
66
+ function setHover(el) {
67
+ if ((hover?.el ?? null) === el)
68
+ return;
69
+ hover = el ? { el, info: describe(el) } : null;
70
+ send({ type: 'pick:hover', target: hover?.info ?? null });
71
+ }
72
+ function setSelection(next) {
73
+ selection = next;
74
+ send({
75
+ type: 'pick:selected',
76
+ targets: selection.map((s) => s.info),
77
+ route: routeOf(win),
78
+ viewport: viewport(),
79
+ });
80
+ }
81
+ function pick(el, additive) {
82
+ const i = selection.findIndex((s) => s.el === el);
83
+ if (additive) {
84
+ setSelection(i >= 0 ? selection.filter((_, j) => j !== i) : [...selection, { el, info: describe(el) }]);
85
+ }
86
+ else {
87
+ setSelection([{ el, info: describe(el) }]);
88
+ setPicking(false);
89
+ }
90
+ }
91
+ function back() {
92
+ if (picking)
93
+ setPicking(false);
94
+ else if (selection.length)
95
+ setSelection([]);
96
+ else
97
+ send({ type: 'pick:cancel' });
98
+ }
99
+ function onKey(e) {
100
+ if (e.key === 'Escape')
101
+ return back();
102
+ const typing = e.target?.closest?.(TYPING);
103
+ if (!typing && (e.key === 'c' || e.key === 'C') && !e.metaKey && !e.ctrlKey && !e.altKey) {
104
+ e.preventDefault();
105
+ setPicking(!picking);
106
+ }
107
+ }
108
+ function onWheel(e) {
109
+ for (let node = elementAt(e.clientX, e.clientY); node; node = node.parentElement) {
110
+ const style = win.getComputedStyle(node);
111
+ if (/(auto|scroll)/.test(style.overflowY + style.overflowX)) {
112
+ const before = node.scrollTop + node.scrollLeft;
113
+ node.scrollBy(e.deltaX, e.deltaY);
114
+ if (node.scrollTop + node.scrollLeft !== before)
115
+ return;
116
+ }
117
+ }
118
+ win.scrollBy(e.deltaX, e.deltaY);
119
+ }
120
+ function onMessage(e) {
121
+ if (e.source !== parent || !isParentMessage(e.data))
122
+ return;
123
+ if (origin ? e.origin !== origin : !allowed.includes(e.origin))
124
+ return;
125
+ origin = e.origin;
126
+ const msg = e.data;
127
+ if (msg.type === 'pick:start')
128
+ setPicking(true);
129
+ else if (msg.type === 'pick:stop')
130
+ setPicking(false);
131
+ else if (msg.type === 'pick:clear')
132
+ clear(msg.id);
133
+ else
134
+ setPins(msg.pins);
135
+ }
136
+ function clear(id) {
137
+ if (id !== undefined && selection.length)
138
+ refs.set(id, selection.map((s) => s.el));
139
+ if (selection.length)
140
+ setSelection([]);
141
+ }
142
+ function setPins(next) {
143
+ pins = next;
144
+ }
145
+ function locatePin(pin) {
146
+ const kept = refs.get(pin.id)?.find((el) => el.isConnected);
147
+ if (kept)
148
+ return kept;
149
+ try {
150
+ return doc.querySelector(pin.selector) ?? undefined;
151
+ }
152
+ catch {
153
+ return undefined;
154
+ }
155
+ }
156
+ const nodes = new Map();
157
+ function place(el, x, y, width, height) {
158
+ el.style.left = `${x}px`;
159
+ el.style.top = `${y}px`;
160
+ if (width !== undefined)
161
+ el.style.width = `${width}px`;
162
+ if (height !== undefined)
163
+ el.style.height = `${height}px`;
164
+ }
165
+ function box(key, rect, kind, text) {
166
+ let el = nodes.get(key);
167
+ if (!el) {
168
+ el = doc.createElement('div');
169
+ el.setAttribute('data-yubi', kind);
170
+ el.style.cssText = `position:absolute;border:2px ${kind === 'hover' ? 'dashed' : 'solid'} ${ACCENT};border-radius:3px;background:rgb(240 136 62 / ${kind === 'hover' ? 0.05 : 0.08});box-sizing:border-box;`;
171
+ const tag = doc.createElement('span');
172
+ tag.setAttribute('data-yubi', 'label');
173
+ tag.style.cssText = `position:absolute;top:-20px;left:-2px;padding:1px 6px;border-radius:3px 3px 3px 0;background:${ACCENT};color:${INK};font:11px/18px ${MONO};white-space:nowrap;`;
174
+ el.append(tag);
175
+ nodes.set(key, el);
176
+ }
177
+ place(el, rect.x, rect.y, rect.width, rect.height);
178
+ const tag = el.firstChild;
179
+ if (tag.textContent !== text)
180
+ tag.textContent = text;
181
+ return el;
182
+ }
183
+ function pinNode(pin, rect) {
184
+ const key = `pin:${pin.id}`;
185
+ let el = nodes.get(key);
186
+ if (!el) {
187
+ el = doc.createElement('button');
188
+ el.type = 'button';
189
+ el.setAttribute('data-yubi', 'pin');
190
+ el.setAttribute('data-id', String(pin.id));
191
+ el.textContent = String(pin.id);
192
+ el.title = `Open comment #${pin.id}`;
193
+ el.style.cssText = `position:absolute;width:22px;height:22px;margin:-11px 0 0 -11px;padding:0;border:0;border-radius:11px 11px 11px 2px;background:${ACCENT};color:${INK};font:600 11px/22px ${SANS};text-align:center;pointer-events:auto;cursor:pointer;box-shadow:0 1px 4px rgb(0 0 0 / 0.4);`;
194
+ el.onclick = () => send({ type: 'pin:open', id: pin.id });
195
+ nodes.set(key, el);
196
+ }
197
+ place(el, rect.x + rect.width, rect.y);
198
+ return el;
199
+ }
200
+ function draw() {
201
+ const layers = [];
202
+ if (picking && hover?.el.isConnected && !selection.some((s) => s.el === hover?.el)) {
203
+ layers.push(box('hover', hover.el.getBoundingClientRect(), 'hover', label(hover.info)));
204
+ }
205
+ selection.forEach((s, i) => {
206
+ if (s.el.isConnected)
207
+ layers.push(box(`sel:${i}`, s.el.getBoundingClientRect(), 'selected', label(s.info)));
208
+ });
209
+ for (const pin of pins) {
210
+ const el = locatePin(pin);
211
+ if (el)
212
+ layers.push(pinNode(pin, el.getBoundingClientRect()));
213
+ }
214
+ for (const [key, el] of nodes)
215
+ if (!layers.includes(el))
216
+ nodes.delete(key);
217
+ if (layers.length !== overlay.childNodes.length ||
218
+ layers.some((el, i) => overlay.childNodes[i] !== el))
219
+ overlay.replaceChildren(...layers);
220
+ raf = win.requestAnimationFrame(draw);
221
+ }
222
+ const onMove = (e) => setHover(elementAt(e.clientX, e.clientY));
223
+ const onLeave = () => setHover(null);
224
+ const onClick = (e) => {
225
+ const el = elementAt(e.clientX, e.clientY);
226
+ if (el)
227
+ pick(el, e.shiftKey || e.metaKey || e.ctrlKey);
228
+ };
229
+ const syncRoute = () => {
230
+ const next = routeOf(win);
231
+ if (next === route)
232
+ return;
233
+ route = next;
234
+ send({ type: 'route', route });
235
+ };
236
+ glass.addEventListener('mousemove', onMove);
237
+ glass.addEventListener('mouseleave', onLeave);
238
+ glass.addEventListener('click', onClick);
239
+ glass.addEventListener('wheel', onWheel, { passive: true });
240
+ win.addEventListener('keydown', onKey, true);
241
+ win.addEventListener('message', onMessage);
242
+ const routeTimer = win.setInterval(syncRoute, options.routeEveryMs ?? 400);
243
+ raf = win.requestAnimationFrame(draw);
244
+ send({ type: 'route', route });
245
+ return {
246
+ start: () => setPicking(true),
247
+ stop: () => setPicking(false),
248
+ clear,
249
+ setPins,
250
+ get picking() {
251
+ return picking;
252
+ },
253
+ get selection() {
254
+ return selection.map((s) => s.info);
255
+ },
256
+ destroy() {
257
+ win.cancelAnimationFrame(raf);
258
+ win.clearInterval(routeTimer);
259
+ win.removeEventListener('keydown', onKey, true);
260
+ win.removeEventListener('message', onMessage);
261
+ root.remove();
262
+ },
263
+ };
264
+ }
265
+ //# sourceMappingURL=picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"picker.js","sourceRoot":"","sources":["../../src/picker/picker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /** Every message in both directions carries this marker; anything without it is ignored. */
2
+ export declare const YUBI: 1;
3
+ export type Frame = {
4
+ type: string;
5
+ file?: string;
6
+ line?: number;
7
+ column?: number;
8
+ name?: string;
9
+ };
10
+ export type Target = {
11
+ tag: string;
12
+ selector: string;
13
+ text: string;
14
+ html: string;
15
+ attrs: Record<string, string>;
16
+ rect: {
17
+ x: number;
18
+ y: number;
19
+ width: number;
20
+ height: number;
21
+ };
22
+ source?: {
23
+ file: string;
24
+ line: number;
25
+ column: number;
26
+ };
27
+ stack: Frame[];
28
+ };
29
+ export type Pin = {
30
+ id: number;
31
+ selector: string;
32
+ };
33
+ export type Viewport = {
34
+ width: number;
35
+ height: number;
36
+ };
37
+ type Marked<T> = T & {
38
+ yubi: typeof YUBI;
39
+ };
40
+ /** A message before the marker is stamped on. */
41
+ export type Unmarked<T> = T extends unknown ? Omit<T, 'yubi'> : never;
42
+ /** Parent window → app document. */
43
+ export type ParentMessage = Marked<{
44
+ type: 'pick:start';
45
+ } | {
46
+ type: 'pick:stop';
47
+ }
48
+ /** Drops the selection; `id` records it as the targets of comment #id so its pin sticks to the picked elements. */
49
+ | {
50
+ type: 'pick:clear';
51
+ id?: number;
52
+ } | {
53
+ type: 'pins:set';
54
+ pins: Pin[];
55
+ }>;
56
+ /** App document → parent window. */
57
+ export type ChildMessage = Marked<{
58
+ type: 'pick:state';
59
+ picking: boolean;
60
+ } | {
61
+ type: 'pick:hover';
62
+ target: Target | null;
63
+ } | {
64
+ type: 'pick:selected';
65
+ targets: Target[];
66
+ route: string;
67
+ viewport: Viewport;
68
+ } | {
69
+ type: 'pick:cancel';
70
+ } | {
71
+ type: 'pin:open';
72
+ id: number;
73
+ } | {
74
+ type: 'route';
75
+ route: string;
76
+ }>;
77
+ export declare const isParentMessage: (data: unknown) => data is ParentMessage;
78
+ export declare const isChildMessage: (data: unknown) => data is ChildMessage;
79
+ export {};