@povio/ui 5.0.0-rc.11 → 5.0.0-rc.12

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.
@@ -1,44 +1,19 @@
1
1
  import { t as createLucideIcon } from "./createLucideIcon-LcezOE4z.js";
2
- import { a as getBoxHighlights, d as Pin, f as Moon, l as useUIConfiguration, o as HighlightOverlay, r as inspectElement, s as InspectorDetails, t as UIConfigurationModal, u as Sun } from "./UIConfigurator-C_2kgJtm.js";
2
+ import { a as inspectElement, c as HighlightOverlay, d as emptyConfiguration, f as useUIConfiguration, g as Moon, h as Pin, i as saveThemeCss, l as InspectorDetails, m as Plus, p as Sun, r as getThemeWriter, s as getBoxHighlights, t as UIConfigurationModal } from "./UIConfigurator-CBU0G6rb.js";
3
3
  import { t as ChevronDown } from "./chevron-down-B4Pr8bD3.js";
4
4
  import { t as ChevronRight } from "./chevron-right-3nKJR5Bb.js";
5
5
  import { i as Search } from "./selectDesktop.cva-CHAMxhfO.js";
6
6
  import { t as Upload } from "./upload-CXU83BJ4.js";
7
7
  import { t as X } from "./x-B6vfz8Ps.js";
8
- import { c as isEditableToken } from "./theme-model-DtTsIxou.js";
8
+ import { c as $542a13ca2fa5b484$export$5b6b19405a83ff9d, n as $f2ff30fde7b014be$export$3ddf2d174ce01153 } from "./Dialog-CKLkS00H.js";
9
+ import { t as $2ec61d1d4f780267$export$a8a3e93435678ff9 } from "./Heading-nI1_VOaE.js";
10
+ import { t as Select } from "./Select-CgIhoWgk.js";
11
+ import { t as ThemeContext } from "./theme.context-B3wQDJ-i.js";
12
+ import { c as isEditableToken, i as exportCss, v as parseThemeCss } from "./theme-model-DtTsIxou.js";
9
13
  import { t as componentArtboards } from "./groups-oQmSeqXn.js";
