@autono/pinbox-toolbar 0.18.0 → 0.20.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 +2 -2
- package/dist/{index-DENhglDF.d.ts → index-xNXeJ2mR.d.ts} +2 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/model-target-BykKL8H5.js +350 -0
- package/dist/model.d.ts +52 -0
- package/dist/model.js +182 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-BsWnyGwB.js → src-DnAkx29T.js} +10 -276
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +1 -1
- package/dist/toolbar.iife.js +50 -8
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +1 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ Both plugin files are **guest-rule code** (`AGENTS.md`): they are evaluated insi
|
|
|
33
33
|
|
|
34
34
|
- **Realtime** — `src/transport.ts` speaks the frozen WS protocol (`hello → catch-up → events`) against `GET /ws`, authenticating at upgrade only via the `pinbox.token.<token>` subprotocol, because a browser cannot set headers on an upgrade. It reconnects with jittered 1s→30s backoff and replays from its persisted cursor, so a dropped socket costs no events. Either side of an excluding protocol window closes `4400` with a clear upgrade message; a bad token closes `4401`.
|
|
35
35
|
- **Offline mirror** — `src/transport/mirror.ts` keeps the cursor, the last-known pin list, and an outbox of pins drawn while offline in `localStorage`, namespaced per endpoint. Reconnect reconciles on one rule: **the hub wins on status, the client wins on new pins**. Storage writes never throw upward — private mode or a full quota degrades the mirror, never the toolbar.
|
|
36
|
-
- **Attachments** —
|
|
36
|
+
- **Attachments** — two capture modes, both webp-encoded **in the browser** and POSTed to `/attachments`; the pin then carries the returned **path — never bytes**. The default is a DOM snapshot of the pinned element (`src/screenshot-dom.ts`: clone, inline computed styles, SVG `foreignObject` → canvas; no permission prompt, images become neutral boxes). Tab capture (`src/screenshot.ts`, `getDisplayMedia` → `createImageBitmap` → `OffscreenCanvas`) is opt-in from the bar's camera button and prompts once per page load. Capture is best-effort and bounded (4 s): wherever the environment cannot capture, the pin ships with structured capture alone.
|
|
37
37
|
|
|
38
38
|
The toolbar's copies of the `ws-protocol.ts` wire constants are mirrored, not imported (core is a **type-only** dependency here — that is what keeps the runtime zero-dependency, and fallow's `allowTypeOnly` enforces it). `e2e/toolbar.test.ts` drives this transport against a real `pinbox serve`, so drift between the two copies fails the build.
|
|
39
39
|
|
|
@@ -41,4 +41,4 @@ The toolbar's copies of the `ws-protocol.ts` wire constants are mirrored, not im
|
|
|
41
41
|
|
|
42
42
|
**This package deliberately declares no `engines` field, and must not gain one.** It is the sole exception to the repo-wide `"engines": { "bun": ">=1.3.0" }` rule. The toolbar is browser code: Bun builds it here, but consumers install it into their own toolchain — a Vite/React app on npm under Node, most often. Declaring a Bun engine would make those installs warn, and refuse outright under `engine-strict`, for a package that never executes on a server runtime at all.
|
|
43
43
|
|
|
44
|
-
Key behaviors (see spec): targeting adapters (`dom` hit-testing | `anchor` attributes for sandboxed-iframe hosts),
|
|
44
|
+
Key behaviors (see spec): targeting adapters (`dom` hit-testing | `anchor` attributes for sandboxed-iframe hosts), a viewport-space overlay re-laid out per frame so pins hold their element through scrolling, sticky headers and inner scroll containers, two pin kinds on the draft (Ask agent / Note — a `comment` pin is never routed to an agent), pending stages (THINKING → WAITING FOR AGENT → NO RESPONSE with Nudge), resolve/unresolve from the inbox with PR-linked pins grouped, one action table behind the bar, the puck's fan menu and the keyboard (capture-phase, modifier-safe, `shortcuts` config), a draggable bar and puck, live status badges over WS with the accept/reopen verification step, localStorage offline mirror with reconnect reconciliation, copy-as-markdown fallback, `Pinbox.init({ getToken })` auth passthrough — the toolbar never renders a login.
|
|
@@ -69,6 +69,8 @@ interface ToolbarState {
|
|
|
69
69
|
* Persisted per endpoint; the bar's camera button flips it.
|
|
70
70
|
*/
|
|
71
71
|
captureMode: "dom" | "tab";
|
|
72
|
+
/** Optional model camera action label; absent preserves the 2D capture toggle. */
|
|
73
|
+
captureLabel?: string | undefined;
|
|
72
74
|
}
|
|
73
75
|
interface Store {
|
|
74
76
|
get(): ToolbarState;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-
|
|
1
|
+
import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-xNXeJ2mR.js";
|
|
2
2
|
export { type CaptureResult, type ConnectionState, type Draft, HubError, type HubEvent, HubTransport, Pinbox, PinboxConfig, PinboxToolbarElement, type StorageLike, type Store, type ToolbarState, type TransportOptions, type UiStatus, type WebSocketLike, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, upsertPin };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-
|
|
1
|
+
import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-DnAkx29T.js";
|
|
2
2
|
export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, upsertPin };
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
//#region src/capture-mode.ts
|
|
2
|
+
function captureKey(prefix) {
|
|
3
|
+
return `${prefix}:capture`;
|
|
4
|
+
}
|
|
5
|
+
function loadCaptureMode(storage, key, fallback) {
|
|
6
|
+
try {
|
|
7
|
+
const raw = storage?.getItem(key);
|
|
8
|
+
if (raw === "dom" || raw === "tab") return raw;
|
|
9
|
+
} catch {}
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
function saveCaptureMode(storage, key, mode) {
|
|
13
|
+
try {
|
|
14
|
+
storage?.setItem(key, mode);
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
//#region src/model-capture.ts
|
|
19
|
+
const handlers = /* @__PURE__ */ new WeakMap();
|
|
20
|
+
/** The first viewer owns the camera until another registered surface is entered. */
|
|
21
|
+
function registerModelCapture(owner, run, error) {
|
|
22
|
+
const entry = {
|
|
23
|
+
run,
|
|
24
|
+
error,
|
|
25
|
+
busy: false
|
|
26
|
+
};
|
|
27
|
+
const entries = handlers.get(owner) ?? [];
|
|
28
|
+
entries.push(entry);
|
|
29
|
+
handlers.set(owner, entries);
|
|
30
|
+
owner.store.update({ captureLabel: "Capture model screenshot (S)" });
|
|
31
|
+
return {
|
|
32
|
+
activate() {
|
|
33
|
+
const index = entries.indexOf(entry);
|
|
34
|
+
if (index < 0) return;
|
|
35
|
+
entries.splice(index, 1);
|
|
36
|
+
entries.unshift(entry);
|
|
37
|
+
},
|
|
38
|
+
destroy() {
|
|
39
|
+
const index = entries.indexOf(entry);
|
|
40
|
+
if (index < 0) return;
|
|
41
|
+
entries.splice(index, 1);
|
|
42
|
+
if (!entries.length) {
|
|
43
|
+
handlers.delete(owner);
|
|
44
|
+
owner.store.update({ captureLabel: void 0 });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function runModelCapture(owner) {
|
|
50
|
+
const entries = handlers.get(owner);
|
|
51
|
+
const entry = entries?.[0];
|
|
52
|
+
if (!entries || !entry) return false;
|
|
53
|
+
if (entries.some((candidate) => candidate.busy)) return true;
|
|
54
|
+
entry.busy = true;
|
|
55
|
+
try {
|
|
56
|
+
Promise.resolve(entry.run()).catch(entry.error).finally(() => {
|
|
57
|
+
entry.busy = false;
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
entry.busy = false;
|
|
61
|
+
entry.error(error);
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
/** Shared by the camera button, puck and S shortcut; preserve the 2D fallback. */
|
|
66
|
+
function toggleToolbarCapture(owner, endpoint, release) {
|
|
67
|
+
if (runModelCapture(owner)) return true;
|
|
68
|
+
const next = owner.store.get().captureMode === "tab" ? "dom" : "tab";
|
|
69
|
+
owner.store.update({ captureMode: next });
|
|
70
|
+
if (next === "dom") release();
|
|
71
|
+
saveCaptureMode(globalThis.localStorage, captureKey(`pinbox:${endpoint}`), next);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/targeting/dom.ts
|
|
76
|
+
/**
|
|
77
|
+
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
78
|
+
* nothing there but page chrome (html/body).
|
|
79
|
+
*
|
|
80
|
+
* Looks THROUGH our own overlay rather than giving up at it. The single-element form could not:
|
|
81
|
+
* the drag-aim grip sits exactly on the point being aimed at, so it is always the topmost thing
|
|
82
|
+
* under the crosshair, and every probe came back "nothing" the moment touch aiming existed.
|
|
83
|
+
*/
|
|
84
|
+
function hitTest(doc, x, y, ignore) {
|
|
85
|
+
const stack = doc.elementsFromPoint?.(x, y) ?? [doc.elementFromPoint(x, y)];
|
|
86
|
+
for (const el of stack) {
|
|
87
|
+
if (!el || el === doc.body || el === doc.documentElement) return null;
|
|
88
|
+
if (!ignore(el)) return el;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
/** CLASS-or-TAG display name with a sibling index when needed (prototype nodeName). */
|
|
93
|
+
function nodeName(el) {
|
|
94
|
+
const key = el.classList[0];
|
|
95
|
+
let name = (key ?? el.tagName).toUpperCase();
|
|
96
|
+
const parent = el.parentElement;
|
|
97
|
+
if (parent) {
|
|
98
|
+
const sibs = [...parent.children].filter((c) => c.classList[0] === key && c.tagName === el.tagName);
|
|
99
|
+
if (sibs.length > 1) name += ` ${sibs.indexOf(el) + 1}`;
|
|
100
|
+
}
|
|
101
|
+
return name;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Human label for a target: an explicit data-pb-el annotation wins; otherwise a
|
|
105
|
+
* CLASS/TAG ancestry chain of at most 3 parts joined with ›, terminating early
|
|
106
|
+
* at the first annotated ancestor.
|
|
107
|
+
*/
|
|
108
|
+
function targetLabel(el) {
|
|
109
|
+
const own = el.getAttribute("data-pb-el");
|
|
110
|
+
if (own) return own;
|
|
111
|
+
const parts = [nodeName(el)];
|
|
112
|
+
const body = el.ownerDocument.body;
|
|
113
|
+
let node = el.parentElement;
|
|
114
|
+
while (node && node !== body && parts.length < 3) {
|
|
115
|
+
const anchor = node.getAttribute("data-pb-el");
|
|
116
|
+
if (anchor) {
|
|
117
|
+
parts.unshift(anchor);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
if (node.classList[0]) parts.unshift(nodeName(node));
|
|
121
|
+
node = node.parentElement;
|
|
122
|
+
}
|
|
123
|
+
return parts.join(" › ");
|
|
124
|
+
}
|
|
125
|
+
const SAFE_ID = /^[A-Za-z][\w-]*$/;
|
|
126
|
+
/** Data attributes trusted as stable hooks, in priority order. */
|
|
127
|
+
const STABLE_DATA_ATTRS = [
|
|
128
|
+
"data-pb-anchor",
|
|
129
|
+
"data-pb-el",
|
|
130
|
+
"data-testid"
|
|
131
|
+
];
|
|
132
|
+
function attrSegment(el, doc) {
|
|
133
|
+
for (const attr of STABLE_DATA_ATTRS) {
|
|
134
|
+
const value = el.getAttribute(attr);
|
|
135
|
+
if (value === null || value.includes("\"") || value.includes("\\")) continue;
|
|
136
|
+
const selector = `${el.tagName.toLowerCase()}[${attr}="${value}"]`;
|
|
137
|
+
if (doc.querySelectorAll(selector).length === 1) return selector;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
function nthSegment(el) {
|
|
142
|
+
const tag = el.tagName.toLowerCase();
|
|
143
|
+
const parent = el.parentElement;
|
|
144
|
+
if (!parent) return tag;
|
|
145
|
+
const sameTag = [...parent.children].filter((c) => c.tagName === el.tagName);
|
|
146
|
+
return sameTag.length > 1 ? `${tag}:nth-of-type(${sameTag.indexOf(el) + 1})` : tag;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Stable CSS path for an element: ids > stable data attributes > an
|
|
150
|
+
* nth-of-type chain. Guaranteed round-trip: querySelector(buildSelector(el)) === el.
|
|
151
|
+
*/
|
|
152
|
+
function buildSelector(el) {
|
|
153
|
+
const doc = el.ownerDocument;
|
|
154
|
+
const segments = [];
|
|
155
|
+
let node = el;
|
|
156
|
+
while (node && node !== doc.documentElement) {
|
|
157
|
+
const id = node.getAttribute("id");
|
|
158
|
+
if (id && SAFE_ID.test(id) && doc.querySelectorAll(`#${id}`).length === 1) {
|
|
159
|
+
segments.unshift(`#${id}`);
|
|
160
|
+
return segments.join(" > ");
|
|
161
|
+
}
|
|
162
|
+
const byAttr = attrSegment(node, doc);
|
|
163
|
+
if (byAttr) {
|
|
164
|
+
segments.unshift(byAttr);
|
|
165
|
+
return segments.join(" > ");
|
|
166
|
+
}
|
|
167
|
+
segments.unshift(nthSegment(node));
|
|
168
|
+
node = node.parentElement;
|
|
169
|
+
}
|
|
170
|
+
return segments.join(" > ");
|
|
171
|
+
}
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/capture.ts
|
|
174
|
+
/** Curated computed-style subset — enough to reconstruct layout intent, tiny on the wire. */
|
|
175
|
+
const STYLE_KEYS = [
|
|
176
|
+
"display",
|
|
177
|
+
"position",
|
|
178
|
+
"font-size",
|
|
179
|
+
"color",
|
|
180
|
+
"background-color",
|
|
181
|
+
"margin",
|
|
182
|
+
"padding",
|
|
183
|
+
"overflow"
|
|
184
|
+
];
|
|
185
|
+
const NEARBY_TEXT_MAX = 160;
|
|
186
|
+
function styleSubset(win, el) {
|
|
187
|
+
let cs;
|
|
188
|
+
try {
|
|
189
|
+
cs = win.getComputedStyle(el);
|
|
190
|
+
} catch {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const out = {};
|
|
194
|
+
for (const key of STYLE_KEYS) {
|
|
195
|
+
const value = cs.getPropertyValue(key);
|
|
196
|
+
if (value !== "") out[key] = value;
|
|
197
|
+
}
|
|
198
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
199
|
+
}
|
|
200
|
+
function ariaMap(el) {
|
|
201
|
+
const out = {};
|
|
202
|
+
for (const name of el.getAttributeNames()) if (name.startsWith("aria-")) out[name] = el.getAttribute(name) ?? "";
|
|
203
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
204
|
+
}
|
|
205
|
+
function nearbyText(el) {
|
|
206
|
+
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
207
|
+
return text === "" ? void 0 : text.slice(0, NEARBY_TEXT_MAX);
|
|
208
|
+
}
|
|
209
|
+
/** The user's selection, only when it intersects the captured element. */
|
|
210
|
+
function selectedText(win, el) {
|
|
211
|
+
try {
|
|
212
|
+
const sel = win.getSelection?.();
|
|
213
|
+
if (!sel || sel.isCollapsed || sel.rangeCount === 0) return void 0;
|
|
214
|
+
if (!sel.getRangeAt(0).intersectsNode(el)) return void 0;
|
|
215
|
+
const text = sel.toString().trim();
|
|
216
|
+
return text === "" ? void 0 : text;
|
|
217
|
+
} catch {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** `fixed` detected via ancestry: any ancestor with computed position: fixed. */
|
|
222
|
+
function isFixed(win, el) {
|
|
223
|
+
for (let node = el; node !== null; node = node.parentElement) try {
|
|
224
|
+
if (win.getComputedStyle(node).position === "fixed") return true;
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
/** Beyond this an element is not a thing you pinned, it is a region. Too many to rewrite as a set. */
|
|
231
|
+
const MAX_RUNS = 40;
|
|
232
|
+
const MAX_RUN_LENGTH = 200;
|
|
233
|
+
/** Text that is not content: a script body or a stylesheet is not something to rewrite. */
|
|
234
|
+
const NON_CONTENT = /* @__PURE__ */ new Set([
|
|
235
|
+
"SCRIPT",
|
|
236
|
+
"STYLE",
|
|
237
|
+
"NOSCRIPT",
|
|
238
|
+
"TEMPLATE"
|
|
239
|
+
]);
|
|
240
|
+
/**
|
|
241
|
+
* The element's text, split the way the browser stores it: one entry per run of characters.
|
|
242
|
+
*
|
|
243
|
+
* This walks TEXT NODES, not elements, and that distinction is the whole point — it makes no
|
|
244
|
+
* assumption about how a site is built. A heading is one run. A nav bar is one per link. A
|
|
245
|
+
* paragraph with a bold word in the middle is three, in reading order, including the halves either
|
|
246
|
+
* side of the bold. An earlier version keyed off "elements with no element children", which
|
|
247
|
+
* quietly lost the "Hello " in `<p>Hello <b>world</b></p>` — text a person can obviously see and
|
|
248
|
+
* would obviously expect to be able to change.
|
|
249
|
+
*
|
|
250
|
+
* `nearbyText` runs them all together, which is fine to read and useless to edit: it cannot tell
|
|
251
|
+
* an agent that "work approach people contact" is four separate places. This can.
|
|
252
|
+
*/
|
|
253
|
+
function textRuns(el) {
|
|
254
|
+
const runs = [];
|
|
255
|
+
const walk = (node) => {
|
|
256
|
+
if (node.nodeType === 3) {
|
|
257
|
+
const text = (node.nodeValue ?? "").trim();
|
|
258
|
+
if (text.length > 0) runs.push(text.slice(0, MAX_RUN_LENGTH));
|
|
259
|
+
return runs.length <= MAX_RUNS;
|
|
260
|
+
}
|
|
261
|
+
if (node.nodeType !== 1 || NON_CONTENT.has(node.tagName)) return true;
|
|
262
|
+
for (const child of node.childNodes) if (!walk(child)) return false;
|
|
263
|
+
return true;
|
|
264
|
+
};
|
|
265
|
+
if (!walk(el) || runs.length === 0) return void 0;
|
|
266
|
+
return runs;
|
|
267
|
+
}
|
|
268
|
+
function buildContext(win, el) {
|
|
269
|
+
const context = {};
|
|
270
|
+
if (el.classList.length > 0) context.classes = [...el.classList];
|
|
271
|
+
const styles = styleSubset(win, el);
|
|
272
|
+
if (styles !== void 0) context.styles = styles;
|
|
273
|
+
const aria = ariaMap(el);
|
|
274
|
+
if (aria !== void 0) context.aria = aria;
|
|
275
|
+
const nearby = nearbyText(el);
|
|
276
|
+
if (nearby !== void 0) context.nearbyText = nearby;
|
|
277
|
+
const selected = selectedText(win, el);
|
|
278
|
+
if (selected !== void 0) context.selectedText = selected;
|
|
279
|
+
const runs = textRuns(el);
|
|
280
|
+
if (runs !== void 0) context.textRuns = runs;
|
|
281
|
+
return Object.keys(context).length > 0 ? context : void 0;
|
|
282
|
+
}
|
|
283
|
+
/** Fills PinInput.target/env from a chosen element (shapes come from the pin schema). */
|
|
284
|
+
function captureTarget(el, opts) {
|
|
285
|
+
const win = el.ownerDocument.defaultView;
|
|
286
|
+
const r = el.getBoundingClientRect();
|
|
287
|
+
const target = {
|
|
288
|
+
url: win.location.href,
|
|
289
|
+
selector: buildSelector(el),
|
|
290
|
+
tag: el.tagName.toLowerCase(),
|
|
291
|
+
rect: {
|
|
292
|
+
x: r.left + win.scrollX,
|
|
293
|
+
y: r.top + win.scrollY,
|
|
294
|
+
width: r.width,
|
|
295
|
+
height: r.height
|
|
296
|
+
},
|
|
297
|
+
fixed: isFixed(win, el)
|
|
298
|
+
};
|
|
299
|
+
if (opts?.anchor !== void 0) target.anchor = opts.anchor;
|
|
300
|
+
if (opts?.at !== void 0 && r.width > 0 && r.height > 0) {
|
|
301
|
+
const fx = (opts.at.x - (r.left + win.scrollX)) / r.width;
|
|
302
|
+
const fy = (opts.at.y - (r.top + win.scrollY)) / r.height;
|
|
303
|
+
if (fx >= 0 && fx <= 1 && fy >= 0 && fy <= 1) target.spot = {
|
|
304
|
+
x: fx,
|
|
305
|
+
y: fy
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
const context = buildContext(win, el);
|
|
309
|
+
if (context !== void 0) target.context = context;
|
|
310
|
+
return {
|
|
311
|
+
target,
|
|
312
|
+
env: {
|
|
313
|
+
viewport: {
|
|
314
|
+
w: win.innerWidth,
|
|
315
|
+
h: win.innerHeight,
|
|
316
|
+
dpr: win.devicePixelRatio
|
|
317
|
+
},
|
|
318
|
+
browser: win.navigator.userAgent,
|
|
319
|
+
os: win.navigator.platform || "unknown",
|
|
320
|
+
colorScheme: win.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/model-target.ts
|
|
326
|
+
const projectors = /* @__PURE__ */ new WeakMap();
|
|
327
|
+
function registerModelProjection(doc, project) {
|
|
328
|
+
const set = projectors.get(doc) ?? /* @__PURE__ */ new Set();
|
|
329
|
+
const entry = (anchor) => project(anchor);
|
|
330
|
+
set.add(entry);
|
|
331
|
+
projectors.set(doc, set);
|
|
332
|
+
return () => {
|
|
333
|
+
set.delete(entry);
|
|
334
|
+
if (!set.size) projectors.delete(doc);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
function projectModelTarget(doc, anchor) {
|
|
338
|
+
for (const project of projectors.get(doc) ?? []) {
|
|
339
|
+
const p = project(anchor);
|
|
340
|
+
if (p && Number.isFinite(p.x) && Number.isFinite(p.y)) return {
|
|
341
|
+
x: p.x,
|
|
342
|
+
y: p.y,
|
|
343
|
+
width: 0,
|
|
344
|
+
height: 0
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
export { targetLabel as a, captureKey as c, hitTest as i, loadCaptureMode as l, registerModelProjection as n, registerModelCapture as o, captureTarget as r, toggleToolbarCapture as s, projectModelTarget as t };
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { d as PinboxToolbarElement } from "./index-xNXeJ2mR.js";
|
|
2
|
+
import { ModelAnchor, ModelAnchor as ModelAnchor$1, Pin } from "@autono/pinbox-core/schema";
|
|
3
|
+
//#region src/model-target.d.ts
|
|
4
|
+
type ModelProjection = (anchor: ModelAnchor$1) => {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
} | null;
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/model-ui.d.ts
|
|
10
|
+
type ModelViewerAdapter = {
|
|
11
|
+
/** Canvas or element containing the model. DOM pins outside it keep working. */
|
|
12
|
+
surface: HTMLElement;
|
|
13
|
+
/** Raycast a viewport point; return a part-local anchor, or null for empty space. */
|
|
14
|
+
pick: (clientX: number, clientY: number) => ModelAnchor$1 | null;
|
|
15
|
+
/** Project to viewport CSS pixels. Null for a stale, hidden or occluded anchor. */
|
|
16
|
+
project: ModelProjection;
|
|
17
|
+
/** Called each update in pin mode, or null when the hover clears. */
|
|
18
|
+
onHover?: (anchor: ModelAnchor$1 | null) => void;
|
|
19
|
+
/** Filter activation for multiple viewers; may accept older revisions for loading. */
|
|
20
|
+
acceptsAnchor?: (anchor: ModelAnchor$1) => boolean;
|
|
21
|
+
/** Animate/load a pin view. Stop stale asynchronous work when signal is aborted. */
|
|
22
|
+
onActivate?: (anchor: ModelAnchor$1, pin: Pin, context: {
|
|
23
|
+
signal: AbortSignal;
|
|
24
|
+
}) => void | Promise<void>;
|
|
25
|
+
/** Camera button and S action. Host owns capture, preview, save and chat/storage UI. */
|
|
26
|
+
onCapture?: () => void | Promise<void>;
|
|
27
|
+
onError?: (error: unknown) => void;
|
|
28
|
+
};
|
|
29
|
+
/** Connect the real Pinbox toolbar, draft card and needle markers to a 3D viewer.
|
|
30
|
+
* Call update() after rendering a frame. Destroy before removing the viewer. */
|
|
31
|
+
declare function attachModelViewer(toolbar: PinboxToolbarElement, adapter: ModelViewerAdapter): {
|
|
32
|
+
openPin: (id: string) => Promise<void>;
|
|
33
|
+
update(): void;
|
|
34
|
+
destroy(): void;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/model.d.ts
|
|
38
|
+
type ModelScene = {
|
|
39
|
+
modelId: string;
|
|
40
|
+
revision: string;
|
|
41
|
+
/** Column-major 4x4 object-to-world matrix (e.g. Three.js matrixWorld.elements). */
|
|
42
|
+
partMatrix: (partId: string) => readonly number[] | undefined;
|
|
43
|
+
};
|
|
44
|
+
type AnchorResolution = {
|
|
45
|
+
status: "visible";
|
|
46
|
+
worldPosition: [number, number, number];
|
|
47
|
+
} | {
|
|
48
|
+
status: "stale" | "missing";
|
|
49
|
+
};
|
|
50
|
+
declare function resolveModelAnchor(anchor: ModelAnchor, scene: ModelScene): AnchorResolution;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { AnchorResolution, type ModelAnchor, ModelScene, type ModelViewerAdapter, attachModelViewer, resolveModelAnchor };
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { n as registerModelProjection, o as registerModelCapture, r as captureTarget } from "./model-target-BykKL8H5.js";
|
|
2
|
+
//#region src/model-hooks.ts
|
|
3
|
+
/** Renderer callbacks stay separate from placement and marker projection. */
|
|
4
|
+
function attachModelHooks(toolbar, adapter) {
|
|
5
|
+
const surface = adapter.surface, win = surface.ownerDocument.defaultView;
|
|
6
|
+
if (!win) throw new Error("The model surface must belong to a browser document");
|
|
7
|
+
const error = adapter.onError ?? ((cause) => console.error("Pinbox model hook failed", cause));
|
|
8
|
+
const capture = adapter.onCapture ? registerModelCapture(toolbar, adapter.onCapture, error) : void 0;
|
|
9
|
+
let point, hovering = false, disposed = false;
|
|
10
|
+
let active = null, controller;
|
|
11
|
+
let activation = Promise.resolve();
|
|
12
|
+
const clear = () => {
|
|
13
|
+
point = void 0;
|
|
14
|
+
if (hovering) {
|
|
15
|
+
hovering = false;
|
|
16
|
+
adapter.onHover?.(null);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const move = (event) => {
|
|
20
|
+
if (!event.composedPath().includes(surface)) {
|
|
21
|
+
clear();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
point = {
|
|
25
|
+
x: event.clientX,
|
|
26
|
+
y: event.clientY
|
|
27
|
+
};
|
|
28
|
+
capture?.activate();
|
|
29
|
+
};
|
|
30
|
+
const focus = () => capture?.activate();
|
|
31
|
+
const activate = (pin) => {
|
|
32
|
+
controller?.abort();
|
|
33
|
+
controller = new AbortController();
|
|
34
|
+
const signal = controller.signal, anchor = pin.target?.model;
|
|
35
|
+
if (!anchor || !adapter.onActivate || adapter.acceptsAnchor && !adapter.acceptsAnchor(anchor)) return Promise.resolve();
|
|
36
|
+
try {
|
|
37
|
+
return Promise.resolve(adapter.onActivate(anchor, pin, { signal })).catch((cause) => {
|
|
38
|
+
if (!signal.aborted) error(cause);
|
|
39
|
+
});
|
|
40
|
+
} catch (cause) {
|
|
41
|
+
error(cause);
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const unsubscribe = toolbar.store.subscribe((state) => {
|
|
46
|
+
if (state.mode !== "placing" && hovering) {
|
|
47
|
+
hovering = false;
|
|
48
|
+
adapter.onHover?.(null);
|
|
49
|
+
}
|
|
50
|
+
if (active === state.activePinId) return;
|
|
51
|
+
const pin = state.pins.find((p) => p.id === state.activePinId);
|
|
52
|
+
if (state.activePinId && !pin) return;
|
|
53
|
+
active = state.activePinId;
|
|
54
|
+
controller?.abort();
|
|
55
|
+
if (pin) activation = activate(pin);
|
|
56
|
+
});
|
|
57
|
+
win.addEventListener("pointermove", move, true);
|
|
58
|
+
win.addEventListener("blur", clear);
|
|
59
|
+
surface.addEventListener("pointerleave", clear);
|
|
60
|
+
surface.addEventListener("pointerdown", focus, true);
|
|
61
|
+
return {
|
|
62
|
+
update() {
|
|
63
|
+
if (disposed || !adapter.onHover || !point || toolbar.store.get().mode !== "placing") return;
|
|
64
|
+
try {
|
|
65
|
+
const anchor = adapter.pick(point.x, point.y);
|
|
66
|
+
hovering = anchor !== null;
|
|
67
|
+
adapter.onHover(anchor);
|
|
68
|
+
} catch (cause) {
|
|
69
|
+
clear();
|
|
70
|
+
error(cause);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async openPin(id) {
|
|
74
|
+
if (disposed) return;
|
|
75
|
+
const pin = toolbar.store.get().pins.find((p) => p.id === id);
|
|
76
|
+
if (!pin) throw new Error("Pin not found.");
|
|
77
|
+
if (active === id) activation = activate(pin);
|
|
78
|
+
toolbar.store.update({
|
|
79
|
+
mode: "idle",
|
|
80
|
+
activePinId: id,
|
|
81
|
+
pinsHidden: false
|
|
82
|
+
});
|
|
83
|
+
await activation;
|
|
84
|
+
},
|
|
85
|
+
destroy() {
|
|
86
|
+
if (disposed) return;
|
|
87
|
+
disposed = true;
|
|
88
|
+
controller?.abort();
|
|
89
|
+
unsubscribe();
|
|
90
|
+
clear();
|
|
91
|
+
capture?.destroy();
|
|
92
|
+
win.removeEventListener("pointermove", move, true);
|
|
93
|
+
win.removeEventListener("blur", clear);
|
|
94
|
+
surface.removeEventListener("pointerleave", clear);
|
|
95
|
+
surface.removeEventListener("pointerdown", focus, true);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/model-ui.ts
|
|
101
|
+
/** Connect the real Pinbox toolbar, draft card and needle markers to a 3D viewer.
|
|
102
|
+
* Call update() after rendering a frame. Destroy before removing the viewer. */
|
|
103
|
+
function attachModelViewer(toolbar, adapter) {
|
|
104
|
+
const doc = adapter.surface.ownerDocument, win = doc.defaultView;
|
|
105
|
+
if (!win) throw new Error("The model surface must belong to a browser document");
|
|
106
|
+
const unregister = registerModelProjection(doc, adapter.project);
|
|
107
|
+
const hooks = attachModelHooks(toolbar, adapter);
|
|
108
|
+
let down;
|
|
109
|
+
const pointerDown = (e) => {
|
|
110
|
+
if (e.composedPath().includes(adapter.surface)) down = {
|
|
111
|
+
x: e.clientX,
|
|
112
|
+
y: e.clientY
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const click = (e) => {
|
|
116
|
+
if (toolbar.store.get().mode !== "placing" || e.composedPath().includes(toolbar) || !e.composedPath().includes(adapter.surface)) return;
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
e.stopImmediatePropagation();
|
|
119
|
+
if (down && Math.hypot(e.clientX - down.x, e.clientY - down.y) > 5) {
|
|
120
|
+
down = void 0;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
down = void 0;
|
|
124
|
+
const model = adapter.pick(e.clientX, e.clientY);
|
|
125
|
+
if (!model) return;
|
|
126
|
+
const at = {
|
|
127
|
+
x: e.clientX + win.scrollX,
|
|
128
|
+
y: e.clientY + win.scrollY
|
|
129
|
+
};
|
|
130
|
+
const captured = captureTarget(adapter.surface, { at });
|
|
131
|
+
captured.target.model = model;
|
|
132
|
+
captured.target.anchor = `3D · ${model.partId}`;
|
|
133
|
+
delete captured.target.spot;
|
|
134
|
+
toolbar.store.place({
|
|
135
|
+
target: captured,
|
|
136
|
+
placedAt: at
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
win.addEventListener("pointerdown", pointerDown, true);
|
|
140
|
+
win.addEventListener("click", click, true);
|
|
141
|
+
let destroyed = false;
|
|
142
|
+
return {
|
|
143
|
+
openPin: hooks.openPin,
|
|
144
|
+
update() {
|
|
145
|
+
if (destroyed || !toolbar.isConnected) return;
|
|
146
|
+
hooks.update();
|
|
147
|
+
const s = toolbar.store.get();
|
|
148
|
+
if (s.pins.some((p) => p.target?.model) || s.draft?.target.target.model) toolbar.store.update({ clock: s.clock });
|
|
149
|
+
},
|
|
150
|
+
destroy() {
|
|
151
|
+
if (destroyed) return;
|
|
152
|
+
destroyed = true;
|
|
153
|
+
hooks.destroy();
|
|
154
|
+
unregister();
|
|
155
|
+
win.removeEventListener("pointerdown", pointerDown, true);
|
|
156
|
+
win.removeEventListener("click", click, true);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/model.ts
|
|
162
|
+
function resolveModelAnchor(anchor, scene) {
|
|
163
|
+
if (anchor.modelId !== scene.modelId || anchor.revision !== scene.revision) return { status: "stale" };
|
|
164
|
+
const values = scene.partMatrix(anchor.partId);
|
|
165
|
+
if (values?.length !== 16 || !values.every(Number.isFinite)) return { status: "missing" };
|
|
166
|
+
const m = values;
|
|
167
|
+
const [x, y, z] = anchor.position;
|
|
168
|
+
const w = m[3] * x + m[7] * y + m[11] * z + m[15];
|
|
169
|
+
if (!Number.isFinite(w) || Math.abs(w) < 1e-12) return { status: "missing" };
|
|
170
|
+
const worldPosition = [
|
|
171
|
+
(m[0] * x + m[4] * y + m[8] * z + m[12]) / w,
|
|
172
|
+
(m[1] * x + m[5] * y + m[9] * z + m[13]) / w,
|
|
173
|
+
(m[2] * x + m[6] * y + m[10] * z + m[14]) / w
|
|
174
|
+
];
|
|
175
|
+
if (!worldPosition.every(Number.isFinite)) return { status: "missing" };
|
|
176
|
+
return {
|
|
177
|
+
status: "visible",
|
|
178
|
+
worldPosition
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
//#endregion
|
|
182
|
+
export { attachModelViewer, resolveModelAnchor };
|
package/dist/react.d.ts
CHANGED
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-
|
|
1
|
+
import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-DnAkx29T.js";
|
|
2
2
|
import { createElement, useEffect, useRef } from "react";
|
|
3
3
|
//#region src/react.ts
|
|
4
4
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as targetLabel, c as captureKey, i as hitTest, l as loadCaptureMode, r as captureTarget, s as toggleToolbarCapture, t as projectModelTarget } from "./model-target-BykKL8H5.js";
|
|
1
2
|
//#region src/anchor-watch.ts
|
|
2
3
|
function watchAnchors(win, onChange) {
|
|
3
4
|
let frame = 0;
|
|
@@ -22,23 +23,6 @@ function watchAnchors(win, onChange) {
|
|
|
22
23
|
} };
|
|
23
24
|
}
|
|
24
25
|
//#endregion
|
|
25
|
-
//#region src/capture-mode.ts
|
|
26
|
-
function captureKey(prefix) {
|
|
27
|
-
return `${prefix}:capture`;
|
|
28
|
-
}
|
|
29
|
-
function loadCaptureMode(storage, key, fallback) {
|
|
30
|
-
try {
|
|
31
|
-
const raw = storage?.getItem(key);
|
|
32
|
-
if (raw === "dom" || raw === "tab") return raw;
|
|
33
|
-
} catch {}
|
|
34
|
-
return fallback;
|
|
35
|
-
}
|
|
36
|
-
function saveCaptureMode(storage, key, mode) {
|
|
37
|
-
try {
|
|
38
|
-
storage?.setItem(key, mode);
|
|
39
|
-
} catch {}
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
26
|
//#region src/ui/actions.ts
|
|
43
27
|
const PIN_GLYPH = "<rect x=\"3\" y=\"1.5\" width=\"10\" height=\"6.5\" rx=\"1\"/><path d=\"M8 8v6.5\"/>";
|
|
44
28
|
const INBOX_GLYPH = "<path d=\"M1.8 8.5h3.4l1 2h3.6l1-2h3.4\"/><path d=\"M2.6 3.2h10.8l1.2 5.3v4a1 1 0 01-1 1H2.4a1 1 0 01-1-1v-4z\"/>";
|
|
@@ -208,10 +192,11 @@ function threadTail(thread) {
|
|
|
208
192
|
];
|
|
209
193
|
}
|
|
210
194
|
function block(pin, thread) {
|
|
211
|
-
const { selector, url, source } = pin.target ?? {};
|
|
195
|
+
const { selector, url, source, model } = pin.target ?? {};
|
|
212
196
|
return [
|
|
213
197
|
`## Pin ${pin.n === void 0 ? pin.id : `#${pin.n} (${pin.id})`} — ${pin.status.toUpperCase()}`,
|
|
214
198
|
`- label: ${line(label(pin))}`,
|
|
199
|
+
...model ? [`- model: ${line(model.modelId)} / ${line(model.partId)} @ ${line(model.revision)}`, `- position: ${model.position.join(", ")} ${model.units}`] : [],
|
|
215
200
|
...selector === void 0 ? [] : [`- selector: \`${line(selector)}\``],
|
|
216
201
|
...(pin.target?.targets ?? []).map((t) => t.selector ?? t.anchor ?? t.tag).filter((locus) => locus !== void 0).map((locus) => `- also: \`${line(locus)}\``),
|
|
217
202
|
...source === void 0 ? [] : [`- source: ${line(source.line === void 0 ? source.file : `${source.file}:${source.line}`)}`],
|
|
@@ -808,256 +793,6 @@ function createMinimize(host) {
|
|
|
808
793
|
};
|
|
809
794
|
}
|
|
810
795
|
//#endregion
|
|
811
|
-
//#region src/targeting/dom.ts
|
|
812
|
-
/**
|
|
813
|
-
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
814
|
-
* nothing there but page chrome (html/body).
|
|
815
|
-
*
|
|
816
|
-
* Looks THROUGH our own overlay rather than giving up at it. The single-element form could not:
|
|
817
|
-
* the drag-aim grip sits exactly on the point being aimed at, so it is always the topmost thing
|
|
818
|
-
* under the crosshair, and every probe came back "nothing" the moment touch aiming existed.
|
|
819
|
-
*/
|
|
820
|
-
function hitTest(doc, x, y, ignore) {
|
|
821
|
-
const stack = doc.elementsFromPoint?.(x, y) ?? [doc.elementFromPoint(x, y)];
|
|
822
|
-
for (const el of stack) {
|
|
823
|
-
if (!el || el === doc.body || el === doc.documentElement) return null;
|
|
824
|
-
if (!ignore(el)) return el;
|
|
825
|
-
}
|
|
826
|
-
return null;
|
|
827
|
-
}
|
|
828
|
-
/** CLASS-or-TAG display name with a sibling index when needed (prototype nodeName). */
|
|
829
|
-
function nodeName(el) {
|
|
830
|
-
const key = el.classList[0];
|
|
831
|
-
let name = (key ?? el.tagName).toUpperCase();
|
|
832
|
-
const parent = el.parentElement;
|
|
833
|
-
if (parent) {
|
|
834
|
-
const sibs = [...parent.children].filter((c) => c.classList[0] === key && c.tagName === el.tagName);
|
|
835
|
-
if (sibs.length > 1) name += ` ${sibs.indexOf(el) + 1}`;
|
|
836
|
-
}
|
|
837
|
-
return name;
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Human label for a target: an explicit data-pb-el annotation wins; otherwise a
|
|
841
|
-
* CLASS/TAG ancestry chain of at most 3 parts joined with ›, terminating early
|
|
842
|
-
* at the first annotated ancestor.
|
|
843
|
-
*/
|
|
844
|
-
function targetLabel(el) {
|
|
845
|
-
const own = el.getAttribute("data-pb-el");
|
|
846
|
-
if (own) return own;
|
|
847
|
-
const parts = [nodeName(el)];
|
|
848
|
-
const body = el.ownerDocument.body;
|
|
849
|
-
let node = el.parentElement;
|
|
850
|
-
while (node && node !== body && parts.length < 3) {
|
|
851
|
-
const anchor = node.getAttribute("data-pb-el");
|
|
852
|
-
if (anchor) {
|
|
853
|
-
parts.unshift(anchor);
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
if (node.classList[0]) parts.unshift(nodeName(node));
|
|
857
|
-
node = node.parentElement;
|
|
858
|
-
}
|
|
859
|
-
return parts.join(" › ");
|
|
860
|
-
}
|
|
861
|
-
const SAFE_ID = /^[A-Za-z][\w-]*$/;
|
|
862
|
-
/** Data attributes trusted as stable hooks, in priority order. */
|
|
863
|
-
const STABLE_DATA_ATTRS = [
|
|
864
|
-
"data-pb-anchor",
|
|
865
|
-
"data-pb-el",
|
|
866
|
-
"data-testid"
|
|
867
|
-
];
|
|
868
|
-
function attrSegment(el, doc) {
|
|
869
|
-
for (const attr of STABLE_DATA_ATTRS) {
|
|
870
|
-
const value = el.getAttribute(attr);
|
|
871
|
-
if (value === null || value.includes("\"") || value.includes("\\")) continue;
|
|
872
|
-
const selector = `${el.tagName.toLowerCase()}[${attr}="${value}"]`;
|
|
873
|
-
if (doc.querySelectorAll(selector).length === 1) return selector;
|
|
874
|
-
}
|
|
875
|
-
return null;
|
|
876
|
-
}
|
|
877
|
-
function nthSegment(el) {
|
|
878
|
-
const tag = el.tagName.toLowerCase();
|
|
879
|
-
const parent = el.parentElement;
|
|
880
|
-
if (!parent) return tag;
|
|
881
|
-
const sameTag = [...parent.children].filter((c) => c.tagName === el.tagName);
|
|
882
|
-
return sameTag.length > 1 ? `${tag}:nth-of-type(${sameTag.indexOf(el) + 1})` : tag;
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Stable CSS path for an element: ids > stable data attributes > an
|
|
886
|
-
* nth-of-type chain. Guaranteed round-trip: querySelector(buildSelector(el)) === el.
|
|
887
|
-
*/
|
|
888
|
-
function buildSelector(el) {
|
|
889
|
-
const doc = el.ownerDocument;
|
|
890
|
-
const segments = [];
|
|
891
|
-
let node = el;
|
|
892
|
-
while (node && node !== doc.documentElement) {
|
|
893
|
-
const id = node.getAttribute("id");
|
|
894
|
-
if (id && SAFE_ID.test(id) && doc.querySelectorAll(`#${id}`).length === 1) {
|
|
895
|
-
segments.unshift(`#${id}`);
|
|
896
|
-
return segments.join(" > ");
|
|
897
|
-
}
|
|
898
|
-
const byAttr = attrSegment(node, doc);
|
|
899
|
-
if (byAttr) {
|
|
900
|
-
segments.unshift(byAttr);
|
|
901
|
-
return segments.join(" > ");
|
|
902
|
-
}
|
|
903
|
-
segments.unshift(nthSegment(node));
|
|
904
|
-
node = node.parentElement;
|
|
905
|
-
}
|
|
906
|
-
return segments.join(" > ");
|
|
907
|
-
}
|
|
908
|
-
//#endregion
|
|
909
|
-
//#region src/capture.ts
|
|
910
|
-
/** Curated computed-style subset — enough to reconstruct layout intent, tiny on the wire. */
|
|
911
|
-
const STYLE_KEYS = [
|
|
912
|
-
"display",
|
|
913
|
-
"position",
|
|
914
|
-
"font-size",
|
|
915
|
-
"color",
|
|
916
|
-
"background-color",
|
|
917
|
-
"margin",
|
|
918
|
-
"padding",
|
|
919
|
-
"overflow"
|
|
920
|
-
];
|
|
921
|
-
const NEARBY_TEXT_MAX = 160;
|
|
922
|
-
function styleSubset(win, el) {
|
|
923
|
-
let cs;
|
|
924
|
-
try {
|
|
925
|
-
cs = win.getComputedStyle(el);
|
|
926
|
-
} catch {
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
const out = {};
|
|
930
|
-
for (const key of STYLE_KEYS) {
|
|
931
|
-
const value = cs.getPropertyValue(key);
|
|
932
|
-
if (value !== "") out[key] = value;
|
|
933
|
-
}
|
|
934
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
935
|
-
}
|
|
936
|
-
function ariaMap(el) {
|
|
937
|
-
const out = {};
|
|
938
|
-
for (const name of el.getAttributeNames()) if (name.startsWith("aria-")) out[name] = el.getAttribute(name) ?? "";
|
|
939
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
940
|
-
}
|
|
941
|
-
function nearbyText(el) {
|
|
942
|
-
const text = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
943
|
-
return text === "" ? void 0 : text.slice(0, NEARBY_TEXT_MAX);
|
|
944
|
-
}
|
|
945
|
-
/** The user's selection, only when it intersects the captured element. */
|
|
946
|
-
function selectedText(win, el) {
|
|
947
|
-
try {
|
|
948
|
-
const sel = win.getSelection?.();
|
|
949
|
-
if (!sel || sel.isCollapsed || sel.rangeCount === 0) return void 0;
|
|
950
|
-
if (!sel.getRangeAt(0).intersectsNode(el)) return void 0;
|
|
951
|
-
const text = sel.toString().trim();
|
|
952
|
-
return text === "" ? void 0 : text;
|
|
953
|
-
} catch {
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
/** `fixed` detected via ancestry: any ancestor with computed position: fixed. */
|
|
958
|
-
function isFixed(win, el) {
|
|
959
|
-
for (let node = el; node !== null; node = node.parentElement) try {
|
|
960
|
-
if (win.getComputedStyle(node).position === "fixed") return true;
|
|
961
|
-
} catch {
|
|
962
|
-
return false;
|
|
963
|
-
}
|
|
964
|
-
return false;
|
|
965
|
-
}
|
|
966
|
-
/** Beyond this an element is not a thing you pinned, it is a region. Too many to rewrite as a set. */
|
|
967
|
-
const MAX_RUNS = 40;
|
|
968
|
-
const MAX_RUN_LENGTH = 200;
|
|
969
|
-
/** Text that is not content: a script body or a stylesheet is not something to rewrite. */
|
|
970
|
-
const NON_CONTENT = /* @__PURE__ */ new Set([
|
|
971
|
-
"SCRIPT",
|
|
972
|
-
"STYLE",
|
|
973
|
-
"NOSCRIPT",
|
|
974
|
-
"TEMPLATE"
|
|
975
|
-
]);
|
|
976
|
-
/**
|
|
977
|
-
* The element's text, split the way the browser stores it: one entry per run of characters.
|
|
978
|
-
*
|
|
979
|
-
* This walks TEXT NODES, not elements, and that distinction is the whole point — it makes no
|
|
980
|
-
* assumption about how a site is built. A heading is one run. A nav bar is one per link. A
|
|
981
|
-
* paragraph with a bold word in the middle is three, in reading order, including the halves either
|
|
982
|
-
* side of the bold. An earlier version keyed off "elements with no element children", which
|
|
983
|
-
* quietly lost the "Hello " in `<p>Hello <b>world</b></p>` — text a person can obviously see and
|
|
984
|
-
* would obviously expect to be able to change.
|
|
985
|
-
*
|
|
986
|
-
* `nearbyText` runs them all together, which is fine to read and useless to edit: it cannot tell
|
|
987
|
-
* an agent that "work approach people contact" is four separate places. This can.
|
|
988
|
-
*/
|
|
989
|
-
function textRuns(el) {
|
|
990
|
-
const runs = [];
|
|
991
|
-
const walk = (node) => {
|
|
992
|
-
if (node.nodeType === 3) {
|
|
993
|
-
const text = (node.nodeValue ?? "").trim();
|
|
994
|
-
if (text.length > 0) runs.push(text.slice(0, MAX_RUN_LENGTH));
|
|
995
|
-
return runs.length <= MAX_RUNS;
|
|
996
|
-
}
|
|
997
|
-
if (node.nodeType !== 1 || NON_CONTENT.has(node.tagName)) return true;
|
|
998
|
-
for (const child of node.childNodes) if (!walk(child)) return false;
|
|
999
|
-
return true;
|
|
1000
|
-
};
|
|
1001
|
-
if (!walk(el) || runs.length === 0) return void 0;
|
|
1002
|
-
return runs;
|
|
1003
|
-
}
|
|
1004
|
-
function buildContext(win, el) {
|
|
1005
|
-
const context = {};
|
|
1006
|
-
if (el.classList.length > 0) context.classes = [...el.classList];
|
|
1007
|
-
const styles = styleSubset(win, el);
|
|
1008
|
-
if (styles !== void 0) context.styles = styles;
|
|
1009
|
-
const aria = ariaMap(el);
|
|
1010
|
-
if (aria !== void 0) context.aria = aria;
|
|
1011
|
-
const nearby = nearbyText(el);
|
|
1012
|
-
if (nearby !== void 0) context.nearbyText = nearby;
|
|
1013
|
-
const selected = selectedText(win, el);
|
|
1014
|
-
if (selected !== void 0) context.selectedText = selected;
|
|
1015
|
-
const runs = textRuns(el);
|
|
1016
|
-
if (runs !== void 0) context.textRuns = runs;
|
|
1017
|
-
return Object.keys(context).length > 0 ? context : void 0;
|
|
1018
|
-
}
|
|
1019
|
-
/** Fills PinInput.target/env from a chosen element (shapes come from the pin schema). */
|
|
1020
|
-
function captureTarget(el, opts) {
|
|
1021
|
-
const win = el.ownerDocument.defaultView;
|
|
1022
|
-
const r = el.getBoundingClientRect();
|
|
1023
|
-
const target = {
|
|
1024
|
-
url: win.location.href,
|
|
1025
|
-
selector: buildSelector(el),
|
|
1026
|
-
tag: el.tagName.toLowerCase(),
|
|
1027
|
-
rect: {
|
|
1028
|
-
x: r.left + win.scrollX,
|
|
1029
|
-
y: r.top + win.scrollY,
|
|
1030
|
-
width: r.width,
|
|
1031
|
-
height: r.height
|
|
1032
|
-
},
|
|
1033
|
-
fixed: isFixed(win, el)
|
|
1034
|
-
};
|
|
1035
|
-
if (opts?.anchor !== void 0) target.anchor = opts.anchor;
|
|
1036
|
-
if (opts?.at !== void 0 && r.width > 0 && r.height > 0) {
|
|
1037
|
-
const fx = (opts.at.x - (r.left + win.scrollX)) / r.width;
|
|
1038
|
-
const fy = (opts.at.y - (r.top + win.scrollY)) / r.height;
|
|
1039
|
-
if (fx >= 0 && fx <= 1 && fy >= 0 && fy <= 1) target.spot = {
|
|
1040
|
-
x: fx,
|
|
1041
|
-
y: fy
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
const context = buildContext(win, el);
|
|
1045
|
-
if (context !== void 0) target.context = context;
|
|
1046
|
-
return {
|
|
1047
|
-
target,
|
|
1048
|
-
env: {
|
|
1049
|
-
viewport: {
|
|
1050
|
-
w: win.innerWidth,
|
|
1051
|
-
h: win.innerHeight,
|
|
1052
|
-
dpr: win.devicePixelRatio
|
|
1053
|
-
},
|
|
1054
|
-
browser: win.navigator.userAgent,
|
|
1055
|
-
os: win.navigator.platform || "unknown",
|
|
1056
|
-
colorScheme: win.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
1057
|
-
}
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
//#endregion
|
|
1061
796
|
//#region src/ui/aim.ts
|
|
1062
797
|
/**
|
|
1063
798
|
* True when aiming has to be done by dragging rather than by pointing.
|
|
@@ -1311,7 +1046,8 @@ function createPlacement(deps) {
|
|
|
1311
1046
|
function placeDraft(e) {
|
|
1312
1047
|
e.preventDefault();
|
|
1313
1048
|
e.stopPropagation();
|
|
1314
|
-
const
|
|
1049
|
+
const el = hover ?? doc.body;
|
|
1050
|
+
const capture = captureTarget(el, { at: {
|
|
1315
1051
|
x: e.pageX,
|
|
1316
1052
|
y: e.pageY
|
|
1317
1053
|
} });
|
|
@@ -2368,8 +2104,8 @@ function createBar(doc, on) {
|
|
|
2368
2104
|
inboxBtn.classList.toggle("lit", state.inboxOpen);
|
|
2369
2105
|
const open = String(openTaskCount(state.pins));
|
|
2370
2106
|
if (count.textContent !== open) count.textContent = open;
|
|
2371
|
-
captureBtn.classList.toggle("lit", state.captureMode === "tab");
|
|
2372
|
-
captureBtn.title = state.captureMode === "tab" ? "Tab capture on — real pixels, Chrome asks once per page load (S)" : "Screenshots: DOM snapshot, no prompt — press for tab capture (S)";
|
|
2107
|
+
captureBtn.classList.toggle("lit", !state.captureLabel && state.captureMode === "tab");
|
|
2108
|
+
captureBtn.title = state.captureLabel ?? (state.captureMode === "tab" ? "Tab capture on — real pixels, Chrome asks once per page load (S)" : "Screenshots: DOM snapshot, no prompt — press for tab capture (S)");
|
|
2373
2109
|
if (hideShown !== state.pinsHidden) {
|
|
2374
2110
|
hideShown = state.pinsHidden;
|
|
2375
2111
|
hideBtn.innerHTML = icon(state.pinsHidden ? EYE_GLYPH : EYE_OFF_GLYPH, 14);
|
|
@@ -2687,6 +2423,7 @@ function anchorRect(doc, pin) {
|
|
|
2687
2423
|
}
|
|
2688
2424
|
/** The same resolution for any captured target — a pin's, or the draft's before it commits. */
|
|
2689
2425
|
function targetRect(doc, target) {
|
|
2426
|
+
if (target?.model) return projectModelTarget(doc, target.model);
|
|
2690
2427
|
const stored = target?.rect;
|
|
2691
2428
|
if (stored === void 0) return null;
|
|
2692
2429
|
const win = doc.defaultView;
|
|
@@ -3016,6 +2753,7 @@ function anchorOf(root, pin, draft) {
|
|
|
3016
2753
|
* labels the card without claiming an element that was never captured.
|
|
3017
2754
|
*/
|
|
3018
2755
|
function labelOf(target) {
|
|
2756
|
+
if (target?.model) return `3D · ${target.model.partId}`;
|
|
3019
2757
|
return target?.anchor ?? target?.tag?.toUpperCase() ?? "PIN";
|
|
3020
2758
|
}
|
|
3021
2759
|
function viewOf(root, state) {
|
|
@@ -3753,11 +3491,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
3753
3491
|
return loadCaptureMode(globalThis.localStorage, key, this.config?.capture ?? "dom");
|
|
3754
3492
|
}
|
|
3755
3493
|
#toggleCapture() {
|
|
3756
|
-
|
|
3757
|
-
this.store.update({ captureMode: next });
|
|
3758
|
-
if (next === "dom") releaseCapture();
|
|
3759
|
-
saveCaptureMode(globalThis.localStorage, captureKey(`pinbox:${this.config?.endpoint ?? ""}`), next);
|
|
3760
|
-
return true;
|
|
3494
|
+
return toggleToolbarCapture(this, this.config?.endpoint ?? "", releaseCapture);
|
|
3761
3495
|
}
|
|
3762
3496
|
/** Theme from the OS when the host set none; the page-level CSS (placing cursor) into <head>. */
|
|
3763
3497
|
#applyPageDefaults() {
|
package/dist/svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-
|
|
1
|
+
import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-xNXeJ2mR.js";
|
|
2
2
|
import { Action } from "svelte/action";
|
|
3
3
|
//#region src/svelte.d.ts
|
|
4
4
|
/**
|
package/dist/svelte.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-
|
|
1
|
+
import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-DnAkx29T.js";
|
|
2
2
|
//#region src/svelte.ts
|
|
3
3
|
/**
|
|
4
4
|
* `<div use:pinbox={{ endpoint }} />` — creates + configures the element BEFORE insertion
|
package/dist/toolbar.iife.js
CHANGED
|
@@ -210,10 +210,11 @@ var Pinbox = (function(exports) {
|
|
|
210
210
|
];
|
|
211
211
|
}
|
|
212
212
|
function block(pin, thread) {
|
|
213
|
-
const { selector, url, source } = pin.target ?? {};
|
|
213
|
+
const { selector, url, source, model } = pin.target ?? {};
|
|
214
214
|
return [
|
|
215
215
|
`## Pin ${pin.n === void 0 ? pin.id : `#${pin.n} (${pin.id})`} — ${pin.status.toUpperCase()}`,
|
|
216
216
|
`- label: ${line(label(pin))}`,
|
|
217
|
+
...model ? [`- model: ${line(model.modelId)} / ${line(model.partId)} @ ${line(model.revision)}`, `- position: ${model.position.join(", ")} ${model.units}`] : [],
|
|
217
218
|
...selector === void 0 ? [] : [`- selector: \`${line(selector)}\``],
|
|
218
219
|
...(pin.target?.targets ?? []).map((t) => t.selector ?? t.anchor ?? t.tag).filter((locus) => locus !== void 0).map((locus) => `- also: \`${line(locus)}\``),
|
|
219
220
|
...source === void 0 ? [] : [`- source: ${line(source.line === void 0 ? source.file : `${source.file}:${source.line}`)}`],
|
|
@@ -810,6 +811,34 @@ var Pinbox = (function(exports) {
|
|
|
810
811
|
};
|
|
811
812
|
}
|
|
812
813
|
//#endregion
|
|
814
|
+
//#region src/model-capture.ts
|
|
815
|
+
const handlers = /* @__PURE__ */ new WeakMap();
|
|
816
|
+
function runModelCapture(owner) {
|
|
817
|
+
const entries = handlers.get(owner);
|
|
818
|
+
const entry = entries?.[0];
|
|
819
|
+
if (!entries || !entry) return false;
|
|
820
|
+
if (entries.some((candidate) => candidate.busy)) return true;
|
|
821
|
+
entry.busy = true;
|
|
822
|
+
try {
|
|
823
|
+
Promise.resolve(entry.run()).catch(entry.error).finally(() => {
|
|
824
|
+
entry.busy = false;
|
|
825
|
+
});
|
|
826
|
+
} catch (error) {
|
|
827
|
+
entry.busy = false;
|
|
828
|
+
entry.error(error);
|
|
829
|
+
}
|
|
830
|
+
return true;
|
|
831
|
+
}
|
|
832
|
+
/** Shared by the camera button, puck and S shortcut; preserve the 2D fallback. */
|
|
833
|
+
function toggleToolbarCapture(owner, endpoint, release) {
|
|
834
|
+
if (runModelCapture(owner)) return true;
|
|
835
|
+
const next = owner.store.get().captureMode === "tab" ? "dom" : "tab";
|
|
836
|
+
owner.store.update({ captureMode: next });
|
|
837
|
+
if (next === "dom") release();
|
|
838
|
+
saveCaptureMode(globalThis.localStorage, captureKey(`pinbox:${endpoint}`), next);
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
//#endregion
|
|
813
842
|
//#region src/targeting/dom.ts
|
|
814
843
|
/**
|
|
815
844
|
* Deepest element under (clientX, clientY) that the caller does not ignore, or null when there is
|
|
@@ -2370,8 +2399,8 @@ var Pinbox = (function(exports) {
|
|
|
2370
2399
|
inboxBtn.classList.toggle("lit", state.inboxOpen);
|
|
2371
2400
|
const open = String(openTaskCount(state.pins));
|
|
2372
2401
|
if (count.textContent !== open) count.textContent = open;
|
|
2373
|
-
captureBtn.classList.toggle("lit", state.captureMode === "tab");
|
|
2374
|
-
captureBtn.title = state.captureMode === "tab" ? "Tab capture on — real pixels, Chrome asks once per page load (S)" : "Screenshots: DOM snapshot, no prompt — press for tab capture (S)";
|
|
2402
|
+
captureBtn.classList.toggle("lit", !state.captureLabel && state.captureMode === "tab");
|
|
2403
|
+
captureBtn.title = state.captureLabel ?? (state.captureMode === "tab" ? "Tab capture on — real pixels, Chrome asks once per page load (S)" : "Screenshots: DOM snapshot, no prompt — press for tab capture (S)");
|
|
2375
2404
|
if (hideShown !== state.pinsHidden) {
|
|
2376
2405
|
hideShown = state.pinsHidden;
|
|
2377
2406
|
hideBtn.innerHTML = icon(state.pinsHidden ? EYE_GLYPH : EYE_OFF_GLYPH, 14);
|
|
@@ -2644,6 +2673,21 @@ var Pinbox = (function(exports) {
|
|
|
2644
2673
|
return `${isDraft ? `<div class="pb-seg" role="radiogroup" aria-label="Pin kind"><button type="button" role="radio" aria-checked="${kind === "note"}" class="${kind === "note" ? "on" : ""}" data-kind="note" title="An agent picks this up">Ask agent</button><button type="button" role="radio" aria-checked="${kind === "comment"}" class="${kind === "comment" ? "on" : ""}" data-kind="comment" title="A remark for people; no agent acts on it">Note</button></div>` : ""}<div class="pb-kbd">⌘ ↵</div><button type="button" class="pb-bt-solid" data-action="send">${hasThread ? "Reply" : kind === "comment" ? "Leave note" : "Comment"}</button>`;
|
|
2645
2674
|
}
|
|
2646
2675
|
//#endregion
|
|
2676
|
+
//#region src/model-target.ts
|
|
2677
|
+
const projectors = /* @__PURE__ */ new WeakMap();
|
|
2678
|
+
function projectModelTarget(doc, anchor) {
|
|
2679
|
+
for (const project of projectors.get(doc) ?? []) {
|
|
2680
|
+
const p = project(anchor);
|
|
2681
|
+
if (p && Number.isFinite(p.x) && Number.isFinite(p.y)) return {
|
|
2682
|
+
x: p.x,
|
|
2683
|
+
y: p.y,
|
|
2684
|
+
width: 0,
|
|
2685
|
+
height: 0
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2688
|
+
return null;
|
|
2689
|
+
}
|
|
2690
|
+
//#endregion
|
|
2647
2691
|
//#region src/ui/pins.ts
|
|
2648
2692
|
/** The prototype's `_h` innerHTML memo, kept off the DOM node. */
|
|
2649
2693
|
const chipMemo = /* @__PURE__ */ new WeakMap();
|
|
@@ -2689,6 +2733,7 @@ var Pinbox = (function(exports) {
|
|
|
2689
2733
|
}
|
|
2690
2734
|
/** The same resolution for any captured target — a pin's, or the draft's before it commits. */
|
|
2691
2735
|
function targetRect(doc, target) {
|
|
2736
|
+
if (target?.model) return projectModelTarget(doc, target.model);
|
|
2692
2737
|
const stored = target?.rect;
|
|
2693
2738
|
if (stored === void 0) return null;
|
|
2694
2739
|
const win = doc.defaultView;
|
|
@@ -3018,6 +3063,7 @@ var Pinbox = (function(exports) {
|
|
|
3018
3063
|
* labels the card without claiming an element that was never captured.
|
|
3019
3064
|
*/
|
|
3020
3065
|
function labelOf(target) {
|
|
3066
|
+
if (target?.model) return `3D · ${target.model.partId}`;
|
|
3021
3067
|
return target?.anchor ?? target?.tag?.toUpperCase() ?? "PIN";
|
|
3022
3068
|
}
|
|
3023
3069
|
function viewOf(root, state) {
|
|
@@ -3755,11 +3801,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
3755
3801
|
return loadCaptureMode(globalThis.localStorage, key, this.config?.capture ?? "dom");
|
|
3756
3802
|
}
|
|
3757
3803
|
#toggleCapture() {
|
|
3758
|
-
|
|
3759
|
-
this.store.update({ captureMode: next });
|
|
3760
|
-
if (next === "dom") releaseCapture();
|
|
3761
|
-
saveCaptureMode(globalThis.localStorage, captureKey(`pinbox:${this.config?.endpoint ?? ""}`), next);
|
|
3762
|
-
return true;
|
|
3804
|
+
return toggleToolbarCapture(this, this.config?.endpoint ?? "", releaseCapture);
|
|
3763
3805
|
}
|
|
3764
3806
|
/** Theme from the OS when the host set none; the page-level CSS (placing cursor) into <head>. */
|
|
3765
3807
|
#applyPageDefaults() {
|
package/dist/vue.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as PinboxToolbarElement, n as PinboxConfig } from "./index-
|
|
1
|
+
import { d as PinboxToolbarElement, n as PinboxConfig } from "./index-xNXeJ2mR.js";
|
|
2
2
|
import { VNode } from "vue";
|
|
3
3
|
//#region src/vue.d.ts
|
|
4
4
|
/** The slice of the Vue component instance this wrapper touches. */
|
package/dist/vue.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autono/pinbox-toolbar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -42,7 +42,11 @@
|
|
|
42
42
|
"types": "./dist/plugins/next.d.ts",
|
|
43
43
|
"default": "./dist/plugins/next.js"
|
|
44
44
|
},
|
|
45
|
-
"./package.json": "./package.json"
|
|
45
|
+
"./package.json": "./package.json",
|
|
46
|
+
"./model": {
|
|
47
|
+
"types": "./dist/model.d.ts",
|
|
48
|
+
"default": "./dist/model.js"
|
|
49
|
+
}
|
|
46
50
|
},
|
|
47
51
|
"scripts": {
|
|
48
52
|
"typecheck": "tsc --noEmit",
|
|
@@ -71,7 +75,7 @@
|
|
|
71
75
|
}
|
|
72
76
|
},
|
|
73
77
|
"devDependencies": {
|
|
74
|
-
"@autono/pinbox-core": "0.
|
|
78
|
+
"@autono/pinbox-core": "0.20.0",
|
|
75
79
|
"@types/react": "^19.2.0",
|
|
76
80
|
"typescript": "^7.0.0",
|
|
77
81
|
"tsdown": "^0.22.0",
|