@cratis/scene 0.111.1 → 0.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,12 @@ TypeScript models for the Studio's low-code canvas: UIElement hierarchy, Externa
6
6
 
7
7
  `PrototypeThumbnail` accepts both live `IComponent` instances and raw `UIElement` data from JSON documents. At render time Scene uses the element's library-qualified `_derivedTypeId` (or `componentName`) to construct a registered component and copy its saved state, recursively hydrating element arrays in all named slots without changing the source document. Without a registered library, it renders a positioned, sized placeholder rather than an empty prototype. Library registration does not trigger a React rerender by itself; rerender the thumbnail after loading bundles to replace placeholders with actual components. Without an open action the thumbnail is a named, non-focusable preview; with one it remains an editor-opening button. The board/document reader does not load bundles or depend on Studio Core.
8
8
 
9
+ ## Layout preview DTOs
10
+
11
+ The six `StudioLayout.*` types (`Stack`, `Row`, `Grid`, `Container`, `Wrap`, and `Canvas`) are backend preview DTOs. They derive from the existing `FrameworkElement`/`UIElement` hierarchy and use the same on-wire `ComponentName`, inherited `Properties`, and `Slots` shape as `ExternalComponent`, so generic board previews can round-trip their nested children. They deliberately do not derive from `ExternalComponent`: the current derived-type converter treats a base with registered subclasses as a discriminator family and then cannot deserialize a generic `externalComponent` itself. This compatibility boundary avoids changing Fundamentals or breaking existing generic component, Dialog, and ConfirmDialog data.
12
+
13
+ A `SceneDocument` is the sole editable model. Layout DTOs are produced only for the legacy board-preview projection; they are not a second Scene model, editor, resolver, or persistence authority.
14
+
9
15
  ## Component class — how it works
10
16
 
11
17
  `Component` (in `Component.ts`) is the abstract base class for every prototype surface component (e.g. the 89 PrimeReact wrappers under `Source/PrimeReact/`). It extends `ExternalComponent` so instances are proper `UIElement` subtypes that flow through the command pipeline without any manual wire-format conversion.
@@ -1,3 +1,4 @@
1
+ import { type Guid } from '@cratis/fundamentals';
1
2
  import { ExternalComponent } from './ExternalComponent';
2
3
  import type { ComponentState, IComponent, PrototypeComponentRenderProps } from './IComponent';
3
4
  import type { ComponentType, ReactNode } from 'react';
@@ -9,7 +10,7 @@ import type { Size } from './Size';
9
10
  * by Fundamentals without any manual wire-format conversion.
10
11
  */
