@proto-labs-inc/wire 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare const PROTOCOL_VERSION = 2;
1
+ export declare const PROTOCOL_VERSION = 3;
2
2
  export type ResolvedPreviewTheme = "light" | "dark";
3
3
  export interface StateDef {
4
4
  id: string;
@@ -57,18 +57,28 @@ export interface ReferenceDef {
57
57
  variant?: string;
58
58
  }
59
59
  export interface VariantSetDef {
60
- /** component id the variants belong to (matches the rig's registration) */
60
+ /** Stable identity for this creation. Older manifests use component as their identity. */
61
+ id?: string;
62
+ /** Unique set address used by the rig, independent of the display title and selected element. */
61
63
  component: string;
64
+ /** This set applies only while this variant in another set is active. */
65
+ requires?: {
66
+ component: string;
67
+ variant: string;
68
+ };
62
69
  title?: string;
63
70
  /** temporary lifecycle state while an agent is creating the variant set */
64
71
  status?: "building";
65
72
  variants: VariantDef[];
66
- /** variant id the prototype's code defaults to */
73
+ /** Variant id the prototype's code defaults to; empty for a references-only set. */
67
74
  default: string;
68
75
  /** preview-state id that showcases this variant set (used by previews) */
69
76
  state?: string;
70
77
  /** variant id that is the pre-variant-set original — pinned last, badged ORIGINAL */
71
78
  baseline?: string;
79
+ /** Marked page regions the set changes, independent of its rig address.
80
+ * A decision across the top bar and a notice is one set. */
81
+ regions?: string[];
72
82
  /** overview copy shown at the bottom of the variants panel */
73
83
  overview?: {
74
84
  title?: string;
@@ -78,6 +88,8 @@ export interface VariantSetDef {
78
88
  }
79
89
  export interface Manifest {
80
90
  schemaVersion: 2;
91
+ /** Trusted Frame app origin, configured by the workspace owner. */
92
+ frameOrigin?: string;
81
93
  /** the slug */
82
94
  name: string;
83
95
  port: number;
@@ -85,9 +97,31 @@ export interface Manifest {
85
97
  states: StateDef[];
86
98
  variantSets: VariantSetDef[];
87
99
  prPlan?: PrPlanDef;
100
+ /** The width (and height) the page was copied at, when the copy only
101
+ * lays out right there (a frozen page): the Frame draws it at this
102
+ * width and scales it down when the area is narrower. */
103
+ viewport?: {
104
+ width: number;
105
+ height: number;
106
+ /** the narrowest width it still lays out at without spilling sideways:
107
+ * the Frame re-flows it down to here and scales it only below */
108
+ minWidth?: number;
109
+ };
88
110
  }
89
111
  export declare const STATE_PARAM = "state";
90
112
  export declare const variantParam: (component: string) => string;
113
+ /** Required ancestors, outermost first. Invalid references and cycles fail closed. */
114
+ export declare function variantSetRequirements(sets: VariantSetDef[], component: string): Array<{
115
+ component: string;
116
+ variant: string;
117
+ }> | null;
118
+ export declare function isVariantSetApplicable(sets: VariantSetDef[], component: string, params: URLSearchParams): boolean;
119
+ /** Apply a selection and its ancestors together, preserving dormant child choices. */
120
+ export declare function applyVariantSelection(sets: VariantSetDef[], params: URLSearchParams, component: string, variant: string): boolean;
121
+ /** CSS properties exposed by the annotation inspector. */
122
+ export declare const ELEMENT_STYLE_PROPERTIES: readonly ["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"];
123
+ export type ElementStyleProperty = typeof ELEMENT_STYLE_PROPERTIES[number];
124
+ export type ElementStyles = Partial<Record<ElementStyleProperty, string>>;
91
125
  export type FrameToRig = {
92
126
  type: "set-state";
93
127
  id: string;
@@ -100,10 +134,10 @@ export type FrameToRig = {
100
134
  variant: string;
101
135
  } | {
102
136
  type: "focus-target";
103
- id: string | null;
137
+ selector: string | null;
104
138
  } | {
105
139
  type: "measure-target";
106
- id: string;
140
+ selector: string;
107
141
  } | {
108
142
  type: "set-comment-mode";
109
143
  on: boolean;
@@ -116,6 +150,13 @@ export type FrameToRig = {
116
150
  } | {
117
151
  type: "set-element-picker";
118
152
  on: boolean;
153
+ } | {
154
+ type: "preview-element-styles";
155
+ selectionId: string;
156
+ styles: ElementStyles;
157
+ } | {
158
+ type: "release-element";
159
+ selectionId: string;
119
160
  };
120
161
  export interface CommentAnchor {
121
162
  /** Bounds of the selected component in the prototype viewport. */
@@ -125,6 +166,9 @@ export interface CommentAnchor {
125
166
  height: number;
126
167
  }
127
168
  export type RigToFrame = {
169
+ type: "frame-tools-status";
170
+ status: "loading" | "ready" | "unavailable";
171
+ } | {
128
172
  type: "hello";
129
173
  protocolVersion: number;
130
174
  rigVersion: string;
@@ -136,14 +180,14 @@ export type RigToFrame = {
136
180
  search: string;
137
181
  } | {
138
182
  type: "comment-target-picked";
139
- /** the clicked [data-proto-id], if the click landed on a marked component */
140
- targetId: string | null;
183
+ /** CSS selector for the exact selected element. */
184
+ selector: string;
141
185
  /** Used by the Frame to place the composer beside the selection. */
142
186
  anchor: CommentAnchor;
143
187
  } | {
144
188
  type: "comment-capture";
145
- /** the clicked [data-proto-id], if the click landed on a marked component */
146
- targetId: string | null;
189
+ /** CSS selector for the exact selected element. */
190
+ selector: string;
147
191
  /** PNG data URL with the highlight composited in; null if capture failed
148
192
  (e.g. modern-screenshot not installed) — the comment still works */
149
193
  image: string | null;
@@ -165,18 +209,22 @@ export type RigToFrame = {
165
209
  type: "target-focus-dismissed";
166
210
  } | {
167
211
  type: "target-measured";
168
- targetId: string;
169
- /** Null when no matching registered component exists in the current state. */
212
+ selector: string;
213
+ /** Null when no matching element exists in the current state. */
170
214
  anchor: CommentAnchor | null;
171
215
  } | {
172
216
  type: "variant-set-target-picked";
173
- targetId: string;
217
+ selector: string;
174
218
  /** Used by the Frame to place the variant-set brief beside the selection. */
175
219
  anchor: CommentAnchor;
176
220
  } | {
177
221
  type: "variant-set-picker-exited";
178
222
  } | {
179
223
  type: "element-target-picked";
224
+ inspection?: {
225
+ selectionId: string;
226
+ styles: ElementStyles;
227
+ };
180
228
  /** A CSS selector scoped as narrowly as possible to a registered ancestor. */
181
229
  selector: string;
182
230
  tagName: string;
@@ -186,6 +234,10 @@ export type RigToFrame = {
186
234
  anchor: CommentAnchor;
187
235
  } | {
188
236
  type: "element-picker-exited";
237
+ } | {
238
+ type: "element-preview-bounds";
239
+ selectionId: string;
240
+ anchor: CommentAnchor | null;
189
241
  };
190
242
  export declare function isFrameToRig(d: unknown): d is FrameToRig;
191
243
  export declare function isRigToFrame(d: unknown): d is RigToFrame;
package/dist/index.js CHANGED
@@ -1,19 +1,74 @@
1
- export const PROTOCOL_VERSION = 2;
1
+ export const PROTOCOL_VERSION = 3;
2
2
  // ─── URL param conventions ─────────────────────────────────────────
3
3
  export const STATE_PARAM = "state";
4
4
  export const variantParam = (component) => `v.${component}`;
5
+ /** Required ancestors, outermost first. Invalid references and cycles fail closed. */
6
+ export function variantSetRequirements(sets, component) {
7
+ const visited = new Set();
8
+ const requirements = [];
9
+ let set = sets.find((candidate) => candidate.component === component);
10
+ if (!set)
11
+ return null;
12
+ while (set) {
13
+ if (visited.has(set.component))
14
+ return null;
15
+ visited.add(set.component);
16
+ const requirement = set.requires;
17
+ if (!requirement)
18
+ break;
19
+ const parent = sets.find((candidate) => candidate.component === requirement.component);
20
+ if (!parent?.variants.some((variant) => variant.id === requirement.variant))
21
+ return null;
22
+ requirements.unshift(requirement);
23
+ set = parent;
24
+ }
25
+ return requirements;
26
+ }
27
+ export function isVariantSetApplicable(sets, component, params) {
28
+ const requirements = variantSetRequirements(sets, component);
29
+ return requirements !== null && requirements.every((requirement) => {
30
+ const parent = sets.find((set) => set.component === requirement.component);
31
+ return (params.get(variantParam(parent.component)) ?? parent.default) === requirement.variant;
32
+ });
33
+ }
34
+ /** Apply a selection and its ancestors together, preserving dormant child choices. */
35
+ export function applyVariantSelection(sets, params, component, variant) {
36
+ const requirements = variantSetRequirements(sets, component);
37
+ const set = sets.find((candidate) => candidate.component === component);
38
+ if (!requirements || !set?.variants.some((candidate) => candidate.id === variant))
39
+ return false;
40
+ for (const requirement of requirements) {
41
+ params.set(variantParam(requirement.component), requirement.variant);
42
+ }
43
+ params.set(variantParam(component), variant);
44
+ return true;
45
+ }
46
+ /** CSS properties exposed by the annotation inspector. */
47
+ export const ELEMENT_STYLE_PROPERTIES = [
48
+ "color", "background-color", "opacity", "font-family", "font-size",
49
+ "font-weight", "line-height", "letter-spacing",
50
+ "padding-top", "padding-right", "padding-bottom", "padding-left",
51
+ "border-radius", "gap",
52
+ ];
5
53
  // ─── guards (tolerant: unknown message types are ignored, never errors) ──
6
54
  export function isFrameToRig(d) {
7
55
  if (typeof d !== "object" || d === null)
8
56
  return false;
9
57
  const message = d;
58
+ if (message.type === "release-element")
59
+ return typeof message.selectionId === "string";
60
+ if (message.type === "preview-element-styles") {
61
+ return typeof message.selectionId === "string" && typeof message.styles === "object" &&
62
+ message.styles !== null && !Array.isArray(message.styles) &&
63
+ Object.entries(message.styles).every(([property, value]) => ELEMENT_STYLE_PROPERTIES.includes(property) && typeof value === "string");
64
+ }
10
65
  const t = message.type;
11
66
  return (t === "set-state" ||
12
67
  (t === "set-theme" &&
13
68
  (message.theme === "light" || message.theme === "dark")) ||
14
69
  t === "set-variant" ||
15
- t === "focus-target" ||
16
- t === "measure-target" ||
70
+ (t === "focus-target" && (message.selector === null || typeof message.selector === "string")) ||
71
+ (t === "measure-target" && typeof message.selector === "string") ||
17
72
  t === "set-comment-mode" ||
18
73
  t === "capture-comment" ||
19
74
  t === "set-variant-set-picker" ||
@@ -22,17 +77,19 @@ export function isFrameToRig(d) {
22
77
  export function isRigToFrame(d) {
23
78
  if (typeof d !== "object" || d === null)
24
79
  return false;
25
- const t = d.type;
26
- return (t === "hello" ||
80
+ const message = d;
81
+ const t = message.type;
82
+ if (["comment-target-picked", "comment-capture", "variant-set-target-picked", "target-measured"].includes(t)) {
83
+ return typeof message.selector === "string";
84
+ }
85
+ return ((t === "frame-tools-status" && ["loading", "ready", "unavailable"].includes(message.status)) ||
86
+ t === "hello" ||
27
87
  t === "state-changed" ||
28
- t === "comment-target-picked" ||
29
- t === "comment-capture" ||
30
88
  t === "thumbnail-capture" ||
31
89
  t === "comment-mode-exited" ||
32
90
  t === "target-focus-dismissed" ||
33
- t === "target-measured" ||
34
- t === "variant-set-target-picked" ||
35
91
  t === "variant-set-picker-exited" ||
92
+ t === "element-preview-bounds" ||
36
93
  t === "element-target-picked" ||
37
94
  t === "element-picker-exited");
38
95
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proto-labs-inc/wire",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "The typed contract between Proto's Frame and the rig inside a prototype: bridge messages and the prototype.json schema.",
5
5
  "license": "MIT",
6
6
  "repository": {