@povio/ui 5.0.0-rc.8 → 5.0.0-rc.9

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 (134) hide show
  1. package/README.md +48 -0
  2. package/dist/Alert-CnvEb1D_.js +244 -0
  3. package/dist/AppCanvasPage--RG31Yyv.js +2421 -0
  4. package/dist/AppCanvasPage-BIFYeEeS.d.ts +80 -0
  5. package/dist/{button.cva-rv5Uq4aW.js → Button-C5rv4Cr2.js} +84 -1
  6. package/dist/{Calendar-BxtOuqIM.js → Calendar-BTjajO6i.js} +3 -3
  7. package/dist/{Checkbox-DYxXYfKL.js → Checkbox-CV1ILXXO.js} +2 -3
  8. package/dist/{checkbox.cva-IfOByVLA.js → CheckboxCheckmark-CpC3wlMs.js} +42 -1
  9. package/dist/{ComponentGallery-Da70Y4_Q.js → ComponentGallery-C6exitZN.js} +17 -17
  10. package/dist/{DatePicker-wulRNGyL.js → DatePicker-DOLL7mkc.js} +6 -6
  11. package/dist/{DatePickerInput-BB_vPlKu.js → DatePickerInput-BLBcySA2.js} +3 -3
  12. package/dist/{DateRangePicker-B0tMVREG.js → DateRangePicker-BF-1CztH.js} +6 -6
  13. package/dist/{DateTimePicker-WGMr0bEH.js → DateTimePicker-Cj0qvJUz.js} +6 -6
  14. package/dist/{FileInput-DP105Iy9.js → FileInput-Br5DQaGQ.js} +2 -2
  15. package/dist/{FileUpload-CgNDIvzg.js → FileUpload--7ub3EZO.js} +3 -3
  16. package/dist/{FormField-AY25LzbW.js → FormField-B99dLt8o.js} +96 -3
  17. package/dist/{FormFieldError-BVAO5p7H.js → FormFieldError-DeOHHLo2.js} +13 -2
  18. package/dist/{InputItem-ZeN-aIz8.js → InputItem-CIxbTxZu.js} +11 -11
  19. package/dist/{Inputs-BC5q5Cvj.js → Inputs-DJSKiaWY.js} +1 -1
  20. package/dist/{NumberRangeInput-B_sMIpP1.js → NumberRangeInput-B8-LHfg3.js} +2 -2
  21. package/dist/{PaginationList-DptMNpNs.js → PaginationList-DtvuP63b.js} +1 -1
  22. package/dist/{Progress-Du30L4_s.js → Progress-B3mY9rIP.js} +27 -2
  23. package/dist/{RadioGroup-CUGNSJql.js → RadioGroup-DOw3PRoX.js} +82 -3
  24. package/dist/{RichTextEditor-VDOpj7sk.js → RichTextEditor-D4yN7Wj7.js} +3 -3
  25. package/dist/{Select-B2pAfwRk.js → Select-UR0ec2Ux.js} +2 -2
  26. package/dist/{Slider-Clc1szFY.js → Slider-CzEmKYBF.js} +1 -1
  27. package/dist/{Switch-CimmMqEk.js → Switch-CqnGEdeD.js} +27 -3
  28. package/dist/{Table-CqepqSfe.js → Table-BN2_J_Ne.js} +1 -2
  29. package/dist/{tag.cva-B3MudLeC.js → Tag-DPg5sL1Z.js} +92 -1
  30. package/dist/{TextArea-B3teIhsJ.js → TextArea-D8WkKsGn.js} +5 -3
  31. package/dist/{TimePicker-CU_yL2sY.js → TimePicker-C4taAOFQ.js} +4 -4
  32. package/dist/{Toast-DPbnLlBi.js → Toast-DtG3pA61.js} +1 -1
  33. package/dist/{ToggleGroup-BULVwErp.js → ToggleGroup-DOk27fgI.js} +1 -1
  34. package/dist/UIConfigurator-BwLRfe-a.js +6704 -0
  35. package/dist/components/alert-dialog.js +1 -1
  36. package/dist/components/alert.js +1 -116
  37. package/dist/components/autocomplete.js +2 -2
  38. package/dist/components/button.js +1 -1
  39. package/dist/components/checkbox-group.js +3 -3
  40. package/dist/components/checkbox.js +1 -1
  41. package/dist/components/date-picker.js +1 -1
  42. package/dist/components/date-range-picker.js +1 -1
  43. package/dist/components/date-time-picker.js +1 -1
  44. package/dist/components/file-input.js +1 -1
  45. package/dist/components/file-upload-container.js +1 -1
  46. package/dist/components/file-upload.js +1 -1
  47. package/dist/components/form-field.js +1 -1
  48. package/dist/components/icon-button.js +1 -1
  49. package/dist/components/inline-icon-button.js +1 -1
  50. package/dist/components/input.js +3 -3
  51. package/dist/components/number-field.js +3 -3
  52. package/dist/components/number-range-input.js +1 -1
  53. package/dist/components/pagination-list.js +1 -1
  54. package/dist/components/pagination.js +1 -1
  55. package/dist/components/progress.js +1 -1
  56. package/dist/components/query-autocomplete.js +1 -1
  57. package/dist/components/query-select.js +2 -2
  58. package/dist/components/radio-group.js +1 -1
  59. package/dist/components/select.js +1 -1
  60. package/dist/components/slider.js +1 -1
  61. package/dist/components/split-button.js +1 -1
  62. package/dist/components/switch.js +1 -1
  63. package/dist/components/tag.js +1 -93
  64. package/dist/components/text-area.js +1 -1
  65. package/dist/components/text-button.js +1 -1
  66. package/dist/components/time-picker.js +1 -1
  67. package/dist/components/toast.js +1 -1
  68. package/dist/components/toggle-button.js +1 -1
  69. package/dist/components/toggle-group.js +1 -1
  70. package/dist/components/use-toast.js +2 -2
  71. package/dist/config/app-canvas-tanstack.d.ts +23 -0
  72. package/dist/config/app-canvas-tanstack.js +62 -0
  73. package/dist/config/app-canvas.d.ts +2 -69
  74. package/dist/config/app-canvas.js +2 -2253
  75. package/dist/config/ui-configurator.js +2 -5383
  76. package/dist/{datePickerValue.utils-CIFHjKwv.js → datePickerValue.utils-DCgoSrP3.js} +1 -1
  77. package/dist/index.js +19 -24
  78. package/dist/rhf/checkbox.js +1 -1
  79. package/dist/rhf/date-picker.js +1 -1
  80. package/dist/rhf/date-range-picker.js +1 -1
  81. package/dist/rhf/date-time-picker.js +1 -1
  82. package/dist/rhf/file-input.js +1 -1
  83. package/dist/rhf/file-upload.js +1 -1
  84. package/dist/rhf/number-range-input.js +1 -1
  85. package/dist/rhf/radio-group.js +1 -1
  86. package/dist/rhf/rich-text-editor.js +1 -1
  87. package/dist/rhf/select.js +1 -1
  88. package/dist/rhf/slider.js +1 -1
  89. package/dist/rhf/switch.js +1 -1
  90. package/dist/rhf/text-area.js +1 -1
  91. package/dist/rhf/time-picker.js +1 -1
  92. package/dist/rhf/toggle-group.js +1 -1
  93. package/dist/rhf.js +19 -24
  94. package/dist/rich-text-editor.js +1 -1
  95. package/dist/{staticDateTimeSegments-sf_ND7zL.js → staticDateTimeSegments-DtU63_dw.js} +1 -1
  96. package/dist/{staticSelect.utils-CzlnLPPd.js → staticSelect.utils-uHa8f_c6.js} +3 -3
  97. package/dist/styles/app-canvas.css +7 -0
  98. package/dist/{table.cva-DlMT-zLD.js → table.cva-D5oQ-QvA.js} +54 -1
  99. package/dist/tanstack/checkbox.js +1 -1
  100. package/dist/tanstack/date-picker.js +1 -1
  101. package/dist/tanstack/date-range-picker.js +1 -1
  102. package/dist/tanstack/date-time-picker.js +1 -1
  103. package/dist/tanstack/file-input.js +1 -1
  104. package/dist/tanstack/file-upload.js +1 -1
  105. package/dist/tanstack/form.js +1 -1
  106. package/dist/tanstack/inputs.js +1 -1
  107. package/dist/tanstack/number-range-input.js +1 -1
  108. package/dist/tanstack/radio-group.js +1 -1
  109. package/dist/tanstack/rich-text-editor.js +1 -1
  110. package/dist/tanstack/select.js +1 -1
  111. package/dist/tanstack/slider.js +1 -1
  112. package/dist/tanstack/switch.js +1 -1
  113. package/dist/tanstack/table.js +2 -2
  114. package/dist/tanstack/text-area.js +1 -1
  115. package/dist/tanstack/time-picker.js +1 -1
  116. package/dist/tanstack/toggle-group.js +1 -1
  117. package/dist/tanstack.js +31 -39
  118. package/dist/{toast.runtime-Zf-XyAC3.js → toast.runtime-cxex9e4E.js} +1 -1
  119. package/dist/{useFieldAccessibility-CcBTgGfm.js → useFieldAccessibility-ScC6Mtd5.js} +1 -1
  120. package/dist/{useFormAutosave-BD_TFqDF.js → useFormAutosave-DfuP7drj.js} +1 -1
  121. package/dist/{useStaticInputHandoff-kkQrc6DA.js → useStaticInputHandoff-NeZVnci-.js} +1 -1
  122. package/dist/{useTableColumnConfig-aVUWC2Mp.js → useTableColumnConfig-Di0_w80v.js} +2 -2
  123. package/package.json +10 -1
  124. package/dist/Button-BPTvffuk.js +0 -86
  125. package/dist/CheckboxCheckmark-D_uc9pon.js +0 -44
  126. package/dist/TextArea.cva-Bb73hriz.js +0 -5
  127. package/dist/formFieldError.cva-CsXaQRHX.js +0 -14
  128. package/dist/formFieldHelper.cva-B8F9LmsE.js +0 -96
  129. package/dist/grip-vertical-BVxcjdfr.js +0 -54
  130. package/dist/inspection-model-LuI36xTg.js +0 -1339
  131. package/dist/progress.cva-WQB8BGbk.js +0 -28
  132. package/dist/radio.cva-DGbvTymp.js +0 -82
  133. package/dist/status.cva-6HxNR4EP.js +0 -130
  134. package/dist/switch.cva-BX3HOxbL.js +0 -27
