@realiizlabs/admin 0.1.1 → 0.7.1
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.
- package/dist/auth/index.cjs +182 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +178 -0
- package/dist/auth/index.d.ts +178 -0
- package/dist/auth/index.js +167 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth-ui/index.cjs +124 -0
- package/dist/auth-ui/index.cjs.map +1 -0
- package/dist/auth-ui/index.d.cts +40 -0
- package/dist/auth-ui/index.d.ts +40 -0
- package/dist/auth-ui/index.js +81 -0
- package/dist/auth-ui/index.js.map +1 -0
- package/dist/chunk-4OCBMOEP.js +42 -0
- package/dist/chunk-4OCBMOEP.js.map +1 -0
- package/dist/chunk-IS52OXZ2.js +252 -0
- package/dist/chunk-IS52OXZ2.js.map +1 -0
- package/dist/forms/index.cjs +256 -0
- package/dist/forms/index.cjs.map +1 -0
- package/dist/forms/index.d.cts +48 -0
- package/dist/forms/index.d.ts +48 -0
- package/dist/forms/index.js +3 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms-ui/index.cjs +584 -0
- package/dist/forms-ui/index.cjs.map +1 -0
- package/dist/forms-ui/index.d.cts +41 -0
- package/dist/forms-ui/index.d.ts +41 -0
- package/dist/forms-ui/index.js +336 -0
- package/dist/forms-ui/index.js.map +1 -0
- package/dist/git/index.cjs +283 -0
- package/dist/git/index.cjs.map +1 -0
- package/dist/git/index.d.cts +258 -0
- package/dist/git/index.d.ts +258 -0
- package/dist/git/index.js +269 -0
- package/dist/git/index.js.map +1 -0
- package/dist/types-DsSMscTh.d.cts +74 -0
- package/dist/types-DsSMscTh.d.ts +74 -0
- package/package.json +55 -3
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { C as ContentTypeEntry, F as FieldSpec } from '../types-DsSMscTh.cjs';
|
|
2
|
+
export { a as Constraints, b as FieldRole, I as InputKind, M as Messages, O as OptionsSource } from '../types-DsSMscTh.cjs';
|
|
3
|
+
import { ZodType } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* walkSchema — a zod object schema (+ optional registry entry) → FieldSpec[].
|
|
7
|
+
*
|
|
8
|
+
* Synchronous and pure. Reads the schema's shape and checks; never parses a
|
|
9
|
+
* value, never calls an options resolver, never imports zod at runtime.
|
|
10
|
+
*
|
|
11
|
+
* Roles from the registry override the schema-derived input kind: a field the
|
|
12
|
+
* entry names as an image is `role: "image"` even though zod sees a string.
|
|
13
|
+
* Hidden fields are still emitted so a renderer can carry their values through.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type Entry = Partial<Omit<ContentTypeEntry, "schema" | "filename" | "id" | "label" | "folder">>;
|
|
17
|
+
declare function walkSchema(schema: unknown, entry?: Entry): FieldSpec[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* defineContentType — validate a registry entry against the schema it describes.
|
|
21
|
+
*
|
|
22
|
+
* Validation is hand-rolled rather than a zod schema on purpose: this module
|
|
23
|
+
* must not import zod at runtime (the host's instance is the only one that
|
|
24
|
+
* should exist). The checks are small and the errors name the bad key.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
declare function defineContentType<S extends ZodType>(entry: ContentTypeEntry<S>): ContentTypeEntry<S>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Labels and placeholders.
|
|
31
|
+
*
|
|
32
|
+
* Precedence (decided 2026-09-08):
|
|
33
|
+
* label: registry.labels[name] → .meta({ label }) → .describe() → titlecase(name)
|
|
34
|
+
* placeholder: registry.placeholders[name] → .meta({ placeholder }) → undefined
|
|
35
|
+
*
|
|
36
|
+
* The registry tier exists for schemas that can't be edited yet (5degrees
|
|
37
|
+
* until its repo transfer). The meta tier is the intended home — one source of
|
|
38
|
+
* truth beside the validation rules. Titlecase is the floor, and it is naive on
|
|
39
|
+
* purpose: an acronym coming out as "Rsvp url" is the signal that a real label
|
|
40
|
+
* is missing.
|
|
41
|
+
*
|
|
42
|
+
* Meta is read from zod's global registry via the schema's own `meta()` /
|
|
43
|
+
* `description` accessors, so no zod value is imported here.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
declare function titlecase(name: string): string;
|
|
47
|
+
|
|
48
|
+
export { ContentTypeEntry, FieldSpec, defineContentType, titlecase, walkSchema };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { C as ContentTypeEntry, F as FieldSpec } from '../types-DsSMscTh.js';
|
|
2
|
+
export { a as Constraints, b as FieldRole, I as InputKind, M as Messages, O as OptionsSource } from '../types-DsSMscTh.js';
|
|
3
|
+
import { ZodType } from 'zod';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* walkSchema — a zod object schema (+ optional registry entry) → FieldSpec[].
|
|
7
|
+
*
|
|
8
|
+
* Synchronous and pure. Reads the schema's shape and checks; never parses a
|
|
9
|
+
* value, never calls an options resolver, never imports zod at runtime.
|
|
10
|
+
*
|
|
11
|
+
* Roles from the registry override the schema-derived input kind: a field the
|
|
12
|
+
* entry names as an image is `role: "image"` even though zod sees a string.
|
|
13
|
+
* Hidden fields are still emitted so a renderer can carry their values through.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type Entry = Partial<Omit<ContentTypeEntry, "schema" | "filename" | "id" | "label" | "folder">>;
|
|
17
|
+
declare function walkSchema(schema: unknown, entry?: Entry): FieldSpec[];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* defineContentType — validate a registry entry against the schema it describes.
|
|
21
|
+
*
|
|
22
|
+
* Validation is hand-rolled rather than a zod schema on purpose: this module
|
|
23
|
+
* must not import zod at runtime (the host's instance is the only one that
|
|
24
|
+
* should exist). The checks are small and the errors name the bad key.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
declare function defineContentType<S extends ZodType>(entry: ContentTypeEntry<S>): ContentTypeEntry<S>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Labels and placeholders.
|
|
31
|
+
*
|
|
32
|
+
* Precedence (decided 2026-09-08):
|
|
33
|
+
* label: registry.labels[name] → .meta({ label }) → .describe() → titlecase(name)
|
|
34
|
+
* placeholder: registry.placeholders[name] → .meta({ placeholder }) → undefined
|
|
35
|
+
*
|
|
36
|
+
* The registry tier exists for schemas that can't be edited yet (5degrees
|
|
37
|
+
* until its repo transfer). The meta tier is the intended home — one source of
|
|
38
|
+
* truth beside the validation rules. Titlecase is the floor, and it is naive on
|
|
39
|
+
* purpose: an acronym coming out as "Rsvp url" is the signal that a real label
|
|
40
|
+
* is missing.
|
|
41
|
+
*
|
|
42
|
+
* Meta is read from zod's global registry via the schema's own `meta()` /
|
|
43
|
+
* `description` accessors, so no zod value is imported here.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
declare function titlecase(name: string): string;
|
|
47
|
+
|
|
48
|
+
export { ContentTypeEntry, FieldSpec, defineContentType, titlecase, walkSchema };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/forms-ui/ContentForm.tsx
|
|
8
|
+
|
|
9
|
+
// src/forms/internals.ts
|
|
10
|
+
function asNode(schema, what = "schema") {
|
|
11
|
+
const n = schema;
|
|
12
|
+
if (!n || typeof n !== "object" || !n._zod?.def?.type) {
|
|
13
|
+
throw new TypeError(`walkSchema: ${what} is not a zod 4 schema`);
|
|
14
|
+
}
|
|
15
|
+
return n;
|
|
16
|
+
}
|
|
17
|
+
function checkMessage(def) {
|
|
18
|
+
const e = def.error;
|
|
19
|
+
if (!e) return void 0;
|
|
20
|
+
if (typeof e === "string") return e;
|
|
21
|
+
const out = e({});
|
|
22
|
+
if (typeof out === "string") return out;
|
|
23
|
+
return out?.message;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/forms/checks.ts
|
|
27
|
+
function readChecks(node) {
|
|
28
|
+
const constraints = {};
|
|
29
|
+
const messages = {};
|
|
30
|
+
for (const c of node._zod.def.checks ?? []) {
|
|
31
|
+
const d = c._zod.def;
|
|
32
|
+
const msg = checkMessage(d);
|
|
33
|
+
let key;
|
|
34
|
+
switch (d.check) {
|
|
35
|
+
case "min_length":
|
|
36
|
+
constraints.min = d.minimum;
|
|
37
|
+
key = "min_length";
|
|
38
|
+
break;
|
|
39
|
+
case "max_length":
|
|
40
|
+
constraints.max = d.maximum;
|
|
41
|
+
key = "max_length";
|
|
42
|
+
break;
|
|
43
|
+
case "length_equals":
|
|
44
|
+
constraints.exact = d.length;
|
|
45
|
+
key = "length_equals";
|
|
46
|
+
break;
|
|
47
|
+
case "greater_than":
|
|
48
|
+
constraints.min = d.inclusive ? d.value : (d.value ?? 0) + 1;
|
|
49
|
+
key = "greater_than";
|
|
50
|
+
break;
|
|
51
|
+
case "less_than":
|
|
52
|
+
constraints.max = d.inclusive ? d.value : (d.value ?? 0) - 1;
|
|
53
|
+
key = "less_than";
|
|
54
|
+
break;
|
|
55
|
+
case "string_format":
|
|
56
|
+
if (d.format === "regex" && d.pattern) {
|
|
57
|
+
constraints.pattern = d.pattern.source;
|
|
58
|
+
constraints.format = "regex";
|
|
59
|
+
key = "regex";
|
|
60
|
+
} else if (d.format === "url" || d.format === "email") {
|
|
61
|
+
constraints.format = d.format;
|
|
62
|
+
key = d.format;
|
|
63
|
+
} else {
|
|
64
|
+
key = d.format;
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case "number_format":
|
|
68
|
+
if (d.format === "safeint" || d.format === "int32") {
|
|
69
|
+
constraints.format = "int";
|
|
70
|
+
key = "int";
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case "custom":
|
|
74
|
+
key = "custom";
|
|
75
|
+
break;
|
|
76
|
+
default:
|
|
77
|
+
key = d.check;
|
|
78
|
+
}
|
|
79
|
+
if (key && msg) messages[key] = msg;
|
|
80
|
+
}
|
|
81
|
+
return { constraints, messages };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/forms/unwrap.ts
|
|
85
|
+
function unwrap(start) {
|
|
86
|
+
let node = start;
|
|
87
|
+
let required = true;
|
|
88
|
+
for (let i = 0; i < 16; i++) {
|
|
89
|
+
const def = node._zod.def;
|
|
90
|
+
if (def.type === "optional" || def.type === "nullable" || def.type === "default") {
|
|
91
|
+
required = false;
|
|
92
|
+
if (!def.innerType) break;
|
|
93
|
+
node = def.innerType;
|
|
94
|
+
} else if (def.type === "pipe") {
|
|
95
|
+
const next = def.out ?? def.in;
|
|
96
|
+
if (!next) break;
|
|
97
|
+
node = next;
|
|
98
|
+
} else {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { node, type: node._zod.def.type, required };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/forms/inputs.ts
|
|
106
|
+
var TEXTAREA_MIN_MAX = 120;
|
|
107
|
+
function inputFor(node) {
|
|
108
|
+
const def = node._zod.def;
|
|
109
|
+
const { constraints, messages } = readChecks(node);
|
|
110
|
+
switch (def.type) {
|
|
111
|
+
case "string": {
|
|
112
|
+
if (constraints.format === "url") return { input: "url", constraints, messages };
|
|
113
|
+
const long = (constraints.max ?? 0) > TEXTAREA_MIN_MAX;
|
|
114
|
+
return { input: long ? "textarea" : "text", constraints, messages };
|
|
115
|
+
}
|
|
116
|
+
case "date":
|
|
117
|
+
return { input: "date", constraints, messages };
|
|
118
|
+
case "number":
|
|
119
|
+
case "int":
|
|
120
|
+
return { input: "number", constraints, messages };
|
|
121
|
+
case "boolean":
|
|
122
|
+
return { input: "boolean", constraints, messages };
|
|
123
|
+
case "enum":
|
|
124
|
+
case "literal": {
|
|
125
|
+
const options = Object.values(def.entries ?? {}).map(String);
|
|
126
|
+
return { input: "select", constraints, messages, options };
|
|
127
|
+
}
|
|
128
|
+
case "array": {
|
|
129
|
+
if (def.element) {
|
|
130
|
+
const item = unwrap(def.element);
|
|
131
|
+
const inner = readChecks(item.node);
|
|
132
|
+
constraints.item = inner;
|
|
133
|
+
}
|
|
134
|
+
return { input: "tags", constraints, messages };
|
|
135
|
+
}
|
|
136
|
+
default:
|
|
137
|
+
return { input: "text", constraints, messages };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/forms/labels.ts
|
|
142
|
+
function readMeta(schema) {
|
|
143
|
+
const s = schema;
|
|
144
|
+
const raw = typeof s.meta === "function" ? s.meta() ?? {} : {};
|
|
145
|
+
const pick = (k) => typeof raw[k] === "string" ? raw[k] : void 0;
|
|
146
|
+
return {
|
|
147
|
+
label: pick("label"),
|
|
148
|
+
placeholder: pick("placeholder"),
|
|
149
|
+
description: pick("description") ?? (typeof s.description === "string" ? s.description : void 0)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function titlecase(name) {
|
|
153
|
+
const words = name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").toLowerCase().trim();
|
|
154
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
155
|
+
}
|
|
156
|
+
function labelFor(name, schema, entry) {
|
|
157
|
+
const fromRegistry = entry?.labels?.[name];
|
|
158
|
+
if (fromRegistry) return fromRegistry;
|
|
159
|
+
const meta = readMeta(schema);
|
|
160
|
+
return meta.label ?? meta.description ?? titlecase(name);
|
|
161
|
+
}
|
|
162
|
+
function placeholderFor(name, schema, entry) {
|
|
163
|
+
return entry?.placeholders?.[name] ?? readMeta(schema).placeholder;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/forms/walk.ts
|
|
167
|
+
function walkSchema(schema, entry = {}) {
|
|
168
|
+
const root = asNode(schema);
|
|
169
|
+
const shape = shapeOf(root);
|
|
170
|
+
const names = Object.keys(shape);
|
|
171
|
+
const specs = names.map((name) => {
|
|
172
|
+
const raw = shape[name];
|
|
173
|
+
const { node, required } = unwrap(raw);
|
|
174
|
+
const info = inputFor(node);
|
|
175
|
+
const role = roleFor(name, entry);
|
|
176
|
+
return {
|
|
177
|
+
name,
|
|
178
|
+
label: labelFor(name, raw, entry),
|
|
179
|
+
input: role === "image" ? "image" : info.input,
|
|
180
|
+
required,
|
|
181
|
+
constraints: info.constraints,
|
|
182
|
+
messages: info.messages,
|
|
183
|
+
placeholder: placeholderFor(name, raw, entry),
|
|
184
|
+
order: 0,
|
|
185
|
+
role,
|
|
186
|
+
options: entry.options?.[name] ?? info.options
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
return applyOrder(specs, entry.order ?? []);
|
|
190
|
+
}
|
|
191
|
+
function shapeOf(root) {
|
|
192
|
+
let node = root;
|
|
193
|
+
for (let i = 0; i < 8 && !node._zod.def.shape; i++) {
|
|
194
|
+
const def = node._zod.def;
|
|
195
|
+
const next = def.innerType ?? def.in;
|
|
196
|
+
if (!next) break;
|
|
197
|
+
node = next;
|
|
198
|
+
}
|
|
199
|
+
const shape = node._zod.def.shape;
|
|
200
|
+
if (!shape) throw new TypeError(`walkSchema: expected an object schema, got "${root._zod.def.type}"`);
|
|
201
|
+
return shape;
|
|
202
|
+
}
|
|
203
|
+
function roleFor(name, entry) {
|
|
204
|
+
if (entry.hidden?.includes(name)) return "hidden";
|
|
205
|
+
if (entry.body === name) return "body";
|
|
206
|
+
if (entry.images?.includes(name)) return "image";
|
|
207
|
+
return "field";
|
|
208
|
+
}
|
|
209
|
+
function applyOrder(specs, order) {
|
|
210
|
+
const rank = new Map(order.map((n, i) => [n, i]));
|
|
211
|
+
const named = specs.filter((s) => rank.has(s.name)).sort((a, b) => rank.get(a.name) - rank.get(b.name));
|
|
212
|
+
const rest = specs.filter((s) => !rank.has(s.name));
|
|
213
|
+
return [...named, ...rest].map((s, i) => ({ ...s, order: i }));
|
|
214
|
+
}
|
|
215
|
+
function Control(p) {
|
|
216
|
+
const cls = ["realiiz-form__control", p.invalid && "realiiz-form__control--invalid"].filter(Boolean).join(" ");
|
|
217
|
+
const common = {
|
|
218
|
+
id: p.id,
|
|
219
|
+
name: p.spec.name,
|
|
220
|
+
"aria-invalid": p.invalid || void 0,
|
|
221
|
+
"aria-describedby": p.invalid ? `${p.id}-error` : void 0,
|
|
222
|
+
onBlur: p.onBlur
|
|
223
|
+
};
|
|
224
|
+
const str = typeof p.value === "string" ? p.value : "";
|
|
225
|
+
if (p.options === "loading") {
|
|
226
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cls, "aria-busy": "true", style: { color: "var(--_muted)" }, children: "Loading options\u2026" });
|
|
227
|
+
}
|
|
228
|
+
if (p.options || p.spec.input === "select") {
|
|
229
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("select", { ...common, className: cls, value: str, onChange: (e) => p.onChange(e.target.value), children: [
|
|
230
|
+
!p.spec.required && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "\u2014" }),
|
|
231
|
+
(p.options ?? []).map((o) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: o, children: o }, o))
|
|
232
|
+
] });
|
|
233
|
+
}
|
|
234
|
+
switch (p.spec.input) {
|
|
235
|
+
case "textarea":
|
|
236
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
237
|
+
"textarea",
|
|
238
|
+
{
|
|
239
|
+
...common,
|
|
240
|
+
className: `${cls} realiiz-form__control--textarea`,
|
|
241
|
+
value: str,
|
|
242
|
+
placeholder: p.spec.placeholder,
|
|
243
|
+
onChange: (e) => p.onChange(e.target.value)
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
case "boolean":
|
|
247
|
+
return /* @__PURE__ */ jsxRuntime.jsx("input", { ...common, type: "checkbox", checked: p.value === true, onChange: (e) => p.onChange(e.target.checked) });
|
|
248
|
+
case "image":
|
|
249
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
250
|
+
"input",
|
|
251
|
+
{
|
|
252
|
+
...common,
|
|
253
|
+
type: "file",
|
|
254
|
+
accept: "image/*",
|
|
255
|
+
className: cls,
|
|
256
|
+
onChange: (e) => p.onChange(e.target.files?.[0] ?? null)
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
case "date":
|
|
260
|
+
return /* @__PURE__ */ jsxRuntime.jsx("input", { ...common, type: "date", className: cls, value: str, onChange: (e) => p.onChange(e.target.value) });
|
|
261
|
+
case "number":
|
|
262
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
263
|
+
"input",
|
|
264
|
+
{
|
|
265
|
+
...common,
|
|
266
|
+
type: "number",
|
|
267
|
+
inputMode: "numeric",
|
|
268
|
+
className: cls,
|
|
269
|
+
value: str,
|
|
270
|
+
min: p.spec.constraints.min,
|
|
271
|
+
max: p.spec.constraints.max,
|
|
272
|
+
step: p.spec.constraints.format === "int" ? 1 : void 0,
|
|
273
|
+
placeholder: p.spec.placeholder,
|
|
274
|
+
onChange: (e) => p.onChange(e.target.value)
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
case "url":
|
|
278
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
279
|
+
"input",
|
|
280
|
+
{
|
|
281
|
+
...common,
|
|
282
|
+
type: "url",
|
|
283
|
+
className: cls,
|
|
284
|
+
value: str,
|
|
285
|
+
placeholder: p.spec.placeholder ?? "https://",
|
|
286
|
+
onChange: (e) => p.onChange(e.target.value)
|
|
287
|
+
}
|
|
288
|
+
);
|
|
289
|
+
case "tags":
|
|
290
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
291
|
+
"input",
|
|
292
|
+
{
|
|
293
|
+
...common,
|
|
294
|
+
type: "text",
|
|
295
|
+
className: cls,
|
|
296
|
+
value: str,
|
|
297
|
+
placeholder: p.spec.placeholder ?? "one, two, three",
|
|
298
|
+
onChange: (e) => p.onChange(e.target.value)
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
default:
|
|
302
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
303
|
+
"input",
|
|
304
|
+
{
|
|
305
|
+
...common,
|
|
306
|
+
type: "text",
|
|
307
|
+
className: cls,
|
|
308
|
+
value: str,
|
|
309
|
+
placeholder: p.spec.placeholder,
|
|
310
|
+
maxLength: p.spec.constraints.max,
|
|
311
|
+
onChange: (e) => p.onChange(e.target.value)
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function Field(p) {
|
|
317
|
+
const id = `${p.formId}-${p.spec.name}`;
|
|
318
|
+
const invalid = p.issues.length > 0;
|
|
319
|
+
const max = p.spec.constraints.max;
|
|
320
|
+
const showCount = typeof max === "number" && (p.spec.input === "text" || p.spec.input === "textarea");
|
|
321
|
+
const len = typeof p.value === "string" ? p.value.length : 0;
|
|
322
|
+
const isCheck = p.spec.input === "boolean";
|
|
323
|
+
const control = /* @__PURE__ */ jsxRuntime.jsx(Control, { id, spec: p.spec, value: p.value, invalid, options: p.options, onChange: p.onChange, onBlur: p.onBlur });
|
|
324
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `realiiz-form__field realiiz-form__field--${p.spec.input}`, "data-field": p.spec.name, children: [
|
|
325
|
+
isCheck ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "realiiz-form__check", htmlFor: id, children: [
|
|
326
|
+
control,
|
|
327
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
328
|
+
p.spec.label,
|
|
329
|
+
p.spec.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
|
|
330
|
+
] })
|
|
331
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
332
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "realiiz-form__label", htmlFor: id, children: [
|
|
333
|
+
p.spec.label,
|
|
334
|
+
p.spec.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
|
|
335
|
+
] }),
|
|
336
|
+
control
|
|
337
|
+
] }),
|
|
338
|
+
p.spec.input === "image" && p.current && !(p.value instanceof File) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__current", children: [
|
|
339
|
+
"Current: ",
|
|
340
|
+
p.current
|
|
341
|
+
] }),
|
|
342
|
+
showCount && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__helper", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "realiiz-form__count", children: [
|
|
343
|
+
len,
|
|
344
|
+
" / ",
|
|
345
|
+
max
|
|
346
|
+
] }) }),
|
|
347
|
+
invalid && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "realiiz-form__error", id: `${id}-error`, children: p.issues.map((i, n) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: i.message }, n)) })
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/forms-ui/issues.ts
|
|
352
|
+
function mapIssues(issues, specs) {
|
|
353
|
+
const byName = new Map(specs.map((s) => [s.name, s]));
|
|
354
|
+
return issues.map((issue) => {
|
|
355
|
+
const field = typeof issue.path[0] === "string" ? issue.path[0] : null;
|
|
356
|
+
const spec = field ? byName.get(field) : void 0;
|
|
357
|
+
const key = keyFor(issue, spec);
|
|
358
|
+
const message = spec?.messages[key] ?? (issue.code === "unrecognized_keys" ? unknownKeysMessage(issue) : void 0) ?? issue.message;
|
|
359
|
+
return { field, key, message };
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
function keyFor(issue, spec) {
|
|
363
|
+
const numeric = issue.origin === "number" || issue.origin === "int";
|
|
364
|
+
switch (issue.code) {
|
|
365
|
+
case "too_small":
|
|
366
|
+
if (spec?.constraints.exact !== void 0) return "length_equals";
|
|
367
|
+
return numeric ? "greater_than" : "min_length";
|
|
368
|
+
case "too_big":
|
|
369
|
+
if (spec?.constraints.exact !== void 0) return "length_equals";
|
|
370
|
+
return numeric ? "less_than" : "max_length";
|
|
371
|
+
case "invalid_format":
|
|
372
|
+
return issue.format ?? "format";
|
|
373
|
+
case "invalid_type":
|
|
374
|
+
return issue.format === "safeint" || issue.format === "int32" ? "int" : "invalid_type";
|
|
375
|
+
default:
|
|
376
|
+
return issue.code;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function unknownKeysMessage(issue) {
|
|
380
|
+
const keys = issue.keys ?? [];
|
|
381
|
+
return `This file has ${keys.length === 1 ? "a field" : "fields"} the site does not recognise: ${keys.join(", ")}. It was probably edited by hand \u2014 remove or rename ${keys.length === 1 ? "it" : "them"} to continue.`;
|
|
382
|
+
}
|
|
383
|
+
function groupIssues(list) {
|
|
384
|
+
const byField = /* @__PURE__ */ new Map();
|
|
385
|
+
const formLevel = [];
|
|
386
|
+
for (const i of list) {
|
|
387
|
+
if (i.field === null) formLevel.push(i);
|
|
388
|
+
else byField.set(i.field, [...byField.get(i.field) ?? [], i]);
|
|
389
|
+
}
|
|
390
|
+
return { byField, formLevel };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/forms-ui/styles.ts
|
|
394
|
+
var FORM_CSS = `
|
|
395
|
+
.realiiz-form{
|
|
396
|
+
--_fg:var(--realiiz-form-fg,#111);
|
|
397
|
+
--_muted:var(--realiiz-form-muted,#666);
|
|
398
|
+
--_border:var(--realiiz-form-border,#cfcfcf);
|
|
399
|
+
--_accent:var(--realiiz-form-accent,#111);
|
|
400
|
+
--_accent-fg:var(--realiiz-form-accent-fg,#fff);
|
|
401
|
+
--_danger:var(--realiiz-form-danger,#b3261e);
|
|
402
|
+
--_radius:var(--realiiz-form-radius,6px);
|
|
403
|
+
--_font:var(--realiiz-form-font,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif);
|
|
404
|
+
--_ring:var(--realiiz-form-focus-ring,0 0 0 3px rgba(0,0,0,.15));
|
|
405
|
+
box-sizing:border-box;max-width:100%;color:var(--_fg);font-family:var(--_font);font-size:15px;line-height:1.4;
|
|
406
|
+
}
|
|
407
|
+
.realiiz-form *,.realiiz-form *::before,.realiiz-form *::after{box-sizing:inherit}
|
|
408
|
+
.realiiz-form fieldset{border:0;margin:0 0 20px;padding:0;min-width:0}
|
|
409
|
+
.realiiz-form legend{font-weight:600;font-size:13px;letter-spacing:.02em;text-transform:uppercase;color:var(--_muted);margin-bottom:12px;padding:0}
|
|
410
|
+
.realiiz-form__field{display:block;margin:0 0 16px}
|
|
411
|
+
.realiiz-form__label{display:block;font-weight:500;margin-bottom:6px}
|
|
412
|
+
.realiiz-form__required{color:var(--_danger);margin-left:3px}
|
|
413
|
+
.realiiz-form__control{display:block;width:100%;max-width:100%;font:inherit;color:inherit;background:#fff;border:1px solid var(--_border);border-radius:var(--_radius);padding:9px 11px;min-height:40px}
|
|
414
|
+
.realiiz-form__control:focus{outline:none;border-color:var(--_accent);box-shadow:var(--_ring)}
|
|
415
|
+
.realiiz-form__control--textarea{min-height:110px;resize:vertical}
|
|
416
|
+
.realiiz-form__control--body{min-height:280px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px}
|
|
417
|
+
.realiiz-form__control--invalid{border-color:var(--_danger)}
|
|
418
|
+
.realiiz-form__check{display:flex;align-items:center;gap:8px;font-weight:500}
|
|
419
|
+
.realiiz-form__check input{width:18px;height:18px;margin:0}
|
|
420
|
+
.realiiz-form__helper{display:flex;justify-content:space-between;gap:12px;font-size:12px;color:var(--_muted);margin-top:4px;min-height:1em}
|
|
421
|
+
.realiiz-form__count{margin-left:auto;font-variant-numeric:tabular-nums}
|
|
422
|
+
.realiiz-form__error{color:var(--_danger);font-size:13px;margin-top:4px}
|
|
423
|
+
.realiiz-form__current{font-size:12px;color:var(--_muted);margin-top:4px;word-break:break-all}
|
|
424
|
+
.realiiz-form__alert{border:1px solid var(--_danger);border-radius:var(--_radius);padding:12px 14px;margin:0 0 20px;color:var(--_danger)}
|
|
425
|
+
.realiiz-form__alert ul{margin:6px 0 0;padding-left:18px}
|
|
426
|
+
.realiiz-form__actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;margin-top:8px}
|
|
427
|
+
.realiiz-form__button{font:inherit;font-weight:600;border-radius:var(--_radius);padding:10px 16px;border:1px solid var(--_border);background:#fff;color:var(--_fg);cursor:pointer;min-height:40px}
|
|
428
|
+
.realiiz-form__button--primary{background:var(--_accent);color:var(--_accent-fg);border-color:var(--_accent)}
|
|
429
|
+
.realiiz-form__button:disabled{opacity:.6;cursor:default}
|
|
430
|
+
`;
|
|
431
|
+
|
|
432
|
+
// src/forms-ui/values.ts
|
|
433
|
+
var PENDING_UPLOAD_PREFIX = "pending-upload/";
|
|
434
|
+
function toControl(spec, raw) {
|
|
435
|
+
if (raw === void 0 || raw === null) return spec.input === "boolean" ? false : "";
|
|
436
|
+
switch (spec.input) {
|
|
437
|
+
case "boolean":
|
|
438
|
+
return Boolean(raw);
|
|
439
|
+
case "date":
|
|
440
|
+
return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);
|
|
441
|
+
case "tags":
|
|
442
|
+
return Array.isArray(raw) ? raw.join(", ") : String(raw);
|
|
443
|
+
default:
|
|
444
|
+
return String(raw);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function toFrontmatter(specs, state, hiddenValues) {
|
|
448
|
+
const frontmatter = { ...hiddenValues };
|
|
449
|
+
const files = {};
|
|
450
|
+
for (const spec of specs) {
|
|
451
|
+
if (spec.role === "hidden" || spec.role === "body") continue;
|
|
452
|
+
const v = state[spec.name];
|
|
453
|
+
if (spec.input === "boolean") {
|
|
454
|
+
if (v === true) frontmatter[spec.name] = true;
|
|
455
|
+
else if (spec.required) frontmatter[spec.name] = false;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (v instanceof File) {
|
|
459
|
+
files[spec.name] = v;
|
|
460
|
+
frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
464
|
+
if (s === "") {
|
|
465
|
+
if (spec.required) frontmatter[spec.name] = spec.input === "tags" ? [] : "";
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
switch (spec.input) {
|
|
469
|
+
case "number": {
|
|
470
|
+
const n = Number(s);
|
|
471
|
+
frontmatter[spec.name] = Number.isNaN(n) ? s : n;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
case "tags":
|
|
475
|
+
frontmatter[spec.name] = s.split(",").map((t) => t.trim()).filter(Boolean);
|
|
476
|
+
break;
|
|
477
|
+
default:
|
|
478
|
+
frontmatter[spec.name] = s;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return { frontmatter, files };
|
|
482
|
+
}
|
|
483
|
+
function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content" }) {
|
|
484
|
+
const formId = react.useId();
|
|
485
|
+
const specs = react.useMemo(() => walkSchema(entry.schema, entry), [entry]);
|
|
486
|
+
const visible = react.useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
|
|
487
|
+
const bodySpec = specs.find((s) => s.role === "body");
|
|
488
|
+
const [state, setState] = react.useState(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));
|
|
489
|
+
const [body, setBody] = react.useState(initialBody);
|
|
490
|
+
const [issues, setIssues] = react.useState([]);
|
|
491
|
+
const [attempted, setAttempted] = react.useState(false);
|
|
492
|
+
const [submitting, setSubmitting] = react.useState(false);
|
|
493
|
+
const [options, setOptions] = react.useState({});
|
|
494
|
+
const hiddenValues = react.useMemo(() => {
|
|
495
|
+
const shown = new Set(visible.map((s) => s.name));
|
|
496
|
+
if (bodySpec) shown.add(bodySpec.name);
|
|
497
|
+
return Object.fromEntries(Object.entries(initialValues).filter(([k]) => !shown.has(k)));
|
|
498
|
+
}, [initialValues, visible, bodySpec]);
|
|
499
|
+
react.useEffect(() => {
|
|
500
|
+
let live = true;
|
|
501
|
+
for (const s of visible) {
|
|
502
|
+
if (typeof s.options !== "function") continue;
|
|
503
|
+
setOptions((o) => ({ ...o, [s.name]: "loading" }));
|
|
504
|
+
Promise.resolve(s.options()).then((list) => {
|
|
505
|
+
if (live) setOptions((o) => ({ ...o, [s.name]: list }));
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return () => {
|
|
509
|
+
live = false;
|
|
510
|
+
};
|
|
511
|
+
}, [visible]);
|
|
512
|
+
const validate = react.useCallback(() => {
|
|
513
|
+
const { frontmatter } = toFrontmatter(specs, state, hiddenValues);
|
|
514
|
+
const result = entry.schema.safeParse(frontmatter);
|
|
515
|
+
setIssues(result.success ? [] : mapIssues(result.error.issues, specs));
|
|
516
|
+
return result;
|
|
517
|
+
}, [entry.schema, specs, state, hiddenValues]);
|
|
518
|
+
const handleSubmit = async (e) => {
|
|
519
|
+
e.preventDefault();
|
|
520
|
+
if (submitting) return;
|
|
521
|
+
setAttempted(true);
|
|
522
|
+
const result = validate();
|
|
523
|
+
if (!result.success) return;
|
|
524
|
+
const { files } = toFrontmatter(specs, state, hiddenValues);
|
|
525
|
+
setSubmitting(true);
|
|
526
|
+
try {
|
|
527
|
+
await onSubmit({ frontmatter: result.data, body, files });
|
|
528
|
+
} finally {
|
|
529
|
+
setSubmitting(false);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
const { byField, formLevel } = groupIssues(issues);
|
|
533
|
+
const optionsFor = (s) => options[s.name] ?? (Array.isArray(s.options) ? s.options : void 0);
|
|
534
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("form", { className: ["realiiz-form", className].filter(Boolean).join(" "), onSubmit: handleSubmit, noValidate: true, "data-realiiz-content-form": entry.id, children: [
|
|
535
|
+
/* @__PURE__ */ jsxRuntime.jsx("style", { children: FORM_CSS }),
|
|
536
|
+
formLevel.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__alert", role: "alert", children: [
|
|
537
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: "This can't be saved yet." }),
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { children: formLevel.map((i, n) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: i.message }, n)) })
|
|
539
|
+
] }),
|
|
540
|
+
/* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
541
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: entry.label }),
|
|
542
|
+
visible.map((s) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
543
|
+
Field,
|
|
544
|
+
{
|
|
545
|
+
formId,
|
|
546
|
+
spec: s,
|
|
547
|
+
value: state[s.name],
|
|
548
|
+
issues: byField.get(s.name) ?? [],
|
|
549
|
+
current: typeof initialValues[s.name] === "string" ? initialValues[s.name] : void 0,
|
|
550
|
+
options: optionsFor(s),
|
|
551
|
+
onChange: (v) => setState((st) => ({ ...st, [s.name]: v })),
|
|
552
|
+
onBlur: () => {
|
|
553
|
+
if (attempted) validate();
|
|
554
|
+
}
|
|
555
|
+
},
|
|
556
|
+
s.name
|
|
557
|
+
))
|
|
558
|
+
] }),
|
|
559
|
+
(showBody || bodySpec) && /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: bodySpec?.label ?? bodyLabel }),
|
|
561
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
|
|
562
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
563
|
+
"textarea",
|
|
564
|
+
{
|
|
565
|
+
id: `${formId}-body`,
|
|
566
|
+
name: "body",
|
|
567
|
+
className: "realiiz-form__control realiiz-form__control--body",
|
|
568
|
+
value: body,
|
|
569
|
+
placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
|
|
570
|
+
onChange: (e) => setBody(e.target.value)
|
|
571
|
+
}
|
|
572
|
+
)
|
|
573
|
+
] }),
|
|
574
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "realiiz-form__actions", children: [
|
|
575
|
+
onCancel && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "realiiz-form__button", onClick: onCancel, children: "Cancel" }),
|
|
576
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "realiiz-form__button realiiz-form__button--primary", disabled: submitting, children: submitting ? `${submitLabel}\u2026` : submitLabel })
|
|
577
|
+
] })
|
|
578
|
+
] });
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
exports.ContentForm = ContentForm;
|
|
582
|
+
exports.PENDING_UPLOAD_PREFIX = PENDING_UPLOAD_PREFIX;
|
|
583
|
+
//# sourceMappingURL=index.cjs.map
|
|
584
|
+
//# sourceMappingURL=index.cjs.map
|