pagiera 0.2.0-alpha.33

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 (192) hide show
  1. package/README.md +528 -0
  2. package/dist/adapters.d.ts +89 -0
  3. package/dist/adapters.d.ts.map +1 -0
  4. package/dist/adapters.js +2 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/client.d.ts +22 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/client.js +33 -0
  9. package/dist/client.js.map +1 -0
  10. package/dist/data.js +210 -0
  11. package/dist/data.js.map +7 -0
  12. package/dist/document.d.ts +63 -0
  13. package/dist/document.d.ts.map +1 -0
  14. package/dist/document.js +25 -0
  15. package/dist/document.js.map +1 -0
  16. package/dist/editor.d.ts +14 -0
  17. package/dist/editor.d.ts.map +1 -0
  18. package/dist/editor.js +59 -0
  19. package/dist/editor.js.map +1 -0
  20. package/dist/full-editor.d.ts +53 -0
  21. package/dist/full-editor.js +11117 -0
  22. package/dist/full-editor.js.map +7 -0
  23. package/dist/full.css +3498 -0
  24. package/dist/index.d.ts +8 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +7 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/internal/app/api/ai-design/route.d.ts +3 -0
  29. package/dist/internal/app/api/ai-design/route.d.ts.map +1 -0
  30. package/dist/internal/app/api/ai-design/route.js +165 -0
  31. package/dist/internal/app/api/ai-design/route.js.map +1 -0
  32. package/dist/internal/app/p/editor/ai-panel.d.ts +27 -0
  33. package/dist/internal/app/p/editor/ai-panel.d.ts.map +1 -0
  34. package/dist/internal/app/p/editor/ai-panel.js +77 -0
  35. package/dist/internal/app/p/editor/ai-panel.js.map +1 -0
  36. package/dist/internal/app/p/editor/data-modal.d.ts +26 -0
  37. package/dist/internal/app/p/editor/data-modal.d.ts.map +1 -0
  38. package/dist/internal/app/p/editor/data-modal.js +94 -0
  39. package/dist/internal/app/p/editor/data-modal.js.map +1 -0
  40. package/dist/internal/app/p/editor/data-panel.d.ts +18 -0
  41. package/dist/internal/app/p/editor/data-panel.d.ts.map +1 -0
  42. package/dist/internal/app/p/editor/data-panel.js +45 -0
  43. package/dist/internal/app/p/editor/data-panel.js.map +1 -0
  44. package/dist/internal/app/p/editor/editor.d.ts +51 -0
  45. package/dist/internal/app/p/editor/editor.d.ts.map +1 -0
  46. package/dist/internal/app/p/editor/editor.js +1551 -0
  47. package/dist/internal/app/p/editor/editor.js.map +1 -0
  48. package/dist/internal/app/p/editor/fields.d.ts +111 -0
  49. package/dist/internal/app/p/editor/fields.d.ts.map +1 -0
  50. package/dist/internal/app/p/editor/fields.js +141 -0
  51. package/dist/internal/app/p/editor/fields.js.map +1 -0
  52. package/dist/internal/app/p/editor/inspector.d.ts +38 -0
  53. package/dist/internal/app/p/editor/inspector.d.ts.map +1 -0
  54. package/dist/internal/app/p/editor/inspector.js +336 -0
  55. package/dist/internal/app/p/editor/inspector.js.map +1 -0
  56. package/dist/internal/app/p/editor/library.d.ts +18 -0
  57. package/dist/internal/app/p/editor/library.d.ts.map +1 -0
  58. package/dist/internal/app/p/editor/library.js +61 -0
  59. package/dist/internal/app/p/editor/library.js.map +1 -0
  60. package/dist/internal/app/p/editor/parts.d.ts +75 -0
  61. package/dist/internal/app/p/editor/parts.d.ts.map +1 -0
  62. package/dist/internal/app/p/editor/parts.js +261 -0
  63. package/dist/internal/app/p/editor/parts.js.map +1 -0
  64. package/dist/internal/app/p/editor/templates-panel.d.ts +6 -0
  65. package/dist/internal/app/p/editor/templates-panel.d.ts.map +1 -0
  66. package/dist/internal/app/p/editor/templates-panel.js +12 -0
  67. package/dist/internal/app/p/editor/templates-panel.js.map +1 -0
  68. package/dist/internal/app/p/editor/use-canvas-view.d.ts +24 -0
  69. package/dist/internal/app/p/editor/use-canvas-view.d.ts.map +1 -0
  70. package/dist/internal/app/p/editor/use-canvas-view.js +242 -0
  71. package/dist/internal/app/p/editor/use-canvas-view.js.map +1 -0
  72. package/dist/internal/app/p/editor/use-editor.d.ts +48 -0
  73. package/dist/internal/app/p/editor/use-editor.d.ts.map +1 -0
  74. package/dist/internal/app/p/editor/use-editor.js +224 -0
  75. package/dist/internal/app/p/editor/use-editor.js.map +1 -0
  76. package/dist/internal/app/p/editor/variables-panel.d.ts +8 -0
  77. package/dist/internal/app/p/editor/variables-panel.d.ts.map +1 -0
  78. package/dist/internal/app/p/editor/variables-panel.js +52 -0
  79. package/dist/internal/app/p/editor/variables-panel.js.map +1 -0
  80. package/dist/internal/components/ui/select.d.ts +12 -0
  81. package/dist/internal/components/ui/select.d.ts.map +1 -0
  82. package/dist/internal/components/ui/select.js +32 -0
  83. package/dist/internal/components/ui/select.js.map +1 -0
  84. package/dist/internal/drizzle/index.d.ts +6 -0
  85. package/dist/internal/drizzle/index.d.ts.map +1 -0
  86. package/dist/internal/drizzle/index.js +20 -0
  87. package/dist/internal/drizzle/index.js.map +1 -0
  88. package/dist/internal/drizzle/schema.d.ts +405 -0
  89. package/dist/internal/drizzle/schema.d.ts.map +1 -0
  90. package/dist/internal/drizzle/schema.js +70 -0
  91. package/dist/internal/drizzle/schema.js.map +1 -0
  92. package/dist/internal/lib/data/context.d.ts +8 -0
  93. package/dist/internal/lib/data/context.d.ts.map +1 -0
  94. package/dist/internal/lib/data/context.js +17 -0
  95. package/dist/internal/lib/data/context.js.map +1 -0
  96. package/dist/internal/lib/data/source.d.ts +54 -0
  97. package/dist/internal/lib/data/source.d.ts.map +1 -0
  98. package/dist/internal/lib/data/source.js +229 -0
  99. package/dist/internal/lib/data/source.js.map +1 -0
  100. package/dist/internal/lib/editor/ai-types.d.ts +44 -0
  101. package/dist/internal/lib/editor/ai-types.d.ts.map +1 -0
  102. package/dist/internal/lib/editor/ai-types.js +2 -0
  103. package/dist/internal/lib/editor/ai-types.js.map +1 -0
  104. package/dist/internal/lib/editor/arrange.d.ts +12 -0
  105. package/dist/internal/lib/editor/arrange.d.ts.map +1 -0
  106. package/dist/internal/lib/editor/arrange.js +72 -0
  107. package/dist/internal/lib/editor/arrange.js.map +1 -0
  108. package/dist/internal/lib/editor/cascade.d.ts +39 -0
  109. package/dist/internal/lib/editor/cascade.d.ts.map +1 -0
  110. package/dist/internal/lib/editor/cascade.js +86 -0
  111. package/dist/internal/lib/editor/cascade.js.map +1 -0
  112. package/dist/internal/lib/editor/icon.d.ts +6 -0
  113. package/dist/internal/lib/editor/icon.d.ts.map +1 -0
  114. package/dist/internal/lib/editor/icon.js +13 -0
  115. package/dist/internal/lib/editor/icon.js.map +1 -0
  116. package/dist/internal/lib/editor/presets.d.ts +10 -0
  117. package/dist/internal/lib/editor/presets.d.ts.map +1 -0
  118. package/dist/internal/lib/editor/presets.js +83 -0
  119. package/dist/internal/lib/editor/presets.js.map +1 -0
  120. package/dist/internal/lib/editor/showcase.d.ts +14 -0
  121. package/dist/internal/lib/editor/showcase.d.ts.map +1 -0
  122. package/dist/internal/lib/editor/showcase.js +219 -0
  123. package/dist/internal/lib/editor/showcase.js.map +1 -0
  124. package/dist/internal/lib/editor/site-templates.d.ts +4 -0
  125. package/dist/internal/lib/editor/site-templates.d.ts.map +1 -0
  126. package/dist/internal/lib/editor/site-templates.js +156 -0
  127. package/dist/internal/lib/editor/site-templates.js.map +1 -0
  128. package/dist/internal/lib/editor/snap.d.ts +34 -0
  129. package/dist/internal/lib/editor/snap.d.ts.map +1 -0
  130. package/dist/internal/lib/editor/snap.js +70 -0
  131. package/dist/internal/lib/editor/snap.js.map +1 -0
  132. package/dist/internal/lib/editor/style.d.ts +53 -0
  133. package/dist/internal/lib/editor/style.d.ts.map +1 -0
  134. package/dist/internal/lib/editor/style.js +313 -0
  135. package/dist/internal/lib/editor/style.js.map +1 -0
  136. package/dist/internal/lib/editor/tree.d.ts +72 -0
  137. package/dist/internal/lib/editor/tree.d.ts.map +1 -0
  138. package/dist/internal/lib/editor/tree.js +301 -0
  139. package/dist/internal/lib/editor/tree.js.map +1 -0
  140. package/dist/internal/lib/editor/types.d.ts +273 -0
  141. package/dist/internal/lib/editor/types.d.ts.map +1 -0
  142. package/dist/internal/lib/editor/types.js +411 -0
  143. package/dist/internal/lib/editor/types.js.map +1 -0
  144. package/dist/internal/lib/editor/validate.d.ts +20 -0
  145. package/dist/internal/lib/editor/validate.d.ts.map +1 -0
  146. package/dist/internal/lib/editor/validate.js +467 -0
  147. package/dist/internal/lib/editor/validate.js.map +1 -0
  148. package/dist/internal/lib/pages.d.ts +83 -0
  149. package/dist/internal/lib/pages.d.ts.map +1 -0
  150. package/dist/internal/lib/pages.js +294 -0
  151. package/dist/internal/lib/pages.js.map +1 -0
  152. package/dist/internal/lib/render/bind.d.ts +18 -0
  153. package/dist/internal/lib/render/bind.d.ts.map +1 -0
  154. package/dist/internal/lib/render/bind.js +28 -0
  155. package/dist/internal/lib/render/bind.js.map +1 -0
  156. package/dist/internal/lib/render/css.d.ts +31 -0
  157. package/dist/internal/lib/render/css.d.ts.map +1 -0
  158. package/dist/internal/lib/render/css.js +239 -0
  159. package/dist/internal/lib/render/css.js.map +1 -0
  160. package/dist/internal/lib/render/load-data.d.ts +15 -0
  161. package/dist/internal/lib/render/load-data.d.ts.map +1 -0
  162. package/dist/internal/lib/render/load-data.js +39 -0
  163. package/dist/internal/lib/render/load-data.js.map +1 -0
  164. package/dist/internal/lib/render/page-render.d.ts +17 -0
  165. package/dist/internal/lib/render/page-render.d.ts.map +1 -0
  166. package/dist/internal/lib/render/page-render.js +117 -0
  167. package/dist/internal/lib/render/page-render.js.map +1 -0
  168. package/dist/internal/lib/utils.d.ts +3 -0
  169. package/dist/internal/lib/utils.d.ts.map +1 -0
  170. package/dist/internal/lib/utils.js +6 -0
  171. package/dist/internal/lib/utils.js.map +1 -0
  172. package/dist/manrope-data.d.ts +3 -0
  173. package/dist/manrope-data.d.ts.map +1 -0
  174. package/dist/manrope-data.js +3 -0
  175. package/dist/manrope-data.js.map +1 -0
  176. package/dist/page.d.ts +9 -0
  177. package/dist/page.d.ts.map +1 -0
  178. package/dist/page.js +148 -0
  179. package/dist/page.js.map +1 -0
  180. package/dist/provider.d.ts +14 -0
  181. package/dist/provider.d.ts.map +1 -0
  182. package/dist/provider.js +45 -0
  183. package/dist/provider.js.map +1 -0
  184. package/dist/runtime.d.ts +8 -0
  185. package/dist/runtime.js +717 -0
  186. package/dist/runtime.js.map +7 -0
  187. package/dist/server.d.ts +31 -0
  188. package/dist/server.js +2267 -0
  189. package/dist/server.js.map +7 -0
  190. package/dist/styles.css +300 -0
  191. package/package.json +94 -0
  192. package/thumbnail.png +0 -0