11
12
  export declare abstract class Component extends ExternalComponent implements IComponent {
12
- id: string;
13
+ id: Guid & string;
13
14
  readonly initialSize: Size;
14
15
  readonly resizeBehavior: IComponent['resizeBehavior'];
15
16
  readonly acceptsChildren: boolean;
@@ -0,0 +1,23 @@
1
+ import React, { type ReactNode } from 'react';
2
+ export declare const inspectorWidth = 360;
3
+ export interface OverlaySize {
4
+ width: number;
5
+ height: number;
6
+ }
7
+ export interface PrototypeOverlayFrameState {
8
+ size: OverlaySize;
9
+ setSize: (size: OverlaySize) => void;
10
+ isInspectorOpen: boolean;
11
+ toggleInspector: () => void;
12
+ close: () => void;
13
+ }
14
+ export interface PrototypeOverlayFrameProps {
15
+ sourceRect: DOMRect;
16
+ initialSize?: OverlaySize;
17
+ centerOffsetX?: number;
18
+ children: (state: PrototypeOverlayFrameState) => ReactNode;
19
+ renderInspector?: (state: PrototypeOverlayFrameState) => ReactNode;
20
+ canClose?: (size: OverlaySize) => Promise<boolean>;
21
+ onClosed: (size: OverlaySize) => void;
22
+ }
23
+ export declare const PrototypeOverlayFrame: ({ sourceRect, initialSize, centerOffsetX, children, renderInspector, canClose, onClosed }: PrototypeOverlayFrameProps) => React.JSX.Element;
@@ -0,0 +1,8 @@
1
+ export declare enum LayoutArrangement {
2
+ Row = "row",
3
+ Column = "column",
4
+ Grid = "grid",
5
+ Wrap = "wrap",
6
+ Freeform = "freeform",
7
+ Grouping = "grouping"
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { LayoutArrangement } from './LayoutArrangement';
3
+ export interface LayoutContainerDescriptor {
4
+ type: string;
5
+ name: string;
6
+ arrangement: LayoutArrangement;
7
+ icon?: ComponentType<{
8
+ className?: string;
9
+ }>;
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { PrototypeComponentRenderProps } from '../../../IComponent';
2
+ import { LayoutArrangement } from './LayoutArrangement';
3
+ export interface LayoutContainerSurfaceProps extends PrototypeComponentRenderProps {
4
+ arrangement: LayoutArrangement;
5
+ }
6
+ export declare const LayoutContainerSurface: ({ component, arrangement, isDragOver }: LayoutContainerSurfaceProps) => import("react").JSX.Element;
7
+ export { LayoutArrangement };
@@ -0,0 +1,4 @@
1
+ import type { CSSProperties } from 'react';
2
+ import { LayoutArrangement } from './LayoutArrangement';
3
+ export declare const arrangementStyle: (arrangement: LayoutArrangement) => CSSProperties;
4
+ export declare const isFlowArrangement: (arrangement: LayoutArrangement) => boolean;
@@ -0,0 +1,5 @@
1
+ import type { PrototypeComponentConstructor } from '../../../IComponent';
2
+ import type { LayoutContainerDescriptor } from './LayoutContainerDescriptor';
3
+ export declare const layoutContainerLibrary = "StudioLayout";
4
+ export declare const layoutContainerCategory = "Layout";
5
+ export declare const createLayoutContainerComponent: (descriptor: LayoutContainerDescriptor) => PrototypeComponentConstructor;
@@ -0,0 +1,5 @@
1
+ export * from './LayoutArrangement';
2
+ export * from './LayoutContainerDescriptor';
3
+ export * from './arrangementStyle';
4
+ export * from './createLayoutContainerComponent';
5
+ export * from './registerLayoutContainers';
@@ -0,0 +1,3 @@
1
+ import type { PrototypeComponentLibraryDefinition } from '../editorTypes';
2
+ import type { LayoutContainerDescriptor } from './LayoutContainerDescriptor';
3
+ export declare const registerLayoutContainers: (descriptors: LayoutContainerDescriptor[]) => PrototypeComponentLibraryDefinition;
@@ -0,0 +1,19 @@
1
+ export interface ThumbnailPoint {
2
+ x: number;
3
+ y: number;
4
+ }
5
+ export interface ThumbnailSize {
6
+ width: number;
7
+ height: number;
8
+ }
9
+ export interface ThumbnailBoxInput {
10
+ native: boolean;
11
+ name: string;
12
+ position: ThumbnailPoint;
13
+ size: ThumbnailSize;
14
+ }
15
+ export interface ThumbnailBox {
16
+ position: ThumbnailPoint;
17
+ size: ThumbnailSize;
18
+ }
19
+ export declare function thumbnailBoxes(inputs: readonly ThumbnailBoxInput[], stack: boolean): ThumbnailBox[];
@@ -1,8 +1,8 @@
1
- import { c as R, f as h, k as st } from "../Scene-DaQtzf1e.js";
2
- import { createContext as k, useContext as O, useEffect as $ } from "react";
3
- import { Fragment as M, jsx as r, jsxs as f } from "react/jsx-runtime";
4
- import { createPortal as B } from "react-dom";
5
- var P = {
1
+ import { c as C, f as p, k as ut } from "../Scene-DaQtzf1e.js";
2
+ import { createContext as G, useContext as U, useEffect as S } from "react";
3
+ import { Fragment as A, jsx as n, jsxs as T } from "react/jsx-runtime";
4
+ import { createPortal as j } from "react-dom";
5
+ var L = {
6
6
  prototypeThumbnail: {
7
7
  openEditor: "Open prototype editor",
8
8
  windowTitle: "Prototype",
@@ -12,25 +12,68 @@ var P = {
12
12
  title: "Prototype preview",
13
13
  close: "Close preview"
14
14
  }
15
- }, W = k(P), A = () => O(W), dt = ({ messages: t, children: e }) => /* @__PURE__ */ r(W.Provider, {
16
- value: t,
17
- children: e
18
- }), C = [
15
+ }, D = G(L), k = () => U(D), xt = ({ messages: e, children: t }) => /* @__PURE__ */ n(D.Provider, {
16
+ value: e,
17
+ children: t
18
+ }), V = [
19
19
  "#f66",
20
20
  "#fa0",
21
21
  "#3c3"
22
- ], G = ({ size: t }) => /* @__PURE__ */ r(M, { children: C.map((e, o) => /* @__PURE__ */ r("div", {
22
+ ], q = ({ size: e }) => /* @__PURE__ */ n(A, { children: V.map((t, o) => /* @__PURE__ */ n("div", {
23
23
  className: "prototype-window-traffic-light",
24
24
  style: {
25
- border: `${t ? Math.max(1, t / 8) : 1.5}px solid ${e}`,
26
- ...t ? {
27
- width: t,
28
- height: t
25
+ border: `${e ? Math.max(1, e / 8) : 1.5}px solid ${t}`,
26
+ ...e ? {
27
+ width: e,
28
+ height: e
29
29
  } : {}
30
30
  }
31
- }, o)) }), U = (t) => {
32
- switch (t) {
33
- case h.flexRow:
31
+ }, o)) }), M = 16, J = 8, K = {
32
+ width: 180,
33
+ height: 36
34
+ }, Q = {
35
+ dataTable: {
36
+ width: 360,
37
+ height: 120
38
+ },
39
+ table: {
40
+ width: 360,
41
+ height: 120
42
+ },
43
+ card: {
44
+ width: 240,
45
+ height: 96
46
+ },
47
+ chart: {
48
+ width: 300,
49
+ height: 150
50
+ }
51
+ }, X = (e) => !(e.width > 0) || !(e.height > 0);
52
+ function Y(e, t) {
53
+ let o = M;
54
+ return e.map((i) => {
55
+ if (!i.native || !X(i.size)) return {
56
+ position: i.position,
57
+ size: i.size
58
+ };
59
+ const r = Q[i.name] ?? K;
60
+ if (!t) return {
61
+ position: i.position,
62
+ size: r
63
+ };
64
+ const s = {
65
+ x: M,
66
+ y: o
67
+ };
68
+ return o += r.height + J, {
69
+ position: s,
70
+ size: r
71
+ };
72
+ });
73
+ }
74
+ var Z = (e) => {
75
+ switch (e) {
76
+ case p.flexRow:
34
77
  return {
35
78
  display: "flex",
36
79
  flexDirection: "row",
@@ -39,14 +82,14 @@ var P = {
39
82
  padding: 12,
40
83
  alignContent: "flex-start"
41
84
  };
42
- case h.flexColumn:
85
+ case p.flexColumn:
43
86
  return {
44
87
  display: "flex",
45
88
  flexDirection: "column",
46
89
  gap: 12,
47
90
  padding: 12
48
91
  };
49
- case h.grid:
92
+ case p.grid:
50
93
  return {
51
94
  display: "grid",
52
95
  gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))",
@@ -57,62 +100,83 @@ var P = {
57
100
  default:
58
101
  return {};
59
102
  }
60
- }, S = 900, j = 640, V = 40, q = 150, J = 16, K = (t) => {
61
- const e = t;
62
- if (e.position && typeof e.position.x == "number") return {
63
- x: e.position.x,
64
- y: e.position.y ?? 0
103
+ }, z = 900, tt = 640, et = 40, ot = 150, it = 16, rt = (e) => {
104
+ const t = e;
105
+ if (t.position && typeof t.position.x == "number") return {
106
+ x: t.position.x,
107
+ y: t.position.y ?? 0
65
108
  };
66
- const o = e.properties?.canvas ?? {};
109
+ const o = t.properties?.canvas ?? {};
67
110
  return {
68
111
  x: o.x ?? 0,
69
112
  y: o.y ?? 0
70
113
  };
71
- }, Q = (t) => {
72
- const e = t;
73
- return e.size && typeof e.size.width == "number" ? {
74
- width: e.size.width,
75
- height: e.size.height ?? 0
114
+ }, at = (e) => {
115
+ const t = e;
116
+ return t.size && typeof t.size.width == "number" ? {
117
+ width: t.size.width,
118
+ height: t.size.height ?? 0
76
119
  } : {
77
- width: e.width ?? 0,
78
- height: e.height ?? 0
120
+ width: t.width ?? 0,
121
+ height: t.height ?? 0
79
122
  };
80
- }, X = ({ elements: t, windowSize: e, layout: o = h.absolute, onClick: s, width: n = q, titleBarHeight: i = J, openTitle: c }) => {
81
- const l = A().prototypeThumbnail, u = e && e.width > 0 ? e.width : S, m = e && e.height > 0 ? e.height : j, v = o !== h.absolute, p = o === h.grid, d = u, T = Math.max(1, m - V), w = n / d, L = Math.round(d * w), N = Math.round(T * w), b = /* @__PURE__ */ f(M, { children: [/* @__PURE__ */ f("div", {
123
+ }, N = (e) => {
124
+ const t = e;
125
+ if (typeof t.componentName != "string" || t.componentName.includes(".")) return;
126
+ const o = t.properties && typeof t.properties == "object" ? t.properties : {}, i = [
127
+ o.label,
128
+ o.text,
129
+ o.title,
130
+ t.componentName
131
+ ].find((r) => typeof r == "string" && r.length > 0);
132
+ return typeof i == "string" ? {
133
+ type: t.componentName,
134
+ label: i
135
+ } : void 0;
136
+ }, nt = ({ elements: e, windowSize: t, layout: o = p.absolute, onClick: i, width: r = ot, titleBarHeight: s = it, openTitle: c }) => {
137
+ const h = k().prototypeThumbnail, w = t && t.width > 0 ? t.width : z, v = t && t.height > 0 ? t.height : tt, d = o !== p.absolute, l = o === p.grid, m = e.filter(Boolean).map(C), F = Y(m.map((a) => ({
138
+ native: N(a) !== void 0,
139
+ name: String(a.componentName ?? ""),
140
+ position: rt(a),
141
+ size: at(a)
142
+ })), !d), f = w, E = Math.max(1, v - et), b = r / f, R = Math.round(f * b), B = Math.round(E * b), O = m.map((a, g) => ({
143
+ element: a,
144
+ ...F[g]
145
+ })), H = /* @__PURE__ */ T(A, { children: [/* @__PURE__ */ T("div", {
82
146
  className: "prototype-thumbnail-title-bar",
83
- style: { height: i },
84
- children: [/* @__PURE__ */ r(G, { size: Math.round(i * 0.375) }), /* @__PURE__ */ r("span", {
147
+ style: { height: s },
148
+ children: [/* @__PURE__ */ n(q, { size: Math.round(s * 0.375) }), /* @__PURE__ */ n("span", {
85
149
  className: "prototype-thumbnail-title",
86
- children: l.windowTitle
150
+ children: h.windowTitle
87
151
  })]
88
- }), /* @__PURE__ */ r("div", {
152
+ }), /* @__PURE__ */ n("div", {
89
153
  className: "prototype-thumbnail-body",
90
154
  inert: !0,
91
- children: /* @__PURE__ */ r("div", {
155
+ children: /* @__PURE__ */ n("div", {
92
156
  className: "prototype-thumbnail-canvas-scaler",
93
157
  style: {
94
- width: d,
95
- height: T,
96
- transform: `scale(${w})`,
158
+ width: f,
159
+ height: E,
160
+ transform: `scale(${b})`,
97
161
  transformOrigin: "top left",
98
- ...U(o)
162
+ ...Z(o)
99
163
  },
100
- children: t.filter(Boolean).map((D) => {
101
- const a = R(D), E = String(a.id), H = K(a), y = Q(a), I = v ? {
164
+ children: O.map(({ element: a, position: g, size: y }) => {
165
+ const I = String(a.id), P = d ? {
102
166
  position: "relative",
103
- width: p ? "100%" : y.width,
167
+ width: l ? "100%" : y.width,
104
168
  height: y.height,
105
169
  flexShrink: 0
106
170
  } : {
107
171
  position: "absolute",
108
- left: H.x,
109
- top: H.y,
172
+ left: g.x,
173
+ top: g.y,
110
174
  width: y.width,
111
175
  height: y.height
112
176
  };
113
- if ("RenderPrototype" in a && typeof a.RenderPrototype == "function") return /* @__PURE__ */ r("div", {
177
+ if ("RenderPrototype" in a && typeof a.RenderPrototype == "function") return /* @__PURE__ */ n("div", {
114
178
  style: {
115
- ...I,
179
+ ...P,
116
180
  pointerEvents: "none",
117
181
  overflow: "hidden"
118
182
  },
@@ -120,98 +184,102 @@ var P = {
120
184
  component: a,
121
185
  label: a.label
122
186
  })
123
- }, E);
124
- const F = "type" in a && typeof a.type == "string" ? ` prototype-thumbnail-element--${a.type}` : "";
125
- return /* @__PURE__ */ r("div", {
126
- className: `prototype-thumbnail-element${F}`,
127
- style: I
128
- }, E);
187
+ }, I);
188
+ const x = N(a), $ = x ? " prototype-thumbnail-element--native" : "type" in a && typeof a.type == "string" ? ` prototype-thumbnail-element--${a.type}` : "";
189
+ return /* @__PURE__ */ n("div", {
190
+ className: `prototype-thumbnail-element${$}`,
191
+ style: P,
192
+ children: x && /* @__PURE__ */ n("span", {
193
+ className: "prototype-thumbnail-element__label",
194
+ children: x.label
195
+ })
196
+ }, I);
129
197
  })
130
198
  })
131
- })] }), x = {
132
- width: L,
133
- height: i + N
199
+ })] }), _ = {
200
+ width: R,
201
+ height: s + B
134
202
  };
135
- return s ? /* @__PURE__ */ r("button", {
203
+ return i ? /* @__PURE__ */ n("button", {
136
204
  className: "prototype-thumbnail",
137
- onClick: s,
138
- title: c ?? l.openEditor,
139
- style: x,
140
- children: b
141
- }) : /* @__PURE__ */ r("div", {
205
+ onClick: i,
206
+ title: c ?? h.openEditor,
207
+ style: _,
208
+ children: H
209
+ }) : /* @__PURE__ */ n("div", {
142
210
  className: "prototype-thumbnail",
143
211
  role: "img",
144
- "aria-label": l.windowTitle,
145
- style: x,
146
- children: b
212
+ "aria-label": h.windowTitle,
213
+ style: _,
214
+ children: H
147
215
  });
148
- }, Y = 900, Z = 640, _ = 40, z = 24, tt = 0.9, et = 0.85, ot = (t, e) => {
149
- const o = t && t.width > 0 ? t.width : Y, s = t && t.height > 0 ? t.height : Z, n = Math.max(0.1, Math.min(1, e.width * tt / o, e.height * et / s)), i = Math.round(o * n);
216
+ }, st = 900, ht = 640, W = 40, pt = 24, lt = 0.9, dt = 0.85, ct = (e, t) => {
217
+ const o = e && e.width > 0 ? e.width : st, i = e && e.height > 0 ? e.height : ht, r = Math.max(0.1, Math.min(1, t.width * lt / o, t.height * dt / i)), s = Math.round(o * r);
150
218
  return {
151
- width: i,
152
- canvasHeight: Math.round(Math.max(1, s - _) * (i / o)),
153
- titleBarHeight: Math.max(z, Math.round(_ * n))
219
+ width: s,
220
+ canvasHeight: Math.round(Math.max(1, i - W) * (s / o)),
221
+ titleBarHeight: Math.max(pt, Math.round(W * r))
154
222
  };
155
- }, rt = () => typeof window > "u" ? {
223
+ }, vt = () => typeof window > "u" ? {
156
224
  width: 1280,
157
225
  height: 800
158
226
  } : {
159
227
  width: window.innerWidth,
160
228
  height: window.innerHeight
161
- }, it = (t, e, o, s) => {
162
- if (!t || t.width <= 0) return;
163
- const n = (s.width - e) / 2, i = (s.height - o) / 2;
164
- return `translate(${Math.round(t.left - n)}px, ${Math.round(t.top - i)}px) scale(${(t.width / e).toFixed(4)})`;
165
- }, g = (t) => t.stopPropagation(), ct = ({ elements: t, windowSize: e, layout: o = h.absolute, sourceRect: s, onClose: n }) => {
166
- const i = A().prototypePreview ?? P.prototypePreview;
167
- $(() => {
168
- const p = (d) => {
169
- d.key === "Escape" && n();
229
+ }, mt = (e, t, o, i) => {
230
+ if (!e || e.width <= 0) return;
231
+ const r = (i.width - t) / 2, s = (i.height - o) / 2;
232
+ return `translate(${Math.round(e.left - r)}px, ${Math.round(e.top - s)}px) scale(${(e.width / t).toFixed(4)})`;
233
+ }, u = (e) => e.stopPropagation(), Tt = ({ elements: e, windowSize: t, layout: o = p.absolute, sourceRect: i, onClose: r }) => {
234
+ const s = k().prototypePreview ?? L.prototypePreview;
235
+ S(() => {
236
+ const l = (m) => {
237
+ m.key === "Escape" && r();
170
238
  };
171
- return window.addEventListener("keydown", p), () => window.removeEventListener("keydown", p);
172
- }, [n]);
173
- const c = rt(), l = ot(e, c), u = l.titleBarHeight + l.canvasHeight, m = it(s, l.width, u, c), v = /* @__PURE__ */ r("div", {
239
+ return window.addEventListener("keydown", l), () => window.removeEventListener("keydown", l);
240
+ }, [r]);
241
+ const c = vt(), h = ct(t, c), w = h.titleBarHeight + h.canvasHeight, v = mt(i, h.width, w, c), d = /* @__PURE__ */ n("div", {
174
242
  className: "prototype-preview-backdrop",
175
243
  role: "dialog",
176
244
  "aria-modal": "true",
177
- "aria-label": i.title,
178
- onClick: n,
179
- onPointerDown: g,
180
- onMouseDown: g,
181
- onWheel: g,
182
- onDoubleClick: g,
183
- children: /* @__PURE__ */ f("div", {
245
+ "aria-label": s.title,
246
+ onClick: r,
247
+ onPointerDown: u,
248
+ onMouseDown: u,
249
+ onWheel: u,
250
+ onDoubleClick: u,
251
+ children: /* @__PURE__ */ T("div", {
184
252
  className: "prototype-preview-window",
185
- style: m ? { "--prototype-preview-from": m } : void 0,
186
- onClick: (p) => p.stopPropagation(),
187
- children: [/* @__PURE__ */ r(X, {
188
- elements: t,
189
- windowSize: e,
253
+ style: v ? { "--prototype-preview-from": v } : void 0,
254
+ onClick: (l) => l.stopPropagation(),
255
+ children: [/* @__PURE__ */ n(nt, {
256
+ elements: e,
257
+ windowSize: t,
190
258
  layout: o,
191
- width: l.width,
192
- titleBarHeight: l.titleBarHeight
193
- }), /* @__PURE__ */ r("button", {
259
+ width: h.width,
260
+ titleBarHeight: h.titleBarHeight
261
+ }), /* @__PURE__ */ n("button", {
194
262
  type: "button",
195
263
  className: "prototype-preview-close",
196
- "aria-label": i.close,
197
- title: i.close,
198
- onClick: n,
264
+ "aria-label": s.close,
265
+ title: s.close,
266
+ onClick: r,
199
267
  children: "×"
200
268
  })]
201
269
  })
202
270
  });
203
- return typeof document > "u" ? v : B(v, document.body);
204
- }, mt = 15;
271
+ return typeof document > "u" ? d : j(d, document.body);
272
+ }, Et = 15;
205
273
  export {
206
- st as AnchorEdges,
207
- h as PrototypeLayout,
208
- ct as PrototypePreview,
209
- X as PrototypeThumbnail,
210
- W as SceneMessagesContext,
211
- dt as SceneMessagesProvider,
212
- G as TrafficLights,
213
- mt as allAnchorEdgesForEditor,
214
- P as defaultSceneMessages,
215
- ot as fitPrototypePreview,
216
- A as useSceneMessages
274
+ ut as AnchorEdges,
275
+ p as PrototypeLayout,
276
+ Tt as PrototypePreview,
277
+ nt as PrototypeThumbnail,
278
+ D as SceneMessagesContext,
279
+ xt as SceneMessagesProvider,
280
+ q as TrafficLights,
281
+ Et as allAnchorEdgesForEditor,
282
+ L as defaultSceneMessages,
283
+ ct as fitPrototypePreview,
284
+ k as useSceneMessages
217
285
  };
@@ -0,0 +1,2 @@
1
+ export * from '../Prototypes/Editor/PrototypeOverlayFrame';
2
+ export * from '../Prototypes/Editor/layoutContainers';
@@ -0,0 +1,214 @@
1
+ import { u as q, x as A } from "../Scene-DaQtzf1e.js";
2
+ import { useCallback as y, useEffect as B, useRef as S, useState as C } from "react";
3
+ import { jsx as u, jsxs as b } from "react/jsx-runtime";
4
+ var $ = 900, X = 640, J = 320, K = 300, at = 360, Q = (t) => t ? {
5
+ width: t.width > 0 ? t.width : $,
6
+ height: t.height > 0 ? t.height : X
7
+ } : {
8
+ width: $,
9
+ height: X
10
+ }, V = [
11
+ "n",
12
+ "s",
13
+ "e",
14
+ "w",
15
+ "ne",
16
+ "nw",
17
+ "se",
18
+ "sw"
19
+ ], st = ({ sourceRect: t, initialSize: n, centerOffsetX: i = 0, children: s, renderInspector: m, canClose: r, onClosed: x }) => {
20
+ const [d, z] = C("entering"), [a, D] = C(() => Q(n)), w = S(a);
21
+ w.current = a;
22
+ const [h, _] = C(!1), k = y(() => _((e) => !e), []), f = S(null);
23
+ B(() => {
24
+ const e = requestAnimationFrame(() => z("visible"));
25
+ return () => cancelAnimationFrame(e);
26
+ }, []);
27
+ const I = y(async () => {
28
+ r && !await r(w.current) || (z("closing"), setTimeout(() => x(w.current), 300));
29
+ }, [r, x]), L = (e, o) => {
30
+ o.stopPropagation(), o.preventDefault(), f.current = {
31
+ direction: e,
32
+ startX: o.clientX,
33
+ startY: o.clientY,
34
+ startWidth: a.width,
35
+ startHeight: a.height
36
+ }, o.target.setPointerCapture(o.pointerId);
37
+ }, R = y((e) => {
38
+ const o = f.current;
39
+ if (!o) return;
40
+ const { direction: p } = o;
41
+ let T = o.startWidth, W = o.startHeight;
42
+ if (p.includes("e") || p.includes("w")) {
43
+ const v = p.includes("e") ? e.clientX - o.startX : o.startX - e.clientX;
44
+ T = Math.max(J, o.startWidth + v * 2);
45
+ }
46
+ if (p.includes("s") || p.includes("n")) {
47
+ const v = p.includes("s") ? e.clientY - o.startY : o.startY - e.clientY;
48
+ W = Math.max(K, o.startHeight + v * 2);
49
+ }
50
+ D({
51
+ width: T,
52
+ height: W
53
+ });
54
+ }, []), N = y(() => {
55
+ f.current = null;
56
+ }, []), H = i - (h ? 360 / 2 : 0), F = {
57
+ size: a,
58
+ setSize: D,
59
+ isInspectorOpen: h,
60
+ toggleInspector: k,
61
+ close: () => {
62
+ I();
63
+ }
64
+ }, Y = t.left + t.width / 2, G = t.top + t.height / 2, M = window.innerWidth / 2 + H - a.width / 2, O = window.innerHeight / 2 - a.height / 2, E = `${Y - M}px ${G - O}px`, j = [
65
+ "prototype-editor-overlay-content",
66
+ d === "visible" ? "prototype-editor-overlay-content--visible" : "",
67
+ d === "closing" ? "prototype-editor-overlay-content--closing" : ""
68
+ ].join(" "), U = ["prototype-editor-overlay-backdrop", d === "visible" ? "prototype-editor-overlay-backdrop--visible" : ""].join(" ");
69
+ return /* @__PURE__ */ b("div", {
70
+ className: U,
71
+ onClick: I,
72
+ children: [/* @__PURE__ */ b("div", {
73
+ className: j,
74
+ style: {
75
+ transformOrigin: E,
76
+ width: a.width,
77
+ height: a.height,
78
+ marginLeft: -(a.width / 2) + H,
79
+ marginTop: -(a.height / 2)
80
+ },
81
+ onClick: (e) => e.stopPropagation(),
82
+ onPointerDown: (e) => e.stopPropagation(),
83
+ children: [d === "visible" && V.map((e) => /* @__PURE__ */ u("div", {
84
+ className: `prototype-editor-overlay-resize prototype-editor-overlay-resize--${e}`,
85
+ onPointerDown: (o) => L(e, o),
86
+ onPointerMove: R,
87
+ onPointerUp: N,
88
+ onPointerCancel: N
89
+ }, e)), s(F)]
90
+ }), m && /* @__PURE__ */ u("aside", {
91
+ className: `prototype-editor-overlay-inspector${h && d === "visible" ? " prototype-editor-overlay-inspector--open" : ""}`,
92
+ style: { width: 360 },
93
+ "aria-hidden": !h,
94
+ inert: !h,
95
+ onClick: (e) => e.stopPropagation(),
96
+ onPointerDown: (e) => e.stopPropagation(),
97
+ children: m(F)
98
+ })]
99
+ });
100
+ }, l = /* @__PURE__ */ (function(t) {
101
+ return t.Row = "row", t.Column = "column", t.Grid = "grid", t.Wrap = "wrap", t.Freeform = "freeform", t.Grouping = "grouping", t;
102
+ })({}), g = 8, Z = (t) => {
103
+ switch (t) {
104
+ case l.Row:
105
+ return {
106
+ display: "flex",
107
+ flexDirection: "row",
108
+ gap: g
109
+ };
110
+ case l.Wrap:
111
+ return {
112
+ display: "flex",
113
+ flexDirection: "row",
114
+ flexWrap: "wrap",
115
+ gap: g
116
+ };
117
+ case l.Grid:
118
+ return {
119
+ display: "grid",
120
+ gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))",
121
+ gap: g
122
+ };
123
+ case l.Freeform:
124
+ return { position: "relative" };
125
+ case l.Column:
126
+ case l.Grouping:
127
+ return {
128
+ display: "flex",
129
+ flexDirection: "column",
130
+ gap: g
131
+ };
132
+ }
133
+ }, tt = (t) => t !== l.Freeform, et = ({ component: t, arrangement: n, isDragOver: i }) => {
134
+ const s = t.children ?? [], m = tt(n);
135
+ return /* @__PURE__ */ b("div", {
136
+ className: `prototype-layout-container${i ? " prototype-layout-container--drag-over" : ""}`,
137
+ "data-layout-arrangement": n,
138
+ style: Z(n),
139
+ children: [s.length === 0 && /* @__PURE__ */ u("div", {
140
+ className: "prototype-layout-container__empty",
141
+ "aria-hidden": "true"
142
+ }), s.map((r) => /* @__PURE__ */ u("div", {
143
+ className: "prototype-layout-container__item",
144
+ style: m ? {
145
+ width: r.size.width,
146
+ height: r.size.height
147
+ } : {
148
+ position: "absolute",
149
+ left: r.position.x,
150
+ top: r.position.y,
151
+ width: r.size.width,
152
+ height: r.size.height
153
+ },
154
+ children: r.RenderPrototype({
155
+ component: r,
156
+ label: r.label
157
+ })
158
+ }, r.id))]
159
+ });
160
+ }, c = "StudioLayout", P = "Layout", ot = (t) => {
161
+ class n extends A {
162
+ componentName = t.type;
163
+ category = P;
164
+ library = c;
165
+ initialSize = {
166
+ width: 260,
167
+ height: 140
168
+ };
169
+ resizeBehavior = "both";
170
+ acceptsChildren = !0;
171
+ get toolbarIcon() {
172
+ return t.icon;
173
+ }
174
+ RenderPrototype(s) {
175
+ return /* @__PURE__ */ u(et, {
176
+ ...s,
177
+ arrangement: t.arrangement
178
+ });
179
+ }
180
+ }
181
+ return n;
182
+ }, lt = (t) => {
183
+ const n = {};
184
+ for (const i of t) {
185
+ const s = ot(i);
186
+ n[i.type] = s, n[`${c}.${i.type}`] = s;
187
+ }
188
+ return q(c, n), {
189
+ name: c,
190
+ bundlePath: "",
191
+ categories: [{
192
+ name: P,
193
+ components: t.map((i) => ({
194
+ name: i.name,
195
+ category: P,
196
+ library: c,
197
+ bundlePath: "",
198
+ type: `${c}.${i.type}`,
199
+ component: new n[i.type]()
200
+ }))
201
+ }]
202
+ };
203
+ };
204
+ export {
205
+ l as LayoutArrangement,
206
+ st as PrototypeOverlayFrame,
207
+ Z as arrangementStyle,
208
+ ot as createLayoutContainerComponent,
209
+ at as inspectorWidth,
210
+ tt as isFlowArrangement,
211
+ P as layoutContainerCategory,
212
+ c as layoutContainerLibrary,
213
+ lt as registerLayoutContainers
214
+ };
package/dist/scene.css CHANGED
@@ -1,2 +1,2 @@
1
- .prototype-window-traffic-light{box-sizing:border-box;background:0 0;border-radius:50%;width:12px;height:12px}.prototype-thumbnail{cursor:default;box-sizing:border-box;background:var(--surface-card);border:none;flex-direction:column;flex-shrink:0;padding:0;display:flex;position:relative;overflow:hidden}.prototype-thumbnail:is(button){cursor:pointer}.prototype-thumbnail-title-bar{border-bottom:1px solid var(--surface-200);background:var(--surface-50);flex-shrink:0;align-items:center;gap:3px;padding:0 5px;display:flex;position:relative}.prototype-thumbnail-title{text-align:center;color:var(--text-color-secondary);pointer-events:none;font-family:system-ui,sans-serif;font-size:8px;position:absolute;left:0;right:0}.prototype-thumbnail-body{background:var(--surface-ground);flex:1;position:relative;overflow:hidden}.prototype-thumbnail-canvas-scaler{position:absolute;top:0;left:0}.prototype-thumbnail-element{box-sizing:border-box;background:var(--surface-200);border:1px solid var(--surface-300);border-radius:2px;position:absolute}.prototype-thumbnail-element--button,.prototype-thumbnail-element--button-hot{background:var(--surface-200);border:1px solid var(--surface-300)}.prototype-thumbnail-element--button-hot{background:var(--primary-color);border-color:var(--primary-color);opacity:.7}.prototype-thumbnail-element--text-input{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--label{border-bottom:1px solid var(--surface-300);background:0 0}.prototype-thumbnail-element--checkbox,.prototype-thumbnail-element--radio,.prototype-thumbnail-element--dropdown{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--image{background:var(--surface-100);border:1px dashed var(--surface-300)}.prototype-thumbnail-element--panel{background:var(--surface-50);border:1px solid var(--surface-200)}.prototype-thumbnail-element--data-table{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--toolbar{background:var(--surface-100);border:1px solid var(--surface-200)}.prototype-thumbnail-element--kpi-card{background:var(--surface-100);border:1px solid var(--primary-300,var(--surface-300));border-radius:3px}.prototype-thumbnail-element--bar-chart{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to top, var(--primary-400,#7c6fe0) 0px, var(--primary-400,#7c6fe0) 60%, transparent 60%, transparent 100%);background-position:10% 0,35% 0,60% 0,85% 0;background-repeat:no-repeat;background-size:20% 100%}.prototype-thumbnail-element--line-chart{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--pie-chart{background:var(--surface-0);border:1px solid var(--surface-300);border-radius:50%}.prototype-thumbnail-element--progress-bar{background:var(--surface-100);border:1px solid var(--surface-300);background-image:linear-gradient(to right, var(--primary-400,#7c6fe0) 0%, var(--primary-400,#7c6fe0) 55%, transparent 55%)}.prototype-thumbnail-element--activity-feed{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to bottom, transparent 0px, transparent 3px, var(--surface-200) 3px, var(--surface-200) 4px);background-size:100% 8px}.prototype-thumbnail-element--nav-topbar{background:var(--surface-100);border:1px solid var(--surface-200);border-radius:2px 2px 0 0}.prototype-thumbnail-element--nav-footer{background:var(--surface-100);border:1px solid var(--surface-200);border-radius:0 0 2px 2px}.prototype-thumbnail-element--nav-sidebar{background:var(--surface-50);border:1px solid var(--surface-200);border-radius:2px 0 0 2px}.prototype-thumbnail-element--content-area{background:var(--surface-ground);border:1.5px dashed var(--surface-300)}.prototype-thumbnail-element--command-form{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to bottom, transparent 0px, transparent 5px, var(--surface-100) 5px, var(--surface-100) 8px);background-position:0 4px;background-size:100% 12px}.prototype-preview-backdrop{z-index:1000;cursor:default;background:#00000073;justify-content:center;align-items:center;animation:.16s ease-out prototype-preview-fade;display:flex;position:fixed;inset:0}.prototype-preview-window{border:1px solid var(--surface-300);transform-origin:0 0;border-radius:8px;animation:.22s cubic-bezier(.2,.8,.2,1) prototype-preview-zoom;position:relative;overflow:hidden;box-shadow:0 24px 64px #00000073}.prototype-preview-window .prototype-thumbnail-title{font-size:13px}.prototype-preview-window .prototype-thumbnail-title-bar{gap:8px;padding:0 12px}.prototype-preview-close{z-index:1;width:28px;height:28px;color:var(--text-color-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;font-size:18px;line-height:1;position:absolute;top:6px;right:8px}.prototype-preview-close:hover{background:var(--surface-200);color:var(--text-color)}@keyframes prototype-preview-zoom{0%{transform:var(--prototype-preview-from,scale(.9));opacity:.4}to{opacity:1;transform:none}}@keyframes prototype-preview-fade{0%{background:0 0}to{background:#00000073}}@media (prefers-reduced-motion:reduce){.prototype-preview-backdrop,.prototype-preview-window{animation:none}}
1
+ .prototype-window-traffic-light{box-sizing:border-box;background:0 0;border-radius:50%;width:12px;height:12px}.prototype-thumbnail{cursor:default;box-sizing:border-box;background:var(--surface-card);border:none;flex-direction:column;flex-shrink:0;padding:0;display:flex;position:relative;overflow:hidden}.prototype-thumbnail:is(button){cursor:pointer}.prototype-thumbnail-title-bar{border-bottom:1px solid var(--surface-200);background:var(--surface-50);flex-shrink:0;align-items:center;gap:3px;padding:0 5px;display:flex;position:relative}.prototype-thumbnail-title{text-align:center;color:var(--text-color-secondary);pointer-events:none;font-family:system-ui,sans-serif;font-size:8px;position:absolute;left:0;right:0}.prototype-thumbnail-body{background:var(--surface-ground);flex:1;position:relative;overflow:hidden}.prototype-thumbnail-canvas-scaler{position:absolute;top:0;left:0}.prototype-thumbnail-element{box-sizing:border-box;background:var(--surface-200);border:1px solid var(--surface-300);border-radius:2px;position:absolute}.prototype-thumbnail-element--button,.prototype-thumbnail-element--button-hot{background:var(--surface-200);border:1px solid var(--surface-300)}.prototype-thumbnail-element--button-hot{background:var(--primary-color);border-color:var(--primary-color);opacity:.7}.prototype-thumbnail-element--native{color:var(--text-color);text-align:center;justify-content:center;align-items:center;padding:2px 6px;font-family:system-ui,sans-serif;font-size:12px;display:flex;overflow:hidden}.prototype-thumbnail-element__label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.prototype-thumbnail-element--text-input{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--label{border-bottom:1px solid var(--surface-300);background:0 0}.prototype-thumbnail-element--checkbox,.prototype-thumbnail-element--radio,.prototype-thumbnail-element--dropdown{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--image{background:var(--surface-100);border:1px dashed var(--surface-300)}.prototype-thumbnail-element--panel{background:var(--surface-50);border:1px solid var(--surface-200)}.prototype-thumbnail-element--data-table{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--toolbar{background:var(--surface-100);border:1px solid var(--surface-200)}.prototype-thumbnail-element--kpi-card{background:var(--surface-100);border:1px solid var(--primary-300,var(--surface-300));border-radius:3px}.prototype-thumbnail-element--bar-chart{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to top, var(--primary-400,#7c6fe0) 0px, var(--primary-400,#7c6fe0) 60%, transparent 60%, transparent 100%);background-position:10% 0,35% 0,60% 0,85% 0;background-repeat:no-repeat;background-size:20% 100%}.prototype-thumbnail-element--line-chart{background:var(--surface-0);border:1px solid var(--surface-300)}.prototype-thumbnail-element--pie-chart{background:var(--surface-0);border:1px solid var(--surface-300);border-radius:50%}.prototype-thumbnail-element--progress-bar{background:var(--surface-100);border:1px solid var(--surface-300);background-image:linear-gradient(to right, var(--primary-400,#7c6fe0) 0%, var(--primary-400,#7c6fe0) 55%, transparent 55%)}.prototype-thumbnail-element--activity-feed{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to bottom, transparent 0px, transparent 3px, var(--surface-200) 3px, var(--surface-200) 4px);background-size:100% 8px}.prototype-thumbnail-element--nav-topbar{background:var(--surface-100);border:1px solid var(--surface-200);border-radius:2px 2px 0 0}.prototype-thumbnail-element--nav-footer{background:var(--surface-100);border:1px solid var(--surface-200);border-radius:0 0 2px 2px}.prototype-thumbnail-element--nav-sidebar{background:var(--surface-50);border:1px solid var(--surface-200);border-radius:2px 0 0 2px}.prototype-thumbnail-element--content-area{background:var(--surface-ground);border:1.5px dashed var(--surface-300)}.prototype-thumbnail-element--command-form{background:var(--surface-0);border:1px solid var(--surface-300);background-image:repeating-linear-gradient(to bottom, transparent 0px, transparent 5px, var(--surface-100) 5px, var(--surface-100) 8px);background-position:0 4px;background-size:100% 12px}.prototype-preview-backdrop{z-index:1000;cursor:default;background:#00000073;justify-content:center;align-items:center;animation:.16s ease-out prototype-preview-fade;display:flex;position:fixed;inset:0}.prototype-preview-window{border:1px solid var(--surface-300);transform-origin:0 0;border-radius:8px;animation:.22s cubic-bezier(.2,.8,.2,1) prototype-preview-zoom;position:relative;overflow:hidden;box-shadow:0 24px 64px #00000073}.prototype-preview-window .prototype-thumbnail-title{font-size:13px}.prototype-preview-window .prototype-thumbnail-title-bar{gap:8px;padding:0 12px}.prototype-preview-close{z-index:1;width:28px;height:28px;color:var(--text-color-secondary);cursor:pointer;background:0 0;border:none;border-radius:50%;font-size:18px;line-height:1;position:absolute;top:6px;right:8px}.prototype-preview-close:hover{background:var(--surface-200);color:var(--text-color)}@keyframes prototype-preview-zoom{0%{transform:var(--prototype-preview-from,scale(.9));opacity:.4}to{opacity:1;transform:none}}@keyframes prototype-preview-fade{0%{background:0 0}to{background:#00000073}}@media (prefers-reduced-motion:reduce){.prototype-preview-backdrop,.prototype-preview-window{animation:none}}.prototype-editor-overlay-backdrop{z-index:50;opacity:0;pointer-events:auto;background:#0000008c;justify-content:center;align-items:center;transition:opacity .28s;display:flex;position:fixed;inset:0}.prototype-editor-overlay-backdrop--visible{opacity:1}.prototype-editor-overlay-content{opacity:0;border-radius:10px;flex-direction:column;transition:transform .28s cubic-bezier(.4,0,.2,1),opacity .28s;display:flex;position:fixed;top:50%;left:50%;overflow:hidden;transform:scale(.08);box-shadow:0 24px 80px #0000008c}.prototype-editor-overlay-content--visible{opacity:1;transform:scale(1)}.prototype-editor-overlay-content--closing{opacity:0;transform:scale(.08)}.prototype-editor-overlay-resize{z-index:10;position:absolute}.prototype-editor-overlay-resize--n{cursor:ns-resize;height:6px;top:0;left:8px;right:8px}.prototype-editor-overlay-resize--s{cursor:ns-resize;height:6px;bottom:0;left:8px;right:8px}.prototype-editor-overlay-resize--e{cursor:ew-resize;width:6px;top:8px;bottom:8px;right:0}.prototype-editor-overlay-resize--w{cursor:ew-resize;width:6px;top:8px;bottom:8px;left:0}.prototype-editor-overlay-resize--ne{cursor:nesw-resize;width:12px;height:12px;top:0;right:0}.prototype-editor-overlay-resize--nw{cursor:nwse-resize;width:12px;height:12px;top:0;left:0}.prototype-editor-overlay-resize--se{cursor:nwse-resize;width:12px;height:12px;bottom:0;right:0}.prototype-editor-overlay-resize--sw{cursor:nesw-resize;width:12px;height:12px;bottom:0;left:0}.prototype-editor-overlay-inspector{background:var(--cratis-surface-card);color:var(--cratis-text-color);border-left:1px solid var(--cratis-surface-border);box-shadow:var(--cratis-shadow-overlay);z-index:12;flex-direction:column;transition:transform .24s cubic-bezier(.4,0,.2,1);display:flex;position:fixed;top:0;bottom:0;right:0;transform:translate(100%)}.prototype-editor-overlay-inspector--open{transform:translate(0)}.prototype-layout-container{box-sizing:border-box;border:1px dashed var(--cratis-surface-border);border-radius:var(--cratis-border-radius);background:color-mix(in srgb, var(--cratis-surface-card) 60%, transparent);width:100%;height:100%;padding:8px;overflow:hidden}.prototype-layout-container--drag-over{border:2px dashed var(--cratis-primary-color)}.prototype-layout-container__item{box-sizing:border-box;flex:none}.prototype-layout-container__empty{border-radius:var(--cratis-border-radius);background:repeating-linear-gradient(45deg, transparent, transparent 6px, var(--cratis-surface-hover) 6px, var(--cratis-surface-hover) 12px);pointer-events:none;flex:auto;min-height:32px}
2
2
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene",
3
- "version": "0.111.1",
3
+ "version": "0.113.0",
4
4
  "description": "Cratis Scene: renders Studio prototypes as thumbnails and previews.",
5
5
  "license": "MIT",
6
6
  "author": "Cratis",
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/Prototypes/index.d.ts",
33
33
  "import": "./dist/Prototypes/index.js"
34
34
  },
35
+ "./ScreenEditor": {
36
+ "types": "./dist/ScreenEditor/index.d.ts",
37
+ "import": "./dist/ScreenEditor/index.js"
38
+ },
35
39
  "./styles": "./dist/scene.css"
36
40
  },
37
41
  "files": [
@@ -50,6 +54,10 @@
50
54
  "types": "./dist/Prototypes/index.d.ts",
51
55
  "import": "./dist/Prototypes/index.js"
52
56
  },
57
+ "./ScreenEditor": {
58
+ "types": "./dist/ScreenEditor/index.d.ts",
59
+ "import": "./dist/ScreenEditor/index.js"
60
+ },
53
61
  "./styles": "./dist/scene.css"
54
62
  }
55
63
  },