@@ -0,0 +1,2421 @@
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-BwLRfe-a.js";
3
+ import { t as ChevronDown } from "./chevron-down-B4Pr8bD3.js";
4
+ import { t as ChevronRight } from "./chevron-right-3nKJR5Bb.js";
5
+ import { i as Search } from "./selectDesktop.cva-CHAMxhfO.js";
6
+ import { t as Upload } from "./upload-CXU83BJ4.js";
7
+ import { t as X } from "./x-B6vfz8Ps.js";
8
+ import { memo, useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
9
+ import { c } from "react/compiler-runtime";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/code-xml.mjs
12
+ /**
13
+ * @license lucide-react v1.43.0 - ISC
14
+ *
15
+ * This source code is licensed under the ISC license.
16
+ * See the LICENSE file in the root directory of this source tree.
17
+ */
18
+ const __iconData$9 = {
19
+ name: "code-xml",
20
+ size: 24,
21
+ node: [
22
+ ["path", {
23
+ d: "m18 16 4-4-4-4",
24
+ key: "1inbqp"
25
+ }],
26
+ ["path", {
27
+ d: "m6 8-4 4 4 4",
28
+ key: "15zrgr"
29
+ }],
30
+ ["path", {
31
+ d: "m14.5 4-5 16",
32
+ key: "e7oirm"
33
+ }]
34
+ ],
35
+ aliases: ["code-2"]
36
+ };
37
+ __iconData$9.node;
38
+ const CodeXml = createLucideIcon(__iconData$9);
39
+ //#endregion
40
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/download.mjs
41
+ /**
42
+ * @license lucide-react v1.43.0 - ISC
43
+ *
44
+ * This source code is licensed under the ISC license.
45
+ * See the LICENSE file in the root directory of this source tree.
46
+ */
47
+ const __iconData$8 = {
48
+ name: "download",
49
+ size: 24,
50
+ node: [
51
+ ["path", {
52
+ d: "M12 15V3",
53
+ key: "m9g1x1"
54
+ }],
55
+ ["path", {
56
+ d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
57
+ key: "ih7n3h"
58
+ }],
59
+ ["path", {
60
+ d: "m7 10 5 5 5-5",
61
+ key: "brsn70"
62
+ }]
63
+ ]
64
+ };
65
+ __iconData$8.node;
66
+ const Download = createLucideIcon(__iconData$8);
67
+ //#endregion
68
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/frame.mjs
69
+ /**
70
+ * @license lucide-react v1.43.0 - ISC
71
+ *
72
+ * This source code is licensed under the ISC license.
73
+ * See the LICENSE file in the root directory of this source tree.
74
+ */
75
+ const __iconData$7 = {
76
+ name: "frame",
77
+ size: 24,
78
+ node: [
79
+ ["line", {
80
+ x1: "22",
81
+ x2: "2",
82
+ y1: "6",
83
+ y2: "6",
84
+ key: "15w7dq"
85
+ }],
86
+ ["line", {
87
+ x1: "22",
88
+ x2: "2",
89
+ y1: "18",
90
+ y2: "18",
91
+ key: "1ip48p"
92
+ }],
93
+ ["line", {
94
+ x1: "6",
95
+ x2: "6",
96
+ y1: "2",
97
+ y2: "22",
98
+ key: "a2lnyx"
99
+ }],
100
+ ["line", {
101
+ x1: "18",
102
+ x2: "18",
103
+ y1: "2",
104
+ y2: "22",
105
+ key: "8vb6jd"
106
+ }]
107
+ ]
108
+ };
109
+ __iconData$7.node;
110
+ const Frame = createLucideIcon(__iconData$7);
111
+ //#endregion
112
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/hand.mjs
113
+ /**
114
+ * @license lucide-react v1.43.0 - ISC
115
+ *
116
+ * This source code is licensed under the ISC license.
117
+ * See the LICENSE file in the root directory of this source tree.
118
+ */
119
+ const __iconData$6 = {
120
+ name: "hand",
121
+ size: 24,
122
+ node: [
123
+ ["path", {
124
+ d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2",
125
+ key: "1fvzgz"
126
+ }],
127
+ ["path", {
128
+ d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2",
129
+ key: "1kc0my"
130
+ }],
131
+ ["path", {
132
+ d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8",
133
+ key: "10h0bg"
134
+ }],
135
+ ["path", {
136
+ d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15",
137
+ key: "1s1gnw"
138
+ }]
139
+ ]
140
+ };
141
+ __iconData$6.node;
142
+ const Hand = createLucideIcon(__iconData$6);
143
+ //#endregion
144
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/monitor.mjs
145
+ /**
146
+ * @license lucide-react v1.43.0 - ISC
147
+ *
148
+ * This source code is licensed under the ISC license.
149
+ * See the LICENSE file in the root directory of this source tree.
150
+ */
151
+ const __iconData$5 = {
152
+ name: "monitor",
153
+ size: 24,
154
+ node: [
155
+ ["rect", {
156
+ width: "20",
157
+ height: "14",
158
+ x: "2",
159
+ y: "3",
160
+ rx: "2",
161
+ key: "48i651"
162
+ }],
163
+ ["line", {
164
+ x1: "8",
165
+ x2: "16",
166
+ y1: "21",
167
+ y2: "21",
168
+ key: "1svkeh"
169
+ }],
170
+ ["line", {
171
+ x1: "12",
172
+ x2: "12",
173
+ y1: "17",
174
+ y2: "21",
175
+ key: "vw1qmm"
176
+ }]
177
+ ]
178
+ };
179
+ __iconData$5.node;
180
+ const Monitor = createLucideIcon(__iconData$5);
181
+ //#endregion
182
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/mouse-pointer-2.mjs
183
+ /**
184
+ * @license lucide-react v1.43.0 - ISC
185
+ *
186
+ * This source code is licensed under the ISC license.
187
+ * See the LICENSE file in the root directory of this source tree.
188
+ */
189
+ const __iconData$4 = {
190
+ name: "mouse-pointer-2",
191
+ size: 24,
192
+ node: [["path", {
193
+ d: "M4.037 4.688a.495.495 0 0 1 .651-.651l16 6.5a.5.5 0 0 1-.063.947l-6.124 1.58a2 2 0 0 0-1.438 1.435l-1.579 6.126a.5.5 0 0 1-.947.063z",
194
+ key: "edeuup"
195
+ }]]
196
+ };
197
+ __iconData$4.node;
198
+ const MousePointer2 = createLucideIcon(__iconData$4);
199
+ //#endregion
200
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/panel-left.mjs
201
+ /**
202
+ * @license lucide-react v1.43.0 - ISC
203
+ *
204
+ * This source code is licensed under the ISC license.
205
+ * See the LICENSE file in the root directory of this source tree.
206
+ */
207
+ const __iconData$3 = {
208
+ name: "panel-left",
209
+ size: 24,
210
+ node: [["rect", {
211
+ width: "18",
212
+ height: "18",
213
+ x: "3",
214
+ y: "3",
215
+ rx: "2",
216
+ key: "afitv7"
217
+ }], ["path", {
218
+ d: "M9 3v18",
219
+ key: "fh3hqa"
220
+ }]],
221
+ aliases: ["sidebar"]
222
+ };
223
+ __iconData$3.node;
224
+ const PanelLeft = createLucideIcon(__iconData$3);
225
+ //#endregion
226
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/panels-top-left.mjs
227
+ /**
228
+ * @license lucide-react v1.43.0 - ISC
229
+ *
230
+ * This source code is licensed under the ISC license.
231
+ * See the LICENSE file in the root directory of this source tree.
232
+ */
233
+ const __iconData$2 = {
234
+ name: "panels-top-left",
235
+ size: 24,
236
+ node: [
237
+ ["rect", {
238
+ width: "18",
239
+ height: "18",
240
+ x: "3",
241
+ y: "3",
242
+ rx: "2",
243
+ key: "afitv7"
244
+ }],
245
+ ["path", {
246
+ d: "M3 9h18",
247
+ key: "1pudct"
248
+ }],
249
+ ["path", {
250
+ d: "M9 21V9",
251
+ key: "1oto5p"
252
+ }]
253
+ ],
254
+ aliases: ["layout"]
255
+ };
256
+ __iconData$2.node;
257
+ const PanelsTopLeft = createLucideIcon(__iconData$2);
258
+ //#endregion
259
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/scan-line.mjs
260
+ /**
261
+ * @license lucide-react v1.43.0 - ISC
262
+ *
263
+ * This source code is licensed under the ISC license.
264
+ * See the LICENSE file in the root directory of this source tree.
265
+ */
266
+ const __iconData$1 = {
267
+ name: "scan-line",
268
+ size: 24,
269
+ node: [
270
+ ["path", {
271
+ d: "M3 7V5a2 2 0 0 1 2-2h2",
272
+ key: "aa7l1z"
273
+ }],
274
+ ["path", {
275
+ d: "M17 3h2a2 2 0 0 1 2 2v2",
276
+ key: "4qcy5o"
277
+ }],
278
+ ["path", {
279
+ d: "M21 17v2a2 2 0 0 1-2 2h-2",
280
+ key: "6vwrx8"
281
+ }],
282
+ ["path", {
283
+ d: "M7 21H5a2 2 0 0 1-2-2v-2",
284
+ key: "ioqczr"
285
+ }],
286
+ ["path", {
287
+ d: "M7 12h10",
288
+ key: "b7w52i"
289
+ }]
290
+ ]
291
+ };
292
+ __iconData$1.node;
293
+ const ScanLine = createLucideIcon(__iconData$1);
294
+ //#endregion
295
+ //#region ../../node_modules/.bun/lucide-react@1.43.0+83d5fd7b249dbeef/node_modules/lucide-react/dist/esm/icons/settings-2.mjs
296
+ /**
297
+ * @license lucide-react v1.43.0 - ISC
298
+ *
299
+ * This source code is licensed under the ISC license.
300
+ * See the LICENSE file in the root directory of this source tree.
301
+ */
302
+ const __iconData = {
303
+ name: "settings-2",
304
+ size: 24,
305
+ node: [
306
+ ["path", {
307
+ d: "M14 17H5",
308
+ key: "gfn3mx"
309
+ }],
310
+ ["path", {
311
+ d: "M19 7h-9",
312
+ key: "6i9tg"
313
+ }],
314
+ ["circle", {
315
+ cx: "17",
316
+ cy: "17",
317
+ r: "3",
318
+ key: "18b49y"
319
+ }],
320
+ ["circle", {
321
+ cx: "7",
322
+ cy: "7",
323
+ r: "3",
324
+ key: "dfmy0x"
325
+ }]
326
+ ]
327
+ };
328
+ __iconData.node;
329
+ const Settings2 = createLucideIcon(__iconData);
330
+ //#endregion
331
+ //#region src/app-canvas/useFrameStartup.ts
332
+ /** Bound concurrent bootstraps, without unloading any admitted page. */
333
+ function useFrameStartup(keys, concurrency = 2) {
334
+ const [admitted, setAdmitted] = useState(() => /* @__PURE__ */ new Set());
335
+ const state = useRef({
336
+ keys: [],
337
+ admitted: /* @__PURE__ */ new Set(),
338
+ pending: /* @__PURE__ */ new Map()
339
+ });
340
+ const pumpRef = useRef(() => {});
341
+ const pump = useCallback(() => {
342
+ const current = state.current;
343
+ let changed = false;
344
+ for (const key of current.keys) {
345
+ if (current.pending.size >= concurrency) break;
346
+ if (current.admitted.has(key)) continue;
347
+ current.admitted.add(key);
348
+ changed = true;
349
+ current.pending.set(key, setTimeout(() => {
350
+ current.pending.delete(key);
351
+ pumpRef.current();
352
+ }, 3e4));
353
+ }
354
+ if (changed) setAdmitted(new Set(current.admitted));
355
+ }, [concurrency]);
356
+ pumpRef.current = pump;
357
+ const signature = JSON.stringify(keys);
358
+ useEffect(() => {
359
+ const next = JSON.parse(signature);
360
+ const wanted = new Set(next);
361
+ const current_0 = state.current;
362
+ current_0.keys = next;
363
+ let changed_0 = false;
364
+ for (const key_0 of current_0.admitted) {
365
+ if (wanted.has(key_0)) continue;
366
+ current_0.admitted.delete(key_0);
367
+ const timer = current_0.pending.get(key_0);
368
+ if (timer !== void 0) clearTimeout(timer);
369
+ current_0.pending.delete(key_0);
370
+ changed_0 = true;
371
+ }
372
+ if (changed_0) setAdmitted(new Set(current_0.admitted));
373
+ pump();
374
+ }, [signature, pump]);
375
+ useEffect(() => () => {
376
+ for (const timer_0 of state.current.pending.values()) clearTimeout(timer_0);
377
+ state.current.pending.clear();
378
+ state.current.admitted.clear();
379
+ }, []);
380
+ return {
381
+ admitted,
382
+ complete: useCallback((key_1) => {
383
+ if (!key_1) return;
384
+ const timer_1 = state.current.pending.get(key_1);
385
+ if (timer_1 === void 0) return;
386
+ clearTimeout(timer_1);
387
+ state.current.pending.delete(key_1);
388
+ pumpRef.current();
389
+ }, [])
390
+ };
391
+ }
392
+ //#endregion
393
+ //#region src/app-canvas/PageLayers.tsx
394
+ const excluded = /* @__PURE__ */ new Set([
395
+ "SCRIPT",
396
+ "STYLE",
397
+ "LINK",
398
+ "META",
399
+ "NOSCRIPT",
400
+ "TEMPLATE"
401
+ ]);
402
+ function childrenOf(element) {
403
+ return Array.from(element.children).filter((child) => child.namespaceURI === "http://www.w3.org/1999/xhtml" && !excluded.has(child.tagName) && !child.hidden && child.getClientRects().length > 0);
404
+ }
405
+ function labelOf(element) {
406
+ return (element.getAttribute("aria-label") || element.getAttribute("alt") || (!element.children.length ? element.textContent?.trim().replace(/\s+/g, " ") : ""))?.slice(0, 48) || (element.id ? `#${element.id}` : "");
407
+ }
408
+ /** Only expanded branches are read and rendered; large pages do not create a second full DOM tree. */
409
+ function PageLayers({ frame, title, selected, onSelect }) {
410
+ const [root, setRoot] = useState(null);
411
+ const [, refresh] = useReducer((value) => value + 1, 0);
412
+ const expanded = useRef(/* @__PURE__ */ new WeakSet());
413
+ const collapsed = useRef(/* @__PURE__ */ new WeakSet());
414
+ const ids = useRef(/* @__PURE__ */ new WeakMap());
415
+ const nextId = useRef(0);
416
+ const [limit, setLimit] = useState(300);
417
+ useEffect(() => {
418
+ if (!frame) return;
419
+ let observer;
420
+ let scheduled = null;
421
+ const update = () => {
422
+ scheduled = null;
423
+ refresh();
424
+ };
425
+ const attach = () => {
426
+ observer?.disconnect();
427
+ try {
428
+ const doc = frame.contentDocument;
429
+ const element = doc?.querySelector("#storybook-root, #app, #root") ?? doc?.body ?? null;
430
+ setRoot(element);
431
+ if (element) {
432
+ observer = new MutationObserver(() => {
433
+ if (scheduled === null) scheduled = requestAnimationFrame(update);
434
+ });
435
+ observer.observe(element, {
436
+ childList: true,
437
+ subtree: true,
438
+ attributes: true,
439
+ characterData: true
440
+ });
441
+ }
442
+ } catch {
443
+ setRoot(null);
444
+ }
445
+ };
446
+ attach();
447
+ frame.addEventListener("load", attach);
448
+ return () => {
449
+ observer?.disconnect();
450
+ frame.removeEventListener("load", attach);
451
+ if (scheduled !== null) cancelAnimationFrame(scheduled);
452
+ };
453
+ }, [frame]);
454
+ useEffect(() => {
455
+ if (!root || !selected || !root.contains(selected)) return;
456
+ for (let parent = selected.parentElement; parent && parent !== root; parent = parent.parentElement) {
457
+ expanded.current.add(parent);
458
+ collapsed.current.delete(parent);
459
+ }
460
+ refresh();
461
+ }, [root, selected]);
462
+ const toggle = (element_0, open) => {
463
+ if (open) {
464
+ expanded.current.delete(element_0);
465
+ collapsed.current.add(element_0);
466
+ } else {
467
+ expanded.current.add(element_0);
468
+ collapsed.current.delete(element_0);
469
+ }
470
+ refresh();
471
+ };
472
+ let count = 0;
473
+ let truncated = false;
474
+ const render = (element_1, depth) => {
475
+ if (++count > limit || depth > 60) {
476
+ truncated = true;
477
+ return null;
478
+ }
479
+ if (!ids.current.has(element_1)) ids.current.set(element_1, ++nextId.current);
480
+ const children = childrenOf(element_1);
481
+ const open_0 = !collapsed.current.has(element_1) && (depth < 2 || expanded.current.has(element_1));
482
+ const label = labelOf(element_1);
483
+ return /* @__PURE__ */ jsxs("div", {
484
+ role: "treeitem",
485
+ "aria-label": `${element_1.localName}${label ? ` ${label}` : ""}`,
486
+ "aria-level": depth + 1,
487
+ "aria-selected": element_1 === selected,
488
+ "aria-expanded": children.length ? open_0 : void 0,
489
+ children: [/* @__PURE__ */ jsxs("div", {
490
+ className: "povio-canvas-layer-row",
491
+ style: { paddingLeft: 8 + depth * 12 },
492
+ children: [children.length ? /* @__PURE__ */ jsx("button", {
493
+ className: "povio-canvas-layer-toggle",
494
+ "aria-label": `${open_0 ? "Collapse" : "Expand"} ${element_1.localName}${label ? ` ${label}` : ""}`,
495
+ onClick: () => toggle(element_1, open_0),
496
+ children: open_0 ? /* @__PURE__ */ jsx(ChevronDown, { size: 12 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 12 })
497
+ }) : /* @__PURE__ */ jsx("span", { className: "povio-canvas-layer-spacer" }), /* @__PURE__ */ jsxs("button", {
498
+ className: "povio-canvas-layer-select",
499
+ title: `${element_1.localName}${label ? ` · ${label}` : ""}`,
500
+ onClick: () => onSelect(element_1),
501
+ onKeyDown: (event) => {
502
+ if (event.key === "ArrowRight" && children.length && !open_0) {
503
+ event.preventDefault();
504
+ toggle(element_1, false);
505
+ }
506
+ if (event.key === "ArrowLeft" && open_0) {
507
+ event.preventDefault();
508
+ toggle(element_1, true);
509
+ }
510
+ },
511
+ children: [/* @__PURE__ */ jsx("code", { children: element_1.localName }), label && /* @__PURE__ */ jsx("span", { children: label })]
512
+ })]
513
+ }), open_0 && !!children.length && /* @__PURE__ */ jsx("div", {
514
+ role: "group",
515
+ children: children.map((child) => render(child, depth + 1))
516
+ })]
517
+ }, ids.current.get(element_1));
518
+ };
519
+ const content = root ? childrenOf(root).map((element_2) => render(element_2, 0)) : null;
520
+ return /* @__PURE__ */ jsxs("section", {
521
+ className: "povio-canvas-layers",
522
+ "aria-label": `Layers for ${title}`,
523
+ children: [
524
+ /* @__PURE__ */ jsxs("header", { children: [
525
+ /* @__PURE__ */ jsx(CodeXml, { size: 14 }),
526
+ /* @__PURE__ */ jsx("strong", { children: "Layers" }),
527
+ /* @__PURE__ */ jsx("span", { children: title })
528
+ ] }),
529
+ /* @__PURE__ */ jsx("div", {
530
+ role: "tree",
531
+ "aria-label": "HTML layers",
532
+ children: content
533
+ }),
534
+ !root && /* @__PURE__ */ jsx("p", { children: "Waiting for the page…" }),
535
+ truncated && /* @__PURE__ */ jsx("button", {
536
+ className: "povio-canvas-more-layers",
537
+ onClick: () => setLimit((value_0) => value_0 + 300),
538
+ children: "Show more layers"
539
+ })
540
+ ]
541
+ });
542
+ }
543
+ //#endregion
544
+ //#region src/app-canvas/CanvasInspector.tsx
545
+ const unknownToken = () => false;
546
+ const readOnly = () => {};
547
+ /** Inspect the live iframe DOM using its own, unscaled viewport coordinates. */
548
+ function useCanvasInspector(enabled, generation) {
549
+ const [selection, setSelection] = useState(null);
550
+ const [measurement, setMeasurement] = useState(null);
551
+ const [pinned, setPinned] = useState(false);
552
+ const [highlightKey, setHighlightKey] = useState(null);
553
+ const hoverFrame = useRef(null);
554
+ const pending = useRef(null);
555
+ const clear = useCallback(() => {
556
+ if (hoverFrame.current !== null) cancelAnimationFrame(hoverFrame.current);
557
+ hoverFrame.current = null;
558
+ pending.current = null;
559
+ setSelection(null);
560
+ setMeasurement(null);
561
+ setPinned(false);
562
+ setHighlightKey(null);
563
+ }, []);
564
+ useEffect(() => {
565
+ if (!enabled) clear();
566
+ }, [enabled, clear]);
567
+ useEffect(() => {
568
+ clear();
569
+ }, [generation, clear]);
570
+ useEffect(() => () => {
571
+ if (hoverFrame.current !== null) cancelAnimationFrame(hoverFrame.current);
572
+ }, []);
573
+ const pick = useCallback((pageId, frame, clientX, clientY, pin) => {
574
+ if (!enabled || pinned && !pin) return;
575
+ const bounds = frame.getBoundingClientRect();
576
+ if (!bounds.width || !bounds.height) return;
577
+ try {
578
+ const doc = frame.contentDocument;
579
+ if (!doc) return;
580
+ const x = (clientX - bounds.left) * frame.clientWidth / bounds.width;
581
+ const y = (clientY - bounds.top) * frame.clientHeight / bounds.height;
582
+ const hit = doc.elementFromPoint(x, y);
583
+ const element = hit?.closest("button,input,textarea,select,a,label,[role],h1,h2,h3,h4,h5,h6,p") ?? hit;
584
+ if (!element || element === doc.documentElement || element === doc.body || element.namespaceURI !== "http://www.w3.org/1999/xhtml") return;
585
+ const target = element;
586
+ setSelection((current) => current?.element === target ? current : {
587
+ pageId,
588
+ element: target
589
+ });
590
+ setHighlightKey(null);
591
+ if (pin) setPinned((current_0) => !(current_0 && selection?.element === target));
592
+ } catch {
593
+ clear();
594
+ }
595
+ }, [
596
+ enabled,
597
+ pinned,
598
+ selection,
599
+ clear
600
+ ]);
601
+ const hover = useCallback((pageId_0, frame_0, x_0, y_0) => {
602
+ if (!enabled || pinned) return;
603
+ pending.current = () => pick(pageId_0, frame_0, x_0, y_0, false);
604
+ if (hoverFrame.current !== null) return;
605
+ hoverFrame.current = requestAnimationFrame(() => {
606
+ hoverFrame.current = null;
607
+ pending.current?.();
608
+ pending.current = null;
609
+ });
610
+ }, [
611
+ enabled,
612
+ pinned,
613
+ pick
614
+ ]);
615
+ const select = useCallback((pageId_1, frame_1, x_1, y_1) => {
616
+ if (hoverFrame.current !== null) cancelAnimationFrame(hoverFrame.current);
617
+ hoverFrame.current = null;
618
+ pending.current = null;
619
+ pick(pageId_1, frame_1, x_1, y_1, true);
620
+ }, [pick]);
621
+ useEffect(() => {
622
+ if (!enabled || !selection) return;
623
+ const { element: element_0 } = selection;
624
+ const doc_0 = element_0.ownerDocument;
625
+ const owner = doc_0.defaultView;
626
+ if (!owner) return;
627
+ let scheduled = null;
628
+ const measure = () => {
629
+ scheduled = null;
630
+ if (!element_0.isConnected) {
631
+ clear();
632
+ return;
633
+ }
634
+ const rect = element_0.getBoundingClientRect();
635
+ const snapshot = inspectElement(element_0, unknownToken);
636
+ const details = highlightKey ? getBoxHighlights(element_0, highlightKey) : null;
637
+ const spacing = highlightKey ? [] : [
638
+ "padding.top",
639
+ "padding.right",
640
+ "padding.bottom",
641
+ "padding.left",
642
+ "margin.top",
643
+ "margin.right",
644
+ "margin.bottom",
645
+ "margin.left",
646
+ "gap.row",
647
+ "gap.column"
648
+ ].flatMap((key) => {
649
+ const [kind, side] = key.split(".");
650
+ const raw = kind === "padding" ? snapshot.box.padding[side] : kind === "margin" ? snapshot.box.margin[side] : snapshot.box.gap[side];
651
+ if (!(Math.abs(Number.parseFloat(raw)) > 0)) return [];
652
+ const details_0 = getBoxHighlights(element_0, key);
653
+ const regions = details_0.regions.filter((region) => region.width > 0 && region.height > 0);
654
+ return regions.length ? [{
655
+ key,
656
+ label: details_0.label,
657
+ regions
658
+ }] : [];
659
+ });
660
+ if (!highlightKey) {
661
+ const seen = new Set(spacing.flatMap((item) => item.regions.map((region_0) => `${item.key}:${region_0.left}:${region_0.top}:${region_0.width}:${region_0.height}`)));
662
+ for (const child of Array.from(element_0.children).slice(0, 80)) {
663
+ if (child.namespaceURI !== "http://www.w3.org/1999/xhtml" || !child.getClientRects().length) continue;
664
+ const style = owner.getComputedStyle(child);
665
+ if (style.display === "none" || style.visibility === "hidden" || /^(absolute|fixed)$/.test(style.position)) continue;
666
+ for (const side_0 of [
667
+ "top",
668
+ "right",
669
+ "bottom",
670
+ "left"
671
+ ]) {
672
+ if (!(Math.abs(Number.parseFloat(style.getPropertyValue(`margin-${side_0}`))) > 0)) continue;
673
+ const key_0 = `margin.${side_0}`;
674
+ const details_1 = getBoxHighlights(child, key_0);
675
+ const regions_0 = details_1.regions.filter((region_1) => {
676
+ const id = `${key_0}:${region_1.left}:${region_1.top}:${region_1.width}:${region_1.height}`;
677
+ if (seen.has(id)) return false;
678
+ seen.add(id);
679
+ return region_1.width > 0 && region_1.height > 0;
680
+ });
681
+ if (regions_0.length) spacing.push({
682
+ key: key_0,
683
+ label: details_1.label,
684
+ regions: regions_0
685
+ });
686
+ }
687
+ }
688
+ }
689
+ const next = {
690
+ snapshot,
691
+ spacing,
692
+ outline: {
693
+ left: rect.left,
694
+ top: rect.top,
695
+ width: rect.width,
696
+ height: rect.height
697
+ },
698
+ highlight: details && highlightKey ? {
699
+ key: highlightKey,
700
+ ...details
701
+ } : null
702
+ };
703
+ setMeasurement((current_1) => JSON.stringify(current_1) === JSON.stringify(next) ? current_1 : next);
704
+ };
705
+ const schedule = () => {
706
+ if (scheduled === null) scheduled = requestAnimationFrame(measure);
707
+ };
708
+ const mutations = new MutationObserver(schedule);
709
+ mutations.observe(doc_0.documentElement, {
710
+ subtree: true,
711
+ childList: true,
712
+ attributes: true,
713
+ characterData: true
714
+ });
715
+ const resize = new ResizeObserver(schedule);
716
+ resize.observe(element_0);
717
+ owner.addEventListener("scroll", schedule, true);
718
+ measure();
719
+ return () => {
720
+ mutations.disconnect();
721
+ resize.disconnect();
722
+ owner.removeEventListener("scroll", schedule, true);
723
+ if (scheduled !== null) cancelAnimationFrame(scheduled);
724
+ };
725
+ }, [
726
+ enabled,
727
+ selection,
728
+ highlightKey,
729
+ clear
730
+ ]);
731
+ return {
732
+ selection,
733
+ selectElement: (pageId_2, element_1) => {
734
+ setSelection({
735
+ pageId: pageId_2,
736
+ element: element_1
737
+ });
738
+ setPinned(true);
739
+ setHighlightKey(null);
740
+ },
741
+ measurement,
742
+ pinned,
743
+ hover,
744
+ select,
745
+ clear,
746
+ highlight: setHighlightKey,
747
+ togglePin: () => setPinned((value) => !value)
748
+ };
749
+ }
750
+ function CanvasInspectorPanel(t0) {
751
+ const $ = c(21);
752
+ const { inspector, onClose } = t0;
753
+ let t1;
754
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
755
+ t1 = /* @__PURE__ */ jsx("strong", { children: "Inspector view" });
756
+ $[0] = t1;
757
+ } else t1 = $[0];
758
+ const t2 = !inspector.selection;
759
+ let t3;
760
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
761
+ t3 = /* @__PURE__ */ jsx(Pin, { size: 14 });
762
+ $[1] = t3;
763
+ } else t3 = $[1];
764
+ let t4;
765
+ if ($[2] !== inspector.pinned || $[3] !== inspector.togglePin || $[4] !== t2) {
766
+ t4 = /* @__PURE__ */ jsx("button", {
767
+ "aria-label": "Pin inspected element",
768
+ "aria-pressed": inspector.pinned,
769
+ disabled: t2,
770
+ onClick: inspector.togglePin,
771
+ children: t3
772
+ });
773
+ $[2] = inspector.pinned;
774
+ $[3] = inspector.togglePin;
775
+ $[4] = t2;
776
+ $[5] = t4;
777
+ } else t4 = $[5];
778
+ let t5;
779
+ if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
780
+ t5 = /* @__PURE__ */ jsx(X, { size: 15 });
781
+ $[6] = t5;
782
+ } else t5 = $[6];
783
+ let t6;
784
+ if ($[7] !== onClose) {
785
+ t6 = /* @__PURE__ */ jsx("button", {
786
+ "aria-label": "Close inspector view",
787
+ onClick: onClose,
788
+ children: t5
789
+ });
790
+ $[7] = onClose;
791
+ $[8] = t6;
792
+ } else t6 = $[8];
793
+ let t7;
794
+ if ($[9] !== t4 || $[10] !== t6) {
795
+ t7 = /* @__PURE__ */ jsxs("header", { children: [
796
+ t1,
797
+ t4,
798
+ t6
799
+ ] });
800
+ $[9] = t4;
801
+ $[10] = t6;
802
+ $[11] = t7;
803
+ } else t7 = $[11];
804
+ const t8 = inspector.pinned ? "Selection pinned · click it again to unpin" : "Hover a component · click to pin";
805
+ let t9;
806
+ if ($[12] !== t8) {
807
+ t9 = /* @__PURE__ */ jsx("p", {
808
+ className: "povio-canvas-inspect-hint",
809
+ children: t8
810
+ });
811
+ $[12] = t8;
812
+ $[13] = t9;
813
+ } else t9 = $[13];
814
+ let t10;
815
+ if ($[14] !== inspector.highlight || $[15] !== inspector.measurement) {
816
+ t10 = inspector.measurement ? /* @__PURE__ */ jsx(InspectorDetails, {
817
+ snapshot: inspector.measurement.snapshot,
818
+ onSelect: readOnly,
819
+ onHighlight: inspector.highlight
820
+ }) : /* @__PURE__ */ jsx("p", {
821
+ className: "povio-canvas-inspect-hint",
822
+ children: "Inspect a live page to see its padding, margins, borders, radii and gaps."
823
+ });
824
+ $[14] = inspector.highlight;
825
+ $[15] = inspector.measurement;
826
+ $[16] = t10;
827
+ } else t10 = $[16];
828
+ let t11;
829
+ if ($[17] !== t10 || $[18] !== t7 || $[19] !== t9) {
830
+ t11 = /* @__PURE__ */ jsxs("aside", {
831
+ className: "studio-inspector-panel povio-canvas-inspector",
832
+ "aria-label": "Inspector view",
833
+ children: [
834
+ t7,
835
+ t9,
836
+ t10
837
+ ]
838
+ });
839
+ $[17] = t10;
840
+ $[18] = t7;
841
+ $[19] = t9;
842
+ $[20] = t11;
843
+ } else t11 = $[20];
844
+ return t11;
845
+ }
846
+ function CanvasInspectionOverlay(t0) {
847
+ const $ = c(9);
848
+ const { measurement, width } = t0;
849
+ let t1;
850
+ if ($[0] !== measurement.outline) {
851
+ t1 = /* @__PURE__ */ jsx("div", {
852
+ className: "studio-hover-outline",
853
+ style: measurement.outline
854
+ });
855
+ $[0] = measurement.outline;
856
+ $[1] = t1;
857
+ } else t1 = $[1];
858
+ let t2;
859
+ if ($[2] !== measurement.highlight || $[3] !== measurement.spacing || $[4] !== width) {
860
+ t2 = measurement.highlight ? /* @__PURE__ */ jsx(HighlightOverlay, {
861
+ highlight: measurement.highlight,
862
+ width
863
+ }) : measurement.spacing.map((highlight) => /* @__PURE__ */ jsxs("div", {
864
+ className: "povio-canvas-auto-spacing",
865
+ children: [/* @__PURE__ */ jsx(HighlightOverlay, {
866
+ highlight,
867
+ width
868
+ }), highlight.regions.map((region) => /* @__PURE__ */ jsx("span", {
869
+ className: "povio-canvas-spacing-value",
870
+ style: {
871
+ left: region.left + region.width / 2,
872
+ top: region.top + region.height / 2
873
+ },
874
+ children: highlight.label.split(": ")[1]
875
+ }, `${region.left}:${region.top}:${region.width}:${region.height}`))]
876
+ }, `${highlight.key}:${highlight.label}:${highlight.regions[0]?.left}:${highlight.regions[0]?.top}`));
877
+ $[2] = measurement.highlight;
878
+ $[3] = measurement.spacing;
879
+ $[4] = width;
880
+ $[5] = t2;
881
+ } else t2 = $[5];
882
+ let t3;
883
+ if ($[6] !== t1 || $[7] !== t2) {
884
+ t3 = /* @__PURE__ */ jsxs("div", {
885
+ className: "povio-canvas-inspection-overlay",
886
+ "aria-hidden": "true",
887
+ children: [t1, t2]
888
+ });
889
+ $[6] = t1;
890
+ $[7] = t2;
891
+ $[8] = t3;
892
+ } else t3 = $[8];
893
+ return t3;
894
+ }
895
+ //#endregion
896
+ //#region src/app-canvas/route-model.ts
897
+ function joinPath(parent, path) {
898
+ return `${parent.replace(/\/$/, "")}/${path.replace(/^\//, "")}`.replace(/\/{2,}/g, "/") || "/";
899
+ }
900
+ function resolvePath(path, params) {
901
+ const missingParams = /* @__PURE__ */ new Set();
902
+ const value = (key, optional = false) => {
903
+ const actualKey = key || "_splat";
904
+ const raw = params[actualKey];
905
+ if (raw === void 0 || !optional && raw === "") {
906
+ if (!optional) missingParams.add(actualKey);
907
+ return "";
908
+ }
909
+ return actualKey === "_splat" ? raw.split("/").map(encodeURIComponent).join("/") : encodeURIComponent(raw);
910
+ };
911
+ return {
912
+ href: `/${path.split("/").map((segment) => {
913
+ if (segment.includes("{")) return segment.replace(/\{(-?)\$([^}]*)\}/g, (_, optional, key) => value(key, optional === "-"));
914
+ return segment.startsWith("$") ? value(segment.slice(1)) : segment;
915
+ }).filter(Boolean).join("/")}`,
916
+ missingParams: [...missingParams]
917
+ };
918
+ }
919
+ /** Catalog route destinations without executing loaders or needing every dynamic parameter. */
920
+ function discoverCanvasPages(router, options = {}) {
921
+ const records = [];
922
+ const visited = /* @__PURE__ */ new Set();
923
+ function visit(route, parentPath, ancestors, root = false) {
924
+ if (visited.has(route)) return;
925
+ visited.add(route);
926
+ const localPath = route.path ?? route.options?.path;
927
+ const path = route.fullPath ?? (localPath ? joinPath(parentPath, localPath) : parentPath);
928
+ const id = route.id ?? route.options?.id ?? path;
929
+ const children = Object.values(route.children ?? {});
930
+ if (!root && localPath !== void 0 && !options.exclude?.includes(id) && !options.excludePaths?.some((excluded) => (excluded.replace(/\/$/, "") || "/") === (path.replace(/\/$/, "") || "/"))) records.push({
931
+ route,
932
+ id,
933
+ path,
934
+ ancestors,
935
+ index: localPath === "/"
936
+ });
937
+ for (const child of children) visit(child, path, [...ancestors, id]);
938
+ }
939
+ visit(router.routeTree, "/", [], true);
940
+ for (const route of Object.values(router.routesById ?? {})) if (!visited.has(route)) visit(route, "/", []);
941
+ const byPath = /* @__PURE__ */ new Map();
942
+ for (const record of records) {
943
+ const key = record.path.replace(/\/$/, "") || "/";
944
+ const existing = byPath.get(key);
945
+ if (!existing || record.index || record.ancestors.length > existing.ancestors.length) byPath.set(key, record);
946
+ }
947
+ const selectedIds = new Set([...byPath.values()].map(({ id }) => id));
948
+ const destinationIds = new Map(records.map((record) => [record.id, byPath.get(record.path.replace(/\/$/, "") || "/")?.id]));
949
+ return [...byPath.values()].map(({ id, path, ancestors }) => {
950
+ const example = options.examples?.[id];
951
+ const resolved = resolvePath(path, example?.params ?? {});
952
+ const query = new URLSearchParams();
953
+ for (const [key, value] of Object.entries(example?.search ?? {})) if (value !== void 0) query.set(key, typeof value === "string" ? value : JSON.stringify(value));
954
+ const queryString = query.toString();
955
+ return {
956
+ id,
957
+ path,
958
+ title: example?.title ?? (path === "/" ? "Home" : path),
959
+ href: resolved.missingParams.length ? void 0 : `${resolved.href}${queryString ? `?${queryString}` : ""}`,
960
+ parentId: [...ancestors].reverse().map((ancestor) => destinationIds.get(ancestor) ?? ancestor).find((ancestor) => ancestor !== id && selectedIds.has(ancestor)),
961
+ missingParams: resolved.missingParams,
962
+ kind: resolved.missingParams.length ? "unresolved" : "page"
963
+ };
964
+ });
965
+ }
966
+ /** Deterministic masonry placement. Full measured heights (including 20,000px pages) are never capped. */
967
+ function layoutCanvasPages(pages, width, heights, positions = {}) {
968
+ const pageWidth = Number.isFinite(width) && width > 0 ? width : 1440;
969
+ const columns = Math.min(3, Math.max(1, pages.length));
970
+ const bottoms = Array.from({ length: columns }, () => 0);
971
+ for (const page of pages) {
972
+ const saved = positions[page.id];
973
+ if (!saved || !Number.isFinite(saved.x) || !Number.isFinite(saved.y)) continue;
974
+ const measured = heights[page.id];
975
+ const height = Number.isFinite(measured) && measured > 0 ? measured : 900;
976
+ for (let i = 0; i < columns; i++) {
977
+ const x = i * (pageWidth + 80);
978
+ if (saved.x < x + pageWidth && saved.x + pageWidth > x) bottoms[i] = Math.max(bottoms[i], saved.y + height + 128);
979
+ }
980
+ }
981
+ const result = [];
982
+ for (const page of pages) {
983
+ const measured = heights[page.id];
984
+ const height = Number.isFinite(measured) && measured > 0 ? measured : 900;
985
+ const column = bottoms.indexOf(Math.min(...bottoms));
986
+ const saved = positions[page.id];
987
+ const position = saved && Number.isFinite(saved.x) && Number.isFinite(saved.y) ? saved : {
988
+ x: column * (pageWidth + 80),
989
+ y: bottoms[column]
990
+ };
991
+ result.push({
992
+ ...page,
993
+ ...position,
994
+ width: pageWidth,
995
+ height
996
+ });
997
+ for (let i = 0; i < columns; i++) {
998
+ const x = i * (pageWidth + 80);
999
+ if (position.x < x + pageWidth && position.x + pageWidth > x) bottoms[i] = Math.max(bottoms[i], position.y + height + 128);
1000
+ }
1001
+ }
1002
+ return result;
1003
+ }
1004
+ /** Match observed navigation back to a catalog destination, including optional and splat routes. */
1005
+ function matchCanvasPageHref(pages, href, base = "http://canvas.local/") {
1006
+ let pathname;
1007
+ try {
1008
+ pathname = new URL(href, base).pathname.replace(/\/$/, "") || "/";
1009
+ } catch {
1010
+ return;
1011
+ }
1012
+ const exact = pages.find((page) => {
1013
+ if (!page.href) return false;
1014
+ try {
1015
+ return (new URL(page.href, base).pathname.replace(/\/$/, "") || "/") === pathname;
1016
+ } catch {
1017
+ return false;
1018
+ }
1019
+ });
1020
+ if (exact) return exact;
1021
+ const escape = (part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1022
+ for (const page of pages) {
1023
+ const pattern = page.path.split("/").filter(Boolean).map((part) => {
1024
+ if (part === "$") return "/.+";
1025
+ if (part.startsWith("$")) return "/[^/]+";
1026
+ let text = "", cursor = 0;
1027
+ let optional = false;
1028
+ for (const match of part.matchAll(/\{(-?)\$([^}]*)\}/g)) {
1029
+ text += escape(part.slice(cursor, match.index)) + (match[2] ? "[^/]+" : ".+");
1030
+ cursor = match.index + match[0].length;
1031
+ optional ||= match[1] === "-";
1032
+ }
1033
+ text += escape(part.slice(cursor));
1034
+ return optional ? `(?:/${text})?` : `/${text}`;
1035
+ }).join("");
1036
+ if (new RegExp(`^${pattern || "/"}$`).test(pathname)) return page;
1037
+ }
1038
+ }
1039
+ //#endregion
1040
+ //#region src/app-canvas/protocol.ts
1041
+ const canvasProtocol = "povio-app-canvas-v1";
1042
+ function canvasFrameIdentity() {
1043
+ if (typeof window === "undefined" || window.parent === window) return null;
1044
+ try {
1045
+ const query = new URLSearchParams(window.location.search);
1046
+ const name = window.name.startsWith(`povio-app-canvas-v1:`) ? JSON.parse(window.name.slice(20)) : null;
1047
+ const id = query.get("__povio_canvas_frame") ?? name?.id;
1048
+ const token = query.get("__povio_canvas_token") ?? name?.token;
1049
+ const height = Number(query.get("__povio_canvas_height") ?? name?.viewportHeight ?? 900);
1050
+ return typeof id === "string" && typeof token === "string" && token.length > 10 ? {
1051
+ id,
1052
+ token,
1053
+ viewportHeight: Math.max(240, Math.min(4e3, height || 900))
1054
+ } : null;
1055
+ } catch {
1056
+ return null;
1057
+ }
1058
+ }
1059
+ const isAppCanvasFrame = () => !!canvasFrameIdentity();
1060
+ function validConfiguration(value) {
1061
+ if (!value || typeof value !== "object") return false;
1062
+ const config = value;
1063
+ return !!config.theme && [
1064
+ "light",
1065
+ "dark",
1066
+ "theme"
1067
+ ].every((key) => {
1068
+ const tokens = config.theme[key];
1069
+ return tokens && typeof tokens === "object" && Object.values(tokens).every((v) => typeof v === "string");
1070
+ }) && !!config.config && typeof config.config === "object";
1071
+ }
1072
+ //#endregion
1073
+ //#region src/app-canvas/manifest.ts
1074
+ function createCanvasManifest() {
1075
+ return {
1076
+ version: 1,
1077
+ positions: {},
1078
+ examples: {},
1079
+ manualEdges: [],
1080
+ hiddenEdges: [],
1081
+ breakpoint: "desktop"
1082
+ };
1083
+ }
1084
+ function isRecord(value) {
1085
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1086
+ }
1087
+ function isJSON(value, ancestors = /* @__PURE__ */ new Set(), depth = 0) {
1088
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1089
+ if (typeof value === "number") return Number.isFinite(value);
1090
+ if (typeof value !== "object" || depth > 30 || ancestors.has(value)) return false;
1091
+ ancestors.add(value);
1092
+ const valid = Object.values(value).every((child) => isJSON(child, ancestors, depth + 1));
1093
+ ancestors.delete(value);
1094
+ return valid;
1095
+ }
1096
+ /** Sanitize either stored JSON text or an imported value, retaining valid v1 entries. */
1097
+ function parseCanvasManifest(input) {
1098
+ const fallback = createCanvasManifest();
1099
+ let value = input;
1100
+ if (typeof value === "string") try {
1101
+ value = JSON.parse(value);
1102
+ } catch {
1103
+ return fallback;
1104
+ }
1105
+ if (!isRecord(value) || value.version !== 1) return fallback;
1106
+ const positions = Object.fromEntries(Object.entries(isRecord(value.positions) ? value.positions : {}).flatMap(([breakpoint, entries]) => {
1107
+ if (!isRecord(entries)) return [];
1108
+ 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, {
1109
+ x: point.x,
1110
+ y: point.y
1111
+ }]] : []))]];
1112
+ }));
1113
+ const examples = Object.fromEntries(Object.entries(isRecord(value.examples) ? value.examples : {}).flatMap(([id, example]) => {
1114
+ if (!isRecord(example)) return [];
1115
+ const clean = {};
1116
+ if (typeof example.title === "string") clean.title = example.title;
1117
+ if (isRecord(example.params)) clean.params = Object.fromEntries(Object.entries(example.params).filter((entry) => typeof entry[1] === "string"));
1118
+ if (isRecord(example.search)) clean.search = Object.fromEntries(Object.entries(example.search).filter(([, item]) => isJSON(item)));
1119
+ return [[id, clean]];
1120
+ }));
1121
+ 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 ? [{
1122
+ from: edge.from,
1123
+ to: edge.to
1124
+ }] : []);
1125
+ return {
1126
+ version: 1,
1127
+ positions,
1128
+ examples,
1129
+ manualEdges: [...new Map(manualEdges.map((edge) => [JSON.stringify(edge), edge])).values()],
1130
+ hiddenEdges: [...new Set((Array.isArray(value.hiddenEdges) ? value.hiddenEdges : []).filter((edge) => typeof edge === "string"))],
1131
+ breakpoint: typeof value.breakpoint === "string" && value.breakpoint.length > 0 ? value.breakpoint : fallback.breakpoint
1132
+ };
1133
+ }
1134
+ //#endregion
1135
+ //#region src/app-canvas/AppCanvas.tsx
1136
+ const defaultCanvasBreakpoints = [
1137
+ {
1138
+ id: "mobile",
1139
+ label: "Mobile",
1140
+ width: 390,
1141
+ height: 844
1142
+ },
1143
+ {
1144
+ id: "tablet",
1145
+ label: "Tablet",
1146
+ width: 768,
1147
+ height: 1024
1148
+ },
1149
+ {
1150
+ id: "desktop",
1151
+ label: "Desktop",
1152
+ width: 1440,
1153
+ height: 900
1154
+ }
1155
+ ];
1156
+ const emptyExamples = {};
1157
+ const emptyExclude = [];
1158
+ function readManifest(key) {
1159
+ if (typeof window === "undefined") return createCanvasManifest();
1160
+ try {
1161
+ return parseCanvasManifest(localStorage.getItem(key));
1162
+ } catch {
1163
+ return createCanvasManifest();
1164
+ }
1165
+ }
1166
+ const PageFrame = memo(function PageFrame(t0) {
1167
+ const $ = c(16);
1168
+ const { id, src, width, height, name, interact, inspect, onLoad, register } = t0;
1169
+ let t1;
1170
+ if ($[0] !== id || $[1] !== register) {
1171
+ t1 = (element) => register(id, element);
1172
+ $[0] = id;
1173
+ $[1] = register;
1174
+ $[2] = t1;
1175
+ } else t1 = $[2];
1176
+ const t2 = !interact && !inspect;
1177
+ const t3 = interact ? 0 : -1;
1178
+ const t4 = `Page ${id}`;
1179
+ let t5;
1180
+ if ($[3] !== id || $[4] !== onLoad) {
1181
+ t5 = (event) => onLoad(id, event.currentTarget);
1182
+ $[3] = id;
1183
+ $[4] = onLoad;
1184
+ $[5] = t5;
1185
+ } else t5 = $[5];
1186
+ let t6;
1187
+ if ($[6] !== height || $[7] !== name || $[8] !== src || $[9] !== t1 || $[10] !== t2 || $[11] !== t3 || $[12] !== t4 || $[13] !== t5 || $[14] !== width) {
1188
+ t6 = /* @__PURE__ */ jsx("iframe", {
1189
+ ref: t1,
1190
+ inert: t2,
1191
+ tabIndex: t3,
1192
+ title: t4,
1193
+ name,
1194
+ src,
1195
+ width,
1196
+ height,
1197
+ className: "povio-canvas-frame",
1198
+ onLoad: t5
1199
+ });
1200
+ $[6] = height;
1201
+ $[7] = name;
1202
+ $[8] = src;
1203
+ $[9] = t1;
1204
+ $[10] = t2;
1205
+ $[11] = t3;
1206
+ $[12] = t4;
1207
+ $[13] = t5;
1208
+ $[14] = width;
1209
+ $[15] = t6;
1210
+ } else t6 = $[15];
1211
+ return t6;
1212
+ });
1213
+ /** Live route artboards. The camera transforms only the canvas, never the iframe viewport width. */
1214
+ function AppCanvas({ router, examples = emptyExamples, exclude = emptyExclude, excludePaths = emptyExclude, breakpoints = defaultCanvasBreakpoints, storageKey = "povio-app-canvas", frameUrl }) {
1215
+ const configuration = useUIConfiguration();
1216
+ const [manifest, setManifest] = useState(() => readManifest(storageKey));
1217
+ const [runtime, setRuntime] = useState({});
1218
+ const [selected, setSelected] = useState(null);
1219
+ const [interact, setInteract] = useState(false);
1220
+ const [inspectMode, setInspectMode] = useState(false);
1221
+ const [dark, setDark] = useState(false);
1222
+ const [mobilePanel, setMobilePanel] = useState(null);
1223
+ const [query, setQuery] = useState("");
1224
+ const [notice, setNotice] = useState("");
1225
+ const [documentGeneration, setDocumentGeneration] = useState(0);
1226
+ const [reload, setReload] = useState({});
1227
+ const zoomOutput = useRef(null);
1228
+ const importInput = useRef(null);
1229
+ const viewport = useRef(null), world = useRef(null);
1230
+ const frames = useRef(/* @__PURE__ */ new Map());
1231
+ const [token] = useState(() => crypto.randomUUID());
1232
+ const revision = useRef(0), camera = useRef({
1233
+ x: 40,
1234
+ y: 40,
1235
+ zoom: .25
1236
+ }), pendingCamera = useRef(null);
1237
+ const pointer = useRef(null);
1238
+ const cameraRevision = useRef(0);
1239
+ const bp = breakpoints.find((item) => item.id === manifest.breakpoint) ?? breakpoints[breakpoints.length - 1] ?? defaultCanvasBreakpoints[2];
1240
+ const inspector = useCanvasInspector(inspectMode, `${bp.id}:${documentGeneration}:${JSON.stringify(reload)}`);
1241
+ const catalog = useMemo(() => discoverCanvasPages(router, {
1242
+ examples: {
1243
+ ...examples,
1244
+ ...manifest.examples
1245
+ },
1246
+ exclude,
1247
+ excludePaths
1248
+ }), [
1249
+ router,
1250
+ examples,
1251
+ manifest.examples,
1252
+ exclude,
1253
+ excludePaths
1254
+ ]);
1255
+ 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}`])), [
1256
+ catalog,
1257
+ documentGeneration,
1258
+ bp.id,
1259
+ reload
1260
+ ]);
1261
+ const startup = useFrameStartup([...startupKeys.values()]);
1262
+ const startupRef = useRef({
1263
+ keys: startupKeys,
1264
+ complete: startup.complete
1265
+ });
1266
+ startupRef.current = {
1267
+ keys: startupKeys,
1268
+ complete: startup.complete
1269
+ };
1270
+ const heights = useMemo(() => Object.fromEntries(catalog.map((page_1) => [page_1.id, runtime[`${bp.id}:${page_1.id}:${page_1.href ?? ""}`]?.height ?? bp.height])), [
1271
+ catalog,
1272
+ runtime,
1273
+ bp
1274
+ ]);
1275
+ const nodes = useMemo(() => layoutCanvasPages(catalog, bp.width, heights, manifest.positions[bp.id] ?? {}), [
1276
+ catalog,
1277
+ bp,
1278
+ heights,
1279
+ manifest.positions
1280
+ ]);
1281
+ const nodeMap = useMemo(() => new Map(nodes.map((node) => [node.id, node])), [nodes]);
1282
+ const latest = useRef({
1283
+ configuration: configuration.active,
1284
+ dark,
1285
+ interact,
1286
+ bp,
1287
+ catalog,
1288
+ frameUrl
1289
+ });
1290
+ latest.current = {
1291
+ configuration: configuration.active,
1292
+ dark,
1293
+ interact,
1294
+ bp,
1295
+ catalog,
1296
+ frameUrl
1297
+ };
1298
+ const register = useCallback((id, frame) => {
1299
+ if (frame) frames.current.set(id, frame);
1300
+ else frames.current.delete(id);
1301
+ }, []);
1302
+ const paint = useCallback(() => {
1303
+ if (world.current) world.current.style.transform = `translate3d(${camera.current.x}px,${camera.current.y}px,0) scale(${camera.current.zoom})`;
1304
+ if (zoomOutput.current) zoomOutput.current.textContent = `${Math.round(camera.current.zoom * 1e3) / 10}%`;
1305
+ const canvas = viewport.current?.closest(".povio-app-canvas");
1306
+ if (canvas) canvas.setAttribute("data-camera-revision", String(++cameraRevision.current));
1307
+ }, []);
1308
+ const schedulePaint = useCallback(() => {
1309
+ if (pendingCamera.current !== null) return;
1310
+ pendingCamera.current = requestAnimationFrame(() => {
1311
+ pendingCamera.current = null;
1312
+ paint();
1313
+ });
1314
+ }, [paint]);
1315
+ const send = useCallback((id_0, type = "configuration") => {
1316
+ const frame_0 = frames.current.get(id_0);
1317
+ if (!frame_0?.contentWindow) return;
1318
+ const current = latest.current;
1319
+ try {
1320
+ frame_0.contentWindow.postMessage({
1321
+ protocol: canvasProtocol,
1322
+ id: id_0,
1323
+ token,
1324
+ viewportHeight: current.bp.height,
1325
+ type,
1326
+ configuration: current.configuration,
1327
+ revision: revision.current,
1328
+ dark: current.dark,
1329
+ interact: current.interact
1330
+ }, new URL(frame_0.src).origin);
1331
+ } catch {
1332
+ setNotice("This configuration could not be sent to the page. Check for non-serializable values.");
1333
+ }
1334
+ }, [token]);
1335
+ const focusPage = useCallback((id_1) => {
1336
+ const node_0 = nodeMap.get(id_1), view = viewport.current;
1337
+ if (!node_0 || !view) return;
1338
+ setSelected(id_1);
1339
+ camera.current.zoom = Math.min(1, Math.max(.08, (view.clientWidth - 100) / node_0.width));
1340
+ camera.current.x = 40 - node_0.x * camera.current.zoom;
1341
+ camera.current.y = 40 - node_0.y * camera.current.zoom;
1342
+ paint();
1343
+ }, [nodeMap, paint]);
1344
+ const focusRef = useRef(focusPage);
1345
+ focusRef.current = focusPage;
1346
+ const matchHref = useCallback((href) => matchCanvasPageHref(latest.current.catalog, href, window.location.href), []);
1347
+ useEffect(() => {
1348
+ const queue = /* @__PURE__ */ new Map();
1349
+ let batch = 0;
1350
+ const flush = () => {
1351
+ batch = 0;
1352
+ const changes = Array.from(queue);
1353
+ queue.clear();
1354
+ setRuntime((current_0) => {
1355
+ const next = { ...current_0 };
1356
+ let changed = false;
1357
+ for (const [key, item_0] of changes) {
1358
+ if (frames.current.get(item_0.id) !== item_0.frame || item_0.frame.contentWindow !== item_0.source) continue;
1359
+ next[key] = {
1360
+ ...next[key],
1361
+ ...item_0.change
1362
+ };
1363
+ changed = true;
1364
+ }
1365
+ return changed ? next : current_0;
1366
+ });
1367
+ };
1368
+ const receive = (event) => {
1369
+ const packet = event.data;
1370
+ if (packet?.protocol !== "povio-app-canvas-v1" || packet.token !== token || typeof packet.id !== "string") return;
1371
+ const frame_1 = frames.current.get(packet.id);
1372
+ if (!frame_1 || event.source !== frame_1.contentWindow || event.origin !== new URL(frame_1.src).origin) return;
1373
+ const key_0 = `${latest.current.bp.id}:${packet.id}:${latest.current.catalog.find((page_2) => page_2.id === packet.id)?.href ?? ""}`;
1374
+ let change = {};
1375
+ if (packet.type === "ready") {
1376
+ startupRef.current.complete(startupRef.current.keys.get(packet.id));
1377
+ change = {
1378
+ ready: true,
1379
+ href: packet.href,
1380
+ timedOut: false
1381
+ };
1382
+ send(packet.id);
1383
+ send(packet.id, "mode");
1384
+ }
1385
+ if (packet.type === "height" && typeof packet.height === "number" && Number.isFinite(packet.height) && packet.height > 0) change.height = packet.height;
1386
+ if (packet.type === "warning" && typeof packet.warning === "string") change.warning = packet.warning;
1387
+ if (packet.type === "ack" && typeof packet.revision === "number") change.revision = packet.revision;
1388
+ if (packet.type === "navigate" && typeof packet.href === "string") {
1389
+ const target = matchHref(packet.href);
1390
+ if (target) focusRef.current(target.id);
1391
+ else setNotice(`Navigation target is not in this canvas: ${packet.href}`);
1392
+ return;
1393
+ }
1394
+ if (!Object.keys(change).length) return;
1395
+ const pending = queue.get(key_0);
1396
+ const previous = pending?.frame === frame_1 && pending.source === event.source ? pending.change : {};
1397
+ queue.set(key_0, {
1398
+ id: packet.id,
1399
+ frame: frame_1,
1400
+ source: event.source,
1401
+ change: {
1402
+ ...previous,
1403
+ ...change
1404
+ }
1405
+ });
1406
+ if (!batch) batch = requestAnimationFrame(flush);
1407
+ };
1408
+ window.addEventListener("message", receive);
1409
+ return () => {
1410
+ window.removeEventListener("message", receive);
1411
+ cancelAnimationFrame(batch);
1412
+ };
1413
+ }, [
1414
+ token,
1415
+ send,
1416
+ matchHref
1417
+ ]);
1418
+ useEffect(() => {
1419
+ const frame_2 = requestAnimationFrame(() => {
1420
+ revision.current++;
1421
+ for (const id_2 of frames.current.keys()) send(id_2);
1422
+ });
1423
+ return () => cancelAnimationFrame(frame_2);
1424
+ }, [
1425
+ configuration.active,
1426
+ dark,
1427
+ send
1428
+ ]);
1429
+ useEffect(() => {
1430
+ for (const id_3 of frames.current.keys()) send(id_3, "mode");
1431
+ }, [interact, send]);
1432
+ useEffect(() => {
1433
+ try {
1434
+ localStorage.setItem(storageKey, JSON.stringify(manifest));
1435
+ } catch {
1436
+ setNotice("Browser storage is unavailable; use Export layout to save this canvas.");
1437
+ }
1438
+ }, [manifest, storageKey]);
1439
+ useEffect(() => {
1440
+ paint();
1441
+ const element = viewport.current;
1442
+ if (!element) return;
1443
+ const wheel = (event_0) => {
1444
+ event_0.preventDefault();
1445
+ if (event_0.ctrlKey || event_0.metaKey) {
1446
+ const box = element.getBoundingClientRect();
1447
+ const x = event_0.clientX - box.left, y = event_0.clientY - box.top;
1448
+ const previous_0 = camera.current.zoom;
1449
+ const unit = event_0.deltaMode === 1 ? 16 : event_0.deltaMode === 2 ? element.clientHeight : 1;
1450
+ const next_0 = Math.max(.005, Math.min(2, previous_0 * Math.exp(-event_0.deltaY * unit * .01)));
1451
+ camera.current.x = x - (x - camera.current.x) * next_0 / previous_0;
1452
+ camera.current.y = y - (y - camera.current.y) * next_0 / previous_0;
1453
+ camera.current.zoom = next_0;
1454
+ } else {
1455
+ camera.current.x -= event_0.deltaX;
1456
+ camera.current.y -= event_0.deltaY;
1457
+ }
1458
+ schedulePaint();
1459
+ };
1460
+ element.addEventListener("wheel", wheel, { passive: false });
1461
+ return () => {
1462
+ element.removeEventListener("wheel", wheel);
1463
+ if (pendingCamera.current !== null) cancelAnimationFrame(pendingCamera.current);
1464
+ pendingCamera.current = null;
1465
+ };
1466
+ }, [paint, schedulePaint]);
1467
+ const fit = () => {
1468
+ if (!nodes.length || !viewport.current) return;
1469
+ const left = Math.min(...nodes.map((n) => n.x)), top = Math.min(...nodes.map((n_0) => n_0.y));
1470
+ const width = Math.max(...nodes.map((n_1) => n_1.x + n_1.width)) - left, height = Math.max(...nodes.map((n_2) => n_2.y + n_2.height + 48)) - top;
1471
+ camera.current.zoom = Math.max(.005, Math.min(1, (viewport.current.clientWidth - 80) / width, (viewport.current.clientHeight - 80) / height));
1472
+ camera.current.x = 40 - left * camera.current.zoom;
1473
+ camera.current.y = 40 - top * camera.current.zoom;
1474
+ paint();
1475
+ };
1476
+ const zoomTo = (value) => {
1477
+ const view_0 = viewport.current;
1478
+ if (!view_0) return;
1479
+ const previous_1 = camera.current.zoom, next_1 = Math.max(.005, Math.min(2, value));
1480
+ camera.current.x = view_0.clientWidth / 2 - (view_0.clientWidth / 2 - camera.current.x) * next_1 / previous_1;
1481
+ camera.current.y = view_0.clientHeight / 2 - (view_0.clientHeight / 2 - camera.current.y) * next_1 / previous_1;
1482
+ camera.current.zoom = next_1;
1483
+ paint();
1484
+ };
1485
+ const stepZoom = (direction) => {
1486
+ const stops = [
1487
+ .005,
1488
+ .01,
1489
+ .02,
1490
+ .05,
1491
+ .1,
1492
+ .25,
1493
+ .5,
1494
+ .75,
1495
+ 1,
1496
+ 1.5,
1497
+ 2
1498
+ ];
1499
+ const current_1 = camera.current.zoom;
1500
+ const next_2 = direction === 1 ? stops.find((value_0) => value_0 > current_1 + 1e-4) : [...stops].reverse().find((value_1) => value_1 < current_1 - 1e-4);
1501
+ if (next_2 !== void 0) zoomTo(next_2);
1502
+ };
1503
+ const startPan = (event_1) => {
1504
+ if (inspectMode && event_1.target.closest(".povio-canvas-frame-cover")) return;
1505
+ if (event_1.button !== 0 || event_1.target.closest("button:not(.povio-canvas-frame-cover),input,select,.povio-canvas-node-header")) return;
1506
+ event_1.currentTarget.setPointerCapture(event_1.pointerId);
1507
+ pointer.current = {
1508
+ id: event_1.pointerId,
1509
+ x: event_1.clientX,
1510
+ y: event_1.clientY,
1511
+ startX: camera.current.x,
1512
+ startY: camera.current.y
1513
+ };
1514
+ };
1515
+ const move = (event_2) => {
1516
+ const current_2 = pointer.current;
1517
+ if (!current_2 || current_2.id !== event_2.pointerId) return;
1518
+ const dx = event_2.clientX - current_2.x, dy = event_2.clientY - current_2.y;
1519
+ if (current_2.node) {
1520
+ const element_0 = world.current?.querySelector(`[data-node-index="${nodes.findIndex((n_3) => n_3.id === current_2.node)}"]`);
1521
+ if (element_0) {
1522
+ element_0.style.left = `${current_2.nodeX + dx / camera.current.zoom}px`;
1523
+ element_0.style.top = `${current_2.nodeY + dy / camera.current.zoom}px`;
1524
+ }
1525
+ } else {
1526
+ camera.current.x = current_2.startX + dx;
1527
+ camera.current.y = current_2.startY + dy;
1528
+ schedulePaint();
1529
+ }
1530
+ };
1531
+ const end = (event_3) => {
1532
+ const current_3 = pointer.current;
1533
+ if (!current_3 || current_3.id !== event_3.pointerId) return;
1534
+ if (current_3.node) {
1535
+ const position = {
1536
+ x: current_3.nodeX + (event_3.clientX - current_3.x) / camera.current.zoom,
1537
+ y: current_3.nodeY + (event_3.clientY - current_3.y) / camera.current.zoom
1538
+ };
1539
+ setManifest((m) => ({
1540
+ ...m,
1541
+ positions: {
1542
+ ...m.positions,
1543
+ [bp.id]: {
1544
+ ...m.positions[bp.id],
1545
+ [current_3.node]: position
1546
+ }
1547
+ }
1548
+ }));
1549
+ }
1550
+ pointer.current = null;
1551
+ if (event_3.currentTarget.hasPointerCapture(event_3.pointerId)) event_3.currentTarget.releasePointerCapture(event_3.pointerId);
1552
+ };
1553
+ const onLoad = useCallback((id_4, frame_3) => {
1554
+ if (frames.current.get(id_4) !== frame_3) return;
1555
+ startupRef.current.complete(startupRef.current.keys.get(id_4));
1556
+ send(id_4);
1557
+ send(id_4, "mode");
1558
+ }, [send]);
1559
+ useEffect(() => {
1560
+ const timer = setTimeout(() => setRuntime((current_4) => {
1561
+ const next_3 = { ...current_4 };
1562
+ for (const page_3 of latest.current.catalog) {
1563
+ const key_1 = `${latest.current.bp.id}:${page_3.id}:${page_3.href ?? ""}`;
1564
+ if (page_3.href && frames.current.has(page_3.id) && !next_3[key_1]?.ready) next_3[key_1] = {
1565
+ ...next_3[key_1],
1566
+ timedOut: true
1567
+ };
1568
+ }
1569
+ return next_3;
1570
+ }), 15e3);
1571
+ return () => clearTimeout(timer);
1572
+ }, [
1573
+ bp.id,
1574
+ catalog,
1575
+ reload,
1576
+ startup.admitted
1577
+ ]);
1578
+ const exportLayout = () => {
1579
+ const url = URL.createObjectURL(new Blob([JSON.stringify(manifest, null, 2)], { type: "application/json" }));
1580
+ const a = document.createElement("a");
1581
+ a.href = url;
1582
+ a.download = "povio-app-canvas.json";
1583
+ a.click();
1584
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
1585
+ };
1586
+ const width_0 = Math.max(1, ...nodes.map((n_4) => n_4.x + n_4.width + 80)), height_0 = Math.max(1, ...nodes.map((n_5) => n_5.y + n_5.height + 128));
1587
+ const ready = nodes.filter((n_6) => runtime[`${bp.id}:${n_6.id}:${n_6.href ?? ""}`]?.ready).length;
1588
+ return /* @__PURE__ */ jsxs("section", {
1589
+ className: "povio-app-canvas",
1590
+ "data-mobile-panel": mobilePanel ?? "none",
1591
+ "data-inspecting": inspectMode,
1592
+ "aria-label": "App canvas",
1593
+ "data-ready-count": ready,
1594
+ "data-page-count": nodes.length,
1595
+ "data-camera-revision": "0",
1596
+ children: [
1597
+ /* @__PURE__ */ jsx("input", {
1598
+ ref: importInput,
1599
+ type: "file",
1600
+ accept: "application/json,.json",
1601
+ hidden: true,
1602
+ "aria-label": "Import canvas layout",
1603
+ onChange: async (event_4) => {
1604
+ const file = event_4.currentTarget.files?.[0];
1605
+ event_4.currentTarget.value = "";
1606
+ if (!file) return;
1607
+ try {
1608
+ const raw = JSON.parse(await file.text());
1609
+ if (raw?.version !== 1) throw new Error("version");
1610
+ setManifest(parseCanvasManifest(raw));
1611
+ setRuntime({});
1612
+ setDocumentGeneration((value_2) => value_2 + 1);
1613
+ setNotice("Layout imported.");
1614
+ } catch {
1615
+ setNotice("Choose a valid version 1 canvas layout JSON file.");
1616
+ }
1617
+ }
1618
+ }),
1619
+ /* @__PURE__ */ jsxs("div", {
1620
+ className: "povio-canvas-body",
1621
+ children: [
1622
+ /* @__PURE__ */ jsxs("aside", {
1623
+ className: "povio-canvas-catalog",
1624
+ "aria-label": "Page catalog",
1625
+ children: [
1626
+ /* @__PURE__ */ jsxs("div", {
1627
+ className: "povio-canvas-brand",
1628
+ children: [/* @__PURE__ */ jsx("strong", { children: "Pages" }), /* @__PURE__ */ jsx("span", {
1629
+ className: "povio-canvas-page-count",
1630
+ children: nodes.length
1631
+ })]
1632
+ }),
1633
+ /* @__PURE__ */ jsxs("div", {
1634
+ className: "povio-canvas-search",
1635
+ children: [
1636
+ /* @__PURE__ */ jsx(Search, { size: 14 }),
1637
+ /* @__PURE__ */ jsx("input", {
1638
+ "aria-label": "Find page",
1639
+ placeholder: "Find page…",
1640
+ value: query,
1641
+ onChange: (event_5) => setQuery(event_5.target.value)
1642
+ }),
1643
+ /* @__PURE__ */ jsx("button", {
1644
+ className: "povio-canvas-panel-close",
1645
+ "aria-label": "Close pages panel",
1646
+ onClick: () => setMobilePanel(null),
1647
+ children: /* @__PURE__ */ jsx(X, { size: 16 })
1648
+ })
1649
+ ]
1650
+ }),
1651
+ /* @__PURE__ */ jsxs("div", {
1652
+ className: "povio-canvas-page-list",
1653
+ children: [nodes.filter((node_1) => `${node_1.title} ${node_1.path}`.toLowerCase().includes(query.toLowerCase())).map((node_2) => /* @__PURE__ */ jsxs("button", {
1654
+ "aria-pressed": selected === node_2.id,
1655
+ onClick: () => {
1656
+ focusPage(node_2.id);
1657
+ setMobilePanel(null);
1658
+ },
1659
+ children: [/* @__PURE__ */ jsx(Frame, { size: 15 }), /* @__PURE__ */ jsxs("span", {
1660
+ className: "povio-canvas-page-label",
1661
+ children: [/* @__PURE__ */ jsx("strong", { children: node_2.title }), node_2.kind === "unresolved" && /* @__PURE__ */ jsx("small", { children: "Needs parameters" })]
1662
+ })]
1663
+ }, node_2.id)), selected && nodeMap.get(selected)?.href && /* @__PURE__ */ jsx(PageLayers, {
1664
+ frame: frames.current.get(selected) ?? null,
1665
+ title: nodeMap.get(selected).title,
1666
+ selected: inspector.selection?.pageId === selected ? inspector.selection.element : void 0,
1667
+ onSelect: (element_1) => {
1668
+ setInteract(false);
1669
+ setInspectMode(true);
1670
+ inspector.selectElement(selected, element_1);
1671
+ setMobilePanel(null);
1672
+ requestAnimationFrame(() => {
1673
+ const node_3 = nodeMap.get(selected), view_1 = viewport.current;
1674
+ if (!node_3 || !view_1 || !element_1.isConnected) return;
1675
+ const rect = element_1.getBoundingClientRect();
1676
+ camera.current.x = view_1.clientWidth / 2 - (node_3.x + rect.left + rect.width / 2) * camera.current.zoom;
1677
+ camera.current.y = view_1.clientHeight / 2 - (node_3.y + 48 + rect.top + rect.height / 2) * camera.current.zoom;
1678
+ paint();
1679
+ });
1680
+ }
1681
+ }, `${bp.id}:${documentGeneration}:${reload[selected] ?? 0}:${selected}:${nodeMap.get(selected)?.href}`)]
1682
+ }),
1683
+ /* @__PURE__ */ jsxs("div", {
1684
+ className: "povio-canvas-sidebar-actions",
1685
+ children: [
1686
+ /* @__PURE__ */ jsx("button", {
1687
+ className: "povio-canvas-file-action",
1688
+ onClick: exportLayout,
1689
+ "aria-label": "Export layout",
1690
+ title: "Export layout",
1691
+ children: /* @__PURE__ */ jsx(Download, { size: 16 })
1692
+ }),
1693
+ /* @__PURE__ */ jsx("button", {
1694
+ className: "povio-canvas-file-action",
1695
+ onClick: () => importInput.current?.click(),
1696
+ "aria-label": "Import layout",
1697
+ title: "Import layout",
1698
+ children: /* @__PURE__ */ jsx(Upload, { size: 16 })
1699
+ }),
1700
+ /* @__PURE__ */ jsxs("span", {
1701
+ className: "povio-canvas-ready",
1702
+ role: "status",
1703
+ children: [
1704
+ /* @__PURE__ */ jsx("i", {}),
1705
+ ready,
1706
+ "/",
1707
+ nodes.length,
1708
+ " pages ready"
1709
+ ]
1710
+ })
1711
+ ]
1712
+ })
1713
+ ]
1714
+ }),
1715
+ /* @__PURE__ */ jsxs("div", {
1716
+ ref: viewport,
1717
+ className: "povio-canvas-viewport",
1718
+ onKeyDownCapture: (event_6) => {
1719
+ if (inspectMode && event_6.key === "Escape") {
1720
+ event_6.preventDefault();
1721
+ inspector.clear();
1722
+ }
1723
+ },
1724
+ onPointerDown: startPan,
1725
+ onPointerMove: move,
1726
+ onPointerUp: end,
1727
+ onPointerCancel: (event_7) => {
1728
+ const current_5 = pointer.current;
1729
+ if (current_5?.node) {
1730
+ const element_2 = world.current?.querySelector(`[data-node-index="${nodes.findIndex((node_4) => node_4.id === current_5.node)}"]`);
1731
+ if (element_2) {
1732
+ element_2.style.left = `${current_5.nodeX}px`;
1733
+ element_2.style.top = `${current_5.nodeY}px`;
1734
+ }
1735
+ }
1736
+ pointer.current = null;
1737
+ if (event_7.currentTarget.hasPointerCapture(event_7.pointerId)) event_7.currentTarget.releasePointerCapture(event_7.pointerId);
1738
+ },
1739
+ children: [
1740
+ /* @__PURE__ */ jsx("div", {
1741
+ className: "povio-canvas-mobile-tools",
1742
+ children: /* @__PURE__ */ jsx("button", {
1743
+ "aria-label": "Toggle pages panel",
1744
+ "aria-pressed": mobilePanel === "pages",
1745
+ onClick: () => setMobilePanel(mobilePanel === "pages" ? null : "pages"),
1746
+ children: /* @__PURE__ */ jsx(PanelLeft, { size: 18 })
1747
+ })
1748
+ }),
1749
+ /* @__PURE__ */ jsx("div", {
1750
+ ref: world,
1751
+ className: "povio-canvas-world",
1752
+ style: {
1753
+ width: width_0,
1754
+ height: height_0
1755
+ },
1756
+ children: nodes.map((node_5, index) => {
1757
+ const state = runtime[`${bp.id}:${node_5.id}:${node_5.href ?? ""}`] ?? {};
1758
+ let source = "";
1759
+ let originError = false;
1760
+ if (node_5.href) try {
1761
+ const url_0 = new URL(frameUrl?.(node_5) ?? node_5.href, window.location.href);
1762
+ if (url_0.origin !== window.location.origin) throw new Error("same origin required");
1763
+ url_0.searchParams.delete("povioCanvas");
1764
+ url_0.searchParams.delete("__povio_canvas_frame");
1765
+ url_0.searchParams.delete("__povio_canvas_token");
1766
+ url_0.searchParams.delete("__povio_canvas_height");
1767
+ source = url_0.href;
1768
+ } catch {
1769
+ originError = true;
1770
+ }
1771
+ const redirected = state.href && node_5.href && new URL(state.href, window.location.href).pathname !== new URL(node_5.href, window.location.href).pathname;
1772
+ return /* @__PURE__ */ jsxs("article", {
1773
+ "data-node-index": index,
1774
+ "data-page-id": node_5.id,
1775
+ className: `povio-canvas-node${selected === node_5.id ? " is-selected" : ""}`,
1776
+ style: {
1777
+ left: node_5.x,
1778
+ top: node_5.y,
1779
+ width: node_5.width
1780
+ },
1781
+ children: [
1782
+ /* @__PURE__ */ jsxs("header", {
1783
+ className: "povio-canvas-node-header",
1784
+ onPointerDown: (event_8) => {
1785
+ if (event_8.button !== 0 || event_8.target.closest("button")) return;
1786
+ event_8.stopPropagation();
1787
+ viewport.current?.setPointerCapture(event_8.pointerId);
1788
+ pointer.current = {
1789
+ id: event_8.pointerId,
1790
+ x: event_8.clientX,
1791
+ y: event_8.clientY,
1792
+ startX: 0,
1793
+ startY: 0,
1794
+ node: node_5.id,
1795
+ nodeX: node_5.x,
1796
+ nodeY: node_5.y
1797
+ };
1798
+ setSelected(node_5.id);
1799
+ },
1800
+ children: [
1801
+ /* @__PURE__ */ jsx("strong", { children: node_5.title }),
1802
+ /* @__PURE__ */ jsxs("span", { children: [
1803
+ bp.width,
1804
+ " × ",
1805
+ Math.round(node_5.height).toLocaleString()
1806
+ ] }),
1807
+ /* @__PURE__ */ jsx("button", {
1808
+ "aria-label": `Focus ${node_5.title}`,
1809
+ onClick: () => focusPage(node_5.id),
1810
+ children: "↗"
1811
+ }),
1812
+ /* @__PURE__ */ jsx("button", {
1813
+ "aria-label": `Reload ${node_5.title}`,
1814
+ onClick: () => {
1815
+ setRuntime((r) => ({
1816
+ ...r,
1817
+ [`${bp.id}:${node_5.id}:${node_5.href ?? ""}`]: {}
1818
+ }));
1819
+ setReload((r_0) => ({
1820
+ ...r_0,
1821
+ [node_5.id]: (r_0[node_5.id] ?? 0) + 1
1822
+ }));
1823
+ },
1824
+ children: "↻"
1825
+ })
1826
+ ]
1827
+ }),
1828
+ !node_5.href ? /* @__PURE__ */ jsxs("form", {
1829
+ className: "povio-canvas-unresolved",
1830
+ onSubmit: (event_9) => {
1831
+ event_9.preventDefault();
1832
+ const form = new FormData(event_9.currentTarget);
1833
+ const enteredParams = Object.fromEntries(node_5.missingParams.map((key_2) => {
1834
+ const value_3 = form.get(key_2);
1835
+ return [key_2, typeof value_3 === "string" ? value_3 : ""];
1836
+ }));
1837
+ setManifest((m_0) => ({
1838
+ ...m_0,
1839
+ examples: {
1840
+ ...m_0.examples,
1841
+ [node_5.id]: {
1842
+ ...examples[node_5.id],
1843
+ ...m_0.examples[node_5.id],
1844
+ params: {
1845
+ ...examples[node_5.id]?.params,
1846
+ ...m_0.examples[node_5.id]?.params,
1847
+ ...enteredParams
1848
+ }
1849
+ }
1850
+ }
1851
+ }));
1852
+ },
1853
+ children: [
1854
+ /* @__PURE__ */ jsx("strong", { children: "Route needs parameters" }),
1855
+ /* @__PURE__ */ jsx("code", { children: node_5.path }),
1856
+ node_5.missingParams.map((key_3) => /* @__PURE__ */ jsxs("label", { children: [key_3, /* @__PURE__ */ jsx("input", {
1857
+ name: key_3,
1858
+ required: true
1859
+ })] }, key_3)),
1860
+ /* @__PURE__ */ jsx("button", { children: "Render page" })
1861
+ ]
1862
+ }) : originError ? /* @__PURE__ */ jsx("p", {
1863
+ className: "povio-canvas-unresolved",
1864
+ children: "Canvas pages must use the same origin."
1865
+ }) : !startup.admitted.has(startupKeys.get(node_5.id) ?? "") ? /* @__PURE__ */ jsx("div", {
1866
+ className: "povio-canvas-unresolved",
1867
+ role: "status",
1868
+ children: "Waiting to load page…"
1869
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
1870
+ /* @__PURE__ */ jsx(PageFrame, {
1871
+ id: node_5.id,
1872
+ interact,
1873
+ inspect: inspectMode,
1874
+ src: source,
1875
+ width: bp.width,
1876
+ height: node_5.height,
1877
+ name: `${canvasProtocol}:${JSON.stringify({
1878
+ id: node_5.id,
1879
+ token,
1880
+ viewportHeight: bp.height
1881
+ })}`,
1882
+ onLoad,
1883
+ register
1884
+ }, `${documentGeneration}:${bp.id}:${source}:${reload[node_5.id] ?? 0}`),
1885
+ !interact && /* @__PURE__ */ jsx("button", {
1886
+ type: "button",
1887
+ className: "povio-canvas-frame-cover",
1888
+ tabIndex: 0,
1889
+ "aria-label": `Select ${node_5.title}`,
1890
+ onKeyDown: (event_10) => {
1891
+ if (event_10.key === "Enter" || event_10.key === " ") {
1892
+ event_10.preventDefault();
1893
+ setSelected(node_5.id);
1894
+ }
1895
+ },
1896
+ onPointerMove: (event_11) => {
1897
+ const frame_4 = frames.current.get(node_5.id);
1898
+ if (frame_4) inspector.hover(node_5.id, frame_4, event_11.clientX, event_11.clientY);
1899
+ },
1900
+ onPointerDown: (event_12) => {
1901
+ setSelected(node_5.id);
1902
+ if (inspectMode) {
1903
+ const frame_5 = frames.current.get(node_5.id);
1904
+ if (frame_5) inspector.select(node_5.id, frame_5, event_12.clientX, event_12.clientY);
1905
+ }
1906
+ },
1907
+ onClick: () => setSelected(node_5.id)
1908
+ }),
1909
+ inspectMode && inspector.selection?.pageId === node_5.id && inspector.measurement && /* @__PURE__ */ jsx(CanvasInspectionOverlay, {
1910
+ measurement: inspector.measurement,
1911
+ width: node_5.width
1912
+ })
1913
+ ] }),
1914
+ (redirected || state.warning || state.timedOut) && /* @__PURE__ */ jsxs("div", {
1915
+ className: "povio-canvas-page-note",
1916
+ children: [
1917
+ redirected ? `Rendered ${state.href} (route redirected). ` : "",
1918
+ state.warning,
1919
+ state.timedOut && !state.ready && startup.admitted.has(startupKeys.get(node_5.id) ?? "") ? " Frame bridge has not connected. Check access or page errors." : ""
1920
+ ]
1921
+ })
1922
+ ]
1923
+ }, node_5.id);
1924
+ })
1925
+ }),
1926
+ /* @__PURE__ */ jsxs("div", {
1927
+ className: "povio-canvas-tool-dock",
1928
+ children: [
1929
+ /* @__PURE__ */ jsxs("div", {
1930
+ className: "povio-canvas-mode-switch",
1931
+ role: "group",
1932
+ "aria-label": "Canvas mode",
1933
+ children: [
1934
+ /* @__PURE__ */ jsxs("button", {
1935
+ "aria-label": "Inspect layouts",
1936
+ title: "Inspect layouts",
1937
+ "aria-pressed": inspectMode,
1938
+ onClick: () => {
1939
+ setInteract(false);
1940
+ setInspectMode(true);
1941
+ },
1942
+ children: [/* @__PURE__ */ jsx(ScanLine, { size: 18 }), /* @__PURE__ */ jsx("span", { children: "Inspect" })]
1943
+ }),
1944
+ /* @__PURE__ */ jsxs("button", {
1945
+ "aria-label": "Pan",
1946
+ title: "Pan",
1947
+ "aria-pressed": !inspectMode && !interact,
1948
+ onClick: () => {
1949
+ setInspectMode(false);
1950
+ setInteract(false);
1951
+ },
1952
+ children: [/* @__PURE__ */ jsx(Hand, { size: 18 }), /* @__PURE__ */ jsx("span", { children: "Pan" })]
1953
+ }),
1954
+ /* @__PURE__ */ jsxs("button", {
1955
+ "aria-label": "Interact",
1956
+ title: "Interact with pages",
1957
+ "aria-pressed": interact,
1958
+ onClick: () => {
1959
+ setInspectMode(false);
1960
+ setInteract(true);
1961
+ },
1962
+ children: [/* @__PURE__ */ jsx(MousePointer2, { size: 18 }), /* @__PURE__ */ jsx("span", { children: "Interact" })]
1963
+ })
1964
+ ]
1965
+ }),
1966
+ /* @__PURE__ */ jsx("span", { className: "povio-canvas-dock-divider" }),
1967
+ /* @__PURE__ */ jsxs("button", {
1968
+ "aria-label": "Auto layout",
1969
+ onClick: () => setManifest((m_1) => ({
1970
+ ...m_1,
1971
+ positions: {
1972
+ ...m_1.positions,
1973
+ [bp.id]: {}
1974
+ }
1975
+ })),
1976
+ children: [/* @__PURE__ */ jsx(PanelsTopLeft, { size: 17 }), /* @__PURE__ */ jsx("span", { children: "Auto layout" })]
1977
+ })
1978
+ ]
1979
+ }),
1980
+ /* @__PURE__ */ jsxs("div", {
1981
+ className: "povio-canvas-camera",
1982
+ children: [
1983
+ /* @__PURE__ */ jsx("button", {
1984
+ className: "povio-canvas-configure",
1985
+ "aria-label": "Configure UI",
1986
+ title: "Configure UI",
1987
+ onClick: configuration.openFloating,
1988
+ children: /* @__PURE__ */ jsx(Settings2, { size: 16 })
1989
+ }),
1990
+ /* @__PURE__ */ jsx("button", {
1991
+ className: "povio-canvas-theme-toggle",
1992
+ onClick: () => setDark(!dark),
1993
+ "aria-pressed": dark,
1994
+ "aria-label": dark ? "Dark" : "Light",
1995
+ title: "Toggle page theme",
1996
+ children: dark ? /* @__PURE__ */ jsx(Moon, { size: 16 }) : /* @__PURE__ */ jsx(Sun, { size: 16 })
1997
+ }),
1998
+ /* @__PURE__ */ jsxs("div", {
1999
+ className: "povio-canvas-breakpoint",
2000
+ children: [/* @__PURE__ */ jsx(Monitor, { size: 15 }), /* @__PURE__ */ jsx("select", {
2001
+ "aria-label": "Canvas breakpoint",
2002
+ value: bp.id,
2003
+ onChange: (e) => {
2004
+ if (e.target.value === bp.id) return;
2005
+ setRuntime({});
2006
+ setManifest((m_2) => ({
2007
+ ...m_2,
2008
+ breakpoint: e.target.value
2009
+ }));
2010
+ },
2011
+ children: breakpoints.map((item_1) => /* @__PURE__ */ jsxs("option", {
2012
+ value: item_1.id,
2013
+ children: [
2014
+ item_1.label,
2015
+ " · ",
2016
+ item_1.width,
2017
+ "px"
2018
+ ]
2019
+ }, item_1.id))
2020
+ })]
2021
+ }),
2022
+ /* @__PURE__ */ jsx("span", { className: "povio-canvas-dock-divider" }),
2023
+ /* @__PURE__ */ jsx("button", {
2024
+ onClick: () => stepZoom(-1),
2025
+ "aria-label": "Zoom out",
2026
+ children: "−"
2027
+ }),
2028
+ /* @__PURE__ */ jsx("button", {
2029
+ className: "povio-canvas-zoom-reset",
2030
+ "aria-label": "Zoom to 100%",
2031
+ title: "Zoom to 100%",
2032
+ onClick: () => zoomTo(1),
2033
+ children: /* @__PURE__ */ jsx("output", {
2034
+ ref: zoomOutput,
2035
+ children: "25%"
2036
+ })
2037
+ }),
2038
+ /* @__PURE__ */ jsx("button", {
2039
+ onClick: () => stepZoom(1),
2040
+ "aria-label": "Zoom in",
2041
+ children: "+"
2042
+ }),
2043
+ /* @__PURE__ */ jsx("button", {
2044
+ onClick: fit,
2045
+ children: "Fit all"
2046
+ })
2047
+ ]
2048
+ })
2049
+ ]
2050
+ }),
2051
+ inspectMode && /* @__PURE__ */ jsx(CanvasInspectorPanel, {
2052
+ inspector,
2053
+ onClose: () => setInspectMode(false)
2054
+ })
2055
+ ]
2056
+ }),
2057
+ notice && /* @__PURE__ */ jsxs("div", {
2058
+ className: "povio-canvas-notice",
2059
+ role: "status",
2060
+ children: [notice, /* @__PURE__ */ jsx("button", {
2061
+ onClick: () => setNotice(""),
2062
+ children: "Dismiss"
2063
+ })]
2064
+ })
2065
+ ]
2066
+ });
2067
+ }
2068
+ //#endregion
2069
+ //#region src/app-canvas/document-frame.ts
2070
+ /** Adapt a same-origin document to an uncropped, full-page canvas artboard. */
2071
+ function startCanvasDocument(doc, options) {
2072
+ const win = doc.defaultView;
2073
+ if (!win) throw new Error("Canvas document must belong to a window.");
2074
+ const viewportHeight = Math.max(1, options.viewportHeight);
2075
+ let interact = options.interact ?? false;
2076
+ let destroyed = false;
2077
+ let frame = 0;
2078
+ let lastHeight = 0;
2079
+ let lastLinks = "";
2080
+ const originals = /* @__PURE__ */ new Map();
2081
+ const mediaOriginals = /* @__PURE__ */ new Map();
2082
+ const liveStyles = /* @__PURE__ */ new Set();
2083
+ const liveMedia = /* @__PURE__ */ new Set();
2084
+ const warnedSheets = /* @__PURE__ */ new WeakSet();
2085
+ const positioned = /* @__PURE__ */ new WeakSet();
2086
+ const set = (style, property, value, priority = "important") => {
2087
+ liveStyles.add(style);
2088
+ if (style.getPropertyValue(property) === value && style.getPropertyPriority(property) === priority) return;
2089
+ let saved = originals.get(style);
2090
+ if (!saved) {
2091
+ saved = /* @__PURE__ */ new Map();
2092
+ originals.set(style, saved);
2093
+ }
2094
+ if (!saved.has(property)) saved.set(property, [style.getPropertyValue(property), style.getPropertyPriority(property)]);
2095
+ style.setProperty(property, value, priority);
2096
+ };
2097
+ const freezeUnits = (style) => {
2098
+ liveStyles.add(style);
2099
+ for (const property of Array.from(style)) {
2100
+ const value = style.getPropertyValue(property);
2101
+ const frozen = value.replace(/(-?(?:\d*\.)?\d+)(?:dvh|svh|lvh|vh)\b/gi, (_, amount) => `${Number(amount) * viewportHeight / 100}px`);
2102
+ if (value !== frozen) set(style, property, frozen, style.getPropertyPriority(property));
2103
+ }
2104
+ };
2105
+ const freezeMedia = (media) => {
2106
+ liveMedia.add(media);
2107
+ if (mediaOriginals.has(media)) return;
2108
+ const original = media.mediaText;
2109
+ const frozen = original.replace(/\([^()]*\b(?:height|orientation|aspect-ratio)\b[^()]*\)/gi, (predicate) => win.matchMedia(predicate).matches ? "(min-width: 0px)" : "(max-width: -1px)");
2110
+ if (frozen !== original) {
2111
+ mediaOriginals.set(media, original);
2112
+ media.mediaText = frozen;
2113
+ }
2114
+ };
2115
+ const visitRules = (rules) => {
2116
+ for (const rule of Array.from(rules)) {
2117
+ if ("style" in rule) freezeUnits(rule.style);
2118
+ if ("media" in rule) freezeMedia(rule.media);
2119
+ if ("cssRules" in rule) visitRules(rule.cssRules);
2120
+ if ("styleSheet" in rule && rule.styleSheet) visitSheet(rule.styleSheet);
2121
+ }
2122
+ };
2123
+ const visitSheet = (sheet) => {
2124
+ try {
2125
+ freezeMedia(sheet.media);
2126
+ visitRules(sheet.cssRules);
2127
+ } catch {
2128
+ if (!warnedSheets.has(sheet)) {
2129
+ warnedSheets.add(sheet);
2130
+ const message = `App canvas cannot normalize inaccessible stylesheet ${sheet.href ?? "(inline)"}. Serve its CSS from the app origin for accurate full-page dimensions.`;
2131
+ if (options.onWarning) options.onWarning(message);
2132
+ else win.console.warn(message);
2133
+ }
2134
+ }
2135
+ };
2136
+ const anchorURL = (anchor) => {
2137
+ try {
2138
+ const url = new URL(anchor.getAttribute("href") ?? "", doc.baseURI);
2139
+ return /^https?:$/.test(url.protocol) && url.origin === new URL(doc.baseURI).origin ? url.href : null;
2140
+ } catch {
2141
+ return null;
2142
+ }
2143
+ };
2144
+ const refresh = () => {
2145
+ if (destroyed) return;
2146
+ if (frame) {
2147
+ win.cancelAnimationFrame(frame);
2148
+ frame = 0;
2149
+ }
2150
+ observer.disconnect();
2151
+ liveStyles.clear();
2152
+ liveMedia.clear();
2153
+ for (const sheet of [...Array.from(doc.styleSheets), ...Array.from(doc.adoptedStyleSheets ?? [])]) visitSheet(sheet);
2154
+ const elements = Array.from(doc.querySelectorAll("*")).filter((el) => "style" in el);
2155
+ for (const el of elements) freezeUnits(el.style);
2156
+ const snapshots = elements.map((el) => ({
2157
+ el,
2158
+ css: win.getComputedStyle(el),
2159
+ rect: el.getBoundingClientRect()
2160
+ }));
2161
+ for (const { el, css, rect } of snapshots) {
2162
+ if (!positioned.has(el) && (css.position === "fixed" || css.position === "sticky")) {
2163
+ positioned.add(el);
2164
+ if (css.position === "sticky") set(el.style, "position", "static");
2165
+ else {
2166
+ set(el.style, "position", "absolute");
2167
+ const containingBlock = el.offsetParent;
2168
+ const parentRect = containingBlock?.getBoundingClientRect();
2169
+ const parentTop = containingBlock && containingBlock !== doc.body && containingBlock !== doc.documentElement ? (parentRect?.top ?? 0) + containingBlock.clientTop - containingBlock.scrollTop : -win.scrollY;
2170
+ const parentLeft = containingBlock && containingBlock !== doc.body && containingBlock !== doc.documentElement ? (parentRect?.left ?? 0) + containingBlock.clientLeft - containingBlock.scrollLeft : -win.scrollX;
2171
+ set(el.style, "top", `${rect.top - parentTop}px`);
2172
+ set(el.style, "left", `${rect.left - parentLeft}px`);
2173
+ set(el.style, "right", "auto");
2174
+ set(el.style, "bottom", "auto");
2175
+ set(el.style, "width", `${rect.width}px`);
2176
+ set(el.style, "height", `${rect.height}px`);
2177
+ }
2178
+ }
2179
+ if (/^(auto|scroll)$/.test(css.overflowY) && el.scrollHeight > el.clientHeight) {
2180
+ let ancestor = el;
2181
+ while (ancestor && ancestor !== doc.documentElement) {
2182
+ set(ancestor.style, "height", "auto");
2183
+ set(ancestor.style, "max-height", "none");
2184
+ set(ancestor.style, "overflow-y", "visible");
2185
+ set(ancestor.style, "flex-shrink", "0");
2186
+ ancestor = ancestor.parentElement;
2187
+ }
2188
+ }
2189
+ }
2190
+ set(doc.documentElement.style, "height", "auto");
2191
+ set(doc.documentElement.style, "min-height", "0px");
2192
+ set(doc.documentElement.style, "overflow-y", "hidden");
2193
+ if (doc.body) {
2194
+ set(doc.body.style, "height", "auto");
2195
+ set(doc.body.style, "min-height", "0px");
2196
+ }
2197
+ let bottom = 0;
2198
+ for (const el of elements) {
2199
+ if (el === doc.documentElement || el === doc.body || !el.getClientRects().length) continue;
2200
+ bottom = Math.max(bottom, el.getBoundingClientRect().bottom + win.scrollY);
2201
+ }
2202
+ if (doc.body) bottom = Math.max(bottom, doc.body.getBoundingClientRect().bottom + win.scrollY);
2203
+ const height = Math.max(viewportHeight, Math.ceil(bottom));
2204
+ if (height !== lastHeight) {
2205
+ lastHeight = height;
2206
+ options.onHeight(height);
2207
+ }
2208
+ if (options.onLinks) {
2209
+ const links = Array.from(doc.querySelectorAll("a[href]")).flatMap((anchor) => {
2210
+ const href = anchorURL(anchor);
2211
+ return href ? [{
2212
+ href,
2213
+ label: anchor.textContent?.trim() || anchor.getAttribute("aria-label") || href
2214
+ }] : [];
2215
+ });
2216
+ const signature = JSON.stringify(links);
2217
+ if (signature !== lastLinks) {
2218
+ lastLinks = signature;
2219
+ options.onLinks(links);
2220
+ }
2221
+ }
2222
+ for (const style of originals.keys()) if (!liveStyles.has(style)) originals.delete(style);
2223
+ for (const media of mediaOriginals.keys()) if (!liveMedia.has(media)) mediaOriginals.delete(media);
2224
+ liveStyles.clear();
2225
+ liveMedia.clear();
2226
+ observer.observe(doc.documentElement, {
2227
+ subtree: true,
2228
+ childList: true,
2229
+ attributes: true,
2230
+ characterData: true
2231
+ });
2232
+ };
2233
+ const schedule = () => {
2234
+ if (!destroyed && !frame) frame = win.requestAnimationFrame(refresh);
2235
+ };
2236
+ const observer = new win.MutationObserver(schedule);
2237
+ const resize = new win.ResizeObserver(schedule);
2238
+ resize.observe(doc.documentElement);
2239
+ if (doc.body) resize.observe(doc.body);
2240
+ const click = (event) => {
2241
+ const anchor = event.target?.closest?.("a[href]");
2242
+ if (interact && !anchor) return;
2243
+ event.preventDefault();
2244
+ event.stopImmediatePropagation();
2245
+ if (anchor) {
2246
+ const href = anchorURL(anchor);
2247
+ if (href) options.onNavigate(href);
2248
+ }
2249
+ };
2250
+ const submit = (event) => {
2251
+ if (!interact) {
2252
+ event.preventDefault();
2253
+ event.stopImmediatePropagation();
2254
+ }
2255
+ };
2256
+ win.addEventListener("click", click, true);
2257
+ win.addEventListener("submit", submit, true);
2258
+ win.addEventListener("resize", schedule);
2259
+ doc.addEventListener("load", schedule, true);
2260
+ doc.fonts?.ready.then(schedule);
2261
+ refresh();
2262
+ return {
2263
+ refresh,
2264
+ setInteract(value) {
2265
+ interact = value;
2266
+ },
2267
+ destroy() {
2268
+ destroyed = true;
2269
+ if (frame) win.cancelAnimationFrame(frame);
2270
+ observer.disconnect();
2271
+ resize.disconnect();
2272
+ win.removeEventListener("click", click, true);
2273
+ win.removeEventListener("submit", submit, true);
2274
+ win.removeEventListener("resize", schedule);
2275
+ doc.removeEventListener("load", schedule, true);
2276
+ for (const [media, value] of mediaOriginals) media.mediaText = value;
2277
+ for (const [style, values] of originals) for (const [property, [value, priority]] of values) if (value) style.setProperty(property, value, priority);
2278
+ else style.removeProperty(property);
2279
+ }
2280
+ };
2281
+ }
2282
+ //#endregion
2283
+ //#region src/app-canvas/AppCanvasFrameBridge.tsx
2284
+ /** Mount once inside the application's existing UIConfigurationProvider. No separate app copy or theme fork. */
2285
+ function AppCanvasFrameBridge({ href }) {
2286
+ const { setPreviewConfiguration } = useUIConfiguration();
2287
+ const [identity] = useState(canvasFrameIdentity);
2288
+ const currentHref = useRef(href);
2289
+ currentHref.current = href;
2290
+ useEffect(() => {
2291
+ if (!identity) return;
2292
+ const send = (data) => window.parent.postMessage({
2293
+ protocol: canvasProtocol,
2294
+ ...identity,
2295
+ ...data
2296
+ }, window.location.origin);
2297
+ const renderer = startCanvasDocument(document, {
2298
+ viewportHeight: identity.viewportHeight,
2299
+ onHeight: (height) => send({
2300
+ type: "height",
2301
+ height
2302
+ }),
2303
+ onNavigate: (url) => send({
2304
+ type: "navigate",
2305
+ href: url
2306
+ }),
2307
+ onWarning: (warning) => send({
2308
+ type: "warning",
2309
+ warning
2310
+ })
2311
+ });
2312
+ let ackFrame = 0;
2313
+ const receive = (event) => {
2314
+ const packet = event.data;
2315
+ if (event.source !== window.parent || event.origin !== window.location.origin || packet?.protocol !== "povio-app-canvas-v1" || packet.token !== identity.token || packet.id !== identity.id) return;
2316
+ if (packet.type === "mode") renderer.setInteract(!!packet.interact);
2317
+ if (packet.type === "configuration" && validConfiguration(packet.configuration)) {
2318
+ setPreviewConfiguration(packet.configuration);
2319
+ document.documentElement.classList.toggle("dark", !!packet.dark);
2320
+ cancelAnimationFrame(ackFrame);
2321
+ ackFrame = requestAnimationFrame(() => {
2322
+ send({
2323
+ type: "ack",
2324
+ revision: packet.revision
2325
+ });
2326
+ });
2327
+ }
2328
+ };
2329
+ window.addEventListener("message", receive);
2330
+ send({
2331
+ type: "ready",
2332
+ href: currentHref.current ?? window.location.href
2333
+ });
2334
+ return () => {
2335
+ cancelAnimationFrame(ackFrame);
2336
+ window.removeEventListener("message", receive);
2337
+ renderer.destroy();
2338
+ setPreviewConfiguration(null);
2339
+ };
2340
+ }, [identity, setPreviewConfiguration]);
2341
+ useEffect(() => {
2342
+ if (identity && href) window.parent.postMessage({
2343
+ protocol: canvasProtocol,
2344
+ ...identity,
2345
+ type: "ready",
2346
+ href
2347
+ }, window.location.origin);
2348
+ }, [href, identity]);
2349
+ return null;
2350
+ }
2351
+ //#endregion
2352
+ //#region src/app-canvas/AppCanvasPage.tsx
2353
+ const APP_CANVAS_PATH = "/_canvas";
2354
+ /** Ready-made route page. Mount beneath the app's UIConfigurationProvider. */
2355
+ function AppCanvasPage(t0) {
2356
+ const $ = c(17);
2357
+ let excludePaths;
2358
+ let props;
2359
+ let t1;
2360
+ if ($[0] !== t0) {
2361
+ ({canvasPath: t1, excludePaths, ...props} = t0);
2362
+ $[0] = t0;
2363
+ $[1] = excludePaths;
2364
+ $[2] = props;
2365
+ $[3] = t1;
2366
+ } else {
2367
+ excludePaths = $[1];
2368
+ props = $[2];
2369
+ t1 = $[3];
2370
+ }
2371
+ const canvasPath = t1 === void 0 ? APP_CANVAS_PATH : t1;
2372
+ let t2;
2373
+ if ($[4] !== excludePaths) {
2374
+ t2 = excludePaths ?? [];
2375
+ $[4] = excludePaths;
2376
+ $[5] = t2;
2377
+ } else t2 = $[5];
2378
+ let t3;
2379
+ if ($[6] !== canvasPath || $[7] !== t2) {
2380
+ t3 = /* @__PURE__ */ new Set([...t2, canvasPath]);
2381
+ $[6] = canvasPath;
2382
+ $[7] = t2;
2383
+ $[8] = t3;
2384
+ } else t3 = $[8];
2385
+ let t4;
2386
+ if ($[9] !== t3) {
2387
+ t4 = [...t3];
2388
+ $[9] = t3;
2389
+ $[10] = t4;
2390
+ } else t4 = $[10];
2391
+ const exclusions = t4;
2392
+ if (isAppCanvasFrame()) return null;
2393
+ let t5;
2394
+ if ($[11] !== exclusions || $[12] !== props) {
2395
+ t5 = /* @__PURE__ */ jsx(AppCanvas, {
2396
+ ...props,
2397
+ excludePaths: exclusions
2398
+ });
2399
+ $[11] = exclusions;
2400
+ $[12] = props;
2401
+ $[13] = t5;
2402
+ } else t5 = $[13];
2403
+ let t6;
2404
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
2405
+ t6 = /* @__PURE__ */ jsx(UIConfigurationModal, {});
2406
+ $[14] = t6;
2407
+ } else t6 = $[14];
2408
+ let t7;
2409
+ if ($[15] !== t5) {
2410
+ t7 = /* @__PURE__ */ jsxs("div", {
2411
+ className: "povio-app-canvas-page",
2412
+ "aria-label": "App canvas page",
2413
+ children: [t5, t6]
2414
+ });
2415
+ $[15] = t5;
2416
+ $[16] = t7;
2417
+ } else t7 = $[16];
2418
+ return t7;
2419
+ }
2420
+ //#endregion
2421
+ 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 };