@proto-labs-inc/rig-core 0.1.0 → 0.1.1
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/element-preview.d.ts +20 -0
- package/dist/element-preview.js +95 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -2
- package/package.json +2 -2
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const INTERNAL_PREVIEW_THEME_PARAM = "__protoTheme";
|
|
|
10
10
|
export declare function applyPreviewTheme(theme: ResolvedPreviewTheme): void;
|
|
11
11
|
/** All addressable state, mirrored to the URL. Keys: "state" | "v.<component>". */
|
|
12
12
|
export declare class RigStore {
|
|
13
|
+
private manifest;
|
|
13
14
|
private values;
|
|
14
15
|
private listeners;
|
|
15
16
|
constructor(manifest: Manifest);
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
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
|
-
import { PROTOCOL_VERSION, STATE_PARAM, variantParam, isFrameToRig, } from "@proto-labs-inc/wire";
|
|
7
|
+
import { PROTOCOL_VERSION, STATE_PARAM, variantParam, applyVariantSelection, isFrameToRig, } from "@proto-labs-inc/wire";
|
|
8
|
+
import { ElementPreview } from "./element-preview.js";
|
|
8
9
|
export { STATE_PARAM, variantParam } from "@proto-labs-inc/wire";
|
|
9
|
-
const RIG_VERSION = "0.1.
|
|
10
|
+
const RIG_VERSION = "0.1.1";
|
|
10
11
|
export const INTERNAL_PREVIEW_THEME_PARAM = "__protoTheme";
|
|
11
12
|
export function applyPreviewTheme(theme) {
|
|
12
13
|
if (typeof document === "undefined")
|
|
@@ -29,9 +30,11 @@ function searchForFrame() {
|
|
|
29
30
|
}
|
|
30
31
|
/** All addressable state, mirrored to the URL. Keys: "state" | "v.<component>". */
|
|
31
32
|
export class RigStore {
|
|
33
|
+
manifest;
|
|
32
34
|
values = new Map();
|
|
33
35
|
listeners = new Set();
|
|
34
36
|
constructor(manifest) {
|
|
37
|
+
this.manifest = manifest;
|
|
35
38
|
if (typeof window === "undefined")
|
|
36
39
|
return;
|
|
37
40
|
const params = new URLSearchParams(window.location.search);
|
|
@@ -61,6 +64,13 @@ export class RigStore {
|
|
|
61
64
|
return this.values.get(key);
|
|
62
65
|
}
|
|
63
66
|
set(key, value) {
|
|
67
|
+
if (key.startsWith("v.")) {
|
|
68
|
+
const params = new URLSearchParams([...this.values]);
|
|
69
|
+
if (!applyVariantSelection(this.manifest.variantSets, params, key.slice(2), value))
|
|
70
|
+
return;
|
|
71
|
+
this.setMany([...params.entries()]);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
64
74
|
this.setMany([[key, value]]);
|
|
65
75
|
}
|
|
66
76
|
setMany(entries) {
|
|
@@ -267,6 +277,7 @@ async function captureThumbnail() {
|
|
|
267
277
|
restingState.remove();
|
|
268
278
|
}
|
|
269
279
|
}
|
|
280
|
+
const elementPreview = new ElementPreview(postToFrame);
|
|
270
281
|
class ElementPicker {
|
|
271
282
|
overlay = null;
|
|
272
283
|
highlight = null;
|
|
@@ -277,6 +288,7 @@ class ElementPicker {
|
|
|
277
288
|
enable(mode) {
|
|
278
289
|
if (typeof document === "undefined")
|
|
279
290
|
return;
|
|
291
|
+
elementPreview.release();
|
|
280
292
|
if (this.active || this.overlay)
|
|
281
293
|
this.disable();
|
|
282
294
|
this.active = true;
|
|
@@ -361,9 +373,11 @@ class ElementPicker {
|
|
|
361
373
|
if (this.mode === "element") {
|
|
362
374
|
const rect = this.rectFor(clickedTarget);
|
|
363
375
|
const text = clickedTarget.textContent?.replace(/\s+/g, " ").trim() || null;
|
|
376
|
+
const inspection = elementPreview.inspect(clickedTarget);
|
|
364
377
|
this.disable();
|
|
365
378
|
postToFrame({
|
|
366
379
|
type: "element-target-picked",
|
|
380
|
+
inspection,
|
|
367
381
|
selector: selectorFor(clickedTarget),
|
|
368
382
|
tagName: clickedTarget.tagName.toLowerCase(),
|
|
369
383
|
text: text?.slice(0, 160) ?? null,
|
|
@@ -494,8 +508,20 @@ export function wireRig(store, manifest) {
|
|
|
494
508
|
};
|
|
495
509
|
media.addEventListener("change", onSystemTheme);
|
|
496
510
|
const onMessage = (e) => {
|
|
511
|
+
if (e.source !== window.parent)
|
|
512
|
+
return;
|
|
497
513
|
if (!isFrameToRig(e.data))
|
|
498
514
|
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();
|
|
499
525
|
if (e.data.type === "set-state") {
|
|
500
526
|
const state = manifest.states.find((s) => s.id === e.data.id);
|
|
501
527
|
store.setState(e.data.id, state?.variants ?? {});
|
|
@@ -565,5 +591,6 @@ export function wireRig(store, manifest) {
|
|
|
565
591
|
media.removeEventListener("change", onSystemTheme);
|
|
566
592
|
window.removeEventListener("message", onMessage);
|
|
567
593
|
elementPicker?.disable();
|
|
594
|
+
elementPreview.release();
|
|
568
595
|
};
|
|
569
596
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proto-labs-inc/rig-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
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.1.
|
|
31
|
+
"@proto-labs-inc/wire": "^0.1.1"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"typescript": "~6.0.2",
|