package/dist/data.js ADDED
@@ -0,0 +1,210 @@
1
+ // src/internal/lib/editor/types.ts
2
+ function sendsBody(method) {
3
+ return method === "POST" || method === "PUT" || method === "PATCH";
4
+ }
5
+
6
+ // src/internal/lib/data/source.ts
7
+ var BLOCKED_HOSTS = /* @__PURE__ */ new Set([
8
+ "localhost",
9
+ "127.0.0.1",
10
+ "0.0.0.0",
11
+ "::1",
12
+ "[::1]",
13
+ "metadata.google.internal",
14
+ "metadata.goog"
15
+ ]);
16
+ var PRIVATE_IPV4 = /^(10\.|127\.|0\.|169\.254\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)/;
17
+ var PRIVATE_SUFFIXES = [".local", ".internal", ".localdomain", ".home.arpa"];
18
+ var MAX_ROWS = 200;
19
+ var MAX_BYTES = 2e6;
20
+ var TIMEOUT_MS = 8e3;
21
+ var MAX_REDIRECTS = 3;
22
+ var DataSourceError = class extends Error {
23
+ constructor(message, status) {
24
+ super(message);
25
+ this.status = status;
26
+ this.name = "DataSourceError";
27
+ }
28
+ };
29
+ function assertFetchableUrl(raw) {
30
+ let url;
31
+ try {
32
+ url = new URL(raw);
33
+ } catch {
34
+ throw new DataSourceError("That is not a valid URL.");
35
+ }
36
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
37
+ throw new DataSourceError("Only http and https URLs can be requested.");
38
+ }
39
+ const host = url.hostname.toLowerCase();
40
+ if (BLOCKED_HOSTS.has(host) || PRIVATE_IPV4.test(host)) {
41
+ throw new DataSourceError(`Requests to ${host} are not allowed.`);
42
+ }
43
+ if (PRIVATE_SUFFIXES.some((suffix) => host.endsWith(suffix))) {
44
+ throw new DataSourceError(`Requests to ${host} are not allowed.`);
45
+ }
46
+ if (host.startsWith("[") && /^\[(fe80|fc|fd)/i.test(host)) {
47
+ throw new DataSourceError("Requests to private addresses are not allowed.");
48
+ }
49
+ return url;
50
+ }
51
+ function selectPath(payload, path) {
52
+ const steps = path.split(".").map((s) => s.trim()).filter(Boolean);
53
+ let cursor = payload;
54
+ for (const step of steps) {
55
+ if (cursor === null || typeof cursor !== "object") return void 0;
56
+ cursor = cursor[step];
57
+ }
58
+ return cursor;
59
+ }
60
+ async function readCapped(response) {
61
+ const reader = response.body?.getReader();
62
+ if (!reader) return "";
63
+ const chunks = [];
64
+ let total = 0;
65
+ while (true) {
66
+ const { done, value } = await reader.read();
67
+ if (done) break;
68
+ total += value.byteLength;
69
+ if (total > MAX_BYTES) {
70
+ await reader.cancel();
71
+ throw new DataSourceError("That response is too large (over 2 MB).");
72
+ }
73
+ chunks.push(value);
74
+ }
75
+ return new TextDecoder().decode(
76
+ chunks.reduce((acc, chunk) => {
77
+ const merged = new Uint8Array(acc.length + chunk.length);
78
+ merged.set(acc);
79
+ merged.set(chunk, acc.length);
80
+ return merged;
81
+ }, new Uint8Array())
82
+ );
83
+ }
84
+ var DEFAULT_REVALIDATE = 60;
85
+ async function loadSource(source, { revalidate = DEFAULT_REVALIDATE, context = EMPTY_CONTEXT } = {}) {
86
+ const request = buildRequest(source, context);
87
+ let target = request.url;
88
+ let response;
89
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
90
+ response = await fetch(target, {
91
+ // Redirects are followed by hand so each hop is validated too.
92
+ redirect: "manual",
93
+ method: request.method,
94
+ headers: request.headers,
95
+ body: request.body,
96
+ signal: AbortSignal.timeout(TIMEOUT_MS),
97
+ // Only GET responses are cacheable; anything with side effects is
98
+ // fetched fresh every time.
99
+ ...revalidate === false || request.method !== "GET" ? { cache: "no-store" } : { next: { revalidate } }
100
+ });
101
+ if (response.status < 300 || response.status >= 400) break;
102
+ const location = response.headers.get("location");
103
+ if (!location) break;
104
+ target = assertFetchableUrl(new URL(location, target).toString());
105
+ response = void 0;
106
+ }
107
+ if (!response) throw new DataSourceError("Too many redirects.");
108
+ if (!response.ok) {
109
+ throw new DataSourceError(`The API replied ${response.status}.`, response.status);
110
+ }
111
+ const text = await readCapped(response);
112
+ let payload;
113
+ try {
114
+ payload = JSON.parse(text);
115
+ } catch {
116
+ throw new DataSourceError("The response was not JSON.");
117
+ }
118
+ const selected = selectPath(payload, source.path);
119
+ const selectedRows = Array.isArray(selected) ? selected : selected !== null && typeof selected === "object" ? [selected] : void 0;
120
+ if (!selectedRows) {
121
+ throw new DataSourceError(
122
+ source.path ? `"${source.path}" is not a list in that response.` : "That response is neither an object nor a list."
123
+ );
124
+ }
125
+ const rows = selectedRows.slice(0, MAX_ROWS).map(
126
+ (row) => row !== null && typeof row === "object" && !Array.isArray(row) ? row : { value: row }
127
+ );
128
+ const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();
129
+ return { rows, keys };
130
+ }
131
+ function resolveTokens(value, context) {
132
+ return value.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_, token) => {
133
+ const [namespace, ...rest] = token.split(".");
134
+ const key = rest.join(".");
135
+ if (namespace === "query") return context.query[key] ?? "";
136
+ if (namespace === "params") return context.params[key] ?? "";
137
+ if (namespace === "page" && key === "slug") return context.page.slug;
138
+ return "";
139
+ });
140
+ }
141
+ function safeHeader(pair, context) {
142
+ const name = pair.key.trim();
143
+ if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) return null;
144
+ const value = resolveTokens(pair.value, context).replace(/[\r\n\0]/g, "").trim();
145
+ return value ? [name, value] : null;
146
+ }
147
+ function buildRequest(source, context) {
148
+ const url = assertFetchableUrl(resolveTokens(source.url, context));
149
+ for (const pair of source.params ?? []) {
150
+ const key = pair.key.trim();
151
+ if (!key) continue;
152
+ const value = resolveTokens(pair.value, context);
153
+ if (value === "") continue;
154
+ url.searchParams.set(key, value);
155
+ }
156
+ const method = source.method ?? "GET";
157
+ const headers = {
158
+ accept: "application/json,text/plain;q=0.9,*/*;q=0.8"
159
+ };
160
+ let body;
161
+ if (sendsBody(method) && source.body?.trim()) {
162
+ body = resolveTokens(source.body, context);
163
+ headers["content-type"] = "application/json";
164
+ }
165
+ for (const pair of source.headers ?? []) {
166
+ const safe = safeHeader(pair, context);
167
+ if (safe) headers[safe[0]] = safe[1];
168
+ }
169
+ return { url: assertFetchableUrl(url.toString()), headers, method, body };
170
+ }
171
+ var EMPTY_CONTEXT = { query: {}, params: {}, page: { slug: "" } };
172
+
173
+ // src/internal/lib/render/load-data.ts
174
+ var PageDataNotFoundError = class extends Error {
175
+ constructor(sourceId) {
176
+ super(`Data source ${sourceId} returned 404.`);
177
+ this.sourceId = sourceId;
178
+ this.name = "PageDataNotFoundError";
179
+ }
180
+ };
181
+ async function loadPageData(elements, sources, options) {
182
+ const used = new Set(
183
+ elements.filter((el) => el.sourceId).map((el) => el.sourceId)
184
+ );
185
+ const wanted = sources.filter((source) => used.has(source.id) && source.url);
186
+ if (wanted.length === 0) return {};
187
+ const results = await Promise.all(
188
+ wanted.map(async (source) => {
189
+ try {
190
+ const { rows } = await loadSource(source, options);
191
+ return [source.id, rows];
192
+ } catch (error) {
193
+ if (error instanceof DataSourceError && error.status === 404 && source.onNotFound === "page-404") {
194
+ throw new PageDataNotFoundError(source.id);
195
+ }
196
+ console.warn(
197
+ `Data source "${source.name}" failed:`,
198
+ error instanceof DataSourceError ? error.message : error
199
+ );
200
+ return [source.id, []];
201
+ }
202
+ })
203
+ );
204
+ return Object.fromEntries(results);
205
+ }
206
+ export {
207
+ PageDataNotFoundError,
208
+ loadPageData
209
+ };
210
+ //# sourceMappingURL=data.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/internal/lib/editor/types.ts", "../src/internal/lib/data/source.ts", "../src/internal/lib/render/load-data.ts"],
4
+ "sourcesContent": ["export const ELEMENT_TYPES = [\n \"Frame\",\n \"Stack\",\n \"Section\",\n \"Container\",\n \"Grid\",\n \"Heading\",\n \"Text\",\n \"Image\",\n \"Button\",\n \"Video\",\n \"Icon\",\n \"Request\",\n \"Repeat\",\n] as const;\n\nexport type ElementType = (typeof ELEMENT_TYPES)[number];\n\n/** One key/value pair on a request; values may carry `{{\u2026}}` tokens. */\nexport type RequestPair = { key: string; value: string };\n\nexport const HTTP_METHODS = [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"] as const;\nexport type HttpMethod = (typeof HTTP_METHODS)[number];\n\nexport const DATA_SOURCE_NOT_FOUND_BEHAVIORS = [\"empty\", \"page-404\"] as const;\nexport type DataSourceNotFoundBehavior = (typeof DATA_SOURCE_NOT_FOUND_BEHAVIORS)[number];\n\n/** Methods that carry a request body. */\nexport function sendsBody(method: HttpMethod) {\n return method === \"POST\" || method === \"PUT\" || method === \"PATCH\";\n}\n\n/**\n * A JSON endpoint the page pulls content from. Sources live on the page so a\n * Repeat block can name one without carrying the URL on every element.\n */\nexport type DataSource = {\n id: string;\n name: string;\n url: string;\n /** Dotted path to the array inside the payload; \"\" when it is the root. */\n path: string;\n method?: HttpMethod;\n /** JSON body for POST/PUT/PATCH; tokens are resolved before sending. */\n body?: string;\n /** Appended to the URL as a query string. */\n params?: RequestPair[];\n /** Sent as request headers \u2014 for API keys and the like. */\n headers?: RequestPair[];\n /** Controls whether an upstream 404 empties this source or rejects the whole page. */\n onNotFound?: DataSourceNotFoundBehavior;\n};\n\n/**\n * Values a request token can read. Everything else resolves to an empty\n * string, so a typo cannot leak an unrelated value into the URL.\n */\nexport type RequestContext = {\n /** The visitor's query string, e.g. `{{query.id}}` on /post?id=5. */\n query: Record<string, string>;\n /** Values captured by a dynamic page path, e.g. `{{params.slug}}`. */\n params: Record<string, string>;\n /** The page being rendered, e.g. `{{page.slug}}`. */\n page: { slug: string };\n};\n\nexport type ResizeHandle = \"nw\" | \"ne\" | \"sw\" | \"se\" | \"n\" | \"s\" | \"w\" | \"e\";\n\n/* ------------------------------------------------------------- breakpoints */\n\nexport const BREAKPOINTS = [\"desktop\", \"tablet\", \"mobile\"] as const;\nexport type Breakpoint = string;\n\nexport type BreakpointDefinition = {\n id: string;\n name: string;\n width: number;\n};\n\nexport const DEFAULT_BREAKPOINTS: BreakpointDefinition[] = [\n { id: \"desktop\", name: \"Desktop\", width: 1280 },\n { id: \"tablet\", name: \"Tablet\", width: 768 },\n { id: \"mobile\", name: \"Mobile\", width: 375 },\n];\n\nexport const BREAKPOINT_WIDTHS: Record<string, number> = {\n desktop: 1280,\n tablet: 768,\n mobile: 375,\n};\n\n/**\n * Styles cascade from the widest breakpoint down, so a value set on desktop\n * holds everywhere until a narrower breakpoint overrides it.\n */\nexport const BREAKPOINT_CHAIN: Record<string, Breakpoint[]> = {\n desktop: [\"desktop\"],\n tablet: [\"desktop\", \"tablet\"],\n mobile: [\"desktop\", \"tablet\", \"mobile\"],\n};\n\n/* ------------------------------------------------------------------ styles */\n\n/** `fill` stretches to the parent, `auto` shrinks to the content. */\nexport type SizeMode = \"fixed\" | \"fill\" | \"auto\";\nexport type Constraint = \"start\" | \"center\" | \"end\" | \"stretch\";\n/** `absolute` positions children by x/y; `stack` lays them out with flexbox. */\nexport type LayoutMode = \"absolute\" | \"stack\";\nexport type Direction = \"row\" | \"column\";\nexport type Justify = \"start\" | \"center\" | \"end\" | \"between\";\nexport type Align = \"start\" | \"center\" | \"end\" | \"stretch\";\nexport type TextAlign = \"left\" | \"center\" | \"right\" | \"justify\";\nexport type TextTransform = \"none\" | \"uppercase\" | \"lowercase\" | \"capitalize\";\nexport type ObjectFit = \"cover\" | \"contain\" | \"fill\" | \"none\";\nexport type Overflow = \"visible\" | \"hidden\" | \"auto\" | \"scroll\";\nexport type Entrance = \"none\" | \"fade\" | \"up\" | \"down\" | \"left\" | \"right\" | \"zoom\";\nexport type MotionCurve = \"ease\" | \"spring\";\nexport type CursorStyle = \"auto\" | \"default\" | \"pointer\" | \"text\" | \"grab\" | \"zoom-in\" | \"none\";\nexport type PositionMode = \"static\" | \"sticky\";\nexport type BgSize = \"cover\" | \"contain\" | \"auto\";\nexport type BlendMode =\n | \"normal\"\n | \"multiply\"\n | \"screen\"\n | \"overlay\"\n | \"darken\"\n | \"lighten\"\n | \"difference\"\n | \"luminosity\";\nexport type BorderStyle = \"solid\" | \"dashed\" | \"dotted\";\n\nexport type ElementStyle = {\n // Box \u2014 x/y only apply inside an `absolute` parent.\n x: number;\n y: number;\n constraintX: Constraint;\n constraintY: Constraint;\n w: number;\n h: number;\n widthMode: SizeMode;\n heightMode: SizeMode;\n\n // How this element arranges its own children.\n layout: LayoutMode;\n direction: Direction;\n gap: number;\n padT: number;\n padR: number;\n padB: number;\n padL: number;\n justify: Justify;\n align: Align;\n wrap: boolean;\n /** Grid columns; only read when `layout` is `stack` on a Grid element. */\n columns: number;\n\n // Appearance\n bg: string;\n /** A full CSS gradient value, or \"\" for none. Painted over `bg`. */\n gradient: string;\n color: string;\n radius: number;\n opacity: number;\n borderW: number;\n borderC: string;\n borderStyle: BorderStyle;\n /** A full CSS box-shadow value, or \"\" for none. */\n shadow: string;\n rotate: number;\n\n // Typography\n fontFamily: string;\n fontSize: number;\n fontWeight: string;\n lineHeight: number;\n letterSpacing: number;\n textAlign: TextAlign;\n textTransform: TextTransform;\n\n // Composition \u2014 the pieces that make a layout feel designed rather than\n // stacked: clipping, sticky rails, imagery, glass and blend effects.\n overflow: Overflow;\n position: PositionMode;\n /** Distance from the top edge while stuck; only read when position is sticky. */\n stickyOffset: number;\n /** Background image URL, or \"\" for none. Painted over `gradient`. */\n bgImage: string;\n bgSize: BgSize;\n bgPosition: string;\n /** Blurs the element's own content, in px. */\n blur: number;\n /** Blurs whatever sits behind the element, in px \u2014 the glass effect. */\n backdropBlur: number;\n blendMode: BlendMode;\n /** Percent; 100 leaves the element alone. */\n scale: number;\n /** A CSS ratio such as \"16/9\", or \"\" to leave height to the layout. */\n aspectRatio: string;\n\n /** Entrance effect, played once when the element scrolls into view. */\n entrance: Entrance;\n /** Milliseconds. */\n entranceDuration: number;\n entranceDelay: number;\n entranceCurve: MotionCurve;\n entranceBezier: string;\n springStiffness: number;\n springDamping: number;\n cursor: CursorStyle;\n\n /** Hidden at this breakpoint. */\n hidden: boolean;\n};\n\nexport const STYLE_KEYS = [\n \"x\",\n \"y\",\n \"constraintX\",\n \"constraintY\",\n \"w\",\n \"h\",\n \"widthMode\",\n \"heightMode\",\n \"layout\",\n \"direction\",\n \"gap\",\n \"padT\",\n \"padR\",\n \"padB\",\n \"padL\",\n \"justify\",\n \"align\",\n \"wrap\",\n \"columns\",\n \"bg\",\n \"gradient\",\n \"color\",\n \"radius\",\n \"opacity\",\n \"borderW\",\n \"borderC\",\n \"borderStyle\",\n \"shadow\",\n \"rotate\",\n \"fontFamily\",\n \"fontSize\",\n \"fontWeight\",\n \"lineHeight\",\n \"letterSpacing\",\n \"textAlign\",\n \"textTransform\",\n \"overflow\",\n \"position\",\n \"stickyOffset\",\n \"bgImage\",\n \"bgSize\",\n \"bgPosition\",\n \"blur\",\n \"backdropBlur\",\n \"blendMode\",\n \"scale\",\n \"aspectRatio\",\n \"entrance\",\n \"entranceDuration\",\n \"entranceDelay\",\n \"entranceCurve\",\n \"entranceBezier\",\n \"springStiffness\",\n \"springDamping\",\n \"cursor\",\n \"hidden\",\n] as const satisfies ReadonlyArray<keyof ElementStyle>;\n\nexport type StyleKey = (typeof STYLE_KEYS)[number];\n\nexport type CanvasElement = {\n id: string;\n type: ElementType;\n name?: string;\n parentId?: string;\n z: number;\n locked?: boolean;\n /** Page-local reusable component metadata. */\n componentRole?: \"master\" | \"instance\";\n componentId?: string;\n componentSourceId?: string;\n variant?: string;\n styleBindings?: Partial<Record<StyleKey, string>>;\n\n // Content is shared across breakpoints.\n content?: string;\n /** Sandboxed HTML/CSS used by code components. */\n code?: string;\n src?: string;\n alt?: string;\n objectFit?: ObjectFit;\n iconName?: \"star\" | \"heart\" | \"arrow-right\" | \"check\" | \"menu\" | \"search\";\n href?: string;\n target?: \"_self\" | \"_blank\";\n interaction?: {\n trigger: \"click\";\n action: \"navigate\" | \"scroll-to\" | \"toggle-layer\" | \"show-layer\" | \"hide-layer\";\n value: string;\n target?: \"_self\" | \"_blank\";\n };\n\n /** Data source read by a Request/Repeat block or a directly-bound element. */\n sourceId?: string;\n /**\n * Inside Request/Repeat, pulls this field off the current object instead\n * of using the element's content. Dotted paths work: \"author.name\".\n */\n binding?: string;\n\n /** Desktop values; every breakpoint falls back to these. */\n base: ElementStyle;\n /** Narrower-breakpoint deltas, applied over `base` in cascade order. */\n overrides?: Record<string, Partial<ElementStyle>>;\n /** Applied on pointer hover, on top of the resolved breakpoint style. */\n hover?: Partial<ElementStyle>;\n /** Applied while the pointer is pressed. */\n press?: Partial<ElementStyle>;\n loop?: { type: \"pulse\" | \"float\" | \"spin\"; duration: number };\n draggable?: boolean;\n};\n\n/* ---------------------------------------------------------------- defaults */\n\nexport const BASE_STYLE: ElementStyle = {\n x: 0,\n y: 0,\n constraintX: \"start\",\n constraintY: \"start\",\n w: 200,\n h: 100,\n widthMode: \"fixed\",\n heightMode: \"fixed\",\n\n layout: \"absolute\",\n direction: \"column\",\n gap: 0,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n justify: \"start\",\n align: \"start\",\n wrap: false,\n columns: 3,\n\n bg: \"transparent\",\n gradient: \"\",\n color: \"#27272a\",\n radius: 0,\n opacity: 100,\n borderW: 0,\n borderC: \"transparent\",\n borderStyle: \"solid\",\n shadow: \"\",\n rotate: 0,\n\n fontFamily: \"inherit\",\n fontSize: 16,\n fontWeight: \"normal\",\n lineHeight: 1.5,\n letterSpacing: 0,\n textAlign: \"left\",\n textTransform: \"none\",\n\n overflow: \"visible\",\n position: \"static\",\n stickyOffset: 0,\n bgImage: \"\",\n bgSize: \"cover\",\n bgPosition: \"center\",\n blur: 0,\n backdropBlur: 0,\n blendMode: \"normal\",\n scale: 100,\n aspectRatio: \"\",\n\n entrance: \"none\",\n entranceDuration: 600,\n entranceDelay: 0,\n entranceCurve: \"ease\",\n entranceBezier: \"0.44, 0, 0.56, 1\",\n springStiffness: 300,\n springDamping: 30,\n cursor: \"auto\",\n\n hidden: false,\n};\n\nexport function makeStyle(overrides: Partial<ElementStyle>): ElementStyle {\n return { ...BASE_STYLE, ...overrides };\n}\n\n/** Every field a freshly dropped element starts with. */\nexport const ELEMENT_DEFAULTS: Record<\n ElementType,\n { style: Partial<ElementStyle>; props?: Partial<CanvasElement> }\n> = {\n Frame: {\n style: {\n w: 640,\n h: 420,\n widthMode: \"fixed\",\n heightMode: \"fixed\",\n layout: \"absolute\",\n direction: \"column\",\n overflow: \"hidden\",\n bg: \"#ffffff\",\n borderW: 1,\n borderC: \"#e4e4e7\",\n radius: 12,\n },\n },\n Stack: {\n style: {\n w: 600,\n h: 160,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n justify: \"start\",\n align: \"stretch\",\n bg: \"transparent\",\n },\n },\n Section: {\n style: {\n w: 1280,\n h: 480,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 24,\n padT: 64,\n padR: 48,\n padB: 64,\n padL: 48,\n justify: \"start\",\n align: \"stretch\",\n bg: \"#ffffff\",\n },\n },\n Container: {\n style: {\n w: 600,\n h: 240,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n padT: 24,\n padR: 24,\n padB: 24,\n padL: 24,\n align: \"stretch\",\n bg: \"transparent\",\n borderW: 1,\n borderC: \"#e4e4e7\",\n radius: 12,\n },\n },\n Grid: {\n style: {\n w: 900,\n h: 300,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n columns: 3,\n gap: 24,\n align: \"stretch\",\n },\n },\n Heading: {\n style: {\n w: 480,\n h: 48,\n widthMode: \"fill\",\n heightMode: \"auto\",\n fontSize: 40,\n fontWeight: \"bold\",\n lineHeight: 1.2,\n letterSpacing: -0.5,\n color: \"#18181b\",\n },\n props: { content: \"Heading\" },\n },\n Text: {\n style: {\n w: 480,\n h: 24,\n widthMode: \"fill\",\n heightMode: \"auto\",\n fontSize: 16,\n lineHeight: 1.6,\n color: \"#52525b\",\n },\n props: { content: \"Text block content\" },\n },\n Image: {\n style: { w: 400, h: 260, widthMode: \"fill\", bg: \"#e4e4e7\", radius: 12 },\n props: { src: \"\", alt: \"\", objectFit: \"cover\" },\n },\n Button: {\n style: {\n w: 140,\n h: 44,\n widthMode: \"auto\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n justify: \"center\",\n align: \"center\",\n padT: 12,\n padR: 22,\n padB: 12,\n padL: 22,\n bg: \"#2563eb\",\n color: \"#ffffff\",\n radius: 8,\n fontSize: 15,\n fontWeight: \"500\",\n textAlign: \"center\",\n },\n props: { content: \"Button\", href: \"\", target: \"_self\" },\n },\n Video: {\n style: { w: 640, h: 360, widthMode: \"fill\", bg: \"#18181b\", radius: 12 },\n props: { src: \"\" },\n },\n Icon: {\n style: { w: 24, h: 24, widthMode: \"fixed\", heightMode: \"fixed\", color: \"#5402E6\" },\n props: { iconName: \"star\" },\n },\n Request: {\n style: {\n w: 900,\n h: 200,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"column\",\n gap: 16,\n align: \"stretch\",\n },\n props: { sourceId: \"\" },\n },\n Repeat: {\n style: {\n w: 900,\n h: 200,\n widthMode: \"fill\",\n heightMode: \"auto\",\n layout: \"stack\",\n direction: \"row\",\n gap: 24,\n align: \"stretch\",\n wrap: true,\n },\n props: { sourceId: \"\" },\n },\n};\n\n/* ----------------------------------------------------------------- helpers */\n\nconst CONTAINER_TYPES: ReadonlySet<ElementType> = new Set([\n \"Frame\",\n \"Stack\",\n \"Section\",\n \"Container\",\n \"Grid\",\n \"Button\",\n \"Request\",\n \"Repeat\",\n]);\n\n/** Whether an element can hold children. */\nexport function isContainer(type: ElementType) {\n return CONTAINER_TYPES.has(type);\n}\n\nconst TEXTUAL_TYPES: ReadonlySet<ElementType> = new Set([\n \"Heading\",\n \"Text\",\n \"Button\",\n]);\n\n/** Whether an element renders editable text. */\nexport function isTextual(type: ElementType) {\n return TEXTUAL_TYPES.has(type);\n}\n\n/** Page-level settings; the canvas behaves as the root container. */\nexport type RootStyle = {\n documentMode: \"page\" | \"component\";\n /** Content is centred inside this width unless `fullWidth` is set. */\n maxWidth: number;\n /** Editable design-surface height; the public page can still grow with content. */\n canvasHeight: number;\n /** Let content run edge to edge instead of being capped at `maxWidth`. */\n fullWidth: boolean;\n bg: string;\n layout: LayoutMode;\n direction: Direction;\n gap: number;\n padT: number;\n padR: number;\n padB: number;\n padL: number;\n align: Align;\n fontFamily: string;\n /** User-defined viewport previews, ordered from widest to narrowest. */\n breakpoints?: BreakpointDefinition[];\n /** The breakpoint whose values live in every element's `base` style. */\n baseBreakpointId?: string;\n variables?: DesignVariable[];\n customFonts?: CustomFont[];\n};\n\nexport type CustomFont = { id: string; name: string; url: string; weight: number; style: \"normal\" | \"italic\" };\n\nexport type DesignVariable = {\n id: string;\n name: string;\n type: \"color\" | \"number\";\n value: string | number;\n};\n\nexport const DEFAULT_ROOT_STYLE: RootStyle = {\n documentMode: \"page\",\n maxWidth: 1280,\n canvasHeight: 800,\n fullWidth: false,\n bg: \"#ffffff\",\n layout: \"stack\",\n direction: \"column\",\n gap: 0,\n padT: 0,\n padR: 0,\n padB: 0,\n padL: 0,\n align: \"stretch\",\n fontFamily: \"inherit\",\n};\n\nexport const FONT_STACKS: Array<{ label: string; value: string }> = [\n { label: \"Inherit\", value: \"inherit\" },\n { label: \"Sans\", value: \"ui-sans-serif, system-ui, sans-serif\" },\n { label: \"Serif\", value: \"ui-serif, Georgia, serif\" },\n { label: \"Mono\", value: \"ui-monospace, SFMono-Regular, monospace\" },\n];\n\nexport const SHADOW_PRESETS: Array<{ label: string; value: string }> = [\n { label: \"None\", value: \"\" },\n { label: \"Small\", value: \"0 1px 2px rgba(0,0,0,0.06)\" },\n { label: \"Medium\", value: \"0 4px 12px rgba(0,0,0,0.08)\" },\n { label: \"Large\", value: \"0 12px 32px rgba(0,0,0,0.12)\" },\n { label: \"Glow\", value: \"0 0 0 4px rgba(37,99,235,0.15)\" },\n];\n\nexport const ASPECT_RATIOS: Array<{ label: string; value: string }> = [\n { label: \"Free\", value: \"\" },\n { label: \"Square 1:1\", value: \"1/1\" },\n { label: \"Photo 4:3\", value: \"4/3\" },\n { label: \"Wide 16:9\", value: \"16/9\" },\n { label: \"Ultra 21:9\", value: \"21/9\" },\n { label: \"Portrait 3:4\", value: \"3/4\" },\n];\n\nexport const ENTRANCES: Array<{ label: string; value: Entrance }> = [\n { label: \"None\", value: \"none\" },\n { label: \"Fade in\", value: \"fade\" },\n { label: \"Rise up\", value: \"up\" },\n { label: \"Drop down\", value: \"down\" },\n { label: \"Slide from left\", value: \"left\" },\n { label: \"Slide from right\", value: \"right\" },\n { label: \"Zoom in\", value: \"zoom\" },\n];\n\nexport const DRAG_MIME = \"application/pagiera-element-type\";\n/** Set when dragging an element that already exists on the canvas. */\nexport const MOVE_MIME = \"application/pagiera-element-id\";\n", "import {\n type DataSource,\n type HttpMethod,\n type RequestContext,\n type RequestPair,\n sendsBody,\n} from \"@/lib/editor/types\";\n\n/**\n * Fetching an author-supplied URL from our server is a server-side request\n * forgery vector: without checks it can reach cloud metadata endpoints, admin\n * services on localhost, or anything else inside the network perimeter. Every\n * hop is validated against this list, and redirects are followed by hand so a\n * public URL cannot bounce into private space.\n */\nconst BLOCKED_HOSTS = new Set([\n \"localhost\",\n \"127.0.0.1\",\n \"0.0.0.0\",\n \"::1\",\n \"[::1]\",\n \"metadata.google.internal\",\n \"metadata.goog\",\n]);\n\nconst PRIVATE_IPV4 =\n /^(10\\.|127\\.|0\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2\\d|3[01])\\.)/;\n\n/** Hostnames that only resolve inside a private network. */\nconst PRIVATE_SUFFIXES = [\".local\", \".internal\", \".localdomain\", \".home.arpa\"];\n\nexport const MAX_ROWS = 200;\nconst MAX_BYTES = 2_000_000;\nconst TIMEOUT_MS = 8000;\nconst MAX_REDIRECTS = 3;\n\nexport class DataSourceError extends Error {\n constructor(message: string, public readonly status?: number) {\n super(message);\n this.name = \"DataSourceError\";\n }\n}\n\nexport function assertFetchableUrl(raw: string): URL {\n let url: URL;\n try {\n url = new URL(raw);\n } catch {\n throw new DataSourceError(\"That is not a valid URL.\");\n }\n\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") {\n throw new DataSourceError(\"Only http and https URLs can be requested.\");\n }\n\n const host = url.hostname.toLowerCase();\n if (BLOCKED_HOSTS.has(host) || PRIVATE_IPV4.test(host)) {\n throw new DataSourceError(`Requests to ${host} are not allowed.`);\n }\n if (PRIVATE_SUFFIXES.some((suffix) => host.endsWith(suffix))) {\n throw new DataSourceError(`Requests to ${host} are not allowed.`);\n }\n // Bracketed IPv6 literals: anything link-local or unique-local.\n if (host.startsWith(\"[\") && /^\\[(fe80|fc|fd)/i.test(host)) {\n throw new DataSourceError(\"Requests to private addresses are not allowed.\");\n }\n\n return url;\n}\n\n/**\n * Walks a dotted path such as `data.items` or `results.0.tags`. An empty path\n * returns the payload itself, which is what a bare JSON array needs.\n */\nexport function selectPath(payload: unknown, path: string): unknown {\n const steps = path.split(\".\").map((s) => s.trim()).filter(Boolean);\n let cursor: unknown = payload;\n for (const step of steps) {\n if (cursor === null || typeof cursor !== \"object\") return undefined;\n cursor = (cursor as Record<string, unknown>)[step];\n }\n return cursor;\n}\n\n/** Reads a body with a hard cap so a huge response cannot exhaust memory. */\nasync function readCapped(response: Response): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return \"\";\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n total += value.byteLength;\n if (total > MAX_BYTES) {\n await reader.cancel();\n throw new DataSourceError(\"That response is too large (over 2 MB).\");\n }\n chunks.push(value);\n }\n return new TextDecoder().decode(\n chunks.reduce<Uint8Array>((acc, chunk) => {\n const merged = new Uint8Array(acc.length + chunk.length);\n merged.set(acc);\n merged.set(chunk, acc.length);\n return merged;\n }, new Uint8Array()),\n );\n}\n\nexport type SourceResult = { rows: Array<Record<string, unknown>>; keys: string[] };\n\n/**\n * Requests a source and normalizes it to rows. A root object becomes one row\n * for Request; an array stays multiple rows for Repeat.\n */\nexport type LoadOptions = {\n /**\n * Seconds to cache the response for. `false` bypasses the cache \u2014 only for\n * the editor's Test button; on a rendered route it would force an\n * otherwise-static page to become dynamic on every request.\n */\n revalidate?: number | false;\n /** Supplies `{{query.\u2026}}` / `{{page.\u2026}}` values for this request. */\n context?: RequestContext;\n};\n\nexport const DEFAULT_REVALIDATE = 60;\n\nexport async function loadSource(\n source: DataSource,\n { revalidate = DEFAULT_REVALIDATE, context = EMPTY_CONTEXT }: LoadOptions = {},\n): Promise<SourceResult> {\n const request = buildRequest(source, context);\n let target = request.url;\n\n let response: Response | undefined;\n for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {\n response = await fetch(target, {\n // Redirects are followed by hand so each hop is validated too.\n redirect: \"manual\",\n method: request.method,\n headers: request.headers,\n body: request.body,\n signal: AbortSignal.timeout(TIMEOUT_MS),\n // Only GET responses are cacheable; anything with side effects is\n // fetched fresh every time.\n ...(revalidate === false || request.method !== \"GET\"\n ? { cache: \"no-store\" as const }\n : { next: { revalidate } }),\n });\n\n if (response.status < 300 || response.status >= 400) break;\n\n const location = response.headers.get(\"location\");\n if (!location) break;\n target = assertFetchableUrl(new URL(location, target).toString());\n response = undefined;\n }\n\n if (!response) throw new DataSourceError(\"Too many redirects.\");\n if (!response.ok) {\n throw new DataSourceError(`The API replied ${response.status}.`, response.status);\n }\n\n const text = await readCapped(response);\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n throw new DataSourceError(\"The response was not JSON.\");\n }\n\n const selected = selectPath(payload, source.path);\n const selectedRows = Array.isArray(selected)\n ? selected\n : selected !== null && typeof selected === \"object\"\n ? [selected]\n : undefined;\n if (!selectedRows) {\n throw new DataSourceError(\n source.path\n ? `\"${source.path}\" is not a list in that response.`\n : \"That response is neither an object nor a list.\",\n );\n }\n\n const rows = selectedRows\n .slice(0, MAX_ROWS)\n .map((row) =>\n row !== null && typeof row === \"object\" && !Array.isArray(row)\n ? (row as Record<string, unknown>)\n : { value: row },\n );\n\n // Union of the keys across the sample, so binding menus list everything.\n const keys = [...new Set(rows.flatMap((row) => Object.keys(row)))].sort();\n return { rows, keys };\n}\n\n/** Reads one binding path off a row and flattens it to something renderable. */\nexport function readBinding(row: Record<string, unknown>, path: string): string {\n const value = selectPath(row, path);\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"object\") return \"\";\n return String(value);\n}\n\n/* ------------------------------------------------------------------ tokens */\n\n/**\n * Resolves `{{query.id}}`, `{{params.slug}}` and `{{page.slug}}`.\n *\n * Only these two namespaces exist: an unknown token becomes an empty string\n * rather than being left in the URL, so a typo cannot send a literal `{{\u2026}}`\n * to the API or accidentally expose something else.\n */\nexport function resolveTokens(value: string, context: RequestContext): string {\n return value.replace(/\\{\\{\\s*([a-zA-Z0-9_.]+)\\s*\\}\\}/g, (_, token: string) => {\n const [namespace, ...rest] = token.split(\".\");\n const key = rest.join(\".\");\n if (namespace === \"query\") return context.query[key] ?? \"\";\n if (namespace === \"params\") return context.params[key] ?? \"\";\n if (namespace === \"page\" && key === \"slug\") return context.page.slug;\n return \"\";\n });\n}\n\n/** Header names and values must not be able to inject extra headers. */\nfunction safeHeader(pair: RequestPair, context: RequestContext) {\n const name = pair.key.trim();\n if (!/^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(name)) return null;\n // Strip anything that could terminate the header line.\n const value = resolveTokens(pair.value, context).replace(/[\\r\\n\\0]/g, \"\").trim();\n return value ? ([name, value] as const) : null;\n}\n\n/**\n * Builds the final URL: tokens resolved, params appended and percent-encoded\n * by `URLSearchParams` so a value can never break out into the query grammar.\n */\nexport function buildRequest(source: DataSource, context: RequestContext) {\n const url = assertFetchableUrl(resolveTokens(source.url, context));\n\n for (const pair of source.params ?? []) {\n const key = pair.key.trim();\n if (!key) continue;\n const value = resolveTokens(pair.value, context);\n // An empty optional filter is dropped rather than sent as `key=`.\n if (value === \"\") continue;\n url.searchParams.set(key, value);\n }\n\n const method: HttpMethod = source.method ?? \"GET\";\n\n const headers: Record<string, string> = {\n accept: \"application/json,text/plain;q=0.9,*/*;q=0.8\",\n };\n\n let body: string | undefined;\n if (sendsBody(method) && source.body?.trim()) {\n body = resolveTokens(source.body, context);\n headers[\"content-type\"] = \"application/json\";\n }\n\n // Author headers last, so they can override the defaults above.\n for (const pair of source.headers ?? []) {\n const safe = safeHeader(pair, context);\n if (safe) headers[safe[0]] = safe[1];\n }\n\n // Re-check after tokens: a token could have rewritten the host.\n return { url: assertFetchableUrl(url.toString()), headers, method, body };\n}\n\nexport const EMPTY_CONTEXT: RequestContext = { query: {}, params: {}, page: { slug: \"\" } };\n", "import { DataSourceError, type LoadOptions, loadSource } from \"@/lib/data/source\";\nimport type { CanvasElement, DataSource } from \"@/lib/editor/types\";\nimport type { PageData } from \"./bind\";\n\n/** Signals that a source elected to turn its upstream 404 into a page 404. */\nexport class PageDataNotFoundError extends Error {\n constructor(public readonly sourceId: string) {\n super(`Data source ${sourceId} returned 404.`);\n this.name = \"PageDataNotFoundError\";\n }\n}\n\n/**\n * Fetches every source a page actually uses. Sources nothing references are\n * skipped, and a source that fails yields no rows rather than failing the\n * whole page \u2014 a broken API should not take the site down.\n */\nexport async function loadPageData(\n elements: CanvasElement[],\n sources: DataSource[],\n options?: LoadOptions,\n): Promise<PageData> {\n const used = new Set(\n elements\n .filter((el) => el.sourceId)\n .map((el) => el.sourceId as string),\n );\n const wanted = sources.filter((source) => used.has(source.id) && source.url);\n if (wanted.length === 0) return {};\n\n const results = await Promise.all(\n wanted.map(async (source) => {\n try {\n const { rows } = await loadSource(source, options);\n return [source.id, rows] as const;\n } catch (error) {\n if (\n error instanceof DataSourceError &&\n error.status === 404 &&\n source.onNotFound === \"page-404\"\n ) {\n throw new PageDataNotFoundError(source.id);\n }\n console.warn(\n `Data source \"${source.name}\" failed:`,\n error instanceof DataSourceError ? error.message : error,\n );\n return [source.id, []] as const;\n }\n }),\n );\n\n return Object.fromEntries(results);\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,63 @@
1
+ export type PagieraBreakpoint = {
2
+ id: string;
3
+ name: string;
4
+ width: number;
5
+ };
6
+ export type PagieraElement = {
7
+ id: string;
8
+ type: string;
9
+ name?: string;
10
+ parentId?: string;
11
+ z: number;
12
+ base: Record<string, unknown>;
13
+ overrides?: Record<string, Record<string, unknown>>;
14
+ content?: string;
15
+ src?: string;
16
+ href?: string;
17
+ target?: "_self" | "_blank";
18
+ alt?: string;
19
+ objectFit?: "cover" | "contain" | "fill" | "none";
20
+ iconName?: "star" | "heart" | "arrow-right" | "check" | "menu" | "search";
21
+ code?: string;
22
+ hover?: Record<string, unknown>;
23
+ press?: Record<string, unknown>;
24
+ interaction?: {
25
+ trigger: "click";
26
+ action: "navigate" | "scroll-to" | "toggle-layer" | "show-layer" | "hide-layer";
27
+ value: string;
28
+ target?: "_self" | "_blank";
29
+ };
30
+ };
31
+ export type PagieraRootStyle = {
32
+ documentMode: "page" | "component";
33
+ maxWidth: number;
34
+ canvasHeight: number;
35
+ fullWidth: boolean;
36
+ bg: string;
37
+ layout: "absolute" | "stack";
38
+ direction: "row" | "column";
39
+ gap: number;
40
+ fontFamily: string;
41
+ breakpoints: PagieraBreakpoint[];
42
+ customFonts?: Array<{
43
+ id: string;
44
+ name: string;
45
+ url: string;
46
+ weight: number;
47
+ style: "normal" | "italic";
48
+ }>;
49
+ variables?: Array<{
50
+ id: string;
51
+ name: string;
52
+ type: "color" | "number";
53
+ value: string | number;
54
+ }>;
55
+ };
56
+ export type PagieraDocument = {
57
+ version: 1;
58
+ elements: PagieraElement[];
59
+ rootStyle: PagieraRootStyle;
60
+ dataSources: unknown[];
61
+ };
62
+ export declare function createDocument(patch?: Partial<PagieraDocument>): PagieraDocument;
63
+ //# sourceMappingURL=document.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAClD,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE;QACV,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,CAAC;QAChF,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;KAC/B,CAAC;CACL,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC3B,YAAY,EAAE,MAAM,GAAG,WAAW,CAAC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,OAAO,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC;IAC7B,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,iBAAiB,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;KAAE,CAAC,CAAC;IAC3G,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC,CAAC;CACrG,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC1B,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,SAAS,EAAE,gBAAgB,CAAC;IAC5B,WAAW,EAAE,OAAO,EAAE,CAAC;CAC1B,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,GAAE,OAAO,CAAC,eAAe,CAAM,GAAG,eAAe,CAuBpF"}
@@ -0,0 +1,25 @@
1
+ export function createDocument(patch = {}) {
2
+ return {
3
+ version: 1,
4
+ elements: [],
5
+ dataSources: [],
6
+ rootStyle: {
7
+ documentMode: "page",
8
+ maxWidth: 1280,
9
+ canvasHeight: 900,
10
+ fullWidth: true,
11
+ bg: "#ffffff",
12
+ layout: "stack",
13
+ direction: "column",
14
+ gap: 0,
15
+ fontFamily: "Inter, ui-sans-serif, sans-serif",
16
+ breakpoints: [
17
+ { id: "desktop", name: "Desktop", width: 1280 },
18
+ { id: "tablet", name: "Tablet", width: 810 },
19
+ { id: "mobile", name: "Mobile", width: 390 }
20
+ ],
21
+ },
22
+ ...patch,
23
+ };
24
+ }
25
+ //# sourceMappingURL=document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.ts"],"names":[],"mappings":"AAsDA,MAAM,UAAU,cAAc,CAAC,QAAkC,EAAE;IAC/D,OAAO;QACH,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE,EAAE;QACZ,WAAW,EAAE,EAAE;QACf,SAAS,EAAE;YACP,YAAY,EAAE,MAAM;YACpB,QAAQ,EAAE,IAAI;YACd,YAAY,EAAE,GAAG;YACjB,SAAS,EAAE,IAAI;YACf,EAAE,EAAE,SAAS;YACb,MAAM,EAAE,OAAO;YACf,SAAS,EAAE,QAAQ;YACnB,GAAG,EAAE,CAAC;YACN,UAAU,EAAE,kCAAkC;YAC9C,WAAW,EAAE;gBACT,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE;gBAC/C,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;gBAC5C,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,EAAE;aAC/C;SACJ;QACD,GAAG,KAAK;KACX,CAAC;AACN,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type PagieraDocument } from "./document.js";
2
+ export type PagieraEditorAdapters = {
3
+ save?: (document: PagieraDocument) => void | Promise<void>;
4
+ generate?: (prompt: string, document: PagieraDocument) => Promise<PagieraDocument>;
5
+ installTemplate?: (templateId: string, document: PagieraDocument) => PagieraDocument | Promise<PagieraDocument>;
6
+ };
7
+ export type PagieraEditorProps = {
8
+ value?: PagieraDocument;
9
+ onChange?: (document: PagieraDocument) => void;
10
+ adapters?: PagieraEditorAdapters;
11
+ className?: string;
12
+ };
13
+ export declare function PagieraEditor({ value, onChange, adapters, className }: PagieraEditorProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,eAAe,EAAuB,MAAM,eAAe,CAAC;AAG1F,MAAM,MAAM,qBAAqB,GAAG;IAChC,IAAI,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACnF,eAAe,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;CACnH,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC7B,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,eAAe,KAAK,IAAI,CAAC;IAC/C,QAAQ,CAAC,EAAE,qBAAqB,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAyBF,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,kBAAkB,+BAiDzF"}
package/dist/editor.js ADDED
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useMemo, useState } from "react";
4
+ import { createDocument } from "./document.js";
5
+ import { PagieraPage } from "./page.js";
6
+ const uid = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `pg-${Date.now()}-${Math.random().toString(36).slice(2)}`;
7
+ const number = (value, fallback = 0) => Number.isFinite(Number(value)) ? Number(value) : fallback;
8
+ function elementLabel(element) {
9
+ return element.name || element.content?.slice(0, 28) || element.type;
10
+ }
11
+ function defaultElement(type) {
12
+ const textual = type === "Heading" || type === "Text" || type === "Button";
13
+ return {
14
+ id: uid(), type, z: Date.now(), name: type,
15
+ content: type === "Heading" ? "Your headline" : type === "Text" ? "Write something meaningful." : type === "Button" ? "Button" : undefined,
16
+ base: {
17
+ x: 0, y: 0, w: textual ? 280 : 360, h: textual ? 48 : 180,
18
+ widthMode: textual ? "auto" : "fill", heightMode: "auto",
19
+ layout: ["Section", "Container", "Stack", "Frame", "Button"].includes(type) ? "stack" : "absolute",
20
+ direction: "column", gap: 12, padT: type === "Section" ? 64 : 0, padR: type === "Section" ? 40 : 0, padB: type === "Section" ? 64 : 0, padL: type === "Section" ? 40 : 0,
21
+ align: "start", justify: "start", bg: type === "Button" ? "#4f8cff" : "transparent", color: type === "Button" ? "#ffffff" : "inherit",
22
+ fontSize: type === "Heading" ? 48 : 16, fontWeight: type === "Heading" || type === "Button" ? "700" : "400", lineHeight: 1.2, opacity: 100, scale: 100,
23
+ },
24
+ };
25
+ }
26
+ export function PagieraEditor({ value, onChange, adapters, className }) {
27
+ const [internal, setInternal] = useState(() => value ?? createDocument());
28
+ const document = value ?? internal;
29
+ const [selectedId, setSelectedId] = useState();
30
+ const [breakpointId, setBreakpointId] = useState("desktop");
31
+ const [panel, setPanel] = useState("layers");
32
+ const [prompt, setPrompt] = useState("");
33
+ const [busy, setBusy] = useState(false);
34
+ const selected = document.elements.find((element) => element.id === selectedId);
35
+ const breakpoint = document.rootStyle.breakpoints.find((item) => item.id === breakpointId) ?? document.rootStyle.breakpoints[0];
36
+ const roots = useMemo(() => document.elements.filter((element) => !element.parentId).sort((a, b) => b.z - a.z), [document.elements]);
37
+ const commit = (next) => { if (!value)
38
+ setInternal(next); onChange?.(next); };
39
+ const patchElement = (id, patch) => commit({ ...document, elements: document.elements.map((element) => element.id === id ? { ...element, ...patch } : element) });
40
+ const patchStyle = (key, nextValue) => {
41
+ if (!selected)
42
+ return;
43
+ if (breakpointId === "desktop")
44
+ patchElement(selected.id, { base: { ...selected.base, [key]: nextValue } });
45
+ else
46
+ patchElement(selected.id, { overrides: { ...selected.overrides, [breakpointId]: { ...selected.overrides?.[breakpointId], [key]: nextValue } } });
47
+ };
48
+ const resolved = selected ? { ...selected.base, ...(breakpointId === "desktop" ? {} : selected.overrides?.[breakpointId]) } : {};
49
+ const add = (type) => { const element = defaultElement(type); commit({ ...document, elements: [...document.elements, element] }); setSelectedId(element.id); setPanel("layers"); };
50
+ return _jsxs("div", { className: `pagiera-editor ${className ?? ""}`, children: [_jsxs("header", { className: "pagiera-editor__topbar", children: [_jsx("strong", { children: "Pagiera" }), _jsx("div", { className: "pagiera-editor__breakpoints", children: document.rootStyle.breakpoints.map((item) => _jsxs("button", { type: "button", "data-active": item.id === breakpointId, onClick: () => setBreakpointId(item.id), children: [item.name, _jsx("small", { children: item.width })] }, item.id)) }), _jsx("button", { type: "button", onClick: () => adapters?.save?.(document), disabled: !adapters?.save, children: "Save" })] }), _jsxs("div", { className: "pagiera-editor__body", children: [_jsxs("aside", { className: "pagiera-editor__left", children: [_jsxs("nav", { children: [_jsx("button", { type: "button", "data-active": panel === "layers", onClick: () => setPanel("layers"), children: "Layers" }), _jsx("button", { type: "button", "data-active": panel === "insert", onClick: () => setPanel("insert"), children: "Insert" }), _jsx("button", { type: "button", "data-active": panel === "ai", onClick: () => setPanel("ai"), children: "AI" })] }), panel === "layers" && _jsx("div", { className: "pagiera-editor__list", children: roots.map((element) => _jsxs("button", { type: "button", "data-active": selectedId === element.id, onClick: () => setSelectedId(element.id), children: [elementLabel(element), _jsx("small", { children: element.type })] }, element.id)) }), panel === "insert" && _jsx("div", { className: "pagiera-editor__insert", children: ["Section", "Container", "Stack", "Grid", "Heading", "Text", "Button", "Image"].map((type) => _jsx("button", { type: "button", onClick: () => add(type), children: type }, type)) }), panel === "ai" && _jsxs("form", { className: "pagiera-editor__ai", onSubmit: async (event) => { event.preventDefault(); if (!adapters?.generate || !prompt.trim())
51
+ return; setBusy(true); try {
52
+ commit(await adapters.generate(prompt, document));
53
+ setPrompt("");
54
+ }
55
+ finally {
56
+ setBusy(false);
57
+ } }, children: [_jsx("textarea", { value: prompt, onChange: (event) => setPrompt(event.target.value), placeholder: "Describe the site\u2026" }), _jsx("button", { type: "submit", disabled: busy || !adapters?.generate, children: busy ? "Building…" : "Generate" })] })] }), _jsx("main", { className: "pagiera-editor__viewport", onClick: () => setSelectedId(undefined), children: _jsxs("div", { className: "pagiera-editor__frame", style: { width: breakpoint.width }, children: [_jsxs("div", { className: "pagiera-editor__frame-label", children: [breakpoint.name, " \u00B7 ", breakpoint.width, "px"] }), _jsx(PagieraPage, { document: document, interactive: false, onElementSelect: setSelectedId })] }) }), _jsx("aside", { className: "pagiera-editor__inspector", children: selected ? _jsxs(_Fragment, { children: [_jsxs("div", { className: "pagiera-editor__inspector-title", children: [_jsx("strong", { children: elementLabel(selected) }), _jsx("small", { children: breakpoint.name })] }), (selected.type === "Heading" || selected.type === "Text" || selected.type === "Button") && _jsxs("label", { children: ["Content", _jsx("textarea", { value: selected.content ?? "", onChange: (event) => patchElement(selected.id, { content: event.target.value }) })] }), _jsxs("div", { className: "pagiera-editor__grid", children: [_jsxs("label", { children: ["Width", _jsx("input", { type: "number", value: number(resolved.w, 200), onChange: (event) => patchStyle("w", Number(event.target.value)) })] }), _jsxs("label", { children: ["Height", _jsx("input", { type: "number", value: number(resolved.h, 100), onChange: (event) => patchStyle("h", Number(event.target.value)) })] })] }), _jsxs("div", { className: "pagiera-editor__grid", children: [_jsxs("label", { children: ["Font size", _jsx("input", { type: "number", value: number(resolved.fontSize, 16), onChange: (event) => patchStyle("fontSize", Number(event.target.value)) })] }), _jsxs("label", { children: ["Radius", _jsx("input", { type: "number", value: number(resolved.radius, 0), onChange: (event) => patchStyle("radius", Number(event.target.value)) })] })] }), _jsxs("label", { children: ["Background", _jsx("input", { type: "text", value: String(resolved.bg ?? "transparent"), onChange: (event) => patchStyle("bg", event.target.value) })] }), _jsxs("label", { children: ["Text color", _jsx("input", { type: "text", value: String(resolved.color ?? "inherit"), onChange: (event) => patchStyle("color", event.target.value) })] }), _jsx("button", { type: "button", className: "pagiera-editor__danger", onClick: () => { commit({ ...document, elements: document.elements.filter((element) => element.id !== selected.id && element.parentId !== selected.id) }); setSelectedId(undefined); }, children: "Delete element" })] }) : _jsx("p", { className: "pagiera-editor__empty", children: "Select an element to edit its properties." }) })] })] });
58
+ }
59
+ //# sourceMappingURL=editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { ComponentType } from "react";
2
+
3
+ export type PagieraMutationResult =
4
+ | { status: "ok"; pageId?: string; slug?: string }
5
+ | { status: "error"; message: string };
6
+
7
+ export type PagieraStudioDocument = {
8
+ elements: unknown[];
9
+ rootStyle: Record<string, unknown>;
10
+ dataSources: unknown[];
11
+ };
12
+
13
+ export type PagieraStudioPage = PagieraStudioDocument & {
14
+ id: string;
15
+ name: string;
16
+ slug: string;
17
+ version: number;
18
+ publishedAt: string | null;
19
+ };
20
+
21
+ export type PagieraStudioAdapters = {
22
+ save(pageId: string, document: PagieraStudioDocument, expectedVersion: number): Promise<
23
+ | { status: "saved"; version: number }
24
+ | ({ status: "conflict"; version: number } & PagieraStudioDocument)
25
+ | { status: "error"; message: string }
26
+ >;
27
+ generate?(request: Record<string, unknown>): Promise<Record<string, unknown>>;
28
+ previewSource?(source: unknown, sampleQuery: string): Promise<
29
+ | { status: "ok"; rows: Array<Record<string, unknown>>; keys: string[]; total: number }
30
+ | { status: "error"; message: string }
31
+ >;
32
+ createPage?(name: string, slug: string): Promise<PagieraMutationResult>;
33
+ renamePage?(id: string, name: string, slug: string): Promise<PagieraMutationResult>;
34
+ duplicatePage?(id: string, name: string, slug: string): Promise<PagieraMutationResult>;
35
+ deletePage?(id: string): Promise<PagieraMutationResult>;
36
+ installTemplate?(id: "nocturne" | "editorial-blog" | "orbit-saas"): Promise<PagieraMutationResult>;
37
+ publishPage?(id: string): Promise<PagieraMutationResult>;
38
+ unpublishPage?(id: string, slug: string): Promise<PagieraMutationResult>;
39
+ navigate?(pageId: string, options?: { replace?: boolean }): void | Promise<void>;
40
+ refresh?(): void;
41
+ previewHref?(pageId: string): string;
42
+ publishedHref?(slug: string): string;
43
+ };
44
+
45
+ export type PagieraStudioProps = {
46
+ page: PagieraStudioPage;
47
+ pages: Array<{ id: string; name: string; slug: string; published?: boolean }>;
48
+ library: unknown[];
49
+ adapters: PagieraStudioAdapters;
50
+ };
51
+
52
+ export const PagieraStudio: ComponentType<PagieraStudioProps>;
53
+ export default PagieraStudio;