@proto-labs-inc/rig-core 0.1.1 → 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/dist/frame-tools-loader.d.ts +2 -0
- package/dist/frame-tools-loader.js +47 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -352
- package/package.json +2 -2
- package/dist/element-preview.d.ts +0 -20
- package/dist/element-preview.js +0 -95
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Stable bootstrap only. Selection behavior is deployed with the Frame app. */
|
|
2
|
+
export function loadFrameTools(frameOrigin) {
|
|
3
|
+
if (window.parent === window)
|
|
4
|
+
return () => { };
|
|
5
|
+
let disposed = false;
|
|
6
|
+
let unmount;
|
|
7
|
+
const report = (status, origin = "*") => {
|
|
8
|
+
if (!disposed)
|
|
9
|
+
window.parent.postMessage({ type: "frame-tools-status", status }, origin);
|
|
10
|
+
};
|
|
11
|
+
const dispose = () => { disposed = true; unmount?.(); };
|
|
12
|
+
if (!frameOrigin) {
|
|
13
|
+
report("unavailable");
|
|
14
|
+
return dispose;
|
|
15
|
+
}
|
|
16
|
+
let origin;
|
|
17
|
+
try {
|
|
18
|
+
const url = new URL(frameOrigin);
|
|
19
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
|
|
20
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && local))
|
|
21
|
+
throw new Error("Invalid Frame origin");
|
|
22
|
+
if (url.username || url.password || url.origin !== frameOrigin)
|
|
23
|
+
throw new Error("Expected an origin");
|
|
24
|
+
origin = url.origin;
|
|
25
|
+
// The origin is pinned in the prototype, never taken from a message or URL parameter.
|
|
26
|
+
if (!document.referrer || new URL(document.referrer).origin !== origin)
|
|
27
|
+
return dispose;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
report("unavailable");
|
|
31
|
+
return dispose;
|
|
32
|
+
}
|
|
33
|
+
report("loading", origin);
|
|
34
|
+
const url = new URL("/frame-tools/v1.js", origin).href;
|
|
35
|
+
void import(/* @vite-ignore */ url).then((runtime) => {
|
|
36
|
+
if (disposed)
|
|
37
|
+
return;
|
|
38
|
+
unmount = runtime.mountFrameTools({ frameOrigin: origin });
|
|
39
|
+
report("ready", origin);
|
|
40
|
+
}).catch((error) => {
|
|
41
|
+
if (disposed)
|
|
42
|
+
return;
|
|
43
|
+
console.warn("Proto selection tools failed to load", error);
|
|
44
|
+
report("unavailable", origin);
|
|
45
|
+
});
|
|
46
|
+
return dispose;
|
|
47
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The rig's framework-free core: URL-state sync, the bridge, and
|
|
3
|
-
*
|
|
2
|
+
* The rig's framework-free core: URL-state sync, the bridge, and the Frame tools
|
|
3
|
+
* loader. Everything here is plain DOM + history + postMessage — no framework.
|
|
4
4
|
* Framework adapters (@proto-labs-inc/rig for React, @proto-labs-inc/rig-vue for Vue) wrap
|
|
5
5
|
* this with their own idioms and nothing else.
|
|
6
6
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The rig's framework-free core: URL-state sync, the bridge, and
|
|
3
|
-
*
|
|
2
|
+
* The rig's framework-free core: URL-state sync, the bridge, and the Frame tools
|
|
3
|
+
* loader. Everything here is plain DOM + history + postMessage — no framework.
|
|
4
4
|
* Framework adapters (@proto-labs-inc/rig for React, @proto-labs-inc/rig-vue for Vue) wrap
|
|
5
5
|
* this with their own idioms and nothing else.
|
|
6
6
|
*/
|
|
7
7
|
import { PROTOCOL_VERSION, STATE_PARAM, variantParam, applyVariantSelection, isFrameToRig, } from "@proto-labs-inc/wire";
|
|
8
|
-
import {
|
|
8
|
+
import { loadFrameTools } from "./frame-tools-loader.js";
|
|
9
9
|
export { STATE_PARAM, variantParam } from "@proto-labs-inc/wire";
|
|
10
|
-
const RIG_VERSION = "0.
|
|
10
|
+
const RIG_VERSION = "0.2.0";
|
|
11
11
|
export const INTERNAL_PREVIEW_THEME_PARAM = "__protoTheme";
|
|
12
12
|
export function applyPreviewTheme(theme) {
|
|
13
13
|
if (typeof document === "undefined")
|
|
@@ -131,48 +131,6 @@ export function postToFrame(msg) {
|
|
|
131
131
|
if (typeof window !== "undefined" && window.parent !== window)
|
|
132
132
|
window.parent.postMessage(msg, "*");
|
|
133
133
|
}
|
|
134
|
-
// ─── comment mode ──────────────────────────────────────────────────
|
|
135
|
-
// The Frame can't touch this document (cross-origin iframe), so the blue
|
|
136
|
-
// selector runs here: hit-test [data-proto-id] under the cursor, draw the
|
|
137
|
-
// highlight, and on click capture the view with the highlight baked in.
|
|
138
|
-
// The highlight is Proto blue (#0088FF): 2px solid at 55%, a 6% fill,
|
|
139
|
-
// radius 4px, 0.12s ease-out entry; the picker layer sits at z-index 99997.
|
|
140
|
-
const COMMENT_BLUE = "#0088FF";
|
|
141
|
-
const TARGET_ATTR = "data-proto-id";
|
|
142
|
-
function selectorFor(element) {
|
|
143
|
-
if (element === document.documentElement)
|
|
144
|
-
return "html";
|
|
145
|
-
if (element === document.body)
|
|
146
|
-
return "body";
|
|
147
|
-
const protoId = element.getAttribute(TARGET_ATTR);
|
|
148
|
-
if (protoId)
|
|
149
|
-
return `[${TARGET_ATTR}="${CSS.escape(protoId)}"]`;
|
|
150
|
-
if (element.id) {
|
|
151
|
-
const idSelector = `#${CSS.escape(element.id)}`;
|
|
152
|
-
if (document.querySelectorAll(idSelector).length === 1)
|
|
153
|
-
return idSelector;
|
|
154
|
-
}
|
|
155
|
-
const owner = element.closest(`[${TARGET_ATTR}]`);
|
|
156
|
-
const boundary = owner ?? document.body;
|
|
157
|
-
const segments = [];
|
|
158
|
-
let current = element;
|
|
159
|
-
while (current && current !== boundary) {
|
|
160
|
-
const tag = current.tagName.toLowerCase();
|
|
161
|
-
const currentTagName = current.tagName;
|
|
162
|
-
const siblings = current.parentElement
|
|
163
|
-
? [...current.parentElement.children].filter((sibling) => sibling.tagName === currentTagName)
|
|
164
|
-
: [];
|
|
165
|
-
const index = siblings.indexOf(current);
|
|
166
|
-
segments.unshift(siblings.length > 1 && index >= 0 ? `${tag}:nth-of-type(${index + 1})` : tag);
|
|
167
|
-
current = current.parentElement;
|
|
168
|
-
}
|
|
169
|
-
const base = owner
|
|
170
|
-
? `[${TARGET_ATTR}="${CSS.escape(owner.getAttribute(TARGET_ATTR) ?? "")}"]`
|
|
171
|
-
: boundary === element
|
|
172
|
-
? element.tagName.toLowerCase()
|
|
173
|
-
: "body";
|
|
174
|
-
return segments.length > 0 ? `${base} > ${segments.join(" > ")}` : base;
|
|
175
|
-
}
|
|
176
134
|
async function waitForCaptureReady() {
|
|
177
135
|
const pendingImages = [...document.images]
|
|
178
136
|
.filter((image) => !image.complete)
|
|
@@ -190,47 +148,6 @@ async function waitForCaptureReady() {
|
|
|
190
148
|
// resting state before the gallery captures them.
|
|
191
149
|
await new Promise((resolve) => window.setTimeout(resolve, 1500));
|
|
192
150
|
}
|
|
193
|
-
async function captureViewport(scale = Math.min(window.devicePixelRatio || 1, 2)) {
|
|
194
|
-
try {
|
|
195
|
-
// Optional peer provided by the prototype, not the rig.
|
|
196
|
-
const mod = (await import("modern-screenshot"));
|
|
197
|
-
const root = document.body;
|
|
198
|
-
const viewportWidth = document.documentElement.clientWidth;
|
|
199
|
-
const viewportHeight = document.documentElement.clientHeight;
|
|
200
|
-
const scrollX = window.scrollX || document.documentElement.scrollLeft;
|
|
201
|
-
const scrollY = window.scrollY || document.documentElement.scrollTop;
|
|
202
|
-
const contentWidth = Math.max(viewportWidth, document.documentElement.scrollWidth, root.scrollWidth);
|
|
203
|
-
const contentHeight = Math.max(viewportHeight, document.documentElement.scrollHeight, root.scrollHeight);
|
|
204
|
-
return await mod.domToDataUrl(root, {
|
|
205
|
-
scale,
|
|
206
|
-
width: viewportWidth,
|
|
207
|
-
height: viewportHeight,
|
|
208
|
-
features: { restoreScrollPosition: true },
|
|
209
|
-
style: {
|
|
210
|
-
margin: "0",
|
|
211
|
-
width: `${contentWidth}px`,
|
|
212
|
-
height: `${contentHeight}px`,
|
|
213
|
-
transform: `translate(${-scrollX}px, ${-scrollY}px)`,
|
|
214
|
-
transformOrigin: "top left",
|
|
215
|
-
overflow: "visible",
|
|
216
|
-
},
|
|
217
|
-
onCloneNode: (cloned) => {
|
|
218
|
-
if (!(cloned instanceof HTMLElement) || (!scrollX && !scrollY))
|
|
219
|
-
return;
|
|
220
|
-
const fixed = [cloned, ...cloned.querySelectorAll("*")].filter((element) => element.style.position === "fixed");
|
|
221
|
-
for (const element of fixed) {
|
|
222
|
-
const existing = element.style.transform;
|
|
223
|
-
element.style.transform =
|
|
224
|
-
`translate(${scrollX}px, ${scrollY}px)` +
|
|
225
|
-
(existing && existing !== "none" ? ` ${existing}` : "");
|
|
226
|
-
}
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
catch {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
151
|
async function captureThumbnail() {
|
|
235
152
|
const restingState = document.createElement("style");
|
|
236
153
|
restingState.textContent = `
|
|
@@ -277,229 +194,13 @@ async function captureThumbnail() {
|
|
|
277
194
|
restingState.remove();
|
|
278
195
|
}
|
|
279
196
|
}
|
|
280
|
-
const elementPreview = new ElementPreview(postToFrame);
|
|
281
|
-
class ElementPicker {
|
|
282
|
-
overlay = null;
|
|
283
|
-
highlight = null;
|
|
284
|
-
cursorStyle = null;
|
|
285
|
-
target = null;
|
|
286
|
-
active = false;
|
|
287
|
-
mode = null;
|
|
288
|
-
enable(mode) {
|
|
289
|
-
if (typeof document === "undefined")
|
|
290
|
-
return;
|
|
291
|
-
elementPreview.release();
|
|
292
|
-
if (this.active || this.overlay)
|
|
293
|
-
this.disable();
|
|
294
|
-
this.active = true;
|
|
295
|
-
this.mode = mode;
|
|
296
|
-
this.cursorStyle = document.createElement("style");
|
|
297
|
-
this.cursorStyle.setAttribute("data-proto-picker-cursor", "");
|
|
298
|
-
this.cursorStyle.textContent =
|
|
299
|
-
"html, body, body * { cursor: crosshair !important; }";
|
|
300
|
-
document.head.appendChild(this.cursorStyle);
|
|
301
|
-
const blue = COMMENT_BLUE;
|
|
302
|
-
this.overlay = document.createElement("div");
|
|
303
|
-
this.overlay.setAttribute("data-proto-rig-overlay", "");
|
|
304
|
-
Object.assign(this.overlay.style, {
|
|
305
|
-
position: "fixed",
|
|
306
|
-
inset: "0",
|
|
307
|
-
zIndex: "99997",
|
|
308
|
-
pointerEvents: "none",
|
|
309
|
-
});
|
|
310
|
-
this.highlight = document.createElement("div");
|
|
311
|
-
Object.assign(this.highlight.style, {
|
|
312
|
-
position: "absolute",
|
|
313
|
-
border: `2px solid color-mix(in srgb, ${blue} 55%, transparent)`,
|
|
314
|
-
borderRadius: "4px",
|
|
315
|
-
backgroundColor: `color-mix(in srgb, ${blue} 6%, transparent)`,
|
|
316
|
-
pointerEvents: "none",
|
|
317
|
-
boxSizing: "border-box",
|
|
318
|
-
opacity: "0",
|
|
319
|
-
transition: "opacity 0.12s ease-out",
|
|
320
|
-
willChange: "opacity",
|
|
321
|
-
});
|
|
322
|
-
this.overlay.appendChild(this.highlight);
|
|
323
|
-
document.body.appendChild(this.overlay);
|
|
324
|
-
document.addEventListener("pointermove", this.onMove, true);
|
|
325
|
-
document.addEventListener("click", this.onClick, true);
|
|
326
|
-
document.addEventListener("keydown", this.onKey, true);
|
|
327
|
-
}
|
|
328
|
-
disable() {
|
|
329
|
-
if (!this.active && !this.overlay && !this.cursorStyle)
|
|
330
|
-
return;
|
|
331
|
-
this.active = false;
|
|
332
|
-
document.removeEventListener("pointermove", this.onMove, true);
|
|
333
|
-
document.removeEventListener("click", this.onClick, true);
|
|
334
|
-
document.removeEventListener("keydown", this.onKey, true);
|
|
335
|
-
this.overlay?.remove();
|
|
336
|
-
this.cursorStyle?.remove();
|
|
337
|
-
this.overlay = null;
|
|
338
|
-
this.highlight = null;
|
|
339
|
-
this.cursorStyle = null;
|
|
340
|
-
this.target = null;
|
|
341
|
-
this.mode = null;
|
|
342
|
-
}
|
|
343
|
-
freezeSelection() {
|
|
344
|
-
this.active = false;
|
|
345
|
-
document.removeEventListener("pointermove", this.onMove, true);
|
|
346
|
-
document.removeEventListener("click", this.onClick, true);
|
|
347
|
-
document.removeEventListener("keydown", this.onKey, true);
|
|
348
|
-
this.cursorStyle?.remove();
|
|
349
|
-
this.cursorStyle = null;
|
|
350
|
-
}
|
|
351
|
-
onMove = (e) => {
|
|
352
|
-
const hit = this.targetAt(e.clientX, e.clientY);
|
|
353
|
-
if (hit === this.target)
|
|
354
|
-
return;
|
|
355
|
-
this.target = hit;
|
|
356
|
-
if (!this.highlight)
|
|
357
|
-
return;
|
|
358
|
-
const rect = this.rectFor(hit);
|
|
359
|
-
Object.assign(this.highlight.style, {
|
|
360
|
-
left: `${rect.left}px`,
|
|
361
|
-
top: `${rect.top}px`,
|
|
362
|
-
width: `${rect.width}px`,
|
|
363
|
-
height: `${rect.height}px`,
|
|
364
|
-
opacity: "1",
|
|
365
|
-
});
|
|
366
|
-
};
|
|
367
|
-
onClick = (e) => {
|
|
368
|
-
e.preventDefault();
|
|
369
|
-
e.stopPropagation();
|
|
370
|
-
const clickedTarget = this.targetAt(e.clientX, e.clientY);
|
|
371
|
-
this.target = clickedTarget;
|
|
372
|
-
const targetId = clickedTarget?.getAttribute(TARGET_ATTR) ?? null;
|
|
373
|
-
if (this.mode === "element") {
|
|
374
|
-
const rect = this.rectFor(clickedTarget);
|
|
375
|
-
const text = clickedTarget.textContent?.replace(/\s+/g, " ").trim() || null;
|
|
376
|
-
const inspection = elementPreview.inspect(clickedTarget);
|
|
377
|
-
this.disable();
|
|
378
|
-
postToFrame({
|
|
379
|
-
type: "element-target-picked",
|
|
380
|
-
inspection,
|
|
381
|
-
selector: selectorFor(clickedTarget),
|
|
382
|
-
tagName: clickedTarget.tagName.toLowerCase(),
|
|
383
|
-
text: text?.slice(0, 160) ?? null,
|
|
384
|
-
anchor: {
|
|
385
|
-
x: rect.left,
|
|
386
|
-
y: rect.top,
|
|
387
|
-
width: rect.width,
|
|
388
|
-
height: rect.height,
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
if (this.mode === "variantSet") {
|
|
394
|
-
if (!targetId)
|
|
395
|
-
return;
|
|
396
|
-
const rect = this.rectFor(clickedTarget);
|
|
397
|
-
this.disable();
|
|
398
|
-
postToFrame({
|
|
399
|
-
type: "variant-set-target-picked",
|
|
400
|
-
targetId,
|
|
401
|
-
anchor: {
|
|
402
|
-
x: rect.left,
|
|
403
|
-
y: rect.top,
|
|
404
|
-
width: rect.width,
|
|
405
|
-
height: rect.height,
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
const rect = this.rectFor(clickedTarget);
|
|
411
|
-
if (this.highlight) {
|
|
412
|
-
Object.assign(this.highlight.style, {
|
|
413
|
-
left: `${rect.left}px`,
|
|
414
|
-
top: `${rect.top}px`,
|
|
415
|
-
width: `${rect.width}px`,
|
|
416
|
-
height: `${rect.height}px`,
|
|
417
|
-
opacity: "1",
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
this.freezeSelection();
|
|
421
|
-
postToFrame({
|
|
422
|
-
type: "comment-target-picked",
|
|
423
|
-
targetId,
|
|
424
|
-
anchor: {
|
|
425
|
-
x: rect.left,
|
|
426
|
-
y: rect.top,
|
|
427
|
-
width: rect.width,
|
|
428
|
-
height: rect.height,
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
|
-
targetAt(x, y) {
|
|
433
|
-
const hit = document.elementFromPoint(x, y);
|
|
434
|
-
if (this.mode === "element")
|
|
435
|
-
return hit ?? document.body;
|
|
436
|
-
return hit?.closest(`[${TARGET_ATTR}]`) ?? document.body;
|
|
437
|
-
}
|
|
438
|
-
rectFor(target) {
|
|
439
|
-
if (target === document.body || target === document.documentElement) {
|
|
440
|
-
return {
|
|
441
|
-
left: 0,
|
|
442
|
-
top: 0,
|
|
443
|
-
width: document.documentElement.clientWidth,
|
|
444
|
-
height: document.documentElement.clientHeight,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
return target.getBoundingClientRect();
|
|
448
|
-
}
|
|
449
|
-
onKey = (e) => {
|
|
450
|
-
if (e.key !== "Escape")
|
|
451
|
-
return;
|
|
452
|
-
e.preventDefault();
|
|
453
|
-
const mode = this.mode;
|
|
454
|
-
this.disable();
|
|
455
|
-
postToFrame({
|
|
456
|
-
type: mode === "variantSet"
|
|
457
|
-
? "variant-set-picker-exited"
|
|
458
|
-
: mode === "element"
|
|
459
|
-
? "element-picker-exited"
|
|
460
|
-
: "comment-mode-exited",
|
|
461
|
-
});
|
|
462
|
-
};
|
|
463
|
-
async captureSelection(requestId) {
|
|
464
|
-
if (!this.target)
|
|
465
|
-
return;
|
|
466
|
-
const targetId = this.target.getAttribute(TARGET_ATTR);
|
|
467
|
-
const rect = this.rectFor(this.target);
|
|
468
|
-
const anchor = {
|
|
469
|
-
x: rect.left,
|
|
470
|
-
y: rect.top,
|
|
471
|
-
width: rect.width,
|
|
472
|
-
height: rect.height,
|
|
473
|
-
};
|
|
474
|
-
if (this.highlight) {
|
|
475
|
-
Object.assign(this.highlight.style, {
|
|
476
|
-
left: `${rect.left}px`,
|
|
477
|
-
top: `${rect.top}px`,
|
|
478
|
-
width: `${rect.width}px`,
|
|
479
|
-
height: `${rect.height}px`,
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
// The page is already interactive when the user picks a comment target.
|
|
483
|
-
// Capture immediately: thumbnail readiness delays can consume the Frame's
|
|
484
|
-
// request timeout on image-heavy prototypes.
|
|
485
|
-
const image = await captureViewport();
|
|
486
|
-
this.disable();
|
|
487
|
-
postToFrame({
|
|
488
|
-
type: "comment-capture",
|
|
489
|
-
targetId,
|
|
490
|
-
image,
|
|
491
|
-
requestId,
|
|
492
|
-
anchor,
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
const elementPicker = typeof window === "undefined" ? null : new ElementPicker();
|
|
497
197
|
/**
|
|
498
198
|
* Wire a store to the bridge: listen for Frame messages, announce the rig,
|
|
499
199
|
* and report the boot-time state (the deep-link fix). Returns a disposer.
|
|
500
200
|
* Call client-side only.
|
|
501
201
|
*/
|
|
502
202
|
export function wireRig(store, manifest) {
|
|
203
|
+
const disposeTools = loadFrameTools(manifest.frameOrigin);
|
|
503
204
|
let controlledTheme = false;
|
|
504
205
|
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
505
206
|
const onSystemTheme = () => {
|
|
@@ -512,16 +213,6 @@ export function wireRig(store, manifest) {
|
|
|
512
213
|
return;
|
|
513
214
|
if (!isFrameToRig(e.data))
|
|
514
215
|
return; // tolerate unknown messages silently
|
|
515
|
-
if (e.data.type === "preview-element-styles") {
|
|
516
|
-
elementPreview.preview(e.data.selectionId, e.data.styles);
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
if (e.data.type === "release-element") {
|
|
520
|
-
elementPreview.release(e.data.selectionId);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
if (["set-state", "set-variant", "set-theme"].includes(e.data.type))
|
|
524
|
-
elementPreview.release();
|
|
525
216
|
if (e.data.type === "set-state") {
|
|
526
217
|
const state = manifest.states.find((s) => s.id === e.data.id);
|
|
527
218
|
store.setState(e.data.id, state?.variants ?? {});
|
|
@@ -532,42 +223,6 @@ export function wireRig(store, manifest) {
|
|
|
532
223
|
}
|
|
533
224
|
else if (e.data.type === "set-variant")
|
|
534
225
|
store.set(variantParam(e.data.component), e.data.variant);
|
|
535
|
-
else if (e.data.type === "measure-target") {
|
|
536
|
-
const target = Array.from(document.querySelectorAll(`[${TARGET_ATTR}]`)).find((candidate) => candidate.getAttribute(TARGET_ATTR) === e.data.id);
|
|
537
|
-
const rect = target?.getBoundingClientRect();
|
|
538
|
-
postToFrame({
|
|
539
|
-
type: "target-measured",
|
|
540
|
-
targetId: e.data.id,
|
|
541
|
-
anchor: rect
|
|
542
|
-
? {
|
|
543
|
-
x: rect.left,
|
|
544
|
-
y: rect.top,
|
|
545
|
-
width: rect.width,
|
|
546
|
-
height: rect.height,
|
|
547
|
-
}
|
|
548
|
-
: null,
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
else if (e.data.type === "set-comment-mode") {
|
|
552
|
-
if (e.data.on)
|
|
553
|
-
elementPicker?.enable("comment");
|
|
554
|
-
else
|
|
555
|
-
elementPicker?.disable();
|
|
556
|
-
}
|
|
557
|
-
else if (e.data.type === "capture-comment")
|
|
558
|
-
void elementPicker?.captureSelection(e.data.requestId);
|
|
559
|
-
else if (e.data.type === "set-variant-set-picker") {
|
|
560
|
-
if (e.data.on)
|
|
561
|
-
elementPicker?.enable("variantSet");
|
|
562
|
-
else
|
|
563
|
-
elementPicker?.disable();
|
|
564
|
-
}
|
|
565
|
-
else if (e.data.type === "set-element-picker") {
|
|
566
|
-
if (e.data.on)
|
|
567
|
-
elementPicker?.enable("element");
|
|
568
|
-
else
|
|
569
|
-
elementPicker?.disable();
|
|
570
|
-
}
|
|
571
226
|
};
|
|
572
227
|
window.addEventListener("message", onMessage);
|
|
573
228
|
postToFrame({
|
|
@@ -590,7 +245,6 @@ export function wireRig(store, manifest) {
|
|
|
590
245
|
return () => {
|
|
591
246
|
media.removeEventListener("change", onSystemTheme);
|
|
592
247
|
window.removeEventListener("message", onMessage);
|
|
593
|
-
|
|
594
|
-
elementPreview.release();
|
|
248
|
+
disposeTools();
|
|
595
249
|
};
|
|
596
250
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proto-labs-inc/rig-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Proto's rig, framework-free: URL-state sync, the postMessage bridge to the Frame, and comment mode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@proto-labs-inc/wire": "^0.
|
|
31
|
+
"@proto-labs-inc/wire": "^0.2.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "~6.0.2",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type ElementStyles, type RigToFrame } from "@proto-labs-inc/wire";
|
|
2
|
-
/** Owns one temporary edit. Never resolves a selector back to a different element. */
|
|
3
|
-
export declare class ElementPreview {
|
|
4
|
-
private post;
|
|
5
|
-
private session;
|
|
6
|
-
private observer;
|
|
7
|
-
private resize;
|
|
8
|
-
private animationFrame;
|
|
9
|
-
private lastBounds;
|
|
10
|
-
constructor(post: (message: RigToFrame) => void);
|
|
11
|
-
inspect(target: Element): {
|
|
12
|
-
selectionId: `${string}-${string}-${string}-${string}-${string}`;
|
|
13
|
-
styles: Partial<Record<"color" | "background-color" | "opacity" | "font-family" | "font-size" | "font-weight" | "line-height" | "letter-spacing" | "padding-top" | "padding-right" | "padding-bottom" | "padding-left" | "border-radius" | "gap", string>>;
|
|
14
|
-
} | undefined;
|
|
15
|
-
preview(id: string, styles: ElementStyles): void;
|
|
16
|
-
private restore;
|
|
17
|
-
release(id?: string): void;
|
|
18
|
-
private scheduleBounds;
|
|
19
|
-
private measure;
|
|
20
|
-
}
|
package/dist/element-preview.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { ELEMENT_STYLE_PROPERTIES } from "@proto-labs-inc/wire";
|
|
2
|
-
/** Owns one temporary edit. Never resolves a selector back to a different element. */
|
|
3
|
-
export class ElementPreview {
|
|
4
|
-
post;
|
|
5
|
-
session = null;
|
|
6
|
-
observer = null;
|
|
7
|
-
resize = null;
|
|
8
|
-
animationFrame = 0;
|
|
9
|
-
lastBounds = "";
|
|
10
|
-
constructor(post) {
|
|
11
|
-
this.post = post;
|
|
12
|
-
}
|
|
13
|
-
inspect(target) {
|
|
14
|
-
this.release();
|
|
15
|
-
if (!(target instanceof HTMLElement || target instanceof SVGElement))
|
|
16
|
-
return undefined;
|
|
17
|
-
const computed = getComputedStyle(target);
|
|
18
|
-
const hasText = Array.from(target.childNodes).some((node) => node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim())) || target.matches("input, textarea, select");
|
|
19
|
-
const typography = new Set(["color", "font-family", "font-size", "font-weight", "line-height", "letter-spacing"]);
|
|
20
|
-
const styles = {};
|
|
21
|
-
for (const property of ELEMENT_STYLE_PROPERTIES) {
|
|
22
|
-
if (typography.has(property) && !hasText)
|
|
23
|
-
continue;
|
|
24
|
-
if (property === "gap" && !/flex|grid/.test(computed.display))
|
|
25
|
-
continue;
|
|
26
|
-
styles[property] = computed.getPropertyValue(property);
|
|
27
|
-
}
|
|
28
|
-
const selectionId = crypto.randomUUID();
|
|
29
|
-
this.session = { id: selectionId, target, original: target.getAttribute("style") };
|
|
30
|
-
this.lastBounds = "";
|
|
31
|
-
this.observer = new MutationObserver(this.scheduleBounds);
|
|
32
|
-
this.observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
33
|
-
this.resize = new ResizeObserver(this.scheduleBounds);
|
|
34
|
-
this.resize.observe(target);
|
|
35
|
-
window.addEventListener("scroll", this.scheduleBounds, true);
|
|
36
|
-
window.addEventListener("resize", this.scheduleBounds);
|
|
37
|
-
return { selectionId, styles };
|
|
38
|
-
}
|
|
39
|
-
preview(id, styles) {
|
|
40
|
-
const session = this.session;
|
|
41
|
-
if (!session || session.id !== id)
|
|
42
|
-
return;
|
|
43
|
-
this.restore();
|
|
44
|
-
for (const property of ELEMENT_STYLE_PROPERTIES) {
|
|
45
|
-
const value = styles[property];
|
|
46
|
-
if (value && CSS.supports(property, value))
|
|
47
|
-
session.target.style.setProperty(property, value, "important");
|
|
48
|
-
}
|
|
49
|
-
this.scheduleBounds();
|
|
50
|
-
}
|
|
51
|
-
restore() {
|
|
52
|
-
if (!this.session)
|
|
53
|
-
return;
|
|
54
|
-
const { target, original } = this.session;
|
|
55
|
-
if (original === null)
|
|
56
|
-
target.removeAttribute("style");
|
|
57
|
-
else
|
|
58
|
-
target.setAttribute("style", original);
|
|
59
|
-
}
|
|
60
|
-
release(id) {
|
|
61
|
-
if (!this.session || (id && id !== this.session.id))
|
|
62
|
-
return;
|
|
63
|
-
const selectionId = this.session.id;
|
|
64
|
-
this.observer?.disconnect();
|
|
65
|
-
this.resize?.disconnect();
|
|
66
|
-
window.removeEventListener("scroll", this.scheduleBounds, true);
|
|
67
|
-
window.removeEventListener("resize", this.scheduleBounds);
|
|
68
|
-
cancelAnimationFrame(this.animationFrame);
|
|
69
|
-
this.animationFrame = 0;
|
|
70
|
-
this.restore();
|
|
71
|
-
this.session = null;
|
|
72
|
-
this.post({ type: "element-preview-bounds", selectionId, anchor: null });
|
|
73
|
-
}
|
|
74
|
-
scheduleBounds = () => {
|
|
75
|
-
if (!this.animationFrame)
|
|
76
|
-
this.animationFrame = requestAnimationFrame(this.measure);
|
|
77
|
-
};
|
|
78
|
-
measure = () => {
|
|
79
|
-
this.animationFrame = 0;
|
|
80
|
-
if (!this.session)
|
|
81
|
-
return;
|
|
82
|
-
const { target, id } = this.session;
|
|
83
|
-
if (!target.isConnected) {
|
|
84
|
-
this.release();
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const rect = target.getBoundingClientRect();
|
|
88
|
-
const anchor = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
|
|
89
|
-
const bounds = JSON.stringify(anchor);
|
|
90
|
-
if (bounds === this.lastBounds)
|
|
91
|
-
return;
|
|
92
|
-
this.lastBounds = bounds;
|
|
93
|
-
this.post({ type: "element-preview-bounds", selectionId: id, anchor });
|
|
94
|
-
};
|
|
95
|
-
}
|