10
- import { Suspense, lazy, memo, useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
14
+ import { Suspense, lazy, memo, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from "react";
11
15
  import { c } from "react/compiler-runtime";
12
16
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
13
- //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/code-xml.mjs
14
- /**
15
- * @license lucide-react v1.43.0 - ISC
16
- *
17
- * This source code is licensed under the ISC license.
18
- * See the LICENSE file in the root directory of this source tree.
19
- */
20
- const __iconData$9 = {
21
- name: "code-xml",
22
- size: 24,
23
- node: [
24
- ["path", {
25
- d: "m18 16 4-4-4-4",
26
- key: "1inbqp"
27
- }],
28
- ["path", {
29
- d: "m6 8-4 4 4 4",
30
- key: "15zrgr"
31
- }],
32
- ["path", {
33
- d: "m14.5 4-5 16",
34
- key: "e7oirm"
35
- }]
36
- ],
37
- aliases: ["code-2"]
38
- };
39
- __iconData$9.node;
40
- const CodeXml = createLucideIcon(__iconData$9);
41
- //#endregion
42
17
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/download.mjs
43
18
  /**
44
19
  * @license lucide-react v1.43.0 - ISC
@@ -46,7 +21,7 @@ const CodeXml = createLucideIcon(__iconData$9);
46
21
  * This source code is licensed under the ISC license.
47
22
  * See the LICENSE file in the root directory of this source tree.
48
23
  */
49
- const __iconData$8 = {
24
+ const __iconData$10 = {
50
25
  name: "download",
51
26
  size: 24,
52
27
  node: [
@@ -64,8 +39,8 @@ const __iconData$8 = {
64
39
  }]
65
40
  ]
66
41
  };
67
- __iconData$8.node;
68
- const Download = createLucideIcon(__iconData$8);
42
+ __iconData$10.node;
43
+ const Download = createLucideIcon(__iconData$10);
69
44
  //#endregion
70
45
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/frame.mjs
71
46
  /**
@@ -74,7 +49,7 @@ const Download = createLucideIcon(__iconData$8);
74
49
  * This source code is licensed under the ISC license.
75
50
  * See the LICENSE file in the root directory of this source tree.
76
51
  */
77
- const __iconData$7 = {
52
+ const __iconData$9 = {
78
53
  name: "frame",
79
54
  size: 24,
80
55
  node: [
@@ -108,8 +83,8 @@ const __iconData$7 = {
108
83
  }]
109
84
  ]
110
85
  };
111
- __iconData$7.node;
112
- const Frame = createLucideIcon(__iconData$7);
86
+ __iconData$9.node;
87
+ const Frame = createLucideIcon(__iconData$9);
113
88
  //#endregion
114
89
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/hand.mjs
115
90
  /**
@@ -118,7 +93,7 @@ const Frame = createLucideIcon(__iconData$7);
118
93
  * This source code is licensed under the ISC license.
119
94
  * See the LICENSE file in the root directory of this source tree.
120
95
  */
121
- const __iconData$6 = {
96
+ const __iconData$8 = {
122
97
  name: "hand",
123
98
  size: 24,
124
99
  node: [
@@ -140,8 +115,8 @@ const __iconData$6 = {
140
115
  }]
141
116
  ]
142
117
  };
143
- __iconData$6.node;
144
- const Hand = createLucideIcon(__iconData$6);
118
+ __iconData$8.node;
119
+ const Hand = createLucideIcon(__iconData$8);
145
120
  //#endregion
146
121
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.mjs
147
122
  /**
@@ -150,7 +125,7 @@ const Hand = createLucideIcon(__iconData$6);
150
125
  * This source code is licensed under the ISC license.
151
126
  * See the LICENSE file in the root directory of this source tree.
152
127
  */
153
- const __iconData$5 = {
128
+ const __iconData$7 = {
154
129
  name: "monitor",
155
130
  size: 24,
156
131
  node: [
@@ -178,8 +153,8 @@ const __iconData$5 = {
178
153
  }]
179
154
  ]
180
155
  };
181
- __iconData$5.node;
182
- const Monitor = createLucideIcon(__iconData$5);
156
+ __iconData$7.node;
157
+ const Monitor = createLucideIcon(__iconData$7);
183
158
  //#endregion
184
159
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mouse-pointer-2.mjs
185
160
  /**
@@ -188,7 +163,7 @@ const Monitor = createLucideIcon(__iconData$5);
188
163
  * This source code is licensed under the ISC license.
189
164
  * See the LICENSE file in the root directory of this source tree.
190
165
  */
191
- const __iconData$4 = {
166
+ const __iconData$6 = {
192
167
  name: "mouse-pointer-2",
193
168
  size: 24,
194
169
  node: [["path", {
@@ -196,8 +171,8 @@ const __iconData$4 = {
196
171
  key: "edeuup"
197
172
  }]]
198
173
  };
199
- __iconData$4.node;
200
- const MousePointer2 = createLucideIcon(__iconData$4);
174
+ __iconData$6.node;
175
+ const MousePointer2 = createLucideIcon(__iconData$6);
201
176
  //#endregion
202
177
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/panel-left.mjs
203
178
  /**
@@ -206,7 +181,7 @@ const MousePointer2 = createLucideIcon(__iconData$4);
206
181
  * This source code is licensed under the ISC license.
207
182
  * See the LICENSE file in the root directory of this source tree.
208
183
  */
209
- const __iconData$3 = {
184
+ const __iconData$5 = {
210
185
  name: "panel-left",
211
186
  size: 24,
212
187
  node: [["rect", {
@@ -222,8 +197,8 @@ const __iconData$3 = {
222
197
  }]],
223
198
  aliases: ["sidebar"]
224
199
  };
225
- __iconData$3.node;
226
- const PanelLeft = createLucideIcon(__iconData$3);
200
+ __iconData$5.node;
201
+ const PanelLeft = createLucideIcon(__iconData$5);
227
202
  //#endregion
228
203
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/panels-top-left.mjs
229
204
  /**
@@ -232,7 +207,7 @@ const PanelLeft = createLucideIcon(__iconData$3);
232
207
  * This source code is licensed under the ISC license.
233
208
  * See the LICENSE file in the root directory of this source tree.
234
209
  */
235
- const __iconData$2 = {
210
+ const __iconData$4 = {
236
211
  name: "panels-top-left",
237
212
  size: 24,
238
213
  node: [
@@ -255,8 +230,29 @@ const __iconData$2 = {
255
230
  ],
256
231
  aliases: ["layout"]
257
232
  };
258
- __iconData$2.node;
259
- const PanelsTopLeft = createLucideIcon(__iconData$2);
233
+ __iconData$4.node;
234
+ const PanelsTopLeft = createLucideIcon(__iconData$4);
235
+ //#endregion
236
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/pencil.mjs
237
+ /**
238
+ * @license lucide-react v1.43.0 - ISC
239
+ *
240
+ * This source code is licensed under the ISC license.
241
+ * See the LICENSE file in the root directory of this source tree.
242
+ */
243
+ const __iconData$3 = {
244
+ name: "pencil",
245
+ size: 24,
246
+ node: [["path", {
247
+ d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
248
+ key: "1a8usu"
249
+ }], ["path", {
250
+ d: "m15 5 4 4",
251
+ key: "1mk7zo"
252
+ }]]
253
+ };
254
+ __iconData$3.node;
255
+ const Pencil = createLucideIcon(__iconData$3);
260
256
  //#endregion
261
257
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/scan-line.mjs
262
258
  /**
@@ -265,7 +261,7 @@ const PanelsTopLeft = createLucideIcon(__iconData$2);
265
261
  * This source code is licensed under the ISC license.
266
262
  * See the LICENSE file in the root directory of this source tree.
267
263
  */
268
- const __iconData$1 = {
264
+ const __iconData$2 = {
269
265
  name: "scan-line",
270
266
  size: 24,
271
267
  node: [
@@ -291,8 +287,8 @@ const __iconData$1 = {
291
287
  }]
292
288
  ]
293
289
  };
294
- __iconData$1.node;
295
- const ScanLine = createLucideIcon(__iconData$1);
290
+ __iconData$2.node;
291
+ const ScanLine = createLucideIcon(__iconData$2);
296
292
  //#endregion
297
293
  //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/settings-2.mjs
298
294
  /**
@@ -301,7 +297,7 @@ const ScanLine = createLucideIcon(__iconData$1);
301
297
  * This source code is licensed under the ISC license.
302
298
  * See the LICENSE file in the root directory of this source tree.
303
299
  */
304
- const __iconData = {
300
+ const __iconData$1 = {
305
301
  name: "settings-2",
306
302
  size: 24,
307
303
  node: [
@@ -327,8 +323,937 @@ const __iconData = {
327
323
  }]
328
324
  ]
329
325
  };
326
+ __iconData$1.node;
327
+ const Settings2 = createLucideIcon(__iconData$1);
328
+ //#endregion
329
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/trash.mjs
330
+ /**
331
+ * @license lucide-react v1.43.0 - ISC
332
+ *
333
+ * This source code is licensed under the ISC license.
334
+ * See the LICENSE file in the root directory of this source tree.
335
+ */
336
+ const __iconData = {
337
+ name: "trash",
338
+ size: 24,
339
+ node: [
340
+ ["path", {
341
+ d: "M10 11v6",
342
+ key: "nco0om"
343
+ }],
344
+ ["path", {
345
+ d: "M14 11v6",
346
+ key: "outv1u"
347
+ }],
348
+ ["path", {
349
+ d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",
350
+ key: "miytrc"
351
+ }],
352
+ ["path", {
353
+ d: "M3 6h18",
354
+ key: "d0wm0j"
355
+ }],
356
+ ["path", {
357
+ d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",
358
+ key: "e791ji"
359
+ }]
360
+ ],
361
+ aliases: ["trash-2"]
362
+ };
330
363
  __iconData.node;
331
- const Settings2 = createLucideIcon(__iconData);
364
+ const Trash = createLucideIcon(__iconData);
365
+ //#endregion
366
+ //#region src/app-canvas/useWorkspaceConfiguration.ts
367
+ function useWorkspaceConfiguration(workspace, ready, generation, setManifest) {
368
+ const $ = c(15);
369
+ const { currentConfiguration, replaceConfiguration, defaults } = useUIConfiguration();
370
+ const loaded = useRef(null);
371
+ const t0 = workspace.id;
372
+ let t1;
373
+ if ($[0] !== defaults) {
374
+ t1 = JSON.stringify(defaults);
375
+ $[0] = defaults;
376
+ $[1] = t1;
377
+ } else t1 = $[1];
378
+ const key = `${generation}:${t0}:${t1}`;
379
+ let t2;
380
+ if ($[2] !== workspace.configuration) {
381
+ t2 = JSON.stringify(workspace.configuration);
382
+ $[2] = workspace.configuration;
383
+ $[3] = t2;
384
+ } else t2 = $[3];
385
+ const saved = t2;
386
+ let t3;
387
+ let t4;
388
+ if ($[4] !== currentConfiguration || $[5] !== defaults || $[6] !== key || $[7] !== ready || $[8] !== replaceConfiguration || $[9] !== saved || $[10] !== setManifest || $[11] !== workspace.configuration || $[12] !== workspace.id) {
389
+ t3 = () => {
390
+ if (!ready) return;
391
+ if (!loaded.current || loaded.current.key !== key || loaded.current.saved !== saved) {
392
+ const value = workspace.configuration ?? (loaded.current ? emptyConfiguration() : currentConfiguration);
393
+ const switchingWorkspace = !!loaded.current && loaded.current.workspaceId !== workspace.id;
394
+ loaded.current = {
395
+ key,
396
+ workspaceId: workspace.id,
397
+ saved
398
+ };
399
+ replaceConfiguration({
400
+ ...value,
401
+ theme: {
402
+ light: Object.fromEntries(Object.entries(value.theme.light).filter((t5) => {
403
+ const [name, value_0] = t5;
404
+ return value_0 !== defaults.light[name];
405
+ })),
406
+ dark: Object.fromEntries(Object.entries(value.theme.dark).filter((t6) => {
407
+ const [name_0, value_1] = t6;
408
+ return value_1 !== defaults.dark[name_0];
409
+ })),
410
+ theme: Object.fromEntries(Object.entries(value.theme.theme).filter((t7) => {
411
+ const [name_1, value_2] = t7;
412
+ return value_2 !== defaults.theme[name_1];
413
+ }))
414
+ }
415
+ }, switchingWorkspace);
416
+ return;
417
+ }
418
+ const snapshot = {
419
+ ...currentConfiguration,
420
+ theme: {
421
+ light: {
422
+ ...defaults.light,
423
+ ...currentConfiguration.theme.light
424
+ },
425
+ dark: {
426
+ ...defaults.dark,
427
+ ...currentConfiguration.theme.dark
428
+ },
429
+ theme: {
430
+ ...defaults.theme,
431
+ ...currentConfiguration.theme.theme
432
+ }
433
+ }
434
+ };
435
+ const serialized = JSON.stringify(snapshot);
436
+ if (serialized === saved) return;
437
+ loaded.current.saved = serialized;
438
+ setManifest((current) => ({
439
+ ...current,
440
+ workspaces: current.workspaces.map((item) => item.id === workspace.id ? {
441
+ ...item,
442
+ configuration: structuredClone(snapshot)
443
+ } : item)
444
+ }));
445
+ };
446
+ t4 = [
447
+ defaults,
448
+ key,
449
+ saved,
450
+ workspace.id,
451
+ workspace.configuration,
452
+ ready,
453
+ currentConfiguration,
454
+ replaceConfiguration,
455
+ setManifest
456
+ ];
457
+ $[4] = currentConfiguration;
458
+ $[5] = defaults;
459
+ $[6] = key;
460
+ $[7] = ready;
461
+ $[8] = replaceConfiguration;
462
+ $[9] = saved;
463
+ $[10] = setManifest;
464
+ $[11] = workspace.configuration;
465
+ $[12] = workspace.id;
466
+ $[13] = t3;
467
+ $[14] = t4;
468
+ } else {
469
+ t3 = $[13];
470
+ t4 = $[14];
471
+ }
472
+ useLayoutEffect(t3, t4);
473
+ }
474
+ //#endregion
475
+ //#region src/app-canvas/SaveCanvasTheme.tsx
476
+ /** Uses the same development-only writer as the configurator's CSS export. */
477
+ function SaveCanvasTheme() {
478
+ const { active, defaults } = useUIConfiguration();
479
+ const [writer, setWriter] = useState(null);
480
+ const [saving, setSaving] = useState(false);
481
+ const [message, setMessage] = useState("");
482
+ const [savedTheme, setSavedTheme] = useState(defaults);
483
+ useEffect(() => setWriter(getThemeWriter()), []);
484
+ useEffect(() => setSavedTheme(defaults), [defaults]);
485
+ const css = exportCss(active.theme, defaults);
486
+ const exported = parseThemeCss(css);
487
+ const currentTheme = {
488
+ light: {
489
+ ...defaults.light,
490
+ ...exported.light
491
+ },
492
+ dark: {
493
+ ...defaults.dark,
494
+ ...exported.dark
495
+ },
496
+ theme: {
497
+ ...defaults.theme,
498
+ ...exported.theme
499
+ }
500
+ };
501
+ const hasChanges = [
502
+ "light",
503
+ "dark",
504
+ "theme"
505
+ ].some((mode) => {
506
+ return [.../* @__PURE__ */ new Set([...Object.keys(currentTheme[mode]), ...Object.keys(savedTheme[mode])])].some((key) => currentTheme[mode][key]?.trim() !== savedTheme[mode][key]?.trim());
507
+ });
508
+ useEffect(() => setMessage(""), [active.theme]);
509
+ if (!writer || !hasChanges && !saving) return null;
510
+ return /* @__PURE__ */ jsxs("div", {
511
+ className: "povio-canvas-save-theme",
512
+ children: [/* @__PURE__ */ jsx("button", {
513
+ disabled: saving,
514
+ title: `Save current CSS settings to ${writer.file}`,
515
+ onClick: async () => {
516
+ setSaving(true);
517
+ setMessage("");
518
+ try {
519
+ await saveThemeCss(writer, css);
520
+ setSavedTheme(currentTheme);
521
+ setMessage(`Saved CSS to ${writer.file}.`);
522
+ } catch (error) {
523
+ setMessage(error instanceof Error ? error.message : "Could not save CSS settings.");
524
+ } finally {
525
+ setSaving(false);
526
+ }
527
+ },
528
+ children: saving ? "Saving theme…" : "Save theme to repo"
529
+ }), message && /* @__PURE__ */ jsx("p", {
530
+ role: "status",
531
+ children: message
532
+ })]
533
+ });
534
+ }
535
+ //#endregion
536
+ //#region src/app-canvas/manifest.ts
537
+ function createCanvasWorkspace(name, id) {
538
+ return {
539
+ id,
540
+ name: name.trim() || "Workspace",
541
+ enabledPages: null,
542
+ positions: {}
543
+ };
544
+ }
545
+ function createCanvasManifest() {
546
+ return {
547
+ version: 2,
548
+ workspaces: [createCanvasWorkspace("All pages", "default")],
549
+ activeWorkspaceId: "default",
550
+ examples: {},
551
+ breakpoint: "desktop"
552
+ };
553
+ }
554
+ function isRecord(value) {
555
+ return value !== null && typeof value === "object" && !Array.isArray(value);
556
+ }
557
+ function isJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
558
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
559
+ if (typeof value === "number") return Number.isFinite(value);
560
+ if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
561
+ ancestors.add(value);
562
+ const valid = Object.values(value).every((child) => isJSON(child, ancestors, depth + 1));
563
+ ancestors.delete(value);
564
+ return valid;
565
+ }
566
+ function safeJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
567
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
568
+ if (typeof value === "number") return Number.isFinite(value);
569
+ if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
570
+ if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
571
+ ancestors.add(value);
572
+ const valid = Object.entries(value).every(([key, child]) => ![
573
+ "__proto__",
574
+ "prototype",
575
+ "constructor"
576
+ ].includes(key) && safeJSON(child, ancestors, depth + 1));
577
+ ancestors.delete(value);
578
+ return valid;
579
+ }
580
+ const stringMap = (value) => isRecord(value) && Object.values(value).every((item) => typeof item === "string");
581
+ const variantValue = (value) => value === null || typeof value === "string" || typeof value === "boolean";
582
+ function validCvaEdits(value) {
583
+ return isRecord(value) && Object.entries(value).every(([slot, definition]) => {
584
+ if (!/^[a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9]*$/.test(slot) || !isRecord(definition) || !Array.isArray(definition.base) || !definition.base.every((item) => typeof item === "string") || !isRecord(definition.config)) return false;
585
+ const config = definition.config;
586
+ return isRecord(config.variants) && Object.values(config.variants).every(stringMap) && isRecord(config.defaultVariants) && Object.values(config.defaultVariants).every(variantValue) && Array.isArray(config.compoundVariants) && config.compoundVariants.every((compound) => isRecord(compound) && Object.values(compound).every((item) => variantValue(item) || Array.isArray(item) && (item.every((part) => typeof part === "string") || item.every((part) => typeof part === "boolean"))));
587
+ });
588
+ }
589
+ /** Returns an independent JSON configuration, or undefined for invalid/unset overrides. */
590
+ function parseCanvasConfiguration(value) {
591
+ if (!isRecord(value) || !safeJSON(value) || !isRecord(value.theme) || ![
592
+ "light",
593
+ "dark",
594
+ "theme"
595
+ ].every((key) => stringMap(value.theme && value.theme[key])) || !isRecord(value.config) || value.overrides !== void 0 && !validCvaEdits(value.overrides)) return void 0;
596
+ return JSON.parse(JSON.stringify(value));
597
+ }
598
+ function parsePositions(input) {
599
+ return Object.fromEntries(Object.entries(isRecord(input) ? input : {}).flatMap(([breakpoint, entries]) => {
600
+ if (!isRecord(entries)) return [];
601
+ return [[breakpoint, Object.fromEntries(Object.entries(entries).flatMap(([id, point]) => isRecord(point) && typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y) ? [[id, {
602
+ x: point.x,
603
+ y: point.y
604
+ }]] : []))]];
605
+ }));
606
+ }
607
+ /** Sanitize either stored JSON text or an imported value, migrating v1 positions into the default workspace. */
608
+ function parseCanvasManifest(input) {
609
+ const fallback = createCanvasManifest();
610
+ let value = input;
611
+ if (typeof value === "string") try {
612
+ value = JSON.parse(value);
613
+ } catch {
614
+ return fallback;
615
+ }
616
+ if (!isRecord(value) || value.version !== 1 && value.version !== 2) return fallback;
617
+ const examples = Object.fromEntries(Object.entries(isRecord(value.examples) ? value.examples : {}).flatMap(([id, example]) => {
618
+ if (!isRecord(example)) return [];
619
+ const clean = {};
620
+ if (typeof example.title === "string") clean.title = example.title;
621
+ if (isRecord(example.params)) clean.params = Object.fromEntries(Object.entries(example.params).filter((entry) => typeof entry[1] === "string"));
622
+ if (isRecord(example.search)) clean.search = Object.fromEntries(Object.entries(example.search).filter(([, item]) => isJSON(item)));
623
+ return [[id, clean]];
624
+ }));
625
+ const workspaces = [];
626
+ const ids = /* @__PURE__ */ new Set();
627
+ if (value.version === 1) workspaces.push({
628
+ ...createCanvasWorkspace("All pages", "default"),
629
+ positions: parsePositions(value.positions)
630
+ });
631
+ else if (Array.isArray(value.workspaces)) for (const entry of value.workspaces) {
632
+ if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id.trim() || ids.has(entry.id)) continue;
633
+ ids.add(entry.id);
634
+ const configuration = parseCanvasConfiguration(entry.configuration);
635
+ workspaces.push({
636
+ id: entry.id,
637
+ name: typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : "Workspace",
638
+ enabledPages: Array.isArray(entry.enabledPages) ? [...new Set(entry.enabledPages.filter((id) => typeof id === "string" && id.length > 0))] : null,
639
+ positions: parsePositions(entry.positions),
640
+ ...configuration ? { configuration } : {}
641
+ });
642
+ }
643
+ if (!workspaces.length) workspaces.push(createCanvasWorkspace("All pages", "default"));
644
+ return {
645
+ version: 2,
646
+ workspaces,
647
+ activeWorkspaceId: typeof value.activeWorkspaceId === "string" && workspaces.some((workspace) => workspace.id === value.activeWorkspaceId) ? value.activeWorkspaceId : workspaces[0].id,
648
+ examples,
649
+ breakpoint: typeof value.breakpoint === "string" && value.breakpoint.length > 0 ? value.breakpoint : fallback.breakpoint
650
+ };
651
+ }
652
+ //#endregion
653
+ //#region src/app-canvas/WorkspaceControls.tsx
654
+ function WorkspaceControls(t0) {
655
+ const $ = c(81);
656
+ const { manifest, setManifest, pages, disabled } = t0;
657
+ const createTrigger = useRef(null);
658
+ const pagesTrigger = useRef(null);
659
+ const editTrigger = useRef(null);
660
+ let t1;
661
+ if ($[0] !== manifest.activeWorkspaceId) {
662
+ t1 = (item) => item.id === manifest.activeWorkspaceId;
663
+ $[0] = manifest.activeWorkspaceId;
664
+ $[1] = t1;
665
+ } else t1 = $[1];
666
+ const workspace = manifest.workspaces.find(t1);
667
+ const [action, setAction] = useState(null);
668
+ const [name, setName] = useState("");
669
+ const [draftPages, setDraftPages] = useState(null);
670
+ const [query, setQuery] = useState("");
671
+ let t2;
672
+ if ($[2] !== setAction) {
673
+ t2 = () => {
674
+ setAction(null);
675
+ };
676
+ $[2] = setAction;
677
+ $[3] = t2;
678
+ } else t2 = $[3];
679
+ useEffect(t2, [workspace.id]);
680
+ let t3;
681
+ if ($[4] !== disabled || $[5] !== setAction) {
682
+ t3 = () => {
683
+ if (disabled) setAction(null);
684
+ };
685
+ $[4] = disabled;
686
+ $[5] = setAction;
687
+ $[6] = t3;
688
+ } else t3 = $[6];
689
+ let t4;
690
+ if ($[7] !== disabled) {
691
+ t4 = [disabled];
692
+ $[7] = disabled;
693
+ $[8] = t4;
694
+ } else t4 = $[8];
695
+ useEffect(t3, t4);
696
+ const open = (next) => {
697
+ setName(next === "create" ? "" : workspace.name);
698
+ setDraftPages(next === "create" ? [] : workspace.enabledPages);
699
+ setQuery("");
700
+ setAction(next);
701
+ };
702
+ let t5;
703
+ if ($[9] !== draftPages) {
704
+ t5 = (id) => draftPages === null || draftPages.includes(id);
705
+ $[9] = draftPages;
706
+ $[10] = t5;
707
+ } else t5 = $[10];
708
+ const enabled = t5;
709
+ const title = action === "create" ? "Create workspace" : action === "rename" ? "Edit workspace" : action === "delete" ? "Delete workspace?" : "Manage pages";
710
+ const save = () => {
711
+ if (disabled || !action) return;
712
+ if ((action === "create" || action === "rename") && !name.trim()) return;
713
+ const created = action === "create" ? {
714
+ ...createCanvasWorkspace(name.trim(), crypto.randomUUID()),
715
+ enabledPages: draftPages
716
+ } : null;
717
+ setManifest((current) => {
718
+ if (created) return {
719
+ ...current,
720
+ workspaces: [...current.workspaces, created],
721
+ activeWorkspaceId: created.id
722
+ };
723
+ if (action === "delete") {
724
+ if (current.workspaces.length < 2) return current;
725
+ const remaining = current.workspaces.filter((item_0) => item_0.id !== workspace.id);
726
+ return {
727
+ ...current,
728
+ workspaces: remaining,
729
+ activeWorkspaceId: remaining[0].id
730
+ };
731
+ }
732
+ return {
733
+ ...current,
734
+ workspaces: current.workspaces.map((item_1) => item_1.id === workspace.id ? {
735
+ ...item_1,
736
+ ...action === "rename" ? { name: name.trim() } : { enabledPages: draftPages }
737
+ } : item_1)
738
+ };
739
+ });
740
+ setAction(null);
741
+ };
742
+ let t6;
743
+ if ($[11] !== query) {
744
+ t6 = (page) => `${page.title} ${page.path}`.toLowerCase().includes(query.toLowerCase());
745
+ $[11] = query;
746
+ $[12] = t6;
747
+ } else t6 = $[12];
748
+ const visiblePages = pages.filter(t6);
749
+ const t7 = workspace.id;
750
+ let t8;
751
+ if ($[13] !== manifest.workspaces) {
752
+ t8 = manifest.workspaces.map(_temp);
753
+ $[13] = manifest.workspaces;
754
+ $[14] = t8;
755
+ } else t8 = $[14];
756
+ let t9;
757
+ if ($[15] !== setManifest) {
758
+ t9 = (id_0) => {
759
+ if (id_0 !== null) setManifest((current_0) => ({
760
+ ...current_0,
761
+ activeWorkspaceId: String(id_0)
762
+ }));
763
+ };
764
+ $[15] = setManifest;
765
+ $[16] = t9;
766
+ } else t9 = $[16];
767
+ let t10;
768
+ if ($[17] !== open) {
769
+ t10 = () => open("create");
770
+ $[17] = open;
771
+ $[18] = t10;
772
+ } else t10 = $[18];
773
+ let t11;
774
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
775
+ t11 = /* @__PURE__ */ jsx(Plus, { size: 15 });
776
+ $[19] = t11;
777
+ } else t11 = $[19];
778
+ let t12;
779
+ if ($[20] !== t10) {
780
+ t12 = /* @__PURE__ */ jsx("button", {
781
+ ref: createTrigger,
782
+ "aria-label": "Add workspace",
783
+ title: "Add workspace",
784
+ onClick: t10,
785
+ children: t11
786
+ });
787
+ $[20] = t10;
788
+ $[21] = t12;
789
+ } else t12 = $[21];
790
+ let t13;
791
+ if ($[22] !== open) {
792
+ t13 = () => open("rename");
793
+ $[22] = open;
794
+ $[23] = t13;
795
+ } else t13 = $[23];
796
+ let t14;
797
+ if ($[24] === Symbol.for("react.memo_cache_sentinel")) {
798
+ t14 = /* @__PURE__ */ jsx(Pencil, { size: 14 });
799
+ $[24] = t14;
800
+ } else t14 = $[24];
801
+ let t15;
802
+ if ($[25] !== t13) {
803
+ t15 = /* @__PURE__ */ jsx("button", {
804
+ ref: editTrigger,
805
+ "aria-label": "Rename workspace",
806
+ title: "Edit workspace",
807
+ onClick: t13,
808
+ children: t14
809
+ });
810
+ $[25] = t13;
811
+ $[26] = t15;
812
+ } else t15 = $[26];
813
+ let t16;
814
+ if ($[27] !== open) {
815
+ t16 = () => open("pages");
816
+ $[27] = open;
817
+ $[28] = t16;
818
+ } else t16 = $[28];
819
+ const t17 = pages.filter((page_0) => workspace.enabledPages === null || workspace.enabledPages.includes(page_0.id));
820
+ let t18;
821
+ if ($[29] !== pages.length || $[30] !== t17) {
822
+ t18 = /* @__PURE__ */ jsxs("span", { children: [
823
+ t17.length,
824
+ "/",
825
+ pages.length
826
+ ] });
827
+ $[29] = pages.length;
828
+ $[30] = t17;
829
+ $[31] = t18;
830
+ } else t18 = $[31];
831
+ let t19;
832
+ if ($[32] !== t16 || $[33] !== t18) {
833
+ t19 = /* @__PURE__ */ jsx("div", {
834
+ className: "povio-canvas-workspace-actions",
835
+ children: /* @__PURE__ */ jsxs("button", {
836
+ ref: pagesTrigger,
837
+ onClick: t16,
838
+ children: [
839
+ "Manage pages",
840
+ " ",
841
+ t18
842
+ ]
843
+ })
844
+ });
845
+ $[32] = t16;
846
+ $[33] = t18;
847
+ $[34] = t19;
848
+ } else t19 = $[34];
849
+ const T0 = $542a13ca2fa5b484$export$5b6b19405a83ff9d;
850
+ const t20 = action === "create" ? createTrigger : action === "pages" ? pagesTrigger : editTrigger;
851
+ const t21 = "bottom start";
852
+ const t22 = 6;
853
+ const t23 = "povio-canvas-workspace-popover";
854
+ const t24 = action !== null;
855
+ let t25;
856
+ if ($[35] !== setAction) {
857
+ t25 = (isOpen) => {
858
+ if (!isOpen) setAction(null);
859
+ };
860
+ $[35] = setAction;
861
+ $[36] = t25;
862
+ } else t25 = $[36];
863
+ const T1 = $f2ff30fde7b014be$export$3ddf2d174ce01153;
864
+ const t26 = action === "delete" ? "alertdialog" : "dialog";
865
+ let t27;
866
+ if ($[37] !== save) {
867
+ t27 = (event) => {
868
+ event.preventDefault();
869
+ save();
870
+ };
871
+ $[37] = save;
872
+ $[38] = t27;
873
+ } else t27 = $[38];
874
+ let t28;
875
+ if ($[39] !== title) {
876
+ t28 = /* @__PURE__ */ jsx($2ec61d1d4f780267$export$a8a3e93435678ff9, {
877
+ slot: "title",
878
+ children: title
879
+ });
880
+ $[39] = title;
881
+ $[40] = t28;
882
+ } else t28 = $[40];
883
+ let t29;
884
+ if ($[41] !== setAction) {
885
+ t29 = () => setAction(null);
886
+ $[41] = setAction;
887
+ $[42] = t29;
888
+ } else t29 = $[42];
889
+ let t30;
890
+ if ($[43] === Symbol.for("react.memo_cache_sentinel")) {
891
+ t30 = /* @__PURE__ */ jsx(X, { size: 12 });
892
+ $[43] = t30;
893
+ } else t30 = $[43];
894
+ let t31;
895
+ if ($[44] !== t29) {
896
+ t31 = /* @__PURE__ */ jsx("button", {
897
+ type: "button",
898
+ "aria-label": "Close workspace dialog",
899
+ onClick: t29,
900
+ children: t30
901
+ });
902
+ $[44] = t29;
903
+ $[45] = t31;
904
+ } else t31 = $[45];
905
+ let t32;
906
+ if ($[46] !== t28 || $[47] !== t31) {
907
+ t32 = /* @__PURE__ */ jsxs("header", {
908
+ className: "povio-canvas-workspace-popover-heading",
909
+ children: [t28, t31]
910
+ });
911
+ $[46] = t28;
912
+ $[47] = t31;
913
+ $[48] = t32;
914
+ } else t32 = $[48];
915
+ const t33 = action === "delete" ? /* @__PURE__ */ jsxs("p", { children: [
916
+ "Delete “",
917
+ workspace.name,
918
+ "” and its saved layout and theme?"
919
+ ] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [(action === "create" || action === "rename") && /* @__PURE__ */ jsxs("label", { children: ["Workspace name", /* @__PURE__ */ jsx("input", {
920
+ autoFocus: true,
921
+ value: name,
922
+ maxLength: 80,
923
+ placeholder: "Workspace name",
924
+ onChange: (event_0) => setName(event_0.target.value),
925
+ required: true
926
+ })] }), (action === "create" || action === "pages") && /* @__PURE__ */ jsxs("div", {
927
+ role: "group",
928
+ "aria-label": "Enabled pages",
929
+ className: "povio-canvas-popover-pages",
930
+ children: [
931
+ /* @__PURE__ */ jsxs("div", {
932
+ className: "povio-canvas-popover-page-tools",
933
+ children: [
934
+ /* @__PURE__ */ jsxs("span", { children: [
935
+ pages.filter((page_1) => enabled(page_1.id)).length,
936
+ "/",
937
+ pages.length,
938
+ " pages"
939
+ ] }),
940
+ /* @__PURE__ */ jsx("button", {
941
+ type: "button",
942
+ onClick: () => setDraftPages(null),
943
+ children: "Enable all"
944
+ }),
945
+ /* @__PURE__ */ jsx("button", {
946
+ type: "button",
947
+ onClick: () => setDraftPages([]),
948
+ children: "Disable all"
949
+ })
950
+ ]
951
+ }),
952
+ /* @__PURE__ */ jsxs("label", {
953
+ className: "povio-canvas-popover-search",
954
+ children: [/* @__PURE__ */ jsx(Search, { size: 12 }), /* @__PURE__ */ jsx("input", {
955
+ "aria-label": "Find workspace pages",
956
+ placeholder: "Find pages…",
957
+ value: query,
958
+ onChange: (event_1) => setQuery(event_1.target.value)
959
+ })]
960
+ }),
961
+ /* @__PURE__ */ jsxs("div", {
962
+ className: "povio-canvas-popover-page-list",
963
+ children: [visiblePages.map((page_2) => /* @__PURE__ */ jsxs("label", { children: [/* @__PURE__ */ jsx("input", {
964
+ type: "checkbox",
965
+ checked: enabled(page_2.id),
966
+ onChange: (event_2) => {
967
+ const ids = draftPages ?? pages.map(_temp2);
968
+ setDraftPages(event_2.target.checked ? [.../* @__PURE__ */ new Set([...ids, page_2.id])] : ids.filter((id_1) => id_1 !== page_2.id));
969
+ }
970
+ }), /* @__PURE__ */ jsx("span", { children: page_2.title })] }, page_2.id)), !visiblePages.length && /* @__PURE__ */ jsx("p", { children: "No matching pages." })]
971
+ })
972
+ ]
973
+ })] });
974
+ let t34;
975
+ if ($[49] !== action || $[50] !== manifest.workspaces.length || $[51] !== setAction) {
976
+ t34 = action === "rename" ? /* @__PURE__ */ jsxs("button", {
977
+ type: "button",
978
+ className: "povio-canvas-workspace-delete",
979
+ disabled: manifest.workspaces.length < 2,
980
+ onClick: () => setAction("delete"),
981
+ children: [/* @__PURE__ */ jsx(Trash, { size: 12 }), " Delete workspace"]
982
+ }) : /* @__PURE__ */ jsx("button", {
983
+ type: "button",
984
+ autoFocus: action === "delete",
985
+ onClick: () => action === "delete" ? setAction("rename") : setAction(null),
986
+ children: action === "delete" ? "Back" : "Cancel"
987
+ });
988
+ $[49] = action;
989
+ $[50] = manifest.workspaces.length;
990
+ $[51] = setAction;
991
+ $[52] = t34;
992
+ } else t34 = $[52];
993
+ const t35 = action === "delete" ? "is-danger" : "is-primary";
994
+ let t36;
995
+ if ($[53] !== action || $[54] !== disabled || $[55] !== name) {
996
+ t36 = disabled || (action === "create" || action === "rename") && !name.trim();
997
+ $[53] = action;
998
+ $[54] = disabled;
999
+ $[55] = name;
1000
+ $[56] = t36;
1001
+ } else t36 = $[56];
1002
+ const t37 = action === "create" ? "Create workspace" : action === "rename" ? "Save name" : action === "delete" ? "Delete" : "Save pages";
1003
+ let t38;
1004
+ if ($[57] !== t35 || $[58] !== t36 || $[59] !== t37) {
1005
+ t38 = /* @__PURE__ */ jsx("button", {
1006
+ type: "submit",
1007
+ className: t35,
1008
+ disabled: t36,
1009
+ children: t37
1010
+ });
1011
+ $[57] = t35;
1012
+ $[58] = t36;
1013
+ $[59] = t37;
1014
+ $[60] = t38;
1015
+ } else t38 = $[60];
1016
+ let t39;
1017
+ if ($[61] !== t34 || $[62] !== t38) {
1018
+ t39 = /* @__PURE__ */ jsxs("div", {
1019
+ className: "povio-canvas-workspace-popover-actions",
1020
+ children: [t34, t38]
1021
+ });
1022
+ $[61] = t34;
1023
+ $[62] = t38;
1024
+ $[63] = t39;
1025
+ } else t39 = $[63];
1026
+ let t40;
1027
+ if ($[64] !== t27 || $[65] !== t32 || $[66] !== t33 || $[67] !== t39) {
1028
+ t40 = /* @__PURE__ */ jsxs("form", {
1029
+ onSubmit: t27,
1030
+ children: [
1031
+ t32,
1032
+ t33,
1033
+ t39
1034
+ ]
1035
+ });
1036
+ $[64] = t27;
1037
+ $[65] = t32;
1038
+ $[66] = t33;
1039
+ $[67] = t39;
1040
+ $[68] = t40;
1041
+ } else t40 = $[68];
1042
+ let t41;
1043
+ if ($[69] !== T1 || $[70] !== action || $[71] !== t26 || $[72] !== t40 || $[73] !== title) {
1044
+ t41 = /* @__PURE__ */ jsx(T1, {
1045
+ "aria-label": title,
1046
+ role: t26,
1047
+ children: t40
1048
+ }, action);
1049
+ $[69] = T1;
1050
+ $[70] = action;
1051
+ $[71] = t26;
1052
+ $[72] = t40;
1053
+ $[73] = title;
1054
+ $[74] = t41;
1055
+ } else t41 = $[74];
1056
+ let t42;
1057
+ if ($[75] !== T0 || $[76] !== t20 || $[77] !== t24 || $[78] !== t25 || $[79] !== t41) {
1058
+ t42 = /* @__PURE__ */ jsx(T0, {
1059
+ triggerRef: t20,
1060
+ placement: t21,
1061
+ offset: t22,
1062
+ className: t23,
1063
+ isOpen: t24,
1064
+ onOpenChange: t25,
1065
+ children: t41
1066
+ });
1067
+ $[75] = T0;
1068
+ $[76] = t20;
1069
+ $[77] = t24;
1070
+ $[78] = t25;
1071
+ $[79] = t41;
1072
+ $[80] = t42;
1073
+ } else t42 = $[80];
1074
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("fieldset", {
1075
+ className: "povio-canvas-workspaces",
1076
+ disabled,
1077
+ children: [/* @__PURE__ */ jsxs("div", {
1078
+ className: "povio-canvas-workspace-picker",
1079
+ children: [
1080
+ /* @__PURE__ */ jsx("div", {
1081
+ className: "povio-canvas-workspace-select",
1082
+ children: /* @__PURE__ */ jsx(Select, {
1083
+ label: "Workspace",
1084
+ hideLabel: true,
1085
+ size: "small",
1086
+ variant: "outlined",
1087
+ as: "default",
1088
+ className: "povio-canvas-workspace-input",
1089
+ selectionMode: "single",
1090
+ isClearable: false,
1091
+ isDisabled: disabled,
1092
+ renderStaticInput: false,
1093
+ value: t7,
1094
+ items: t8,
1095
+ onChange: t9
1096
+ })
1097
+ }),
1098
+ t12,
1099
+ t15
1100
+ ]
1101
+ }), t19]
1102
+ }), t42] });
1103
+ }
1104
+ function _temp2(item_3) {
1105
+ return item_3.id;
1106
+ }
1107
+ function _temp(item_2) {
1108
+ return {
1109
+ id: item_2.id,
1110
+ label: item_2.name
1111
+ };
1112
+ }
1113
+ const canvasStorageTokenHeader = "x-povio-canvas-token";
1114
+ //#endregion
1115
+ //#region src/app-canvas/useCanvasStorage.ts
1116
+ function connectionFromDocument() {
1117
+ if (typeof document === "undefined") return null;
1118
+ try {
1119
+ const value = JSON.parse(document.querySelector(`meta[name="povio-ui-canvas-storage"]`)?.content ?? "null");
1120
+ return value && typeof value.endpoint === "string" && value.endpoint.startsWith("/") && !value.endpoint.startsWith("//") && typeof value.token === "string" && typeof value.file === "string" ? value : null;
1121
+ } catch {
1122
+ return null;
1123
+ }
1124
+ }
1125
+ function localManifest(key) {
1126
+ try {
1127
+ return parseCanvasManifest(localStorage.getItem(key));
1128
+ } catch {
1129
+ return createCanvasManifest();
1130
+ }
1131
+ }
1132
+ /** Repo is authoritative when connected; local storage remains an offline draft cache. */
1133
+ function useCanvasStorage(key, enabled = true) {
1134
+ const [connection] = useState(() => enabled ? connectionFromDocument() : null);
1135
+ const [manifest, setManifest] = useState(() => localManifest(key));
1136
+ const [ready, setReady] = useState(!connection);
1137
+ const [status, setStatus] = useState(connection ? "Loading workspace…" : "Saved in this browser");
1138
+ const [error, setError] = useState("");
1139
+ const latest = useRef(manifest);
1140
+ latest.current = manifest;
1141
+ const revision = useRef(null);
1142
+ const saved = useRef("");
1143
+ const busy = useRef(false);
1144
+ const paused = useRef(false);
1145
+ const alive = useRef(true);
1146
+ const loadGeneration = useRef(0);
1147
+ useEffect(() => {
1148
+ alive.current = true;
1149
+ return () => {
1150
+ alive.current = false;
1151
+ };
1152
+ }, []);
1153
+ const load = useCallback(async () => {
1154
+ if (!connection) return;
1155
+ const generation = ++loadGeneration.current;
1156
+ setReady(false);
1157
+ setError("");
1158
+ setStatus("Loading workspace…");
1159
+ paused.current = false;
1160
+ try {
1161
+ const response = await fetch(connection.endpoint, {
1162
+ headers: { [canvasStorageTokenHeader]: connection.token },
1163
+ cache: "no-store"
1164
+ });
1165
+ const result = await response.json();
1166
+ if (!response.ok) throw new Error(result.error ?? "Could not read workspace configuration.");
1167
+ if (!alive.current || generation !== loadGeneration.current) return;
1168
+ revision.current = result.revision;
1169
+ if (result.manifest) {
1170
+ const next = parseCanvasManifest(result.manifest);
1171
+ saved.current = JSON.stringify(next);
1172
+ latest.current = next;
1173
+ setManifest(next);
1174
+ } else saved.current = "";
1175
+ setStatus(`Saved to ${connection.file}`);
1176
+ setReady(true);
1177
+ } catch (cause) {
1178
+ if (!alive.current || generation !== loadGeneration.current) return;
1179
+ paused.current = true;
1180
+ setError(cause instanceof Error ? cause.message : "Could not load repository settings.");
1181
+ setStatus("Repository unavailable");
1182
+ setReady(true);
1183
+ }
1184
+ }, [connection]);
1185
+ useEffect(() => {
1186
+ load();
1187
+ }, [load]);
1188
+ const save = useCallback(async () => {
1189
+ if (!connection || busy.current || paused.current || !ready) return;
1190
+ busy.current = true;
1191
+ try {
1192
+ while (alive.current && !paused.current) {
1193
+ const value = latest.current;
1194
+ const serialized = JSON.stringify(value);
1195
+ if (serialized === saved.current) break;
1196
+ setStatus("Saving to repository…");
1197
+ const response_0 = await fetch(connection.endpoint, {
1198
+ method: "POST",
1199
+ headers: {
1200
+ "Content-Type": "application/json",
1201
+ [canvasStorageTokenHeader]: connection.token
1202
+ },
1203
+ body: JSON.stringify({
1204
+ manifest: value,
1205
+ revision: revision.current
1206
+ })
1207
+ });
1208
+ const result_0 = await response_0.json();
1209
+ if (!response_0.ok) throw new Error(response_0.status === 409 ? "Workspace file changed elsewhere. Export your draft, then reload repository settings." : result_0.error ?? "Could not save workspace settings.");
1210
+ revision.current = result_0.revision;
1211
+ saved.current = serialized;
1212
+ }
1213
+ if (alive.current) {
1214
+ setError("");
1215
+ setStatus(`Saved to ${connection.file}`);
1216
+ }
1217
+ } catch (cause_0) {
1218
+ paused.current = true;
1219
+ if (alive.current) {
1220
+ setError(cause_0 instanceof Error ? cause_0.message : "Could not save workspace settings.");
1221
+ setStatus("Workspace not saved to repository");
1222
+ }
1223
+ } finally {
1224
+ busy.current = false;
1225
+ }
1226
+ }, [connection, ready]);
1227
+ useEffect(() => {
1228
+ if (!ready) return;
1229
+ try {
1230
+ localStorage.setItem(key, JSON.stringify(manifest));
1231
+ } catch {
1232
+ if (!connection) setStatus("Browser storage unavailable — export your layout");
1233
+ }
1234
+ if (!connection || paused.current || JSON.stringify(manifest) === saved.current) return;
1235
+ setStatus("Saving to repository…");
1236
+ const timer = setTimeout(() => {
1237
+ save();
1238
+ }, 350);
1239
+ return () => clearTimeout(timer);
1240
+ }, [
1241
+ manifest,
1242
+ key,
1243
+ connection,
1244
+ ready,
1245
+ save
1246
+ ]);
1247
+ return {
1248
+ manifest,
1249
+ setManifest,
1250
+ ready,
1251
+ status,
1252
+ error,
1253
+ repositoryFile: connection?.file,
1254
+ reload: load
1255
+ };
1256
+ }
332
1257
  //#endregion
