@hanzo/ui 8.0.21 → 8.0.23

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 (48) hide show
  1. package/dist/ThemeToggleNext-AEDLHX5X.js +5 -0
  2. package/dist/{ThemeToggleNext-OMBC67MO.js.map → ThemeToggleNext-AEDLHX5X.js.map} +1 -1
  3. package/dist/ThemeToggleNext-N6ASYJ6J.cjs +20 -0
  4. package/dist/{ThemeToggleNext-LI3SBFW7.cjs.map → ThemeToggleNext-N6ASYJ6J.cjs.map} +1 -1
  5. package/dist/chunk-2BM42AZR.cjs +2301 -0
  6. package/dist/chunk-2BM42AZR.cjs.map +1 -0
  7. package/dist/{chunk-T3UAC7ZZ.js → chunk-77AMCLN4.js} +4 -4
  8. package/dist/{chunk-T3UAC7ZZ.js.map → chunk-77AMCLN4.js.map} +1 -1
  9. package/dist/chunk-7P7R7FC5.js +2231 -0
  10. package/dist/chunk-7P7R7FC5.js.map +1 -0
  11. package/dist/{chunk-57YEM34R.cjs → chunk-7XDEUEWI.cjs} +11 -2
  12. package/dist/chunk-7XDEUEWI.cjs.map +1 -0
  13. package/dist/{chunk-J3P63OX6.cjs → chunk-ARV3P4CE.cjs} +5 -5
  14. package/dist/{chunk-J3P63OX6.cjs.map → chunk-ARV3P4CE.cjs.map} +1 -1
  15. package/dist/{chunk-P6IHE2O3.js → chunk-FVQXROBK.js} +11 -3
  16. package/dist/chunk-FVQXROBK.js.map +1 -0
  17. package/dist/{chunk-WN3YJYGQ.cjs → chunk-NBZDIMP3.cjs} +11 -11
  18. package/dist/chunk-NBZDIMP3.cjs.map +1 -0
  19. package/dist/{chunk-WW6C6EHS.js → chunk-P2P6ST6J.js} +4 -4
  20. package/dist/chunk-P2P6ST6J.js.map +1 -0
  21. package/dist/framework/CollectionsBrowser.d.ts +44 -0
  22. package/dist/framework/DocTypeDetail.d.ts +13 -0
  23. package/dist/framework/DocTypeRecords.d.ts +43 -0
  24. package/dist/framework/Loader.d.ts +4 -0
  25. package/dist/framework/client.d.ts +87 -0
  26. package/dist/framework/data.d.ts +18 -0
  27. package/dist/framework/fields.d.ts +75 -0
  28. package/dist/framework/index.d.ts +23 -0
  29. package/dist/framework/types.d.ts +81 -0
  30. package/dist/framework.cjs +757 -0
  31. package/dist/framework.cjs.map +1 -0
  32. package/dist/framework.d.ts +1 -0
  33. package/dist/framework.js +727 -0
  34. package/dist/framework.js.map +1 -0
  35. package/dist/product/index.cjs +323 -2340
  36. package/dist/product/index.cjs.map +1 -1
  37. package/dist/product/index.js +4 -2231
  38. package/dist/product/index.js.map +1 -1
  39. package/dist/product/instrument.d.ts +6 -0
  40. package/dist/product/social/index.cjs +16 -16
  41. package/dist/product/social/index.js +2 -2
  42. package/package.json +7 -1
  43. package/dist/ThemeToggleNext-LI3SBFW7.cjs +0 -20
  44. package/dist/ThemeToggleNext-OMBC67MO.js +0 -5
  45. package/dist/chunk-57YEM34R.cjs.map +0 -1
  46. package/dist/chunk-P6IHE2O3.js.map +0 -1
  47. package/dist/chunk-WN3YJYGQ.cjs.map +0 -1
  48. package/dist/chunk-WW6C6EHS.js.map +0 -1
