@kekonic/diagrams-studio 1.0.0-rc.4

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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/browser/assets/SourceEditor-CTsSCxbb.js +23 -0
  4. package/dist/browser/assets/codicon-Brq4_Ui5.ttf +0 -0
  5. package/dist/browser/assets/dist-xSRpPWs-.js +49 -0
  6. package/dist/browser/assets/editor-CPgU7loC.css +1 -0
  7. package/dist/browser/assets/editor.api-Pc7YbpSQ.js +423 -0
  8. package/dist/browser/assets/editor.worker-r79G8Edd.js +30 -0
  9. package/dist/browser/assets/format-CEbYvQUd.css +1 -0
  10. package/dist/browser/assets/format-CX-V5tOl.js +425 -0
  11. package/dist/browser/assets/github-dark-C-LZuMrd.js +1 -0
  12. package/dist/browser/assets/github-light-EUqPIrTm.js +1 -0
  13. package/dist/browser/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  14. package/dist/browser/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  15. package/dist/browser/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  16. package/dist/browser/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  17. package/dist/browser/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  18. package/dist/browser/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  19. package/dist/browser/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  20. package/dist/browser/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  21. package/dist/browser/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  22. package/dist/browser/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  23. package/dist/browser/assets/index-D15KDLkg.css +1 -0
  24. package/dist/browser/assets/index-D4UABSTV.js +3411 -0
  25. package/dist/browser/assets/inter-latin-500-normal-BL9OpVg8.woff +0 -0
  26. package/dist/browser/assets/inter-latin-500-normal-DIm8MnOp.js +1 -0
  27. package/dist/browser/assets/language-BHyJ3vcB.js +11 -0
  28. package/dist/browser/assets/monaco-features-DiflRtGt.css +1 -0
  29. package/dist/browser/assets/monaco-features-i8AvDstT.js +28 -0
  30. package/dist/browser/assets/wasm-BnjxR4X6.js +1 -0
  31. package/dist/browser/favicon.svg +8 -0
  32. package/dist/browser/index.html +17 -0
  33. package/dist/index.d.mts +173 -0
  34. package/dist/index.mjs +457 -0
  35. package/dist/index.mjs.map +1 -0
  36. package/package.json +72 -0
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" fill="none">
2
+ <rect width="48" height="48" fill="#432990"/>
3
+ <path d="M14 36V12H34" stroke="#f8f7ff" stroke-width="5" stroke-linejoin="miter"/>
4
+ <path d="M14 24H29" stroke="#f8f7ff" stroke-width="5" stroke-linejoin="miter"/>
5
+ <rect x="29.5" y="7.5" width="9" height="9" fill="#f8f7ff"/>
6
+ <rect x="24.5" y="19.5" width="9" height="9" fill="#f8f7ff"/>
7
+ <rect x="9.5" y="31.5" width="9" height="9" fill="#f8f7ff"/>
8
+ </svg>
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark light" />
7
+ <meta name="kdiagram-icon-api" content="./__kdiagram/icons" />
8
+ <meta name="kdiagram-public-studio" content="https://diagrams.kekonic.com/studio/" />
9
+ <link rel="icon" href="./favicon.svg" type="image/svg+xml" />
10
+ <title>Kekonic Diagrams Studio</title>
11
+ <script type="module" crossorigin src="./assets/index-D4UABSTV.js"></script>
12
+ <link rel="stylesheet" crossorigin href="./assets/index-D15KDLkg.css">
13
+ </head>
14
+ <body>
15
+ <div id="app"></div>
16
+ </body>
17
+ </html>
@@ -0,0 +1,173 @@
1
+ import { Diagnostic, GraphModel, InteractiveRenderOptions, RenderResult, RenderStats, SourceRange, ThemeMode } from "@kekonic/diagrams";
2
+
3
+ //#region src/protocol.d.ts
4
+ declare const STUDIO_PROTOCOL_VERSION: 1;
5
+ type StudioDocument = {
6
+ id: string;
7
+ path: string;
8
+ label: string;
9
+ revision: number;
10
+ source: string;
11
+ };
12
+ type StudioCapabilities = {
13
+ write: boolean;
14
+ export: true;
15
+ };
16
+ type StudioSelection = {
17
+ documentId: string;
18
+ range?: SourceRange;
19
+ graphElement?: {
20
+ type: "node" | "edge";
21
+ id: string;
22
+ };
23
+ };
24
+ type StudioViewport = {
25
+ zoom: number;
26
+ x: number;
27
+ y: number;
28
+ };
29
+ type StudioPresentation = {
30
+ theme: "dark" | "light";
31
+ options: InteractiveRenderOptions;
32
+ };
33
+ type StudioRender = {
34
+ documentId: string;
35
+ revision: number;
36
+ ok: boolean;
37
+ svg?: string;
38
+ diagnostics: Diagnostic[];
39
+ graph?: GraphModel;
40
+ stats: RenderStats;
41
+ };
42
+ type StudioServerMessage = {
43
+ version: 1;
44
+ type: "ready";
45
+ sessionId: string;
46
+ documents: StudioDocument[];
47
+ activeDocumentId: string;
48
+ capabilities: StudioCapabilities;
49
+ presentation: StudioPresentation;
50
+ } | ({
51
+ version: 1;
52
+ type: "document";
53
+ reason: "open" | "external" | "saved";
54
+ } & StudioDocument) | ({
55
+ version: 1;
56
+ type: "render";
57
+ } & StudioRender) | {
58
+ version: 1;
59
+ type: "selection";
60
+ selection: StudioSelection;
61
+ } | {
62
+ version: 1;
63
+ type: "viewport";
64
+ viewport: StudioViewport;
65
+ } | {
66
+ version: 1;
67
+ type: "presentation";
68
+ presentation: StudioPresentation;
69
+ } | {
70
+ version: 1;
71
+ type: "saved";
72
+ documentId: string;
73
+ revision: number;
74
+ } | {
75
+ version: 1;
76
+ type: "error";
77
+ code: string;
78
+ message: string;
79
+ requestType?: string;
80
+ };
81
+ type StudioClientMessage = {
82
+ version: 1;
83
+ type: "open";
84
+ documentId: string;
85
+ } | {
86
+ version: 1;
87
+ type: "source";
88
+ documentId: string;
89
+ revision: number;
90
+ source: string;
91
+ } | {
92
+ version: 1;
93
+ type: "save";
94
+ documentId: string;
95
+ revision: number;
96
+ source: string;
97
+ } | {
98
+ version: 1;
99
+ type: "selection";
100
+ selection: StudioSelection;
101
+ } | {
102
+ version: 1;
103
+ type: "viewport";
104
+ viewport: StudioViewport;
105
+ } | {
106
+ version: 1;
107
+ type: "presentation";
108
+ presentation: StudioPresentation;
109
+ };
110
+ declare function parseStudioClientMessage(value: unknown): StudioClientMessage;
111
+ declare function studioMessageJson(message: StudioServerMessage): string;
112
+ //#endregion
113
+ //#region src/preview.d.ts
114
+ type StudioRenderer = (source: string, options: InteractiveRenderOptions) => Promise<RenderResult>;
115
+ type StudioPreviewCoordinator = {
116
+ render(documentId: string, revision: number, source: string, options: InteractiveRenderOptions): Promise<StudioRender | undefined>;
117
+ invalidate(documentId: string): void;
118
+ };
119
+ /**
120
+ * Drops stale async renders so every host observes monotonically newer document results.
121
+ * Rendering stays injected, keeping this coordinator browser-safe and renderer-neutral.
122
+ */
123
+ declare function createStudioPreviewCoordinator(renderer: StudioRenderer): StudioPreviewCoordinator;
124
+ //#endregion
125
+ //#region src/state.d.ts
126
+ type StudioState = {
127
+ connected: boolean;
128
+ sessionId?: string;
129
+ documents: StudioDocument[];
130
+ activeDocumentId?: string;
131
+ capabilities: StudioCapabilities;
132
+ presentation: StudioPresentation;
133
+ render?: StudioRender;
134
+ selection?: StudioSelection;
135
+ viewport: StudioViewport;
136
+ error?: {
137
+ code: string;
138
+ message: string;
139
+ };
140
+ };
141
+ declare const INITIAL_STUDIO_STATE: StudioState;
142
+ declare function reduceStudioMessage(state: StudioState, message: StudioServerMessage): StudioState;
143
+ //#endregion
144
+ //#region src/presentation.d.ts
145
+ type StudioPresentationControls = {
146
+ /** Diagram palette authored through `render.theme`. */theme: "dark" | "light";
147
+ direction: "" | "LR" | "RL" | "TD" | "BT";
148
+ density: "" | "compact" | "normal" | "spacious";
149
+ spacingScale: number | null;
150
+ nodePlacement: "" | "straight" | "balanced" | "basic";
151
+ groupLayout: "" | "auto" | "compound" | "flat" | "swimlane";
152
+ modelOrder: "" | "on" | "off";
153
+ groupGap: "" | "40" | "90" | "170";
154
+ edgeGaps: "" | "tight" | "normal" | "wide";
155
+ edgeStyle: "" | "metro" | "rounded" | "orthogonal" | "straight" | "bezier";
156
+ crossings: "" | "jumps" | "smart" | "gaps" | "none";
157
+ roundedCorners: boolean;
158
+ showPorts: boolean;
159
+ showBounds: boolean;
160
+ };
161
+ declare const DEFAULT_STUDIO_PRESENTATION: StudioPresentationControls;
162
+ declare function buildStudioRenderOptions(options: StudioPresentationControls, diagramTheme?: ThemeMode): InteractiveRenderOptions;
163
+ //#endregion
164
+ //#region src/source-settings.d.ts
165
+ type StudioSourceSetting = "theme" | "direction" | "density" | "groupLayout" | "edgeStyle" | "crossings";
166
+ type StudioSourceSettings = Pick<StudioPresentationControls, StudioSourceSetting>;
167
+ declare function readStudioSourceSettings(source: string): StudioSourceSettings;
168
+ /** Apply one end-user diagram setting as a minimal source edit. Empty values remove the setting. */
169
+ declare function updateStudioSourceSetting(source: string, setting: StudioSourceSetting, value: string): string;
170
+ declare function resetStudioSourceSettings(source: string): string;
171
+ //#endregion
172
+ export { DEFAULT_STUDIO_PRESENTATION, INITIAL_STUDIO_STATE, STUDIO_PROTOCOL_VERSION, type StudioCapabilities, type StudioClientMessage, type StudioDocument, type StudioPresentation, type StudioPresentationControls, type StudioPreviewCoordinator, type StudioRender, type StudioRenderer, type StudioSelection, type StudioServerMessage, type StudioSourceSetting, type StudioSourceSettings, type StudioState, type StudioViewport, buildStudioRenderOptions, createStudioPreviewCoordinator, parseStudioClientMessage, readStudioSourceSettings, reduceStudioMessage, resetStudioSourceSettings, studioMessageJson, updateStudioSourceSetting };
173
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,457 @@
1
+ import { parse } from "@kekonic/diagrams-core";
2
+ //#region src/protocol.ts
3
+ const STUDIO_PROTOCOL_VERSION = 1;
4
+ function parseStudioClientMessage(value) {
5
+ if (!isRecord(value) || value.version !== 1 || typeof value.type !== "string") throw new Error("Expected a version 1 studio message");
6
+ switch (value.type) {
7
+ case "open": return {
8
+ version: 1,
9
+ type: "open",
10
+ documentId: requiredString(value.documentId, "documentId")
11
+ };
12
+ case "source":
13
+ case "save": return {
14
+ version: 1,
15
+ type: value.type,
16
+ documentId: requiredString(value.documentId, "documentId"),
17
+ revision: requiredRevision(value.revision),
18
+ source: requiredString(value.source, "source", true)
19
+ };
20
+ case "selection": return {
21
+ version: 1,
22
+ type: "selection",
23
+ selection: parseSelection(value.selection)
24
+ };
25
+ case "viewport": return {
26
+ version: 1,
27
+ type: "viewport",
28
+ viewport: parseViewport(value.viewport)
29
+ };
30
+ case "presentation": return {
31
+ version: 1,
32
+ type: "presentation",
33
+ presentation: parsePresentation(value.presentation)
34
+ };
35
+ default: throw new Error(`Unknown studio message type: ${value.type}`);
36
+ }
37
+ }
38
+ function studioMessageJson(message) {
39
+ return JSON.stringify(message);
40
+ }
41
+ function parseSelection(value) {
42
+ if (!isRecord(value)) throw new Error("selection must be an object");
43
+ const selection = { documentId: requiredString(value.documentId, "selection.documentId") };
44
+ if (value.range != null) selection.range = value.range;
45
+ if (isRecord(value.graphElement)) {
46
+ const type = value.graphElement.type;
47
+ if (type !== "node" && type !== "edge") throw new Error("Invalid graph element type");
48
+ selection.graphElement = {
49
+ type,
50
+ id: requiredString(value.graphElement.id, "selection.graphElement.id")
51
+ };
52
+ }
53
+ return selection;
54
+ }
55
+ function parseViewport(value) {
56
+ if (!isRecord(value)) throw new Error("viewport must be an object");
57
+ const zoom = requiredFinite(value.zoom, "viewport.zoom");
58
+ if (zoom < .1 || zoom > 8) throw new Error("viewport.zoom must be between 0.1 and 8");
59
+ return {
60
+ zoom,
61
+ x: requiredFinite(value.x, "viewport.x"),
62
+ y: requiredFinite(value.y, "viewport.y")
63
+ };
64
+ }
65
+ function parsePresentation(value) {
66
+ if (!isRecord(value) || value.theme !== "dark" && value.theme !== "light") throw new Error("presentation.theme must be dark or light");
67
+ if (!isRecord(value.options)) throw new Error("presentation.options must be an object");
68
+ return {
69
+ theme: value.theme,
70
+ options: value.options
71
+ };
72
+ }
73
+ function requiredString(value, label, allowEmpty = false) {
74
+ if (typeof value !== "string" || !allowEmpty && value.length === 0) throw new Error(`${label} must be a string`);
75
+ return value;
76
+ }
77
+ function requiredRevision(value) {
78
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error("revision must be a non-negative safe integer");
79
+ return value;
80
+ }
81
+ function requiredFinite(value, label) {
82
+ if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${label} must be finite`);
83
+ return value;
84
+ }
85
+ function isRecord(value) {
86
+ return value != null && typeof value === "object" && !Array.isArray(value);
87
+ }
88
+ //#endregion
89
+ //#region src/preview.ts
90
+ /**
91
+ * Drops stale async renders so every host observes monotonically newer document results.
92
+ * Rendering stays injected, keeping this coordinator browser-safe and renderer-neutral.
93
+ */
94
+ function createStudioPreviewCoordinator(renderer) {
95
+ const generations = /* @__PURE__ */ new Map();
96
+ return {
97
+ async render(documentId, revision, source, options) {
98
+ const generation = (generations.get(documentId) ?? 0) + 1;
99
+ generations.set(documentId, generation);
100
+ const result = await renderer(source, options);
101
+ if (generations.get(documentId) !== generation) return void 0;
102
+ return {
103
+ documentId,
104
+ revision,
105
+ ok: result.ok,
106
+ svg: result.svg,
107
+ diagnostics: uniqueDiagnostics(result.diagnostics),
108
+ graph: result.graph,
109
+ stats: result.stats
110
+ };
111
+ },
112
+ invalidate(documentId) {
113
+ generations.set(documentId, (generations.get(documentId) ?? 0) + 1);
114
+ }
115
+ };
116
+ }
117
+ function uniqueDiagnostics(diagnostics) {
118
+ const seen = /* @__PURE__ */ new Set();
119
+ return diagnostics.filter((diagnostic) => {
120
+ const { start, end } = diagnostic.range;
121
+ const key = `${diagnostic.code}:${diagnostic.severity}:${start.offset}:${end.offset}:${diagnostic.message}`;
122
+ if (seen.has(key)) return false;
123
+ seen.add(key);
124
+ return true;
125
+ });
126
+ }
127
+ //#endregion
128
+ //#region src/state.ts
129
+ const INITIAL_STUDIO_STATE = {
130
+ connected: false,
131
+ documents: [],
132
+ capabilities: {
133
+ write: false,
134
+ export: true
135
+ },
136
+ presentation: {
137
+ theme: "dark",
138
+ options: { theme: "dark" }
139
+ },
140
+ viewport: {
141
+ zoom: 1,
142
+ x: 0,
143
+ y: 0
144
+ }
145
+ };
146
+ function reduceStudioMessage(state, message) {
147
+ switch (message.type) {
148
+ case "ready": return {
149
+ ...state,
150
+ connected: true,
151
+ sessionId: message.sessionId,
152
+ documents: message.documents,
153
+ activeDocumentId: message.activeDocumentId,
154
+ capabilities: message.capabilities,
155
+ presentation: message.presentation,
156
+ error: void 0
157
+ };
158
+ case "document": {
159
+ const documents = state.documents.some((item) => item.id === message.id) ? state.documents.map((item) => item.id === message.id ? documentFromMessage(message) : item) : [...state.documents, documentFromMessage(message)];
160
+ return {
161
+ ...state,
162
+ documents,
163
+ activeDocumentId: message.id,
164
+ error: void 0
165
+ };
166
+ }
167
+ case "render":
168
+ if (message.documentId !== state.activeDocumentId) return state;
169
+ if (state.render && message.revision < state.render.revision) return state;
170
+ return {
171
+ ...state,
172
+ render: message
173
+ };
174
+ case "selection": return {
175
+ ...state,
176
+ selection: message.selection
177
+ };
178
+ case "viewport": return {
179
+ ...state,
180
+ viewport: message.viewport
181
+ };
182
+ case "presentation": return {
183
+ ...state,
184
+ presentation: message.presentation
185
+ };
186
+ case "saved": return state;
187
+ case "error": return {
188
+ ...state,
189
+ error: {
190
+ code: message.code,
191
+ message: message.message
192
+ }
193
+ };
194
+ }
195
+ }
196
+ function documentFromMessage(message) {
197
+ return {
198
+ id: message.id,
199
+ path: message.path,
200
+ label: message.label,
201
+ revision: message.revision,
202
+ source: message.source
203
+ };
204
+ }
205
+ //#endregion
206
+ //#region src/presentation.ts
207
+ const DEFAULT_STUDIO_PRESENTATION = {
208
+ theme: "dark",
209
+ direction: "",
210
+ density: "",
211
+ spacingScale: null,
212
+ nodePlacement: "",
213
+ groupLayout: "",
214
+ modelOrder: "",
215
+ groupGap: "",
216
+ edgeGaps: "",
217
+ edgeStyle: "",
218
+ crossings: "",
219
+ roundedCorners: false,
220
+ showPorts: false,
221
+ showBounds: false
222
+ };
223
+ const EDGE_GAP_PRESETS = {
224
+ tight: {
225
+ edgeNodeSpacing: 28,
226
+ edgeEdgeSpacing: 14,
227
+ edgeLabelSpacing: 10
228
+ },
229
+ normal: {
230
+ edgeNodeSpacing: 36,
231
+ edgeEdgeSpacing: 28,
232
+ edgeLabelSpacing: 16
233
+ },
234
+ wide: {
235
+ edgeNodeSpacing: 72,
236
+ edgeEdgeSpacing: 56,
237
+ edgeLabelSpacing: 28
238
+ }
239
+ };
240
+ function buildStudioRenderOptions(options, diagramTheme = options.theme) {
241
+ const layout = {};
242
+ if (options.spacingScale != null) layout.spacingScale = options.spacingScale;
243
+ if (options.density) layout.density = options.density;
244
+ if (options.direction) layout.direction = options.direction;
245
+ if (options.nodePlacement) layout.nodePlacement = options.nodePlacement;
246
+ if (options.groupLayout) layout.groupLayout = options.groupLayout;
247
+ if (options.modelOrder === "on") layout.considerModelOrder = true;
248
+ if (options.modelOrder === "off") layout.considerModelOrder = false;
249
+ if (options.groupGap) layout.groupGap = Number(options.groupGap);
250
+ if (options.edgeGaps) Object.assign(layout, EDGE_GAP_PRESETS[options.edgeGaps]);
251
+ const edges = {};
252
+ if (options.edgeStyle) edges.route = options.edgeStyle;
253
+ if (options.crossings) edges.crossings = options.crossings;
254
+ return {
255
+ theme: diagramTheme,
256
+ layout,
257
+ edges,
258
+ ...options.roundedCorners ? { roundedCorners: true } : {},
259
+ debug: {
260
+ showPorts: options.showPorts,
261
+ showBounds: options.showBounds
262
+ }
263
+ };
264
+ }
265
+ //#endregion
266
+ //#region src/source-settings.ts
267
+ const EMPTY_SETTINGS = {
268
+ theme: "dark",
269
+ direction: "",
270
+ density: "",
271
+ groupLayout: "",
272
+ edgeStyle: "",
273
+ crossings: ""
274
+ };
275
+ function readStudioSourceSettings(source) {
276
+ const diagram = firstDiagram(source);
277
+ if (!diagram) return { ...EMPTY_SETTINGS };
278
+ const result = { ...EMPTY_SETTINGS };
279
+ for (const statement of diagram.statements) if (statement.type === "Directive" && statement.name === "direction") result.direction = oneOf(String(statement.value ?? ""), [
280
+ "LR",
281
+ "RL",
282
+ "TD",
283
+ "BT"
284
+ ]);
285
+ else if (statement.type === "Directive" && statement.name === "density") result.density = oneOf(String(statement.value ?? ""), [
286
+ "compact",
287
+ "normal",
288
+ "spacious"
289
+ ]);
290
+ else if (statement.type === "LayoutBlock") {
291
+ const direction = stringProperty(statement.properties, "direction");
292
+ const density = stringProperty(statement.properties, "density");
293
+ if (direction) result.direction = oneOf(direction, [
294
+ "LR",
295
+ "RL",
296
+ "TD",
297
+ "BT"
298
+ ]);
299
+ if (density) result.density = oneOf(density, [
300
+ "compact",
301
+ "normal",
302
+ "spacious"
303
+ ]);
304
+ const groupLayout = stringProperty(statement.properties, "groupLayout");
305
+ if (groupLayout) result.groupLayout = oneOf(groupLayout, [
306
+ "auto",
307
+ "compound",
308
+ "flat",
309
+ "swimlane"
310
+ ]);
311
+ } else if (statement.type === "EdgePolicyBlock") {
312
+ const edgeStyle = stringProperty(statement.properties, "route");
313
+ const crossings = stringProperty(statement.properties, "crossings");
314
+ if (edgeStyle) result.edgeStyle = oneOf(edgeStyle, [
315
+ "metro",
316
+ "rounded",
317
+ "orthogonal",
318
+ "straight",
319
+ "bezier"
320
+ ]);
321
+ if (crossings) result.crossings = oneOf(crossings, [
322
+ "jumps",
323
+ "smart",
324
+ "gaps",
325
+ "none"
326
+ ]);
327
+ } else if (statement.type === "RenderBlock") result.theme = oneOf(String(statement.properties.theme ?? "dark"), ["dark", "light"]) || "dark";
328
+ return result;
329
+ }
330
+ /** Apply one end-user diagram setting as a minimal source edit. Empty values remove the setting. */
331
+ function updateStudioSourceSetting(source, setting, value) {
332
+ const diagram = firstDiagram(source);
333
+ if (!diagram) return source;
334
+ if (setting === "direction" || setting === "density") {
335
+ const layout = diagram.statements.find((item) => item.type === "LayoutBlock");
336
+ if (value && layout?.properties[setting] != null) return updateBlock(source, diagram, "LayoutBlock", "layout", setting, value);
337
+ const withoutDirective = updateDirective(source, diagram, setting, value);
338
+ if (value) return withoutDirective;
339
+ const reparsed = firstDiagram(withoutDirective);
340
+ return reparsed ? updateBlock(withoutDirective, reparsed, "LayoutBlock", "layout", setting, "") : withoutDirective;
341
+ }
342
+ if (setting === "groupLayout") return updateBlock(source, diagram, "LayoutBlock", "layout", "groupLayout", value);
343
+ if (setting === "theme") return updateBlock(source, diagram, "RenderBlock", "render", "theme", value);
344
+ return updateBlock(source, diagram, "EdgePolicyBlock", "edges", setting === "edgeStyle" ? "route" : "crossings", value);
345
+ }
346
+ function resetStudioSourceSettings(source) {
347
+ return Object.keys(EMPTY_SETTINGS).reduce((current, setting) => updateStudioSourceSetting(current, setting, ""), source);
348
+ }
349
+ function firstDiagram(source) {
350
+ const first = parse(source).body[0];
351
+ return first?.type === "Diagram" ? first : null;
352
+ }
353
+ function updateDirective(source, diagram, name, value) {
354
+ const statement = diagram.statements.find((item) => item.type === "Directive" && item.name === name);
355
+ if (statement) {
356
+ if (!value) return removePolicyStatement(source, diagram, statement);
357
+ return replaceRange(source, statement.range.start.offset, statement.range.end.offset, `${name} ${value}`);
358
+ }
359
+ return value ? insertPolicyStatement(source, diagram, `${name} ${value}`, policyRank(name)) : source;
360
+ }
361
+ function updateBlock(source, diagram, type, keyword, property, value) {
362
+ const block = diagram.statements.find((item) => item.type === type);
363
+ if (!block) return value ? insertPolicyStatement(source, diagram, printBlock(keyword, { [property]: value }), policyRank(keyword)) : source;
364
+ const blockSource = source.slice(block.range.start.offset, block.range.end.offset);
365
+ const openingBrace = blockSource.indexOf("{");
366
+ const closingBrace = blockSource.lastIndexOf("}");
367
+ if (!blockSource.slice(openingBrace + 1, closingBrace).includes("\n")) {
368
+ const properties = { ...block.properties };
369
+ if (value) properties[property] = value;
370
+ else delete properties[property];
371
+ if (Object.keys(properties).length === 0) return removePolicyStatement(source, diagram, block);
372
+ return replaceRange(source, block.range.start.offset, block.range.end.offset, printBlock(keyword, properties));
373
+ }
374
+ const propertyLine = new RegExp(`^([\\t ]*${escapeRegex(property)}[\\t ]*:[\\t ]*)(.*?)([\\t ]*//.*)?$`, "m");
375
+ const match = propertyLine.exec(blockSource);
376
+ if (match) {
377
+ const updatedBlock = value ? blockSource.replace(propertyLine, `$1${value}$3`) : removeMatchedLine(blockSource, match.index, match[0].length);
378
+ if (!updatedBlock.slice(updatedBlock.indexOf("{") + 1, updatedBlock.lastIndexOf("}")).trim()) return removePolicyStatement(source, diagram, block);
379
+ return replaceRange(source, block.range.start.offset, block.range.end.offset, updatedBlock);
380
+ }
381
+ if (!value) return source;
382
+ const addition = `${indentationAt(source, block.range.start.offset)} ${property}: ${value}\n`;
383
+ const updatedBlock = `${blockSource.slice(0, closingBrace)}${addition}${blockSource.slice(closingBrace)}`;
384
+ return replaceRange(source, block.range.start.offset, block.range.end.offset, updatedBlock);
385
+ }
386
+ function printBlock(keyword, properties, indent = "") {
387
+ return `${keyword} {\n${Object.entries(properties).map(([key, value]) => `${indent} ${key}: ${printValue(value)}`).join("\n")}\n${indent}}`;
388
+ }
389
+ function insertPolicyStatement(source, diagram, statement, rank) {
390
+ const indent = `${indentationAt(source, diagram.range.start.offset)} `;
391
+ const rendered = statement.split("\n").map((line) => `${indent}${line}`).join("\n");
392
+ const target = diagram.statements.find((item) => policyRank(item) > rank);
393
+ if (target) {
394
+ const insertionOffset = source.lastIndexOf("\n", target.range.start.offset - 1) + 1;
395
+ const separatesContent = policyRank(target) === Number.POSITIVE_INFINITY;
396
+ return `${source.slice(0, insertionOffset)}${rendered}\n${separatesContent ? "\n" : ""}${source.slice(insertionOffset)}`;
397
+ }
398
+ const closingBrace = source.lastIndexOf("}", diagram.range.end.offset);
399
+ if (closingBrace < 0) return source;
400
+ return `${source.slice(0, closingBrace)}${rendered}\n${source.slice(closingBrace)}`;
401
+ }
402
+ function policyRank(value) {
403
+ const name = typeof value === "string" ? value : value.type === "Directive" ? value.name : value.type === "LayoutBlock" ? "layout" : value.type === "EdgePolicyBlock" ? "edges" : value.type === "RenderBlock" ? "render" : value.type === "PresentationBlock" ? "presentation" : "content";
404
+ const order = [
405
+ "direction",
406
+ "density",
407
+ "layout",
408
+ "edges",
409
+ "render",
410
+ "presentation"
411
+ ];
412
+ return order.indexOf(name) === -1 ? Number.POSITIVE_INFINITY : order.indexOf(name);
413
+ }
414
+ function removeStatementLine(source, statement) {
415
+ const start = source.lastIndexOf("\n", statement.range.start.offset - 1) + 1;
416
+ const nextNewline = source.indexOf("\n", statement.range.end.offset);
417
+ return replaceRange(source, start, nextNewline < 0 ? statement.range.end.offset : nextNewline + 1, "");
418
+ }
419
+ function removePolicyStatement(source, diagram, statement) {
420
+ const updated = removeStatementLine(source, statement);
421
+ if (diagram.statements.some((item) => item !== statement && policyRank(item) < Number.POSITIVE_INFINITY)) return updated;
422
+ const firstStatement = firstDiagram(updated)?.statements[0];
423
+ if (!firstStatement) return updated;
424
+ const lineStart = updated.lastIndexOf("\n", firstStatement.range.start.offset - 1) + 1;
425
+ const preceding = updated.slice(0, lineStart);
426
+ return preceding.endsWith("\n\n") ? `${preceding.slice(0, -1)}${updated.slice(lineStart)}` : updated;
427
+ }
428
+ function indentationAt(source, offset) {
429
+ const lineStart = source.lastIndexOf("\n", offset - 1) + 1;
430
+ return /^\s*/.exec(source.slice(lineStart, offset))?.[0] ?? "";
431
+ }
432
+ function replaceRange(source, start, end, replacement) {
433
+ return `${source.slice(0, start)}${replacement}${source.slice(end)}`;
434
+ }
435
+ function stringProperty(properties, name) {
436
+ const value = properties[name];
437
+ return typeof value === "string" ? value : "";
438
+ }
439
+ function printValue(value) {
440
+ return Array.isArray(value) ? `[${value.join(", ")}]` : String(value);
441
+ }
442
+ function removeMatchedLine(source, start, length) {
443
+ const lineStart = source.lastIndexOf("\n", start - 1) + 1;
444
+ const nextNewline = source.indexOf("\n", start + length);
445
+ const lineEnd = nextNewline < 0 ? start + length : nextNewline + 1;
446
+ return `${source.slice(0, lineStart)}${source.slice(lineEnd)}`;
447
+ }
448
+ function escapeRegex(value) {
449
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
450
+ }
451
+ function oneOf(value, allowed) {
452
+ return allowed.includes(value) ? value : "";
453
+ }
454
+ //#endregion
455
+ export { DEFAULT_STUDIO_PRESENTATION, INITIAL_STUDIO_STATE, STUDIO_PROTOCOL_VERSION, buildStudioRenderOptions, createStudioPreviewCoordinator, parseStudioClientMessage, readStudioSourceSettings, reduceStudioMessage, resetStudioSourceSettings, studioMessageJson, updateStudioSourceSetting };
456
+
457
+ //# sourceMappingURL=index.mjs.map