333
1258
  //#region src/app-canvas/useFrameStartup.ts
334
1259
  /** Bound concurrent bootstraps, without unloading any admitted page. */
@@ -408,7 +1333,7 @@ function labelOf(element) {
408
1333
  return (element.getAttribute("aria-label") || element.getAttribute("alt") || (!element.children.length ? element.textContent?.trim().replace(/\s+/g, " ") : ""))?.slice(0, 48) || (element.id ? `#${element.id}` : "");
409
1334
  }
410
1335
  /** Only expanded branches are read and rendered; large pages do not create a second full DOM tree. */
411
- function PageLayers({ frame, title, selected, onSelect }) {
1336
+ function PageLayers({ frame, title, selected, onSelect, onHover }) {
412
1337
  const [root, setRoot] = useState(null);
413
1338
  const [, refresh] = useReducer((value) => value + 1, 0);
414
1339
  const expanded = useRef(/* @__PURE__ */ new WeakSet());
@@ -490,7 +1415,13 @@ function PageLayers({ frame, title, selected, onSelect }) {
490
1415
  "aria-expanded": children.length ? open_0 : void 0,
491
1416
  children: [/* @__PURE__ */ jsxs("div", {
492
1417
  className: "povio-canvas-layer-row",
493
- style: { paddingLeft: 8 + depth * 12 },
1418
+ onMouseEnter: () => onHover(element_1),
1419
+ onMouseLeave: () => onHover(null),
1420
+ onFocus: () => onHover(element_1),
1421
+ onBlur: (event) => {
1422
+ if (!event.currentTarget.contains(event.relatedTarget)) onHover(null);
1423
+ },
1424
+ style: { paddingLeft: 4 + depth * 10 },
494
1425
  children: [children.length ? /* @__PURE__ */ jsx("button", {
495
1426
  className: "povio-canvas-layer-toggle",
496
1427
  "aria-label": `${open_0 ? "Collapse" : "Expand"} ${element_1.localName}${label ? ` ${label}` : ""}`,
@@ -500,13 +1431,13 @@ function PageLayers({ frame, title, selected, onSelect }) {
500
1431
  className: "povio-canvas-layer-select",
501
1432
  title: `${element_1.localName}${label ? ` · ${label}` : ""}`,
502
1433
  onClick: () => onSelect(element_1),
503
- onKeyDown: (event) => {
504
- if (event.key === "ArrowRight" && children.length && !open_0) {
505
- event.preventDefault();
1434
+ onKeyDown: (event_0) => {
1435
+ if (event_0.key === "ArrowRight" && children.length && !open_0) {
1436
+ event_0.preventDefault();
506
1437
  toggle(element_1, false);
507
1438
  }
508
- if (event.key === "ArrowLeft" && open_0) {
509
- event.preventDefault();
1439
+ if (event_0.key === "ArrowLeft" && open_0) {
1440
+ event_0.preventDefault();
510
1441
  toggle(element_1, true);
511
1442
  }
512
1443
  },
@@ -523,11 +1454,6 @@ function PageLayers({ frame, title, selected, onSelect }) {
523
1454
  className: "povio-canvas-layers",
524
1455
  "aria-label": `Layers for ${title}`,
525
1456
  children: [
526
- /* @__PURE__ */ jsxs("header", { children: [
527
- /* @__PURE__ */ jsx(CodeXml, { size: 14 }),
528
- /* @__PURE__ */ jsx("strong", { children: "Layers" }),
529
- /* @__PURE__ */ jsx("span", { children: title })
530
- ] }),
531
1457
  /* @__PURE__ */ jsx("div", {
532
1458
  role: "tree",
533
1459
  "aria-label": "HTML layers",
@@ -894,6 +1820,64 @@ function CanvasInspectionOverlay(t0) {
894
1820
  } else t3 = $[8];
895
1821
  return t3;
896
1822
  }
1823
+ /** Temporary layer hover never replaces the pinned inspector selection. */
1824
+ function CanvasLayerHighlight(t0) {
1825
+ const $ = c(5);
1826
+ const { element } = t0;
1827
+ const [bounds, setBounds] = useState(null);
1828
+ let t1;
1829
+ let t2;
1830
+ if ($[0] !== element) {
1831
+ t1 = () => {
1832
+ const owner = element.ownerDocument.defaultView;
1833
+ const measure = () => {
1834
+ if (!element.isConnected) {
1835
+ setBounds(null);
1836
+ return;
1837
+ }
1838
+ const rect = element.getBoundingClientRect();
1839
+ setBounds({
1840
+ left: rect.left,
1841
+ top: rect.top,
1842
+ width: rect.width,
1843
+ height: rect.height
1844
+ });
1845
+ };
1846
+ measure();
1847
+ const observer = new ResizeObserver(measure);
1848
+ observer.observe(element);
1849
+ owner?.addEventListener("scroll", measure, true);
1850
+ owner?.addEventListener("resize", measure);
1851
+ return () => {
1852
+ observer.disconnect();
1853
+ owner?.removeEventListener("scroll", measure, true);
1854
+ owner?.removeEventListener("resize", measure);
1855
+ };
1856
+ };
1857
+ t2 = [element];
1858
+ $[0] = element;
1859
+ $[1] = t1;
1860
+ $[2] = t2;
1861
+ } else {
1862
+ t1 = $[1];
1863
+ t2 = $[2];
1864
+ }
1865
+ useLayoutEffect(t1, t2);
1866
+ let t3;
1867
+ if ($[3] !== bounds) {
1868
+ t3 = bounds ? /* @__PURE__ */ jsx("div", {
1869
+ className: "povio-canvas-inspection-overlay povio-canvas-layer-highlight",
1870
+ "aria-hidden": "true",
1871
+ children: /* @__PURE__ */ jsx("div", {
1872
+ className: "studio-hover-outline",
1873
+ style: bounds
1874
+ })
1875
+ }) : null;
1876
+ $[3] = bounds;
1877
+ $[4] = t3;
1878
+ } else t3 = $[4];
1879
+ return t3;
1880
+ }
897
1881
  //#endregion
898
1882
  //#region src/app-canvas/route-model.ts
899
1883
  function joinPath(parent, path) {
@@ -1072,68 +2056,6 @@ function validConfiguration(value) {
1072
2056
  }) && !!config.config && typeof config.config === "object";
1073
2057
  }
1074
2058
  //#endregion
1075
- //#region src/app-canvas/manifest.ts
1076
- function createCanvasManifest() {
1077
- return {
1078
- version: 1,
1079
- positions: {},
1080
- examples: {},
1081
- manualEdges: [],
1082
- hiddenEdges: [],
1083
- breakpoint: "desktop"
1084
- };
1085
- }
1086
- function isRecord(value) {
1087
- return value !== null && typeof value === "object" && !Array.isArray(value);
1088
- }
1089
- function isJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
1090
- if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1091
- if (typeof value === "number") return Number.isFinite(value);
1092
- if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
1093
- ancestors.add(value);
1094
- const valid = Object.values(value).every((child) => isJSON(child, ancestors, depth + 1));
1095
- ancestors.delete(value);
1096
- return valid;
1097
- }
1098
- /** Sanitize either stored JSON text or an imported value, retaining valid v1 entries. */
1099
- function parseCanvasManifest(input) {
1100
- const fallback = createCanvasManifest();
1101
- let value = input;
1102
- if (typeof value === "string") try {
1103
- value = JSON.parse(value);
1104
- } catch {
1105
- return fallback;
1106
- }
1107
- if (!isRecord(value) || value.version !== 1) return fallback;
1108
- const positions = Object.fromEntries(Object.entries(isRecord(value.positions) ? value.positions : {}).flatMap(([breakpoint, entries]) => {
1109
- if (!isRecord(entries)) return [];
1110
- return [[breakpoint, Object.fromEntries(Object.entries(entries).flatMap(([id, point]) => isRecord(point) && typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y) ? [[id, {
1111
- x: point.x,
1112
- y: point.y
1113
- }]] : []))]];
1114
- }));
1115
- const examples = Object.fromEntries(Object.entries(isRecord(value.examples) ? value.examples : {}).flatMap(([id, example]) => {
1116
- if (!isRecord(example)) return [];
1117
- const clean = {};
1118
- if (typeof example.title === "string") clean.title = example.title;
1119
- if (isRecord(example.params)) clean.params = Object.fromEntries(Object.entries(example.params).filter((entry) => typeof entry[1] === "string"));
1120
- if (isRecord(example.search)) clean.search = Object.fromEntries(Object.entries(example.search).filter(([, item]) => isJSON(item)));
1121
- return [[id, clean]];
1122
- }));
1123
- const manualEdges = (Array.isArray(value.manualEdges) ? value.manualEdges : []).flatMap((edge) => isRecord(edge) && typeof edge.from === "string" && edge.from.length > 0 && typeof edge.to === "string" && edge.to.length > 0 ? [{
1124
- from: edge.from,
1125
- to: edge.to
1126
- }] : []);
1127
- return {
1128
- version: 1,
1129
- positions,
1130
- examples,
1131
- manualEdges: [...new Map(manualEdges.map((edge) => [JSON.stringify(edge), edge])).values()],
1132
- hiddenEdges: [...new Set((Array.isArray(value.hiddenEdges) ? value.hiddenEdges : []).filter((edge) => typeof edge === "string"))],
1133
- breakpoint: typeof value.breakpoint === "string" && value.breakpoint.length > 0 ? value.breakpoint : fallback.breakpoint
1134
- };
1135
- }
1136
- //#endregion
1137
2059
  //#region src/app-canvas/AppCanvas.tsx
1138
2060
  const defaultCanvasBreakpoints = [
1139
2061
  {
@@ -1160,14 +2082,6 @@ const emptyExclude = [];
1160
2082
  /** Zoom per wheel pixel, and the largest delta one wheel event may contribute. */
1161
2083
  const ZOOM_SENSITIVITY = .006;
1162
2084
  const ZOOM_DELTA_LIMIT = 40;
1163
- function readManifest(key) {
1164
- if (typeof window === "undefined") return createCanvasManifest();
1165
- try {
1166
- return parseCanvasManifest(localStorage.getItem(key));
1167
- } catch {
1168
- return createCanvasManifest();
1169
- }
1170
- }
1171
2085
  const PageFrame = memo(function PageFrame(t0) {
1172
2086
  const $ = c(16);
1173
2087
  const { id, src, width, height, name, interact, inspect, onLoad, register } = t0;
@@ -1216,10 +2130,13 @@ const PageFrame = memo(function PageFrame(t0) {
1216
2130
  return t6;
1217
2131
  });
1218
2132
  /** Live route artboards. The camera transforms only the canvas, never the iframe viewport width. */
1219
- function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, excludePaths = emptyExclude, breakpoints = defaultCanvasBreakpoints, storageKey = "povio-app-canvas", frameUrl, componentsHref }) {
2133
+ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, excludePaths = emptyExclude, breakpoints = defaultCanvasBreakpoints, storageKey = "povio-app-canvas", frameUrl, componentsHref, repositoryStorage = true }) {
1220
2134
  const configuration = useUIConfiguration();
1221
- const [manifest, setManifest] = useState(() => readManifest(storageKey));
2135
+ const storage = useCanvasStorage(storageKey, repositoryStorage);
2136
+ const { manifest, setManifest } = storage;
2137
+ const workspace = manifest.workspaces.find((item) => item.id === manifest.activeWorkspaceId);
1222
2138
  const [runtime, setRuntime] = useState({});
2139
+ const [hoveredLayer, setHoveredLayer] = useState(null);
1223
2140
  const [selected, setSelected] = useState(null);
1224
2141
  const [interact, setInteract] = useState(false);
1225
2142
  const [inspectMode, setInspectMode] = useState(false);
@@ -1230,7 +2147,16 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1230
2147
  const [query, setQuery] = useState("");
1231
2148
  const [notice, setNotice] = useState("");
1232
2149
  const [documentGeneration, setDocumentGeneration] = useState(0);
2150
+ useWorkspaceConfiguration(workspace, storage.ready, documentGeneration, setManifest);
1233
2151
  const [reload, setReload] = useState({});
2152
+ const [showLayers, setShowLayers] = useState(true);
2153
+ useEffect(() => setHoveredLayer(null), [
2154
+ selected,
2155
+ showLayers,
2156
+ manifest.activeWorkspaceId,
2157
+ manifest.breakpoint,
2158
+ documentGeneration
2159
+ ]);
1234
2160
  const zoomOutput = useRef(null);
1235
2161
  const importInput = useRef(null);
1236
2162
  const viewport = useRef(null), world = useRef(null);
@@ -1243,12 +2169,12 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1243
2169
  }), pendingCamera = useRef(null);
1244
2170
  const pointer = useRef(null);
1245
2171
  const cameraRevision = useRef(0);
1246
- const bp = breakpoints.find((item) => item.id === manifest.breakpoint) ?? breakpoints[breakpoints.length - 1] ?? defaultCanvasBreakpoints[2];
2172
+ const bp = breakpoints.find((item_0) => item_0.id === manifest.breakpoint) ?? breakpoints[breakpoints.length - 1] ?? defaultCanvasBreakpoints[2];
1247
2173
  const isKnownToken = useCallback((name) => isEditableToken(name, configuration.defaults), [configuration.defaults]);
1248
2174
  const { hostComponents } = configuration;
1249
2175
  useEffect(() => hostComponents(), [hostComponents]);
1250
- const inspector = useCanvasInspector(inspectMode, `${content}:${bp.id}:${documentGeneration}:${JSON.stringify(reload)}`, isKnownToken);
1251
- const pageCatalog = useMemo(() => discoverCanvasPages(router, {
2176
+ const inspector = useCanvasInspector(inspectMode, `${manifest.activeWorkspaceId}:${content}:${bp.id}:${documentGeneration}:${JSON.stringify(reload)}`, isKnownToken);
2177
+ const allPages = useMemo(() => discoverCanvasPages(router, {
1252
2178
  examples: {
1253
2179
  ...examples,
1254
2180
  ...manifest.examples
@@ -1262,6 +2188,11 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1262
2188
  exclude,
1263
2189
  excludePaths
1264
2190
  ]);
2191
+ const pageCatalog = useMemo(() => storage.ready ? allPages.filter((page) => workspace.enabledPages === null || workspace.enabledPages.includes(page.id)) : [], [
2192
+ allPages,
2193
+ workspace.enabledPages,
2194
+ storage.ready
2195
+ ]);
1265
2196
  const componentCatalog = useMemo(() => componentsHref ? componentArtboards.map((group) => ({
1266
2197
  id: `components:${group.toLowerCase()}`,
1267
2198
  title: group,
@@ -1271,7 +2202,7 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1271
2202
  kind: "page"
1272
2203
  })) : [], [componentsHref]);
1273
2204
  const catalog = content === "components" && componentCatalog.length ? componentCatalog : pageCatalog;
1274
- const startupKeys = useMemo(() => new Map(catalog.filter((page) => page.href).map((page_0) => [page_0.id, `${documentGeneration}:${bp.id}:${page_0.id}:${page_0.href}:${reload[page_0.id] ?? 0}`])), [
2205
+ const startupKeys = useMemo(() => new Map(catalog.filter((page_0) => page_0.href).map((page_1) => [page_1.id, `${documentGeneration}:${bp.id}:${page_1.id}:${page_1.href}:${reload[page_1.id] ?? 0}`])), [
1275
2206
  catalog,
1276
2207
  documentGeneration,
1277
2208
  bp.id,
@@ -1286,18 +2217,37 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1286
2217
  keys: startupKeys,
1287
2218
  complete: startup.complete
1288
2219
  };
1289
- const heights = useMemo(() => Object.fromEntries(catalog.map((page_1) => [page_1.id, runtime[`${bp.id}:${page_1.id}:${page_1.href ?? ""}`]?.height ?? bp.height])), [
2220
+ const heights = useMemo(() => Object.fromEntries(catalog.map((page_2) => [page_2.id, runtime[`${bp.id}:${page_2.id}:${page_2.href ?? ""}`]?.height ?? bp.height])), [
1290
2221
  catalog,
1291
2222
  runtime,
1292
2223
  bp
1293
2224
  ]);
1294
- const nodes = useMemo(() => layoutCanvasPages(catalog, bp.width, heights, manifest.positions[bp.id] ?? {}), [
2225
+ const nodes = useMemo(() => layoutCanvasPages(catalog, bp.width, heights, workspace.positions[bp.id] ?? {}), [
1295
2226
  catalog,
1296
2227
  bp,
1297
2228
  heights,
1298
- manifest.positions
2229
+ workspace.positions
1299
2230
  ]);
1300
2231
  const nodeMap = useMemo(() => new Map(nodes.map((node) => [node.id, node])), [nodes]);
2232
+ useLayoutEffect(() => {
2233
+ const gesture = pointer.current;
2234
+ if (gesture && viewport.current?.hasPointerCapture(gesture.id)) viewport.current.releasePointerCapture(gesture.id);
2235
+ pointer.current = null;
2236
+ setSelected(null);
2237
+ setQuery("");
2238
+ camera.current = {
2239
+ x: 40,
2240
+ y: 40,
2241
+ zoom: .25
2242
+ };
2243
+ }, [manifest.activeWorkspaceId]);
2244
+ useEffect(() => {
2245
+ if (selected && !nodeMap.has(selected)) setSelected(null);
2246
+ }, [selected, nodeMap]);
2247
+ useEffect(() => {
2248
+ const valid = new Set(catalog.map((page_3) => `${bp.id}:${page_3.id}:${page_3.href ?? ""}`));
2249
+ setRuntime((current) => Object.keys(current).some((key_0) => !valid.has(key_0)) ? Object.fromEntries(Object.entries(current).filter(([key]) => valid.has(key))) : current);
2250
+ }, [catalog, bp.id]);
1301
2251
  const latest = useRef({
1302
2252
  configuration: configuration.active,
1303
2253
  dark,
@@ -1328,6 +2278,9 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1328
2278
  const canvas = viewport.current?.closest(".povio-app-canvas");
1329
2279
  if (canvas) canvas.setAttribute("data-camera-revision", String(++cameraRevision.current));
1330
2280
  }, []);
2281
+ useLayoutEffect(() => {
2282
+ paint();
2283
+ }, [manifest.activeWorkspaceId, paint]);
1331
2284
  const schedulePaint = useCallback(() => {
1332
2285
  if (pendingCamera.current !== null) return;
1333
2286
  pendingCamera.current = requestAnimationFrame(() => {
@@ -1338,18 +2291,18 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1338
2291
  const send = useCallback((id_0, type = "configuration") => {
1339
2292
  const frame_0 = frames.current.get(id_0);
1340
2293
  if (!frame_0?.contentWindow) return;
1341
- const current = latest.current;
2294
+ const current_0 = latest.current;
1342
2295
  try {
1343
2296
  frame_0.contentWindow.postMessage({
1344
2297
  protocol: canvasProtocol,
1345
2298
  id: id_0,
1346
2299
  token,
1347
- viewportHeight: current.bp.height,
2300
+ viewportHeight: current_0.bp.height,
1348
2301
  type,
1349
- configuration: current.configuration,
2302
+ configuration: current_0.configuration,
1350
2303
  revision: revision.current,
1351
- dark: current.dark,
1352
- interact: current.interact
2304
+ dark: current_0.dark,
2305
+ interact: current_0.interact
1353
2306
  }, new URL(frame_0.src).origin);
1354
2307
  } catch {
1355
2308
  setNotice("This configuration could not be sent to the page. Check for non-serializable values.");
@@ -1359,7 +2312,8 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1359
2312
  const node_0 = nodeMap.get(id_1), view = viewport.current;
1360
2313
  if (!node_0 || !view) return;
1361
2314
  setSelected(id_1);
1362
- camera.current.zoom = Math.min(1, Math.max(.08, (view.clientWidth - 100) / node_0.width));
2315
+ setShowLayers(true);
2316
+ camera.current.zoom = Math.min(1, Math.max(.005, (view.clientWidth - 80) / node_0.width));
1363
2317
  camera.current.x = 40 - node_0.x * camera.current.zoom;
1364
2318
  camera.current.y = 40 - node_0.y * camera.current.zoom;
1365
2319
  paint();
@@ -1374,18 +2328,18 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1374
2328
  batch = 0;
1375
2329
  const changes = Array.from(queue);
1376
2330
  queue.clear();
1377
- setRuntime((current_0) => {
1378
- const next = { ...current_0 };
2331
+ setRuntime((current_1) => {
2332
+ const next = { ...current_1 };
1379
2333
  let changed = false;
1380
- for (const [key, item_0] of changes) {
1381
- if (frames.current.get(item_0.id) !== item_0.frame || item_0.frame.contentWindow !== item_0.source) continue;
1382
- next[key] = {
1383
- ...next[key],
1384
- ...item_0.change
2334
+ for (const [key_1, item_1] of changes) {
2335
+ if (frames.current.get(item_1.id) !== item_1.frame || item_1.frame.contentWindow !== item_1.source) continue;
2336
+ next[key_1] = {
2337
+ ...next[key_1],
2338
+ ...item_1.change
1385
2339
  };
1386
2340
  changed = true;
1387
2341
  }
1388
- return changed ? next : current_0;
2342
+ return changed ? next : current_1;
1389
2343
  });
1390
2344
  };
1391
2345
  const receive = (event) => {
@@ -1393,7 +2347,7 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1393
2347
  if (packet?.protocol !== "povio-app-canvas-v1" || packet.token !== token || typeof packet.id !== "string") return;
1394
2348
  const frame_1 = frames.current.get(packet.id);
1395
2349
  if (!frame_1 || event.source !== frame_1.contentWindow || event.origin !== new URL(frame_1.src).origin) return;
1396
- const key_0 = `${latest.current.bp.id}:${packet.id}:${latest.current.catalog.find((page_2) => page_2.id === packet.id)?.href ?? ""}`;
2350
+ const key_2 = `${latest.current.bp.id}:${packet.id}:${latest.current.catalog.find((page_4) => page_4.id === packet.id)?.href ?? ""}`;
1397
2351
  let change = {};
1398
2352
  if (packet.type === "ready") {
1399
2353
  startupRef.current.complete(startupRef.current.keys.get(packet.id));
@@ -1415,9 +2369,9 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1415
2369
  return;
1416
2370
  }
1417
2371
  if (!Object.keys(change).length) return;
1418
- const pending = queue.get(key_0);
2372
+ const pending = queue.get(key_2);
1419
2373
  const previous = pending?.frame === frame_1 && pending.source === event.source ? pending.change : {};
1420
- queue.set(key_0, {
2374
+ queue.set(key_2, {
1421
2375
  id: packet.id,
1422
2376
  frame: frame_1,
1423
2377
  source: event.source,
@@ -1452,19 +2406,13 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1452
2406
  useEffect(() => {
1453
2407
  for (const id_3 of frames.current.keys()) send(id_3, "mode");
1454
2408
  }, [interact, send]);
1455
- useEffect(() => {
1456
- try {
1457
- localStorage.setItem(storageKey, JSON.stringify(manifest));
1458
- } catch {
1459
- setNotice("Browser storage is unavailable; use Export layout to save this canvas.");
1460
- }
1461
- }, [manifest, storageKey]);
1462
2409
  useEffect(() => {
1463
2410
  paint();
1464
2411
  const element = viewport.current;
1465
2412
  if (!element) return;
1466
2413
  const wheel = (event_0) => {
1467
2414
  event_0.preventDefault();
2415
+ if (pointer.current) return;
1468
2416
  if (event_0.ctrlKey || event_0.metaKey) {
1469
2417
  const box = element.getBoundingClientRect();
1470
2418
  const x = event_0.clientX - box.left, y = event_0.clientY - box.top;
@@ -1534,58 +2482,74 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1534
2482
  1.5,
1535
2483
  2
1536
2484
  ];
1537
- const current_1 = camera.current.zoom;
1538
- const next_3 = direction === 1 ? stops.find((value_0) => value_0 > current_1 + 1e-4) : [...stops].reverse().find((value_1) => value_1 < current_1 - 1e-4);
2485
+ const current_2 = camera.current.zoom;
2486
+ const next_3 = direction === 1 ? stops.find((value_0) => value_0 > current_2 + 1e-4) : [...stops].reverse().find((value_1) => value_1 < current_2 - 1e-4);
1539
2487
  if (next_3 !== void 0) zoomTo(next_3);
1540
2488
  };
1541
2489
  const startPan = (event_1) => {
1542
2490
  if (inspectMode && event_1.target.closest(".povio-canvas-frame-cover")) return;
1543
2491
  if (event_1.button !== 0 || event_1.target.closest("button:not(.povio-canvas-frame-cover),input,select,.povio-canvas-node-header")) return;
2492
+ event_1.preventDefault();
1544
2493
  event_1.currentTarget.setPointerCapture(event_1.pointerId);
1545
2494
  pointer.current = {
1546
2495
  id: event_1.pointerId,
1547
2496
  x: event_1.clientX,
1548
2497
  y: event_1.clientY,
2498
+ lastX: event_1.clientX,
2499
+ lastY: event_1.clientY,
1549
2500
  startX: camera.current.x,
1550
2501
  startY: camera.current.y
1551
2502
  };
1552
2503
  };
1553
2504
  const move = (event_2) => {
1554
- const current_2 = pointer.current;
1555
- if (!current_2 || current_2.id !== event_2.pointerId) return;
1556
- const dx = event_2.clientX - current_2.x, dy = event_2.clientY - current_2.y;
1557
- if (current_2.node) {
1558
- const element_0 = world.current?.querySelector(`[data-node-index="${nodes.findIndex((n_3) => n_3.id === current_2.node)}"]`);
2505
+ const current_3 = pointer.current;
2506
+ if (!current_3 || current_3.id !== event_2.pointerId) return;
2507
+ if (event_2.buttons === 0) {
2508
+ end(event_2, true);
2509
+ return;
2510
+ }
2511
+ current_3.lastX = event_2.clientX;
2512
+ current_3.lastY = event_2.clientY;
2513
+ const dx = event_2.clientX - current_3.x, dy = event_2.clientY - current_3.y;
2514
+ if (current_3.node) {
2515
+ const element_0 = world.current?.querySelector(`[data-node-index="${nodes.findIndex((n_3) => n_3.id === current_3.node)}"]`);
1559
2516
  if (element_0) {
1560
- element_0.style.left = `${current_2.nodeX + dx / camera.current.zoom}px`;
1561
- element_0.style.top = `${current_2.nodeY + dy / camera.current.zoom}px`;
2517
+ element_0.style.left = `${current_3.nodeX + dx / camera.current.zoom}px`;
2518
+ element_0.style.top = `${current_3.nodeY + dy / camera.current.zoom}px`;
1562
2519
  }
1563
2520
  } else {
1564
- camera.current.x = current_2.startX + dx;
1565
- camera.current.y = current_2.startY + dy;
2521
+ camera.current.x = current_3.startX + dx;
2522
+ camera.current.y = current_3.startY + dy;
1566
2523
  schedulePaint();
1567
2524
  }
1568
2525
  };
1569
- const end = (event_3) => {
1570
- const current_3 = pointer.current;
1571
- if (!current_3 || current_3.id !== event_3.pointerId) return;
1572
- if (current_3.node) {
2526
+ const end = (event_3, useLastPosition = false) => {
2527
+ const current_4 = pointer.current;
2528
+ if (!current_4 || current_4.id !== event_3.pointerId) return;
2529
+ pointer.current = null;
2530
+ if (current_4.node) {
1573
2531
  const position = {
1574
- x: current_3.nodeX + (event_3.clientX - current_3.x) / camera.current.zoom,
1575
- y: current_3.nodeY + (event_3.clientY - current_3.y) / camera.current.zoom
2532
+ x: current_4.nodeX + ((useLastPosition ? current_4.lastX : event_3.clientX) - current_4.x) / camera.current.zoom,
2533
+ y: current_4.nodeY + ((useLastPosition ? current_4.lastY : event_3.clientY) - current_4.y) / camera.current.zoom
1576
2534
  };
1577
2535
  setManifest((m) => ({
1578
2536
  ...m,
1579
- positions: {
1580
- ...m.positions,
1581
- [bp.id]: {
1582
- ...m.positions[bp.id],
1583
- [current_3.node]: position
2537
+ workspaces: m.workspaces.map((item_2) => item_2.id === m.activeWorkspaceId ? {
2538
+ ...item_2,
2539
+ positions: {
2540
+ ...item_2.positions,
2541
+ [bp.id]: {
2542
+ ...item_2.positions[bp.id],
2543
+ ...Object.fromEntries(nodes.map((node_1) => [node_1.id, {
2544
+ x: node_1.x,
2545
+ y: node_1.y
2546
+ }])),
2547
+ [current_4.node]: position
2548
+ }
1584
2549
  }
1585
- }
2550
+ } : item_2)
1586
2551
  }));
1587
2552
  }
1588
- pointer.current = null;
1589
2553
  if (event_3.currentTarget.hasPointerCapture(event_3.pointerId)) event_3.currentTarget.releasePointerCapture(event_3.pointerId);
1590
2554
  };
1591
2555
  const onLoad = useCallback((id_4, frame_3) => {
@@ -1595,12 +2559,12 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1595
2559
  send(id_4, "mode");
1596
2560
  }, [send]);
1597
2561
  useEffect(() => {
1598
- const timer = setTimeout(() => setRuntime((current_4) => {
1599
- const next_4 = { ...current_4 };
1600
- for (const page_3 of latest.current.catalog) {
1601
- const key_1 = `${latest.current.bp.id}:${page_3.id}:${page_3.href ?? ""}`;
1602
- if (page_3.href && frames.current.has(page_3.id) && !next_4[key_1]?.ready) next_4[key_1] = {
1603
- ...next_4[key_1],
2562
+ const timer = setTimeout(() => setRuntime((current_5) => {
2563
+ const next_4 = { ...current_5 };
2564
+ for (const page_5 of latest.current.catalog) {
2565
+ const key_3 = `${latest.current.bp.id}:${page_5.id}:${page_5.href ?? ""}`;
2566
+ if (page_5.href && frames.current.has(page_5.id) && !next_4[key_3]?.ready) next_4[key_3] = {
2567
+ ...next_4[key_3],
1604
2568
  timedOut: true
1605
2569
  };
1606
2570
  }
@@ -1644,13 +2608,13 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1644
2608
  if (!file) return;
1645
2609
  try {
1646
2610
  const raw = JSON.parse(await file.text());
1647
- if (raw?.version !== 1) throw new Error("version");
2611
+ if (raw?.version !== 1 && raw?.version !== 2) throw new Error("version");
1648
2612
  setManifest(parseCanvasManifest(raw));
1649
2613
  setRuntime({});
1650
2614
  setDocumentGeneration((value_2) => value_2 + 1);
1651
2615
  setNotice("Layout imported.");
1652
2616
  } catch {
1653
- setNotice("Choose a valid version 1 canvas layout JSON file.");
2617
+ setNotice("Choose a valid canvas workspace JSON file.");
1654
2618
  }
1655
2619
  }
1656
2620
  }),
@@ -1661,6 +2625,12 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1661
2625
  className: "povio-canvas-catalog",
1662
2626
  "aria-label": "Page catalog",
1663
2627
  children: [
2628
+ /* @__PURE__ */ jsx(WorkspaceControls, {
2629
+ manifest,
2630
+ setManifest,
2631
+ pages: allPages,
2632
+ disabled: !storage.ready
2633
+ }),
1664
2634
  /* @__PURE__ */ jsxs("div", {
1665
2635
  className: "povio-canvas-brand povio-canvas-content-switch",
1666
2636
  role: "group",
@@ -1699,37 +2669,49 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1699
2669
  })
1700
2670
  ]
1701
2671
  }),
1702
- /* @__PURE__ */ jsxs("div", {
2672
+ /* @__PURE__ */ jsx("div", {
1703
2673
  className: "povio-canvas-page-list",
1704
- children: [nodes.filter((node_1) => `${node_1.title} ${node_1.path}`.toLowerCase().includes(query.toLowerCase())).map((node_2) => /* @__PURE__ */ jsxs("button", {
1705
- "aria-pressed": selected === node_2.id,
1706
- onClick: () => {
1707
- focusPage(node_2.id);
1708
- setMobilePanel(null);
1709
- },
1710
- children: [/* @__PURE__ */ jsx(Frame, { size: 15 }), /* @__PURE__ */ jsxs("span", {
1711
- className: "povio-canvas-page-label",
1712
- children: [/* @__PURE__ */ jsx("strong", { children: node_2.title }), node_2.kind === "unresolved" && /* @__PURE__ */ jsx("small", { children: "Needs parameters" })]
2674
+ children: nodes.filter((node_2) => `${node_2.title} ${node_2.path}`.toLowerCase().includes(query.toLowerCase())).map((node_3) => /* @__PURE__ */ jsxs("div", {
2675
+ className: "povio-canvas-page-entry",
2676
+ children: [/* @__PURE__ */ jsxs("button", {
2677
+ "aria-expanded": node_3.href ? selected === node_3.id && showLayers : void 0,
2678
+ "aria-pressed": selected === node_3.id,
2679
+ onClick: () => {
2680
+ if (selected === node_3.id && showLayers) setShowLayers(false);
2681
+ else focusPage(node_3.id);
2682
+ },
2683
+ children: [node_3.href ? selected === node_3.id && showLayers ? /* @__PURE__ */ jsx(ChevronDown, { size: 12 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 12 }) : /* @__PURE__ */ jsx(Frame, { size: 15 }), /* @__PURE__ */ jsxs("span", {
2684
+ className: "povio-canvas-page-label",
2685
+ children: [/* @__PURE__ */ jsx("strong", { children: node_3.title }), node_3.kind === "unresolved" && /* @__PURE__ */ jsx("small", { children: "Needs parameters" })]
2686
+ })]
2687
+ }), showLayers && selected === node_3.id && node_3.href && /* @__PURE__ */ jsx("div", {
2688
+ className: "povio-canvas-inline-layers",
2689
+ children: /* @__PURE__ */ jsx(PageLayers, {
2690
+ frame: frames.current.get(selected) ?? null,
2691
+ title: nodeMap.get(selected).title,
2692
+ selected: inspector.selection?.pageId === selected ? inspector.selection.element : void 0,
2693
+ onHover: (element_1) => setHoveredLayer(element_1 ? {
2694
+ pageId: node_3.id,
2695
+ element: element_1
2696
+ } : null),
2697
+ onSelect: (element_2) => {
2698
+ setInteract(false);
2699
+ setInspectMode(true);
2700
+ inspector.selectElement(selected, element_2);
2701
+ setMobilePanel(null);
2702
+ requestAnimationFrame(() => {
2703
+ const node_4 = nodeMap.get(selected), view_1 = viewport.current;
2704
+ if (!node_4 || !view_1 || !element_2.isConnected) return;
2705
+ const rect = element_2.getBoundingClientRect();
2706
+ camera.current.zoom = Math.min(camera.current.zoom, Math.max(.005, (view_1.clientWidth - 80) / node_4.width));
2707
+ camera.current.x = 40 - node_4.x * camera.current.zoom;
2708
+ camera.current.y = view_1.clientHeight / 2 - (node_4.y + 48 + rect.top + rect.height / 2) * camera.current.zoom;
2709
+ paint();
2710
+ });
2711
+ }
2712
+ }, `${bp.id}:${documentGeneration}:${reload[selected] ?? 0}:${selected}:${nodeMap.get(selected)?.href}`)
1713
2713
  })]
1714
- }, node_2.id)), selected && nodeMap.get(selected)?.href && /* @__PURE__ */ jsx(PageLayers, {
1715
- frame: frames.current.get(selected) ?? null,
1716
- title: nodeMap.get(selected).title,
1717
- selected: inspector.selection?.pageId === selected ? inspector.selection.element : void 0,
1718
- onSelect: (element_1) => {
1719
- setInteract(false);
1720
- setInspectMode(true);
1721
- inspector.selectElement(selected, element_1);
1722
- setMobilePanel(null);
1723
- requestAnimationFrame(() => {
1724
- const node_3 = nodeMap.get(selected), view_1 = viewport.current;
1725
- if (!node_3 || !view_1 || !element_1.isConnected) return;
1726
- const rect = element_1.getBoundingClientRect();
1727
- camera.current.x = view_1.clientWidth / 2 - (node_3.x + rect.left + rect.width / 2) * camera.current.zoom;
1728
- camera.current.y = view_1.clientHeight / 2 - (node_3.y + 48 + rect.top + rect.height / 2) * camera.current.zoom;
1729
- paint();
1730
- });
1731
- }
1732
- }, `${bp.id}:${documentGeneration}:${reload[selected] ?? 0}:${selected}:${nodeMap.get(selected)?.href}`)]
2714
+ }, node_3.id))
1733
2715
  }),
1734
2716
  /* @__PURE__ */ jsxs("div", {
1735
2717
  className: "povio-canvas-sidebar-actions",
@@ -1762,6 +2744,22 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1762
2744
  ]
1763
2745
  })
1764
2746
  ]
2747
+ }),
2748
+ /* @__PURE__ */ jsxs("div", {
2749
+ className: "povio-canvas-storage-status",
2750
+ role: "status",
2751
+ children: [
2752
+ storage.status,
2753
+ /* @__PURE__ */ jsx(SaveCanvasTheme, {}),
2754
+ storage.error && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("p", { children: storage.error }), /* @__PURE__ */ jsx("button", {
2755
+ onClick: () => {
2756
+ setRuntime({});
2757
+ setDocumentGeneration((value_3) => value_3 + 1);
2758
+ storage.reload();
2759
+ },
2760
+ children: "Reload repository settings"
2761
+ })] })
2762
+ ]
1765
2763
  })
1766
2764
  ]
1767
2765
  }),
@@ -1777,18 +2775,8 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1777
2775
  onPointerDown: startPan,
1778
2776
  onPointerMove: move,
1779
2777
  onPointerUp: end,
1780
- onPointerCancel: (event_7) => {
1781
- const current_5 = pointer.current;
1782
- if (current_5?.node) {
1783
- const element_2 = world.current?.querySelector(`[data-node-index="${nodes.findIndex((node_4) => node_4.id === current_5.node)}"]`);
1784
- if (element_2) {
1785
- element_2.style.left = `${current_5.nodeX}px`;
1786
- element_2.style.top = `${current_5.nodeY}px`;
1787
- }
1788
- }
1789
- pointer.current = null;
1790
- if (event_7.currentTarget.hasPointerCapture(event_7.pointerId)) event_7.currentTarget.releasePointerCapture(event_7.pointerId);
1791
- },
2778
+ onPointerCancel: (event_7) => end(event_7, true),
2779
+ onLostPointerCapture: (event_8) => end(event_8, true),
1792
2780
  children: [
1793
2781
  /* @__PURE__ */ jsx("div", {
1794
2782
  className: "povio-canvas-mobile-tools",
@@ -1834,14 +2822,18 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1834
2822
  children: [
1835
2823
  /* @__PURE__ */ jsxs("header", {
1836
2824
  className: "povio-canvas-node-header",
1837
- onPointerDown: (event_8) => {
1838
- if (event_8.button !== 0 || event_8.target.closest("button")) return;
1839
- event_8.stopPropagation();
1840
- viewport.current?.setPointerCapture(event_8.pointerId);
2825
+ onDragStart: (event_9) => event_9.preventDefault(),
2826
+ onPointerDown: (event_10) => {
2827
+ if (event_10.button !== 0 || event_10.target.closest("button")) return;
2828
+ event_10.preventDefault();
2829
+ event_10.stopPropagation();
2830
+ viewport.current?.setPointerCapture(event_10.pointerId);
1841
2831
  pointer.current = {
1842
- id: event_8.pointerId,
1843
- x: event_8.clientX,
1844
- y: event_8.clientY,
2832
+ id: event_10.pointerId,
2833
+ x: event_10.clientX,
2834
+ y: event_10.clientY,
2835
+ lastX: event_10.clientX,
2836
+ lastY: event_10.clientY,
1845
2837
  startX: 0,
1846
2838
  startY: 0,
1847
2839
  node: node_5.id,
@@ -1880,12 +2872,12 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1880
2872
  }),
1881
2873
  !node_5.href ? /* @__PURE__ */ jsxs("form", {
1882
2874
  className: "povio-canvas-unresolved",
1883
- onSubmit: (event_9) => {
1884
- event_9.preventDefault();
1885
- const form = new FormData(event_9.currentTarget);
1886
- const enteredParams = Object.fromEntries(node_5.missingParams.map((key_2) => {
1887
- const value_3 = form.get(key_2);
1888
- return [key_2, typeof value_3 === "string" ? value_3 : ""];
2875
+ onSubmit: (event_11) => {
2876
+ event_11.preventDefault();
2877
+ const form = new FormData(event_11.currentTarget);
2878
+ const enteredParams = Object.fromEntries(node_5.missingParams.map((key_4) => {
2879
+ const value_4 = form.get(key_4);
2880
+ return [key_4, typeof value_4 === "string" ? value_4 : ""];
1889
2881
  }));
1890
2882
  setManifest((m_0) => ({
1891
2883
  ...m_0,
@@ -1906,10 +2898,10 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1906
2898
  children: [
1907
2899
  /* @__PURE__ */ jsx("strong", { children: "Route needs parameters" }),
1908
2900
  /* @__PURE__ */ jsx("code", { children: node_5.path }),
1909
- node_5.missingParams.map((key_3) => /* @__PURE__ */ jsxs("label", { children: [key_3, /* @__PURE__ */ jsx("input", {
1910
- name: key_3,
2901
+ node_5.missingParams.map((key_5) => /* @__PURE__ */ jsxs("label", { children: [key_5, /* @__PURE__ */ jsx("input", {
2902
+ name: key_5,
1911
2903
  required: true
1912
- })] }, key_3)),
2904
+ })] }, key_5)),
1913
2905
  /* @__PURE__ */ jsx("button", { children: "Render page" })
1914
2906
  ]
1915
2907
  }) : originError ? /* @__PURE__ */ jsx("p", {
@@ -1940,25 +2932,26 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1940
2932
  className: "povio-canvas-frame-cover",
1941
2933
  tabIndex: 0,
1942
2934
  "aria-label": `Select ${node_5.title}`,
1943
- onKeyDown: (event_10) => {
1944
- if (event_10.key === "Enter" || event_10.key === " ") {
1945
- event_10.preventDefault();
2935
+ onKeyDown: (event_12) => {
2936
+ if (event_12.key === "Enter" || event_12.key === " ") {
2937
+ event_12.preventDefault();
1946
2938
  setSelected(node_5.id);
1947
2939
  }
1948
2940
  },
1949
- onPointerMove: (event_11) => {
2941
+ onPointerMove: (event_13) => {
1950
2942
  const frame_4 = frames.current.get(node_5.id);
1951
- if (frame_4) inspector.hover(node_5.id, frame_4, event_11.clientX, event_11.clientY);
2943
+ if (frame_4) inspector.hover(node_5.id, frame_4, event_13.clientX, event_13.clientY);
1952
2944
  },
1953
- onPointerDown: (event_12) => {
2945
+ onPointerDown: (event_14) => {
1954
2946
  setSelected(node_5.id);
1955
2947
  if (inspectMode) {
1956
2948
  const frame_5 = frames.current.get(node_5.id);
1957
- if (frame_5) inspector.select(node_5.id, frame_5, event_12.clientX, event_12.clientY);
2949
+ if (frame_5) inspector.select(node_5.id, frame_5, event_14.clientX, event_14.clientY);
1958
2950
  }
1959
2951
  },
1960
2952
  onClick: () => setSelected(node_5.id)
1961
2953
  }),
2954
+ hoveredLayer?.pageId === node_5.id && /* @__PURE__ */ jsx(CanvasLayerHighlight, { element: hoveredLayer.element }),
1962
2955
  inspectMode && inspector.selection?.pageId === node_5.id && inspector.measurement && /* @__PURE__ */ jsx(CanvasInspectionOverlay, {
1963
2956
  measurement: inspector.measurement,
1964
2957
  width: node_5.width
@@ -1976,6 +2969,10 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
1976
2969
  }, node_5.id);
1977
2970
  })
1978
2971
  }),
2972
+ !nodes.length && /* @__PURE__ */ jsx("div", {
2973
+ className: "povio-canvas-empty",
2974
+ children: storage.ready ? "Enable pages in this workspace to start arranging your canvas." : "Loading workspace…"
2975
+ }),
1979
2976
  /* @__PURE__ */ jsxs("div", {
1980
2977
  className: "povio-canvas-tool-dock",
1981
2978
  children: [
@@ -2021,10 +3018,13 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
2021
3018
  "aria-label": "Auto layout",
2022
3019
  onClick: () => setManifest((m_1) => ({
2023
3020
  ...m_1,
2024
- positions: {
2025
- ...m_1.positions,
2026
- [bp.id]: {}
2027
- }
3021
+ workspaces: m_1.workspaces.map((item_3) => item_3.id === m_1.activeWorkspaceId ? {
3022
+ ...item_3,
3023
+ positions: {
3024
+ ...item_3.positions,
3025
+ [bp.id]: {}
3026
+ }
3027
+ } : item_3)
2028
3028
  })),
2029
3029
  children: [/* @__PURE__ */ jsx(PanelsTopLeft, { size: 17 }), /* @__PURE__ */ jsx("span", { children: "Auto layout" })]
2030
3030
  })
@@ -2048,29 +3048,33 @@ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, e
2048
3048
  title: "Toggle page theme",
2049
3049
  children: dark ? /* @__PURE__ */ jsx(Moon, { size: 16 }) : /* @__PURE__ */ jsx(Sun, { size: 16 })
2050
3050
  }),
2051
- /* @__PURE__ */ jsxs("div", {
2052
- className: "povio-canvas-breakpoint",
2053
- children: [/* @__PURE__ */ jsx(Monitor, { size: 15 }), /* @__PURE__ */ jsx("select", {
2054
- "aria-label": "Canvas breakpoint",
3051
+ /* @__PURE__ */ jsx("div", {
3052
+ className: "povio-canvas-breakpoint povio-canvas-workspace-select",
3053
+ children: /* @__PURE__ */ jsx(Select, {
3054
+ label: "Canvas breakpoint",
3055
+ hideLabel: true,
3056
+ size: "small",
3057
+ variant: "outlined",
3058
+ as: "default",
3059
+ className: "povio-canvas-workspace-input",
3060
+ selectionMode: "single",
3061
+ isClearable: false,
3062
+ renderStaticInput: false,
3063
+ leadingContent: /* @__PURE__ */ jsx(Monitor, { size: 15 }),
2055
3064
  value: bp.id,
2056
- onChange: (e) => {
2057
- if (e.target.value === bp.id) return;
3065
+ items: breakpoints.map((item_4) => ({
3066
+ id: item_4.id,
3067
+ label: `${item_4.label} · ${item_4.width}px`
3068
+ })),
3069
+ onChange: (id_5) => {
3070
+ if (id_5 === null || id_5 === bp.id) return;
2058
3071
  setRuntime({});
2059
3072
  setManifest((m_2) => ({
2060
3073
  ...m_2,
2061
- breakpoint: e.target.value
3074
+ breakpoint: String(id_5)
2062
3075
  }));
2063
- },
2064
- children: breakpoints.map((item_1) => /* @__PURE__ */ jsxs("option", {
2065
- value: item_1.id,
2066
- children: [
2067
- item_1.label,
2068
- " · ",
2069
- item_1.width,
2070
- "px"
2071
- ]
2072
- }, item_1.id))
2073
- })]
3076
+ }
3077
+ })
2074
3078
  }),
2075
3079
  /* @__PURE__ */ jsx("span", { className: "povio-canvas-dock-divider" }),
2076
3080
  /* @__PURE__ */ jsx("button", {
@@ -2407,10 +3411,10 @@ function AppCanvasFrameBridge({ href }) {
2407
3411
  const APP_CANVAS_PATH = "/_canvas";
2408
3412
  /** Must match `componentsFrameParam`; kept inline so the canvas page does not load the gallery eagerly. */
2409
3413
  const componentsParam = "povio-components";
2410
- const CanvasComponentsFrame = lazy(() => import("./CanvasComponentsFrame-C0Sf9RcC.js"));
3414
+ const CanvasComponentsFrame = lazy(() => import("./CanvasComponentsFrame-CZXY0Dxi.js"));
2411
3415
  /** Ready-made route page. Mount beneath the app's UIConfigurationProvider. */
2412
3416
  function AppCanvasPage(t0) {
2413
- const $ = c(21);
3417
+ const $ = c(24);
2414
3418
  let excludePaths;
2415
3419
  let props;
2416
3420
  let t1;
@@ -2426,72 +3430,88 @@ function AppCanvasPage(t0) {
2426
3430
  t1 = $[3];
2427
3431
  }
2428
3432
  const canvasPath = t1 === void 0 ? APP_CANVAS_PATH : t1;
3433
+ const updateTheme = ThemeContext.useTheme()?.updateTheme;
2429
3434
  let t2;
2430
- if ($[4] !== excludePaths) {
2431
- t2 = excludePaths ?? [];
2432
- $[4] = excludePaths;
2433
- $[5] = t2;
2434
- } else t2 = $[5];
2435
3435
  let t3;
2436
- if ($[6] !== canvasPath || $[7] !== t2) {
2437
- t3 = /* @__PURE__ */ new Set([...t2, canvasPath]);
2438
- $[6] = canvasPath;
2439
- $[7] = t2;
2440
- $[8] = t3;
2441
- } else t3 = $[8];
3436
+ if ($[4] !== updateTheme) {
3437
+ t2 = () => {
3438
+ if (!isAppCanvasFrame()) updateTheme?.("light");
3439
+ };
3440
+ t3 = [updateTheme];
3441
+ $[4] = updateTheme;
3442
+ $[5] = t2;
3443
+ $[6] = t3;
3444
+ } else {
3445
+ t2 = $[5];
3446
+ t3 = $[6];
3447
+ }
3448
+ useEffect(t2, t3);
2442
3449
  let t4;
2443
- if ($[9] !== t3) {
2444
- t4 = [...t3];
2445
- $[9] = t3;
3450
+ if ($[7] !== excludePaths) {
3451
+ t4 = excludePaths ?? [];
3452
+ $[7] = excludePaths;
3453
+ $[8] = t4;
3454
+ } else t4 = $[8];
3455
+ let t5;
3456
+ if ($[9] !== canvasPath || $[10] !== t4) {
3457
+ t5 = /* @__PURE__ */ new Set([...t4, canvasPath]);
3458
+ $[9] = canvasPath;
2446
3459
  $[10] = t4;
2447
- } else t4 = $[10];
2448
- const exclusions = t4;
3460
+ $[11] = t5;
3461
+ } else t5 = $[11];
3462
+ let t6;
3463
+ if ($[12] !== t5) {
3464
+ t6 = [...t5];
3465
+ $[12] = t5;
3466
+ $[13] = t6;
3467
+ } else t6 = $[13];
3468
+ const exclusions = t6;
2449
3469
  if (isAppCanvasFrame()) {
2450
- let t5;
2451
- if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
3470
+ let t7;
3471
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
2452
3472
  const group = new URLSearchParams(window.location.search).get(componentsParam);
2453
- t5 = group ? /* @__PURE__ */ jsx(Suspense, {
3473
+ t7 = group ? /* @__PURE__ */ jsx(Suspense, {
2454
3474
  fallback: null,
2455
3475
  children: /* @__PURE__ */ jsx(CanvasComponentsFrame, { group })
2456
3476
  }) : null;
2457
- $[11] = t5;
2458
- } else t5 = $[11];
2459
- return t5;
3477
+ $[14] = t7;
3478
+ } else t7 = $[14];
3479
+ return t7;
2460
3480
  }
2461
- let t5;
2462
- if ($[12] !== canvasPath) {
2463
- t5 = (group_0) => `${canvasPath}?${componentsParam}=${encodeURIComponent(group_0)}`;
2464
- $[12] = canvasPath;
2465
- $[13] = t5;
2466
- } else t5 = $[13];
2467
- let t6;
2468
- if ($[14] !== exclusions || $[15] !== props || $[16] !== t5) {
2469
- t6 = /* @__PURE__ */ jsx(AppCanvas, {
3481
+ let t7;
3482
+ if ($[15] !== canvasPath) {
3483
+ t7 = (group_0) => `${canvasPath}?${componentsParam}=${encodeURIComponent(group_0)}`;
3484
+ $[15] = canvasPath;
3485
+ $[16] = t7;
3486
+ } else t7 = $[16];
3487
+ let t8;
3488
+ if ($[17] !== exclusions || $[18] !== props || $[19] !== t7) {
3489
+ t8 = /* @__PURE__ */ jsx(AppCanvas, {
2470
3490
  ...props,
2471
3491
  excludePaths: exclusions,
2472
- componentsHref: t5
3492
+ componentsHref: t7
2473
3493
  });
2474
- $[14] = exclusions;
2475
- $[15] = props;
2476
- $[16] = t5;
2477
- $[17] = t6;
2478
- } else t6 = $[17];
2479
- let t7;
2480
- if ($[18] === Symbol.for("react.memo_cache_sentinel")) {
2481
- t7 = /* @__PURE__ */ jsx(UIConfigurationModal, {});
2482
- $[18] = t7;
2483
- } else t7 = $[18];
2484
- let t8;
2485
- if ($[19] !== t6) {
2486
- t8 = /* @__PURE__ */ jsxs("div", {
3494
+ $[17] = exclusions;
3495
+ $[18] = props;
3496
+ $[19] = t7;
3497
+ $[20] = t8;
3498
+ } else t8 = $[20];
3499
+ let t9;
3500
+ if ($[21] === Symbol.for("react.memo_cache_sentinel")) {
3501
+ t9 = /* @__PURE__ */ jsx(UIConfigurationModal, {});
3502
+ $[21] = t9;
3503
+ } else t9 = $[21];
3504
+ let t10;
3505
+ if ($[22] !== t8) {
3506
+ t10 = /* @__PURE__ */ jsxs("div", {
2487
3507
  className: "povio-app-canvas-page",
2488
3508
  "aria-label": "App canvas page",
2489
- children: [t6, t7]
3509
+ children: [t8, t9]
2490
3510
  });
2491
- $[19] = t6;
2492
- $[20] = t8;
2493
- } else t8 = $[20];
2494
- return t8;
3511
+ $[22] = t8;
3512
+ $[23] = t10;
3513
+ } else t10 = $[23];
3514
+ return t10;
2495
3515
  }
2496
3516
  //#endregion
2497
3517
  export { defaultCanvasBreakpoints as a, layoutCanvasPages as c, AppCanvas as i, AppCanvasPage as n, isAppCanvasFrame as o, AppCanvasFrameBridge as r, discoverCanvasPages as s, APP_CANVAS_PATH as t };