@@ -0,0 +1,757 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var chunk2BM42AZR_cjs = require('./chunk-2BM42AZR.cjs');
5
+ require('./chunk-ARV3P4CE.cjs');
6
+ var chunkNBZDIMP3_cjs = require('./chunk-NBZDIMP3.cjs');
7
+ require('./chunk-7XDEUEWI.cjs');
8
+ require('./chunk-MWBFAV3D.cjs');
9
+ var gui = require('@hanzo/gui');
10
+ var jsxRuntime = require('react/jsx-runtime');
11
+ var react = require('react');
12
+ var lucideIcons2 = require('@hanzogui/lucide-icons-2');
13
+ var data = require('@hanzo/data');
14
+
15
+ // src/framework/types.ts
16
+ var REDACTED_MARKER = "__set__";
17
+
18
+ // src/framework/fields.ts
19
+ var TYPE_MAP = {
20
+ Data: "text",
21
+ SmallText: "text",
22
+ Text: "longText",
23
+ LongText: "longText",
24
+ RichText: "richText",
25
+ Int: "number",
26
+ Float: "number",
27
+ Currency: "currency",
28
+ Check: "boolean",
29
+ Date: "date",
30
+ Datetime: "dateTime",
31
+ Select: "select",
32
+ Link: "relation",
33
+ Table: "json",
34
+ Attach: "url",
35
+ JSON: "json",
36
+ Password: "text"
37
+ };
38
+ var ACRONYMS = /* @__PURE__ */ new Set(["id", "url", "seo", "api", "ip"]);
39
+ function humanize(name) {
40
+ return name.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim().split(/\s+/).filter(Boolean).map((w) => ACRONYMS.has(w.toLowerCase()) ? w.toUpperCase() : w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
41
+ }
42
+ function selectOptions(options) {
43
+ return (options ?? "").split("\n").map((s) => s.replace(/\r$/, "").trim()).filter(Boolean).map((v) => ({ value: v, label: v }));
44
+ }
45
+ function metadataFor(f) {
46
+ switch (f.fieldtype) {
47
+ case "Select":
48
+ return { options: selectOptions(f.options) };
49
+ case "Currency":
50
+ return { currencyCode: "USD" };
51
+ case "Int":
52
+ return { decimals: 0 };
53
+ case "Link":
54
+ return { objectName: f.options };
55
+ default:
56
+ return void 0;
57
+ }
58
+ }
59
+ function autonameSource(dt) {
60
+ const a = (dt.autoname ?? "").trim();
61
+ return a.startsWith("field:") ? a.slice("field:".length).trim() : "";
62
+ }
63
+ function docTypeToFields(dt, opts = {}) {
64
+ const src = autonameSource(dt);
65
+ return (dt.fields ?? []).filter((f) => !f.hidden).map((f) => {
66
+ const readOnly = f.readOnly || opts.editing && f.fieldname === src || void 0;
67
+ const metadata = metadataFor(f);
68
+ return {
69
+ name: f.fieldname,
70
+ label: f.label || humanize(f.fieldname),
71
+ type: TYPE_MAP[f.fieldtype] ?? "text",
72
+ ...metadata ? { metadata } : {},
73
+ ...readOnly ? { readOnly: true } : {}
74
+ };
75
+ });
76
+ }
77
+ function statusField(dt) {
78
+ const f = (dt.fields ?? []).find(
79
+ (x) => x.fieldtype === "Select" && x.fieldname === "status" && (x.options ?? "").includes("Published")
80
+ );
81
+ return f ? f.fieldname : "";
82
+ }
83
+ function listHiddenFields(dt) {
84
+ const anyListed = (dt.fields ?? []).some((f) => f.inListView);
85
+ if (!anyListed) return [];
86
+ return (dt.fields ?? []).filter((f) => !f.inListView && !f.hidden).map((f) => f.fieldname);
87
+ }
88
+ function isMediaDoctype(dt) {
89
+ return (dt.fields ?? []).some((f) => f.fieldtype === "Attach" && f.reqd);
90
+ }
91
+ function mediaFileField(dt) {
92
+ const f = (dt.fields ?? []).find((x) => x.fieldtype === "Attach" && x.reqd) ?? (dt.fields ?? []).find((x) => x.fieldtype === "Attach");
93
+ return f ? f.fieldname : "";
94
+ }
95
+ function moduleDoctypes(dts, module) {
96
+ return dts.filter((dt) => (dt.module ?? "") === module);
97
+ }
98
+ var PROJECT_FIELD = "project";
99
+ function hasProjectField(dt) {
100
+ return (dt.fields ?? []).some((f) => f.fieldname === PROJECT_FIELD);
101
+ }
102
+ function titleOf(doc, dt) {
103
+ if (dt.titleField && typeof doc[dt.titleField] === "string" && doc[dt.titleField]) return String(doc[dt.titleField]);
104
+ return typeof doc.name === "string" && doc.name ? doc.name : "(untitled)";
105
+ }
106
+ function slugify(s) {
107
+ return String(s ?? "").normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
108
+ }
109
+ function isValidDoctypeName(name) {
110
+ return /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(name.trim());
111
+ }
112
+ var secToMs = (v) => {
113
+ const n = Number(v);
114
+ return Number.isFinite(n) && n > 0 ? n * 1e3 : void 0;
115
+ };
116
+ var numOrNull = (v) => {
117
+ const n = Number(v);
118
+ return Number.isFinite(n) ? n : null;
119
+ };
120
+ function coerceForDisplay(f, v) {
121
+ switch (f.fieldtype) {
122
+ case "Currency":
123
+ return { amount: numOrNull(v), currencyCode: "USD" };
124
+ case "Check":
125
+ return v === 1 || v === true || v === "1";
126
+ case "Datetime":
127
+ return typeof v === "string" ? v.replace(" ", "T") : v;
128
+ case "Password":
129
+ return v === REDACTED_MARKER ? "" : v;
130
+ // never surface the marker
131
+ default:
132
+ return v;
133
+ }
134
+ }
135
+ function toRecord(doc, dt) {
136
+ const rec = {
137
+ id: doc.name,
138
+ name: doc.name,
139
+ doctype: doc.doctype,
140
+ docstatus: doc.docstatus,
141
+ createdAt: secToMs(doc.createdAt),
142
+ updatedAt: secToMs(doc.updatedAt)
143
+ };
144
+ for (const f of dt.fields ?? []) {
145
+ if (!(f.fieldname in doc)) continue;
146
+ rec[f.fieldname] = coerceForDisplay(f, doc[f.fieldname]);
147
+ }
148
+ return rec;
149
+ }
150
+ function coerceForWrite(f, v) {
151
+ switch (f.fieldtype) {
152
+ case "Currency":
153
+ return v && typeof v === "object" && "amount" in v ? Number(v.amount) : Number(v);
154
+ case "Check":
155
+ return Boolean(v);
156
+ case "Int":
157
+ case "Float":
158
+ return Number(v);
159
+ case "Datetime":
160
+ if (v instanceof Date) return v.toISOString();
161
+ if (typeof v === "number" && Number.isFinite(v)) return new Date(v).toISOString();
162
+ return v;
163
+ case "Link":
164
+ if (v == null || v === "") return "";
165
+ if (typeof v === "object") return String(v.id ?? v.value ?? "");
166
+ return v;
167
+ default:
168
+ return v;
169
+ }
170
+ }
171
+ function enrichLinks(record, dt, optionsByField) {
172
+ const out = { ...record };
173
+ for (const f of dt.fields ?? []) {
174
+ if (f.fieldtype !== "Link") continue;
175
+ const raw = out[f.fieldname];
176
+ if (typeof raw === "string" && raw) {
177
+ const opt = (optionsByField[f.fieldname] ?? []).find((o) => o.value === raw);
178
+ out[f.fieldname] = { id: raw, label: opt?.label ?? raw };
179
+ }
180
+ }
181
+ return out;
182
+ }
183
+ function linkFields(dt) {
184
+ return (dt.fields ?? []).filter((f) => f.fieldtype === "Link");
185
+ }
186
+ function newDraft(dt) {
187
+ const out = {};
188
+ for (const f of dt.fields ?? []) {
189
+ if (f.default === void 0 || f.default === "") continue;
190
+ out[f.fieldname] = f.fieldtype === "Check" ? f.default === "1" || f.default === "true" : f.default;
191
+ }
192
+ return out;
193
+ }
194
+ function savePayload(draft, dt) {
195
+ const src = autonameSource(dt);
196
+ const out = {};
197
+ for (const f of dt.fields ?? []) {
198
+ if (f.readOnly) continue;
199
+ let v = draft[f.fieldname];
200
+ if (v === void 0) continue;
201
+ v = coerceForWrite(f, v);
202
+ if (f.fieldname === src && typeof v === "string") v = slugify(v);
203
+ out[f.fieldname] = v;
204
+ }
205
+ return out;
206
+ }
207
+
208
+ // src/framework/client.ts
209
+ var enc = encodeURIComponent;
210
+ var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
211
+ function rows(payload) {
212
+ if (Array.isArray(payload)) return payload.filter((x) => x && typeof x === "object");
213
+ const o = asRecord(payload);
214
+ for (const k of ["data", "items", "rows"]) {
215
+ if (Array.isArray(o[k])) return o[k].filter((x) => x && typeof x === "object");
216
+ }
217
+ return [];
218
+ }
219
+ var asDoc = (v) => asRecord(v);
220
+ var asDocType = (v) => {
221
+ const r = asRecord(v);
222
+ return { ...r, name: String(r.name ?? ""), fields: Array.isArray(r.fields) ? r.fields : [] };
223
+ };
224
+ function listQuery(q) {
225
+ if (!q) return "";
226
+ const p = new URLSearchParams();
227
+ if (q.filters && Object.keys(q.filters).length) p.set("filters", JSON.stringify(q.filters));
228
+ if (q.fields && q.fields.length) p.set("fields", q.fields.join(","));
229
+ if (q.orderBy) p.set("order_by", q.orderBy);
230
+ if (q.limit) p.set("limit", String(q.limit));
231
+ const s = p.toString();
232
+ return s ? `?${s}` : "";
233
+ }
234
+ function createFrameworkClient(t) {
235
+ return {
236
+ /** The DocType registry (schemas) — the collection definitions of every app lane. */
237
+ doctypes: {
238
+ list: () => t.get("doctypes").then((r) => rows(r).map(asDocType)),
239
+ get: (name) => t.get(`doctypes/${enc(name)}`).then(asDocType),
240
+ create: (dt) => t.post("doctypes", dt).then(asDocType),
241
+ update: (name, dt) => t.put(`doctypes/${enc(name)}`, dt).then(asDocType),
242
+ remove: (name) => t.del(`doctypes/${enc(name)}`)
243
+ },
244
+ /** Generic, metadata-driven document CRUD — the SAME calls for ANY doctype. */
245
+ records: {
246
+ list: (doctype, q) => t.get(`${enc(doctype)}${listQuery(q)}`).then((r) => rows(r).map(asDoc)),
247
+ get: (doctype, name) => t.get(`${enc(doctype)}/${enc(name)}`).then(asDoc),
248
+ create: (doctype, data) => t.post(enc(doctype), data).then(asDoc),
249
+ update: (doctype, name, data) => t.put(`${enc(doctype)}/${enc(name)}`, data).then(asDoc),
250
+ remove: (doctype, name) => t.del(`${enc(doctype)}/${enc(name)}`),
251
+ submit: (doctype, name) => t.post(`${enc(doctype)}/${enc(name)}/submit`).then(asDoc),
252
+ cancel: (doctype, name) => t.post(`${enc(doctype)}/${enc(name)}/cancel`).then(asDoc)
253
+ },
254
+ /** App-lane fixtures: list the registered lanes, inspect one, install into the org. */
255
+ modules: {
256
+ list: () => t.get("modules").then(
257
+ (r) => rows(r).map((m) => ({
258
+ module: String(m.module ?? ""),
259
+ doctypes: Array.isArray(m.doctypes) ? m.doctypes : []
260
+ }))
261
+ ),
262
+ get: (module) => t.get(`modules/${enc(module)}`).then((v) => {
263
+ const r = asRecord(v);
264
+ return {
265
+ module: String(r.module ?? module),
266
+ doctypes: Array.isArray(r.doctypes) ? r.doctypes : [],
267
+ installed: Array.isArray(r.installed) ? r.installed : []
268
+ };
269
+ }),
270
+ install: (module) => t.post(`modules/${enc(module)}/install`).then((v) => {
271
+ const r = asRecord(v);
272
+ return {
273
+ module: String(r.module ?? module),
274
+ created: Array.isArray(r.created) ? r.created : [],
275
+ existing: Array.isArray(r.existing) ? r.existing : []
276
+ };
277
+ })
278
+ },
279
+ /** Per-org role assignments (grant editors; the owner is seeded System Manager). */
280
+ roles: {
281
+ list: () => t.get("roles").then((r) => rows(r).map((x) => ({ user: String(x.user ?? ""), role: String(x.role ?? "") }))),
282
+ assign: (user, role) => t.post("roles", { user, role }).then(() => void 0),
283
+ revoke: (user, role) => t.del(`roles/${enc(user)}/${enc(role)}`)
284
+ }
285
+ };
286
+ }
287
+ var FrameworkHttpError = class extends Error {
288
+ constructor(status, message) {
289
+ super(message);
290
+ this.status = status;
291
+ this.name = "FrameworkHttpError";
292
+ }
293
+ status;
294
+ };
295
+ function fetchTransport(opts) {
296
+ const base = opts.baseUrl.replace(/\/+$/, "");
297
+ const doFetch = opts.fetchImpl ?? ((...a) => fetch(...a));
298
+ async function req(method, path, body) {
299
+ const tok = opts.token?.();
300
+ const res = await doFetch(`${base}/${path}`, {
301
+ method,
302
+ headers: {
303
+ Accept: "application/json",
304
+ ...body === void 0 ? null : { "Content-Type": "application/json" },
305
+ ...tok ? { Authorization: `Bearer ${tok}` } : null
306
+ },
307
+ ...body === void 0 ? null : { body: JSON.stringify(body) }
308
+ });
309
+ const text = await res.text();
310
+ let payload = null;
311
+ try {
312
+ payload = text ? JSON.parse(text) : null;
313
+ } catch {
314
+ payload = null;
315
+ }
316
+ if (!res.ok) {
317
+ const o = asRecord(payload);
318
+ const said = [o.error, o.msg, o.message].find((v) => typeof v === "string" && v.trim() !== "");
319
+ throw new FrameworkHttpError(res.status, typeof said === "string" ? said : `HTTP ${res.status}`);
320
+ }
321
+ return payload;
322
+ }
323
+ return {
324
+ get: (p) => req("GET", p),
325
+ post: (p, b) => req("POST", p, b),
326
+ put: (p, b) => req("PUT", p, b),
327
+ del: (p) => req("DELETE", p).then(() => void 0)
328
+ };
329
+ }
330
+
331
+ // src/framework/data.ts
332
+ async function loadLinkOptions(client, dt) {
333
+ const links = linkFields(dt);
334
+ const out = {};
335
+ await Promise.all(
336
+ links.map(async (f) => {
337
+ const target = f.options;
338
+ if (!target) return;
339
+ const [tdt, docs] = await Promise.all([
340
+ client.doctypes.get(target).catch(() => null),
341
+ client.records.list(target, { limit: 200 }).catch(() => [])
342
+ ]);
343
+ out[f.fieldname] = docs.map((d) => ({
344
+ value: String(d.name),
345
+ label: tdt ? titleOf(d, tdt) : String(d.name)
346
+ }));
347
+ })
348
+ );
349
+ return out;
350
+ }
351
+ function makeFieldOptions(linkOptions) {
352
+ return (field) => {
353
+ if (field.type === "relation") return linkOptions[field.name] ?? [];
354
+ if (field.type === "select" || field.type === "multiSelect") {
355
+ return field.metadata?.options;
356
+ }
357
+ return void 0;
358
+ };
359
+ }
360
+ function Loader({ label, size = 40 }) {
361
+ return /* @__PURE__ */ jsxRuntime.jsxs(gui.YStack, { flex: 1, minH: 280, items: "center", justify: "center", gap: "$3", children: [
362
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: "@keyframes hz-pulse{0%,100%{opacity:.45}50%{opacity:1}}.hz-pulse{animation:hz-pulse 1.5s ease-in-out infinite;display:inline-flex}@media (prefers-reduced-motion:reduce){.hz-pulse{animation:none}}" }),
363
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hz-pulse", children: /* @__PURE__ */ jsxRuntime.jsx(chunk2BM42AZR_cjs.HanzoMark, { size }) }),
364
+ label ? /* @__PURE__ */ jsxRuntime.jsx(gui.Text, { fontSize: "$3", color: "$color11", children: label }) : null
365
+ ] });
366
+ }
367
+ function CollectionsBrowser({ client, module, label, subtitle, onOpen, setupDescription, setupBullets, renderBuilder }) {
368
+ const [state, setState] = react.useState({ phase: "loading" });
369
+ const [busy, setBusy] = react.useState(false);
370
+ const [creating, setCreating] = react.useState(false);
371
+ const [actionError, setActionError] = react.useState(null);
372
+ const load = react.useCallback(
373
+ async (signal) => {
374
+ setState({ phase: "loading" });
375
+ try {
376
+ const [dts, mod] = await Promise.all([
377
+ client.doctypes.list(),
378
+ client.modules.get(module).catch(() => null)
379
+ // module may not be registered on older cloud
380
+ ]);
381
+ if (signal.cancelled) return;
382
+ setState({ phase: "ready", collections: moduleDoctypes(dts, module), registered: Boolean(mod && mod.doctypes.length) });
383
+ } catch (e) {
384
+ if (!signal.cancelled) setState({ phase: "error", error: chunkNBZDIMP3_cjs.classifyBackend(e) });
385
+ }
386
+ },
387
+ [client, module]
388
+ );
389
+ react.useEffect(() => {
390
+ const signal = { cancelled: false };
391
+ void load(signal);
392
+ return () => {
393
+ signal.cancelled = true;
394
+ };
395
+ }, [load]);
396
+ const reload = react.useCallback(() => void load({ cancelled: false }), [load]);
397
+ const install = react.useCallback(async () => {
398
+ setBusy(true);
399
+ setActionError(null);
400
+ try {
401
+ await client.modules.install(module);
402
+ reload();
403
+ } catch (e) {
404
+ setActionError(chunkNBZDIMP3_cjs.classifyBackend(e).message);
405
+ } finally {
406
+ setBusy(false);
407
+ }
408
+ }, [client, module, reload]);
409
+ if (state.phase === "loading") return /* @__PURE__ */ jsxRuntime.jsx(Loader, { label: `Loading ${label}\u2026` });
410
+ if (state.phase === "error") {
411
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
412
+ /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.PageHeader, { title: label, subtitle }),
413
+ /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.BackendStateCard, { state: state.error, onRetry: reload, hint: "framework \xB7 GET /v1/framework/doctypes" })
414
+ ] });
415
+ }
416
+ const { collections } = state;
417
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
418
+ /* @__PURE__ */ jsxRuntime.jsx(
419
+ chunkNBZDIMP3_cjs.PageHeader,
420
+ {
421
+ title: label,
422
+ subtitle,
423
+ actions: renderBuilder && collections.length && !creating ? /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.PrimaryButton, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Plus, { size: 15 }), onPress: () => {
424
+ setCreating(true);
425
+ setActionError(null);
426
+ }, children: "New collection" }) : void 0
427
+ }
428
+ ),
429
+ actionError ? /* @__PURE__ */ jsxRuntime.jsx(gui.Card, { borderWidth: 1, borderColor: "$red7", bg: "$red2", p: "$3", mb: "$3", maxWidth: 620, children: /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { gap: "$2", items: "center", children: [
430
+ /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.TriangleAlert, { size: 15 }),
431
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Text, { fontSize: "$3", color: "$red11", children: actionError })
432
+ ] }) }) : null,
433
+ creating && renderBuilder ? /* @__PURE__ */ jsxRuntime.jsx(gui.YStack, { mb: "$3", children: renderBuilder({
434
+ onSaved: (name) => {
435
+ setCreating(false);
436
+ onOpen(name);
437
+ },
438
+ onCancel: () => setCreating(false)
439
+ }) }) : null,
440
+ collections.length === 0 && !creating ? /* @__PURE__ */ jsxRuntime.jsx(
441
+ chunkNBZDIMP3_cjs.EmptyState,
442
+ {
443
+ icon: lucideIcons2.Boxes,
444
+ title: `Set up ${label}`,
445
+ description: setupDescription ?? `${label} is a set of content collections \u2014 Pages, Posts, Articles, Media, and Navigation \u2014 as DocTypes on the Hanzo Framework, per organization.`,
446
+ bullets: setupBullets ?? [
447
+ "Installs the default collections into your organization",
448
+ "Content is documents on the framework \u2014 versioned, permissioned, per-org",
449
+ "Add your own collections and fields any time"
450
+ ],
451
+ primary: state.registered ? { label: busy ? "Setting up\u2026" : `Set up ${label}`, onPress: install } : void 0,
452
+ secondary: renderBuilder ? { label: "New collection", onPress: () => setCreating(true) } : void 0
453
+ }
454
+ ) : !creating ? /* @__PURE__ */ jsxRuntime.jsx(gui.XStack, { gap: "$3", flexWrap: "wrap", children: collections.map((dt) => /* @__PURE__ */ jsxRuntime.jsxs(
455
+ gui.YStack,
456
+ {
457
+ onPress: () => onOpen(dt.name),
458
+ hoverStyle: { borderColor: "$color8" },
459
+ cursor: "pointer",
460
+ borderWidth: 1,
461
+ borderColor: "$borderColor",
462
+ rounded: "$4",
463
+ p: "$4",
464
+ gap: "$2",
465
+ width: 240,
466
+ children: [
467
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { gap: "$2", items: "center", children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Boxes, { size: 16 }),
469
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Text, { fontSize: "$4", fontWeight: "700", numberOfLines: 1, children: dt.name })
470
+ ] }),
471
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.Text, { fontSize: "$2", color: "$color10", children: [
472
+ dt.fields?.length ?? 0,
473
+ " field",
474
+ (dt.fields?.length ?? 0) === 1 ? "" : "s",
475
+ dt.isSubmittable ? " \xB7 submittable" : ""
476
+ ] })
477
+ ]
478
+ },
479
+ dt.name
480
+ )) }) : null
481
+ ] });
482
+ }
483
+ function DocTypeRecords({ client, doctype, onOpen, onCreate, project, title, renderMedia }) {
484
+ const [state, setState] = react.useState({ phase: "loading" });
485
+ const [mutationError, setMutationError] = react.useState(null);
486
+ const load = react.useCallback(
487
+ async (signal) => {
488
+ setState({ phase: "loading" });
489
+ try {
490
+ const dt = await client.doctypes.get(doctype);
491
+ const linkOptions = await loadLinkOptions(client, dt);
492
+ const filters = project && hasProjectField(dt) ? { [PROJECT_FIELD]: project } : void 0;
493
+ const docs = await client.records.list(doctype, { limit: 200, ...filters ? { filters } : {} });
494
+ if (signal.cancelled) return;
495
+ const records = docs.map((d) => enrichLinks(toRecord(d, dt), dt, linkOptions));
496
+ setState({ phase: "ready", dt, fields: docTypeToFields(dt), docs, records, linkOptions });
497
+ } catch (e) {
498
+ if (!signal.cancelled) setState({ phase: "error", error: chunkNBZDIMP3_cjs.classifyBackend(e) });
499
+ }
500
+ },
501
+ [client, doctype, project]
502
+ );
503
+ react.useEffect(() => {
504
+ const signal = { cancelled: false };
505
+ void load(signal);
506
+ return () => {
507
+ signal.cancelled = true;
508
+ };
509
+ }, [load]);
510
+ const reload = react.useCallback(() => void load({ cancelled: false }), [load]);
511
+ const fieldOptions = react.useMemo(
512
+ () => state.phase === "ready" ? makeFieldOptions(state.linkOptions) : void 0,
513
+ [state]
514
+ );
515
+ const onEditCommit = react.useCallback(
516
+ async (record, field, value) => {
517
+ if (state.phase !== "ready") return;
518
+ const name = typeof record.name === "string" ? record.name : "";
519
+ if (!name) return;
520
+ setMutationError(null);
521
+ try {
522
+ const body = savePayload({ ...record, [field.name]: value }, state.dt);
523
+ const saved = await client.records.update(doctype, name, body);
524
+ const next = enrichLinks(toRecord(saved, state.dt), state.dt, state.linkOptions);
525
+ setState((s) => s.phase === "ready" ? { ...s, records: s.records.map((r) => r.name === name ? next : r) } : s);
526
+ } catch (e) {
527
+ setMutationError(chunkNBZDIMP3_cjs.classifyBackend(e).message);
528
+ throw e;
529
+ }
530
+ },
531
+ [client, doctype, state]
532
+ );
533
+ if (state.phase === "error") {
534
+ return /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.BackendStateCard, { state: state.error, onRetry: reload, hint: `framework \xB7 GET /v1/framework/${doctype}` });
535
+ }
536
+ const refresh = /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.RefreshCw, { size: 15 }), onPress: reload, children: "Refresh" });
537
+ if (renderMedia && state.phase === "ready" && isMediaDoctype(state.dt)) {
538
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderMedia({ dt: state.dt, docs: state.docs, onOpen, onChanged: reload, toolbarExtra: refresh }) });
539
+ }
540
+ const ready = state.phase === "ready" ? state : void 0;
541
+ return /* @__PURE__ */ jsxRuntime.jsx(
542
+ data.RecordsView,
543
+ {
544
+ title,
545
+ fields: ready?.fields ?? [],
546
+ records: ready?.records ?? [],
547
+ loading: state.phase === "loading",
548
+ onOpen: (r) => onOpen(String(r.name ?? r.id ?? "")),
549
+ onCreate,
550
+ createLabel: "New record",
551
+ onEditCommit,
552
+ fieldOptions,
553
+ toolbarExtra: refresh,
554
+ empty: mutationError ?? `No records in ${doctype} yet.`
555
+ }
556
+ );
557
+ }
558
+ function DocTypeDetail({ client, doctype, name, project, onBack, onView }) {
559
+ const isNew = name === "new";
560
+ const [state, setState] = react.useState({ phase: "loading" });
561
+ const [editing, setEditing] = react.useState(isNew);
562
+ const [draft, setDraft] = react.useState({});
563
+ const [busy, setBusy] = react.useState(false);
564
+ const [saveError, setSaveError] = react.useState(null);
565
+ const [confirmingDelete, setConfirmingDelete] = react.useState(false);
566
+ const load = react.useCallback(
567
+ async (signal) => {
568
+ setState({ phase: "loading" });
569
+ try {
570
+ const dt2 = await client.doctypes.get(doctype);
571
+ const linkOptions = await loadLinkOptions(client, dt2);
572
+ const doc = isNew ? null : await client.records.get(doctype, name);
573
+ if (signal.cancelled) return;
574
+ const record2 = doc ? enrichLinks(toRecord(doc, dt2), dt2, linkOptions) : null;
575
+ setState({ phase: "ready", dt: dt2, record: record2, linkOptions });
576
+ const seed = newDraft(dt2);
577
+ if (record2 == null && project && hasProjectField(dt2)) seed[PROJECT_FIELD] = project;
578
+ setDraft(record2 ? { ...record2 } : seed);
579
+ setEditing(isNew);
580
+ setSaveError(null);
581
+ setConfirmingDelete(false);
582
+ } catch (e) {
583
+ if (!signal.cancelled) setState({ phase: "error", error: chunkNBZDIMP3_cjs.classifyBackend(e) });
584
+ }
585
+ },
586
+ [client, doctype, name, isNew, project]
587
+ );
588
+ react.useEffect(() => {
589
+ const signal = { cancelled: false };
590
+ void load(signal);
591
+ return () => {
592
+ signal.cancelled = true;
593
+ };
594
+ }, [load]);
595
+ const reload = react.useCallback(() => void load({ cancelled: false }), [load]);
596
+ const ready = state.phase === "ready" ? state : void 0;
597
+ const dt = ready?.dt;
598
+ const record = ready?.record ?? null;
599
+ const fields = react.useMemo(
600
+ () => dt ? docTypeToFields(dt, { editing: !isNew }) : [],
601
+ [dt, isNew]
602
+ );
603
+ const fieldOptions = react.useMemo(() => ready ? makeFieldOptions(ready.linkOptions) : void 0, [ready]);
604
+ const title = dt ? isNew ? `New ${doctype}` : titleOf(record ?? {}, dt) : doctype;
605
+ const onChange = react.useCallback((field, value) => {
606
+ setDraft((d) => ({ ...d, [field]: value }));
607
+ }, []);
608
+ const reflect = react.useCallback(
609
+ (saved) => {
610
+ if (!dt || !ready) return;
611
+ const next = enrichLinks(toRecord(saved, dt), dt, ready.linkOptions);
612
+ setState({ phase: "ready", dt, record: next, linkOptions: ready.linkOptions });
613
+ setDraft({ ...next });
614
+ },
615
+ [dt, ready]
616
+ );
617
+ const save = react.useCallback(async () => {
618
+ if (!dt) return;
619
+ setBusy(true);
620
+ setSaveError(null);
621
+ try {
622
+ const body = savePayload(draft, dt);
623
+ if (isNew) {
624
+ const created = await client.records.create(doctype, body);
625
+ if (created.name) onView(String(created.name));
626
+ else onBack();
627
+ return;
628
+ }
629
+ if (!record?.name) throw new Error("This record has no name to update.");
630
+ reflect(await client.records.update(doctype, String(record.name), body));
631
+ setEditing(false);
632
+ } catch (e) {
633
+ setSaveError(chunkNBZDIMP3_cjs.classifyBackend(e).message);
634
+ } finally {
635
+ setBusy(false);
636
+ }
637
+ }, [client, doctype, draft, dt, isNew, onBack, onView, record, reflect]);
638
+ const run = react.useCallback(
639
+ async (op) => {
640
+ setBusy(true);
641
+ setSaveError(null);
642
+ try {
643
+ const saved = await op();
644
+ if (saved) reflect(saved);
645
+ } catch (e) {
646
+ setSaveError(chunkNBZDIMP3_cjs.classifyBackend(e).message);
647
+ } finally {
648
+ setBusy(false);
649
+ }
650
+ },
651
+ [reflect]
652
+ );
653
+ const remove = react.useCallback(async () => {
654
+ if (!record?.name) return;
655
+ setBusy(true);
656
+ setSaveError(null);
657
+ try {
658
+ await client.records.remove(doctype, String(record.name));
659
+ onBack();
660
+ } catch (e) {
661
+ setSaveError(chunkNBZDIMP3_cjs.classifyBackend(e).message);
662
+ setBusy(false);
663
+ setConfirmingDelete(false);
664
+ }
665
+ }, [client, doctype, onBack, record]);
666
+ const backButton = /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.ArrowLeft, { size: 15 }), onPress: onBack, children: "Back" });
667
+ if (state.phase === "error") {
668
+ return /* @__PURE__ */ jsxRuntime.jsxs(gui.YStack, { gap: "$3", children: [
669
+ backButton,
670
+ /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.BackendStateCard, { state: state.error, onRetry: reload, hint: `framework \xB7 ${doctype}/${name}` })
671
+ ] });
672
+ }
673
+ if (!dt) return /* @__PURE__ */ jsxRuntime.jsx(gui.YStack, { gap: "$3", children: backButton });
674
+ const statusF = statusField(dt);
675
+ const status = record ? String(record[statusF] ?? "") : "";
676
+ const docstatus = record ? Number(record.docstatus ?? 0) : 0;
677
+ const canPublish = !editing && !isNew && statusF && docstatus === 0;
678
+ const canSubmit = !editing && !isNew && dt.isSubmittable && docstatus === 0;
679
+ const canCancel = !editing && !isNew && dt.isSubmittable && docstatus === 1;
680
+ return /* @__PURE__ */ jsxRuntime.jsxs(gui.YStack, { gap: "$3", children: [
681
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { items: "center", justify: "space-between", gap: "$4", flexWrap: "wrap", children: [
682
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { items: "center", gap: "$3", flex: 1, children: [
683
+ backButton,
684
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.YStack, { flex: 1, children: [
685
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Text, { fontSize: "$6", fontWeight: "800", numberOfLines: 1, children: title }),
686
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.Text, { fontSize: "$2", color: "$color10", children: [
687
+ doctype,
688
+ status ? ` \xB7 ${status}` : "",
689
+ docstatus === 1 ? " \xB7 submitted" : docstatus === 2 ? " \xB7 cancelled" : ""
690
+ ] })
691
+ ] })
692
+ ] }),
693
+ /* @__PURE__ */ jsxRuntime.jsx(gui.XStack, { items: "center", gap: "$2", flexWrap: "wrap", children: editing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
694
+ !isNew ? /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.X, { size: 15 }), disabled: busy, onPress: () => {
695
+ setDraft(record ? { ...record } : {});
696
+ setEditing(false);
697
+ setSaveError(null);
698
+ }, children: "Cancel" }) : null,
699
+ /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.PrimaryButton, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Save, { size: 15 }), disabled: busy, onPress: save, children: busy ? "Saving\u2026" : isNew ? "Create" : "Save" })
700
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
701
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", theme: "red", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Trash2, { size: 15 }), disabled: busy, onPress: () => setConfirmingDelete(true), children: "Delete" }),
702
+ canCancel ? /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Ban, { size: 15 }), disabled: busy, onPress: () => run(() => client.records.cancel(doctype, String(record.name))), children: "Cancel doc" }) : null,
703
+ canSubmit ? /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Send, { size: 15 }), disabled: busy, onPress: () => run(() => client.records.submit(doctype, String(record.name))), children: "Submit" }) : null,
704
+ canPublish ? status === "Published" ? /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.PenOff, { size: 15 }), disabled: busy, onPress: () => run(async () => client.records.update(doctype, String(record.name), savePayload({ ...record, [statusF]: "Draft" }, dt))), children: "Unpublish" }) : /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.PrimaryButton, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Globe, { size: 15 }), disabled: busy, onPress: () => run(async () => client.records.update(doctype, String(record.name), savePayload({ ...record, [statusF]: "Published" }, dt))), children: "Publish" }) : null,
705
+ /* @__PURE__ */ jsxRuntime.jsx(chunkNBZDIMP3_cjs.PrimaryButton, { size: "$2", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.Pencil, { size: 15 }), onPress: () => setEditing(true), children: "Edit" })
706
+ ] }) })
707
+ ] }),
708
+ confirmingDelete ? /* @__PURE__ */ jsxRuntime.jsxs(gui.Card, { borderWidth: 1, borderColor: "$red7", bg: "$red2", p: "$3", gap: "$2", maxWidth: 620, children: [
709
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.Text, { fontSize: "$3", fontWeight: "700", children: [
710
+ "Delete \u201C",
711
+ title,
712
+ "\u201D? This cannot be undone."
713
+ ] }),
714
+ /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { gap: "$2", children: [
715
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", theme: "red", disabled: busy, onPress: remove, children: busy ? "Deleting\u2026" : "Confirm delete" }),
716
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Button, { size: "$2", disabled: busy, onPress: () => setConfirmingDelete(false), children: "Cancel" })
717
+ ] })
718
+ ] }) : null,
719
+ saveError ? /* @__PURE__ */ jsxRuntime.jsx(gui.Card, { borderWidth: 1, borderColor: "$red7", bg: "$red2", p: "$3", maxWidth: 620, children: /* @__PURE__ */ jsxRuntime.jsxs(gui.XStack, { gap: "$2", items: "center", children: [
720
+ /* @__PURE__ */ jsxRuntime.jsx(lucideIcons2.TriangleAlert, { size: 15 }),
721
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Text, { fontSize: "$3", color: "$red11", children: saveError })
722
+ ] }) }) : null,
723
+ /* @__PURE__ */ jsxRuntime.jsx(gui.Card, { borderWidth: 1, borderColor: "$borderColor", p: "$4", maxWidth: 760, children: editing ? /* @__PURE__ */ jsxRuntime.jsx(data.RecordForm, { fields, values: draft, onChange, fieldOptions }) : record ? /* @__PURE__ */ jsxRuntime.jsx(data.RecordDetail, { title, fields, record, fieldOptions }) : null })
724
+ ] });
725
+ }
726
+
727
+ exports.CollectionsBrowser = CollectionsBrowser;
728
+ exports.DocTypeDetail = DocTypeDetail;
729
+ exports.DocTypeRecords = DocTypeRecords;
730
+ exports.FrameworkHttpError = FrameworkHttpError;
731
+ exports.Loader = Loader;
732
+ exports.PROJECT_FIELD = PROJECT_FIELD;
733
+ exports.REDACTED_MARKER = REDACTED_MARKER;
734
+ exports.autonameSource = autonameSource;
735
+ exports.createFrameworkClient = createFrameworkClient;
736
+ exports.docTypeToFields = docTypeToFields;
737
+ exports.enrichLinks = enrichLinks;
738
+ exports.fetchTransport = fetchTransport;
739
+ exports.hasProjectField = hasProjectField;
740
+ exports.humanize = humanize;
741
+ exports.isMediaDoctype = isMediaDoctype;
742
+ exports.isValidDoctypeName = isValidDoctypeName;
743
+ exports.linkFields = linkFields;
744
+ exports.listHiddenFields = listHiddenFields;
745
+ exports.listQuery = listQuery;
746
+ exports.loadLinkOptions = loadLinkOptions;
747
+ exports.makeFieldOptions = makeFieldOptions;
748
+ exports.mediaFileField = mediaFileField;
749
+ exports.moduleDoctypes = moduleDoctypes;
750
+ exports.newDraft = newDraft;
751
+ exports.savePayload = savePayload;
752
+ exports.slugify = slugify;
753
+ exports.statusField = statusField;
754
+ exports.titleOf = titleOf;
755
+ exports.toRecord = toRecord;
756
+ //# sourceMappingURL=framework.cjs.map
757
+ //# sourceMappingURL=framework.cjs.map