@realiizlabs/admin 0.7.2 → 0.8.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.
@@ -1,9 +1,53 @@
1
1
  "use client";
2
- import { walkSchema } from '../chunk-IS52OXZ2.js';
3
- import { FORM_CSS } from '../chunk-4OCBMOEP.js';
4
- import { useId, useMemo, useState, useEffect, useCallback } from 'react';
2
+ import { walkSchema } from '../chunk-J6FI73PF.js';
3
+ import { FORM_CSS } from '../chunk-CE2DRQBD.js';
4
+ import { useState, useRef, useCallback, useEffect, useId, useMemo } from 'react';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
6
 
7
+ function ChipsInput({ id, name, value, onChange, onBlur, placeholder, invalid, max }) {
8
+ const [draft, setDraft] = useState("");
9
+ const commit = () => {
10
+ const parts = draft.split(",").map((s) => s.trim()).filter(Boolean);
11
+ if (parts.length) onChange([...value, ...parts.filter((p) => !value.includes(p))]);
12
+ setDraft("");
13
+ };
14
+ const onKey = (e) => {
15
+ if (e.key === "Enter" || e.key === ",") {
16
+ e.preventDefault();
17
+ commit();
18
+ } else if (e.key === "Backspace" && draft === "" && value.length) {
19
+ onChange(value.slice(0, -1));
20
+ }
21
+ };
22
+ return /* @__PURE__ */ jsxs("div", { className: ["realiiz-chips", invalid && "realiiz-form__control--invalid"].filter(Boolean).join(" "), "data-name": name, children: [
23
+ value.map((tag) => /* @__PURE__ */ jsxs("span", { className: "realiiz-chip", children: [
24
+ tag,
25
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": `Remove ${tag}`, onClick: () => onChange(value.filter((t) => t !== tag)), children: "\xD7" })
26
+ ] }, tag)),
27
+ /* @__PURE__ */ jsx(
28
+ "input",
29
+ {
30
+ id,
31
+ type: "text",
32
+ className: "realiiz-chips__input",
33
+ value: draft,
34
+ placeholder: value.length ? "" : placeholder,
35
+ onChange: (e) => setDraft(e.target.value),
36
+ onKeyDown: onKey,
37
+ onBlur: () => {
38
+ commit();
39
+ onBlur();
40
+ },
41
+ "aria-invalid": invalid || void 0
42
+ }
43
+ ),
44
+ typeof max === "number" && /* @__PURE__ */ jsxs("span", { className: "realiiz-chips__count", children: [
45
+ value.length,
46
+ " of ",
47
+ max
48
+ ] })
49
+ ] });
50
+ }
7
51
  function Control(p) {
8
52
  const cls = ["realiiz-form__control", p.invalid && "realiiz-form__control--invalid"].filter(Boolean).join(" ");
9
53
  const common = {
@@ -78,18 +122,6 @@ function Control(p) {
78
122
  onChange: (e) => p.onChange(e.target.value)
79
123
  }
80
124
  );
81
- case "tags":
82
- return /* @__PURE__ */ jsx(
83
- "input",
84
- {
85
- ...common,
86
- type: "text",
87
- className: cls,
88
- value: str,
89
- placeholder: p.spec.placeholder ?? "one, two, three",
90
- onChange: (e) => p.onChange(e.target.value)
91
- }
92
- );
93
125
  default:
94
126
  return /* @__PURE__ */ jsx(
95
127
  "input",
@@ -105,24 +137,61 @@ function Control(p) {
105
137
  );
106
138
  }
107
139
  }
140
+ var defaultLabel = (label, help) => /* @__PURE__ */ jsx("span", { className: "realiiz-form__help", title: help, children: label });
141
+ function LengthGauge({ length: raw, range, suffix }) {
142
+ const [min, max] = range;
143
+ const length = raw === 0 ? 0 : raw + (suffix?.length ?? 0);
144
+ const state = length === 0 ? "empty" : length < min ? "short" : length <= max ? "ok" : "over";
145
+ const scale = Math.round(max * 1.3);
146
+ const pct = (n) => `${Math.min(100, n / scale * 100)}%`;
147
+ const verdict = state === "ok" ? "good length" : state === "over" ? `over ${max} \u2014 may be cut off` : `aim for ${min}\u2013${max}`;
148
+ const incl = suffix && length > 0 ? ` incl. \u201C${suffix.trim()}\u201D` : "";
149
+ return /* @__PURE__ */ jsxs("span", { className: `realiiz-gauge realiiz-gauge--${state}`, title: `Recommended ${min}\u2013${max} characters${suffix ? ` including \u201C${suffix.trim()}\u201D` : ""}`, children: [
150
+ /* @__PURE__ */ jsxs("span", { className: "realiiz-gauge__bar", "aria-hidden": "true", children: [
151
+ /* @__PURE__ */ jsx("span", { className: "realiiz-gauge__zone", style: { left: pct(min), width: `calc(${pct(max)} - ${pct(min)})` } }),
152
+ /* @__PURE__ */ jsx("span", { className: "realiiz-gauge__fill", style: { width: pct(length) } })
153
+ ] }),
154
+ /* @__PURE__ */ jsxs("span", { className: "realiiz-gauge__text", children: [
155
+ /* @__PURE__ */ jsx("b", { children: length }),
156
+ " characters",
157
+ incl,
158
+ " \xB7 ",
159
+ verdict
160
+ ] })
161
+ ] });
162
+ }
108
163
  function Field(p) {
109
164
  const id = `${p.formId}-${p.spec.name}`;
110
165
  const invalid = p.issues.length > 0;
111
166
  const max = p.spec.constraints.max;
112
- const showCount = typeof max === "number" && (p.spec.input === "text" || p.spec.input === "textarea");
167
+ const isText = p.spec.input === "text" || p.spec.input === "textarea";
113
168
  const len = typeof p.value === "string" ? p.value.length : 0;
114
169
  const isCheck = p.spec.input === "boolean";
115
- const control = /* @__PURE__ */ jsx(Control, { id, spec: p.spec, value: p.value, invalid, options: p.options, onChange: p.onChange, onBlur: p.onBlur });
170
+ const isTags = p.spec.input === "tags";
171
+ const labelText = p.spec.help ? (p.renderLabel ?? defaultLabel)(p.spec.label, p.spec.help) : p.spec.label;
172
+ const control = isTags ? /* @__PURE__ */ jsx(
173
+ ChipsInput,
174
+ {
175
+ id,
176
+ name: p.spec.name,
177
+ value: Array.isArray(p.value) ? p.value : [],
178
+ onChange: (v) => p.onChange(v),
179
+ onBlur: p.onBlur,
180
+ placeholder: p.spec.placeholder,
181
+ invalid,
182
+ max: p.spec.constraints.exact ?? p.spec.constraints.max
183
+ }
184
+ ) : /* @__PURE__ */ jsx(Control, { id, spec: p.spec, value: p.value, invalid, options: p.options, onChange: p.onChange, onBlur: p.onBlur });
116
185
  return /* @__PURE__ */ jsxs("div", { className: `realiiz-form__field realiiz-form__field--${p.spec.input}`, "data-field": p.spec.name, children: [
117
186
  isCheck ? /* @__PURE__ */ jsxs("label", { className: "realiiz-form__check", htmlFor: id, children: [
118
187
  control,
119
188
  /* @__PURE__ */ jsxs("span", { children: [
120
- p.spec.label,
189
+ labelText,
121
190
  p.spec.required && /* @__PURE__ */ jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
122
191
  ] })
123
192
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
124
193
  /* @__PURE__ */ jsxs("label", { className: "realiiz-form__label", htmlFor: id, children: [
125
- p.spec.label,
194
+ labelText,
126
195
  p.spec.required && /* @__PURE__ */ jsx("span", { className: "realiiz-form__required", "aria-hidden": "true", children: "*" })
127
196
  ] }),
128
197
  control
@@ -131,15 +200,529 @@ function Field(p) {
131
200
  "Current: ",
132
201
  p.current
133
202
  ] }),
134
- showCount && /* @__PURE__ */ jsx("div", { className: "realiiz-form__helper", children: /* @__PURE__ */ jsxs("span", { className: "realiiz-form__count", children: [
135
- len,
136
- " / ",
137
- max
138
- ] }) }),
203
+ isText && (p.spec.recommended || typeof max === "number") && /* @__PURE__ */ jsxs("div", { className: "realiiz-form__helper", children: [
204
+ p.spec.recommended ? /* @__PURE__ */ jsx(LengthGauge, { length: len, range: p.spec.recommended, suffix: p.spec.suffix }) : /* @__PURE__ */ jsx("span", {}),
205
+ typeof max === "number" && /* @__PURE__ */ jsxs("span", { className: "realiiz-form__count", children: [
206
+ len,
207
+ " / ",
208
+ max
209
+ ] })
210
+ ] }),
139
211
  invalid && /* @__PURE__ */ jsx("div", { className: "realiiz-form__error", id: `${id}-error`, children: p.issues.map((i, n) => /* @__PURE__ */ jsx("div", { children: i.message }, n)) })
140
212
  ] });
141
213
  }
142
214
 
215
+ // src/forms-ui/md/table.ts
216
+ var ROW = /^\s*\|.*\|\s*$/;
217
+ var SEP = /^\s*\|[\s:|-]+\|\s*$/;
218
+ function tableCells(line) {
219
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split(/(?<!\\)\|/).map((c) => c.trim());
220
+ }
221
+ function tableAt(v, pos) {
222
+ const lines = v.split("\n");
223
+ let i = 0, off = 0;
224
+ while (i < lines.length && pos > off + lines[i].length) {
225
+ off += lines[i].length + 1;
226
+ i++;
227
+ }
228
+ if (i >= lines.length || !ROW.test(lines[i])) return null;
229
+ let s = i, e = i;
230
+ while (s > 0 && ROW.test(lines[s - 1])) s--;
231
+ while (e < lines.length - 1 && ROW.test(lines[e + 1])) e++;
232
+ if (e - s < 1 || !SEP.test(lines[s + 1])) return null;
233
+ const start = lines.slice(0, s).reduce((n, l) => n + l.length + 1, 0);
234
+ const end = start + lines.slice(s, e + 1).join("\n").length;
235
+ const align = tableCells(lines[s + 1]).map((c) => c.startsWith(":") && c.endsWith(":") ? "c" : c.endsWith(":") ? "r" : c.startsWith(":") ? "L" : "l");
236
+ const rows = lines.slice(s, e + 1).filter((_, n) => n !== 1).map(tableCells);
237
+ const col = Math.max(0, (lines[i].slice(0, pos - off).match(/(?<!\\)\|/g) || []).length - 1);
238
+ const row = i - s <= 1 ? 0 : i - s - 1;
239
+ return { start, end, rows, align, row, col };
240
+ }
241
+ function renderTable(rows, align) {
242
+ const cols = Math.max(...rows.map((r) => r.length));
243
+ const w = [];
244
+ for (let c = 0; c < cols; c++) w[c] = Math.max(3, ...rows.map((r) => (r[c] || "").length));
245
+ const pad = (t, c) => (t || "").padEnd(w[c]);
246
+ const line = (r) => "| " + Array.from({ length: cols }, (_, c) => pad(r[c], c)).join(" | ") + " |";
247
+ const sep = "|" + Array.from({ length: cols }, (_, c) => align[c] === "c" ? ":" + "-".repeat(w[c]) + ":" : align[c] === "r" ? " " + "-".repeat(w[c] - 1) + ": " : align[c] === "L" ? ":" + "-".repeat(w[c] - 1) + " " : " " + "-".repeat(w[c]) + " ").join("|") + "|";
248
+ return [line(rows[0]), sep, ...rows.slice(1).map(line)].join("\n");
249
+ }
250
+ function newTable(rows, cols) {
251
+ const head = Array.from({ length: cols }, (_, i) => `Column ${i + 1}`);
252
+ const grid = [head, ...Array.from({ length: rows }, () => Array(cols).fill(""))];
253
+ return { text: renderTable(grid, Array(cols).fill("l")), firstHeader: head[0] };
254
+ }
255
+ function applyTableOp(v, pos, op) {
256
+ const t = tableAt(v, pos);
257
+ if (!t) return null;
258
+ const rows = t.rows.map((r) => r.slice());
259
+ const cols = Math.max(...rows.map((r) => r.length));
260
+ const align = t.align.slice();
261
+ if (op === "rowAfter") {
262
+ rows.splice(Math.max(1, t.row + 1), 0, Array(cols).fill(""));
263
+ } else if (op === "rowDel") {
264
+ if (t.row === 0 || rows.length <= 2) return { error: "A table needs its header row and at least one row below it." };
265
+ rows.splice(t.row, 1);
266
+ } else if (op === "colAfter") {
267
+ rows.forEach((r) => {
268
+ while (r.length < cols) r.push("");
269
+ r.splice(t.col + 1, 0, "");
270
+ });
271
+ align.splice(t.col + 1, 0, "l");
272
+ } else if (op === "colDel") {
273
+ if (cols <= 1) return { error: "That's the last column \u2014 delete the table instead." };
274
+ rows.forEach((r) => r.splice(t.col, 1));
275
+ align.splice(t.col, 1);
276
+ }
277
+ const out = renderTable(rows, align);
278
+ return { text: v.slice(0, t.start) + out + v.slice(t.end), a: t.start, b: t.start + out.length };
279
+ }
280
+
281
+ // src/forms-ui/md/edits.ts
282
+ var toggleWrap = (mark, placeholder = "text") => (s, a, b) => {
283
+ const sel = s.slice(a, b);
284
+ const n = mark.length;
285
+ if (sel.length >= 2 * n && sel.startsWith(mark) && sel.endsWith(mark)) {
286
+ const inner = sel.slice(n, sel.length - n);
287
+ return { text: s.slice(0, a) + inner + s.slice(b), a, b: a + inner.length };
288
+ }
289
+ if (s.slice(a - n, a) === mark && s.slice(b, b + n) === mark) {
290
+ return { text: s.slice(0, a - n) + sel + s.slice(b + n), a: a - n, b: a - n + sel.length };
291
+ }
292
+ const body = sel || placeholder;
293
+ return { text: s.slice(0, a) + mark + body + mark + s.slice(b), a: a + n, b: a + n + body.length };
294
+ };
295
+ var PREFIX_RE = /^(#{1,6}\s+|[-*+]\s+|\d+[.)]\s+|>\s?)/;
296
+ var lineSpan = (s, a, b) => {
297
+ const start = s.lastIndexOf("\n", a - 1) + 1;
298
+ const endIdx = s.indexOf("\n", Math.max(b, a));
299
+ return { start, end: endIdx === -1 ? s.length : endIdx };
300
+ };
301
+ var toggleLines = (prefix, matches) => (s, a, b) => {
302
+ const { start, end } = lineSpan(s, a, b);
303
+ const lines = s.slice(start, end).split("\n");
304
+ const allOn = lines.every((l) => l.trim() === "" || matches(l));
305
+ const out = lines.map((l, i) => {
306
+ const bare = l.replace(PREFIX_RE, "");
307
+ return allOn ? bare : (typeof prefix === "string" ? prefix : prefix(i)) + bare;
308
+ });
309
+ const replaced = out.join("\n");
310
+ return { text: s.slice(0, start) + replaced + s.slice(end), a: start, b: start + replaced.length };
311
+ };
312
+ var LINK_RE = /\[([^\]\n]*)\]\(([^)\n]*)\)/g;
313
+ function linkAt(s, a, b) {
314
+ for (const m of s.matchAll(LINK_RE)) {
315
+ if (s[m.index - 1] === "!") continue;
316
+ const from = m.index, to = from + m[0].length;
317
+ if (a >= from && b <= to) return { from, to, label: m[1] };
318
+ if (from > b) break;
319
+ }
320
+ return null;
321
+ }
322
+ var toggleLink = (s, a, b) => {
323
+ const hit = linkAt(s, a, b);
324
+ if (hit) return { text: s.slice(0, hit.from) + hit.label + s.slice(hit.to), a: hit.from, b: hit.from + hit.label.length };
325
+ const label = s.slice(a, b) || "link text";
326
+ const urlStart = a + label.length + 3;
327
+ return { text: s.slice(0, a) + `[${label}](https://)` + s.slice(b), a: urlStart, b: urlStart + 8 };
328
+ };
329
+ var insertImage = (s, a, b) => {
330
+ const alt = s.slice(a, b) || "description";
331
+ const urlStart = a + alt.length + 4;
332
+ return { text: s.slice(0, a) + `![${alt}](https://)` + s.slice(b), a: urlStart, b: urlStart + 8 };
333
+ };
334
+ var insertDot = (s, a, b) => {
335
+ const t = " \xB7 ";
336
+ return { text: s.slice(0, a) + t + s.slice(b), a: a + t.length, b: a + t.length };
337
+ };
338
+ var blockBefore = (s, a) => a === 0 || s.slice(0, a).endsWith("\n\n") ? "" : s.slice(0, a).endsWith("\n") ? "\n" : "\n\n";
339
+ var insertRule = (s, a, b) => {
340
+ const before = blockBefore(s, a);
341
+ const t = before + "---\n\n";
342
+ return { text: s.slice(0, a) + t + s.slice(b), a: a + t.length, b: a + t.length };
343
+ };
344
+ var insertTable = (rows, cols) => (s, a, b) => {
345
+ const { text, firstHeader } = newTable(rows, cols);
346
+ const before = blockBefore(s, a);
347
+ const t = before + text + "\n";
348
+ const at = a + before.length + text.indexOf(firstHeader);
349
+ return { text: s.slice(0, a) + t + s.slice(b), a: at, b: at + firstHeader.length };
350
+ };
351
+ var toggleTerm = (component = "Term", attr = "definition") => (s, a, b) => {
352
+ const re = new RegExp(`<${component}\\s+${attr}="([^"]*)"\\s*>([\\s\\S]*?)</${component}>`, "g");
353
+ for (const m of s.matchAll(re)) {
354
+ const from = m.index, to = from + m[0].length;
355
+ if (a >= from && b <= to) return { text: s.slice(0, from) + m[2] + s.slice(to), a: from, b: from + m[2].length };
356
+ if (from > b) break;
357
+ }
358
+ const word = s.slice(a, b) || "term";
359
+ const open = `<${component} ${attr}="`;
360
+ const text = s.slice(0, a) + open + `">${word}</${component}>` + s.slice(b);
361
+ return { text, a: a + open.length, b: a + open.length };
362
+ };
363
+ var TOOLS = [
364
+ { id: "bold", title: "Bold \u2014 \u2318B", apply: toggleWrap("**") },
365
+ { id: "italic", title: "Italic \u2014 \u2318I", apply: toggleWrap("_") },
366
+ { id: "h2", title: "Section heading", apply: toggleLines("## ", (l) => /^##\s/.test(l)) },
367
+ { id: "h3", title: "Sub-heading", apply: toggleLines("### ", (l) => /^###\s/.test(l)) },
368
+ { id: "ul", title: "Bulleted list", apply: toggleLines("- ", (l) => /^[-*+]\s/.test(l)) },
369
+ { id: "ol", title: "Numbered list", apply: toggleLines((i) => `${i + 1}. `, (l) => /^\d+[.)]\s/.test(l)) },
370
+ { id: "quote", title: "Quote", apply: toggleLines("> ", (l) => /^>/.test(l)) },
371
+ { id: "link", title: "Link", apply: toggleLink },
372
+ { id: "code", title: "Inline code", apply: toggleWrap("`", "code") },
373
+ { id: "image", title: "Image", apply: insertImage },
374
+ { id: "dot", title: "Separator between details ( \xB7 )", apply: insertDot },
375
+ { id: "rule", title: "Divider line across the page", apply: insertRule }
376
+ ];
377
+ var toolById = (id) => TOOLS.find((t) => t.id === id);
378
+ function activeTools(s, a, b) {
379
+ const on = /* @__PURE__ */ new Set();
380
+ const sel = s.slice(a, b);
381
+ const wrapped = (m) => s.slice(a - m.length, a) === m && s.slice(b, b + m.length) === m || sel.length >= 2 * m.length && sel.startsWith(m) && sel.endsWith(m);
382
+ if (wrapped("**") || spanAt(s, a, b, /\*\*[^*\n]+\*\*/g, 2)) on.add("bold");
383
+ if (wrapped("_") || spanAt(s, a, b, /(?<![\w_])_[^_\n]+_(?![\w_])/g, 1)) on.add("italic");
384
+ if (wrapped("`") || spanAt(s, a, b, /`[^`\n]+`/g, 1)) on.add("code");
385
+ if (linkAt(s, a, b)) on.add("link");
386
+ if (spanAt(s, a, b, /<([A-Z][\w]*)\s[^>]*>[\s\S]*?<\/\1>/g, 0)) on.add("term");
387
+ const { start, end } = lineSpan(s, a, a);
388
+ const line = s.slice(start, end);
389
+ if (/^##\s/.test(line)) on.add("h2");
390
+ else if (/^###\s/.test(line)) on.add("h3");
391
+ else if (/^[-*+]\s/.test(line)) on.add("ul");
392
+ else if (/^\d+[.)]\s/.test(line)) on.add("ol");
393
+ else if (/^>/.test(line)) on.add("quote");
394
+ return on;
395
+ }
396
+ function spanAt(s, a, b, re, pad) {
397
+ for (const m of s.matchAll(re)) {
398
+ const from = m.index, to = from + m[0].length;
399
+ if (a >= from + pad && b <= to - pad) return true;
400
+ if (from > b) break;
401
+ }
402
+ return false;
403
+ }
404
+ var Svg = ({ children, fill = "none" }) => /* @__PURE__ */ jsx("svg", { className: "realiiz-md__ico", viewBox: "0 0 24 24", fill, stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children });
405
+ var MD_ICONS = {
406
+ bold: /* @__PURE__ */ jsx("b", { children: "B" }),
407
+ italic: /* @__PURE__ */ jsx("i", { children: "I" }),
408
+ h2: /* @__PURE__ */ jsxs(Svg, { children: [
409
+ /* @__PURE__ */ jsx("path", { d: "M6 5v14" }),
410
+ /* @__PURE__ */ jsx("path", { d: "M14 5v14" }),
411
+ /* @__PURE__ */ jsx("path", { d: "M6 12h8" }),
412
+ /* @__PURE__ */ jsx("path", { d: "M17.5 19v-6.5l-1.6 1.2" })
413
+ ] }),
414
+ h3: /* @__PURE__ */ jsxs(Svg, { children: [
415
+ /* @__PURE__ */ jsx("path", { d: "M5 5v14" }),
416
+ /* @__PURE__ */ jsx("path", { d: "M12 5v14" }),
417
+ /* @__PURE__ */ jsx("path", { d: "M5 12h7" }),
418
+ /* @__PURE__ */ jsx("path", { d: "M16 12.5h3.5l-2 2.2a1.9 1.9 0 1 1-1.5 3.2" })
419
+ ] }),
420
+ ul: /* @__PURE__ */ jsxs(Svg, { children: [
421
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "6.5", x2: "20", y2: "6.5" }),
422
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "12", x2: "20", y2: "12" }),
423
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "17.5", x2: "20", y2: "17.5" }),
424
+ /* @__PURE__ */ jsx("circle", { cx: "4.6", cy: "6.5", r: "1.3", fill: "currentColor", stroke: "none" }),
425
+ /* @__PURE__ */ jsx("circle", { cx: "4.6", cy: "12", r: "1.3", fill: "currentColor", stroke: "none" }),
426
+ /* @__PURE__ */ jsx("circle", { cx: "4.6", cy: "17.5", r: "1.3", fill: "currentColor", stroke: "none" })
427
+ ] }),
428
+ ol: /* @__PURE__ */ jsxs(Svg, { children: [
429
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "6.5", x2: "20", y2: "6.5" }),
430
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "12", x2: "20", y2: "12" }),
431
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "17.5", x2: "20", y2: "17.5" }),
432
+ /* @__PURE__ */ jsx("path", { d: "M4 5.6 5.2 5v3.4" }),
433
+ /* @__PURE__ */ jsx("path", { d: "M3.6 11.2h1.8L3.6 13.7h1.9" }),
434
+ /* @__PURE__ */ jsx("path", { d: "M3.7 16.3h1.7v1.2H4.2h1.2v1.2H3.7" })
435
+ ] }),
436
+ quote: /* @__PURE__ */ jsxs(Svg, { children: [
437
+ /* @__PURE__ */ jsx("path", { d: "M9 7c-2.4 0-4 1.7-4 4 0 1.9 1.3 3.2 3 3.2.4 0 .8-.1 1-.2-.3 1.4-1.5 2.6-3 3l.7 1.5c2.8-.8 4.8-3.3 4.8-6.6C11.5 8.7 10.5 7 9 7z", fill: "currentColor", stroke: "none" }),
438
+ /* @__PURE__ */ jsx("path", { d: "M18 7c-2.4 0-4 1.7-4 4 0 1.9 1.3 3.2 3 3.2.4 0 .8-.1 1-.2-.3 1.4-1.5 2.6-3 3l.7 1.5c2.8-.8 4.8-3.3 4.8-6.6C20.5 8.7 19.5 7 18 7z", fill: "currentColor", stroke: "none" })
439
+ ] }),
440
+ link: /* @__PURE__ */ jsxs(Svg, { children: [
441
+ /* @__PURE__ */ jsx("path", { d: "M10 13a5 5 0 0 0 7.5.5l2.5-2.5a5 5 0 0 0-7-7l-1.5 1.4" }),
442
+ /* @__PURE__ */ jsx("path", { d: "M14 11a5 5 0 0 0-7.5-.5L4 13a5 5 0 0 0 7 7l1.4-1.4" })
443
+ ] }),
444
+ code: /* @__PURE__ */ jsxs(Svg, { children: [
445
+ /* @__PURE__ */ jsx("path", { d: "m8 7-5 5 5 5" }),
446
+ /* @__PURE__ */ jsx("path", { d: "m16 7 5 5-5 5" }),
447
+ /* @__PURE__ */ jsx("path", { d: "m14 4-4 16" })
448
+ ] }),
449
+ // A tooltip: a tall speech bubble with an "i" — what the reader will actually see on the page.
450
+ term: /* @__PURE__ */ jsxs(Svg, { children: [
451
+ /* @__PURE__ */ jsx("path", { d: "M5 2.5h14a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2h-4.5L12 21.5 9.5 17.5H5a2 2 0 0 1-2-2v-11a2 2 0 0 1 2-2z" }),
452
+ /* @__PURE__ */ jsx("path", { d: "M12 9v4.5" }),
453
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "6.2", r: ".9", fill: "currentColor", stroke: "none" })
454
+ ] }),
455
+ image: /* @__PURE__ */ jsxs(Svg, { children: [
456
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
457
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "8.5", r: "1.6" }),
458
+ /* @__PURE__ */ jsx("path", { d: "m21 15-5-5L5 21" })
459
+ ] }),
460
+ dot: /* @__PURE__ */ jsx("span", { className: "realiiz-md__dot", children: "\xB7" }),
461
+ rule: /* @__PURE__ */ jsx(Svg, { children: /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "21", y2: "12" }) }),
462
+ table: /* @__PURE__ */ jsxs(Svg, { children: [
463
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
464
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "9.5", x2: "21", y2: "9.5" }),
465
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" }),
466
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "9.5", x2: "12", y2: "20" })
467
+ ] }),
468
+ expand: /* @__PURE__ */ jsxs(Svg, { children: [
469
+ /* @__PURE__ */ jsx("path", { d: "M9 3H3v6" }),
470
+ /* @__PURE__ */ jsx("path", { d: "M15 21h6v-6" }),
471
+ /* @__PURE__ */ jsx("path", { d: "M3 3l7 7" }),
472
+ /* @__PURE__ */ jsx("path", { d: "M21 21l-7-7" })
473
+ ] }),
474
+ contract: /* @__PURE__ */ jsxs(Svg, { children: [
475
+ /* @__PURE__ */ jsx("path", { d: "M4 10h6V4" }),
476
+ /* @__PURE__ */ jsx("path", { d: "M20 14h-6v6" }),
477
+ /* @__PURE__ */ jsx("path", { d: "M3 3l7 7" }),
478
+ /* @__PURE__ */ jsx("path", { d: "M21 21l-7-7" })
479
+ ] })
480
+ };
481
+ var MAX = 6;
482
+ function TableGrid({ onPick }) {
483
+ const [open, setOpen] = useState(false);
484
+ const [hot, setHot] = useState([0, 0]);
485
+ const wrap = useRef(null);
486
+ useEffect(() => {
487
+ if (!open) return;
488
+ const away = (e) => {
489
+ if (!wrap.current?.contains(e.target)) setOpen(false);
490
+ };
491
+ const esc = (e) => {
492
+ if (e.key === "Escape") setOpen(false);
493
+ };
494
+ document.addEventListener("mousedown", away);
495
+ document.addEventListener("keydown", esc);
496
+ return () => {
497
+ document.removeEventListener("mousedown", away);
498
+ document.removeEventListener("keydown", esc);
499
+ };
500
+ }, [open]);
501
+ const [r, c] = hot;
502
+ const label = r && c ? `${c} column${c === 1 ? "" : "s"} \xD7 ${r} row${r === 1 ? "" : "s"}` : "Pick a size";
503
+ return /* @__PURE__ */ jsxs("span", { className: ["realiiz-md__tgwrap", open && "is-open"].filter(Boolean).join(" "), ref: wrap, children: [
504
+ /* @__PURE__ */ jsx("button", { type: "button", "data-tool": "table", "data-tip": "New table \u2014 pick a size", "aria-label": "Insert a table", "aria-expanded": open, onClick: () => setOpen((o) => !o), children: MD_ICONS.table }),
505
+ open && /* @__PURE__ */ jsxs("span", { className: "realiiz-md__tgrid", role: "dialog", "aria-label": "Table size", onMouseLeave: () => setHot([0, 0]), children: [
506
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__tgcells", children: Array.from({ length: MAX * MAX }, (_, n) => {
507
+ const rr = Math.floor(n / MAX) + 1, cc = n % MAX + 1;
508
+ return /* @__PURE__ */ jsx(
509
+ "i",
510
+ {
511
+ className: rr <= r && cc <= c ? "hot" : void 0,
512
+ onMouseEnter: () => setHot([rr, cc]),
513
+ onClick: () => {
514
+ setOpen(false);
515
+ onPick(rr, cc);
516
+ },
517
+ "aria-label": `${cc} \xD7 ${rr}`,
518
+ role: "button"
519
+ },
520
+ n
521
+ );
522
+ }) }),
523
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__tglab", children: label })
524
+ ] })
525
+ ] });
526
+ }
527
+ var FORMAT = ["bold", "italic"];
528
+ var BLOCK = ["h2", "h3", "ul", "ol", "quote"];
529
+ var INLINE = ["link", "code"];
530
+ var INSERT = ["image", "dot", "rule"];
531
+ var TABLE_OPS = [
532
+ { op: "rowAfter", label: "+ Row", tip: "Insert a row below this one" },
533
+ { op: "rowDel", label: "\u2212 Row", tip: "Delete this row" },
534
+ { op: "colAfter", label: "+ Col", tip: "Insert a column to the right" },
535
+ { op: "colDel", label: "\u2212 Col", tip: "Delete this column" }
536
+ ];
537
+ function MarkdownEditor({ id, name = "body", value, onChange, placeholder, renderPreview, previewClassName, minHeight = 320, termComponent }) {
538
+ const [mode, setMode] = useState(() => renderPreview && value.trim() ? "preview" : "edit");
539
+ const [preview, setPreview] = useState(null);
540
+ const [loading, setLoading] = useState(false);
541
+ const [expanded, setExpanded] = useState(false);
542
+ const [on, setOn] = useState(() => /* @__PURE__ */ new Set());
543
+ const [inTable, setInTable] = useState(false);
544
+ const [note, setNote] = useState(null);
545
+ const ta = useRef(null);
546
+ const sync = useCallback(() => {
547
+ const el = ta.current;
548
+ if (!el) return;
549
+ setOn(activeTools(el.value, el.selectionStart, el.selectionEnd));
550
+ setInTable(Boolean(tableAt(el.value, el.selectionStart)));
551
+ }, []);
552
+ const commit = (edit) => {
553
+ const el = ta.current;
554
+ if (!el) return;
555
+ const { text, a, b } = edit;
556
+ const from = commonPrefix(el.value, text);
557
+ const oldTail = el.value.length - from, newTail = text.length - from;
558
+ const suffix = commonSuffix(el.value.slice(from), text.slice(from));
559
+ const oldEnd = from + oldTail - suffix, newEnd = from + newTail - suffix;
560
+ let ok = false;
561
+ el.focus();
562
+ el.setSelectionRange(from, oldEnd);
563
+ try {
564
+ ok = document.execCommand("insertText", false, text.slice(from, newEnd));
565
+ } catch {
566
+ ok = false;
567
+ }
568
+ if (!ok || el.value !== text) onChange(text);
569
+ requestAnimationFrame(() => {
570
+ el.setSelectionRange(a, b);
571
+ sync();
572
+ });
573
+ };
574
+ const run = (apply) => {
575
+ const el = ta.current;
576
+ if (!el) return;
577
+ commit(apply(value, el.selectionStart, el.selectionEnd));
578
+ };
579
+ const runTable = (op) => {
580
+ const el = ta.current;
581
+ if (!el) return;
582
+ const r = applyTableOp(value, el.selectionStart, op);
583
+ if (!r) return;
584
+ if ("error" in r) {
585
+ setNote(r.error);
586
+ return;
587
+ }
588
+ setNote(null);
589
+ commit(r);
590
+ };
591
+ const onKey = (e) => {
592
+ if (e.key === "Escape" && expanded) {
593
+ e.preventDefault();
594
+ setExpanded(false);
595
+ return;
596
+ }
597
+ if (!(e.metaKey || e.ctrlKey)) return;
598
+ if (e.key === "b") {
599
+ e.preventDefault();
600
+ run(toolById("bold").apply);
601
+ }
602
+ if (e.key === "i") {
603
+ e.preventDefault();
604
+ run(toolById("italic").apply);
605
+ }
606
+ if (e.key === "k") {
607
+ e.preventDefault();
608
+ run(toolById("link").apply);
609
+ }
610
+ };
611
+ useEffect(() => {
612
+ if (!expanded) return;
613
+ const prev = document.body.style.overflow;
614
+ document.body.style.overflow = "hidden";
615
+ const esc = (e) => {
616
+ if (e.key === "Escape") setExpanded(false);
617
+ };
618
+ document.addEventListener("keydown", esc);
619
+ return () => {
620
+ document.body.style.overflow = prev;
621
+ document.removeEventListener("keydown", esc);
622
+ };
623
+ }, [expanded]);
624
+ useEffect(() => {
625
+ if (mode !== "preview" || !renderPreview) return;
626
+ let live = true;
627
+ setLoading(true);
628
+ renderPreview(value).then((r) => {
629
+ if (live) setPreview(r);
630
+ }).catch((err) => {
631
+ if (live) setPreview({ error: err instanceof Error ? err.message : String(err) });
632
+ }).finally(() => {
633
+ if (live) setLoading(false);
634
+ });
635
+ return () => {
636
+ live = false;
637
+ };
638
+ }, [mode, value, renderPreview]);
639
+ const termTool = termComponent ? { id: "term", title: `Tooltip \u2014 wraps the selection in <${termComponent}>`, apply: toggleTerm(termComponent) } : null;
640
+ const btn = (toolId) => {
641
+ const t = toolId === "term" && termTool ? termTool : toolById(toolId);
642
+ return /* @__PURE__ */ jsx("button", { type: "button", "data-tool": t.id, "data-tip": t.title, "aria-label": t.title, "aria-pressed": on.has(t.id) || void 0, className: on.has(t.id) ? "on" : void 0, onMouseDown: (e) => e.preventDefault(), onClick: () => run(t.apply), children: MD_ICONS[t.id] }, t.id);
643
+ };
644
+ return /* @__PURE__ */ jsxs("div", { className: ["realiiz-md", expanded && "realiiz-md--fs"].filter(Boolean).join(" "), children: [
645
+ renderPreview && /* @__PURE__ */ jsxs("span", { className: "realiiz-md__mode", role: "tablist", "aria-label": "Editor mode", children: [
646
+ /* @__PURE__ */ jsx("button", { type: "button", role: "tab", "aria-selected": mode === "edit", onClick: () => setMode("edit"), children: "Edit Markdown" }),
647
+ /* @__PURE__ */ jsx("button", { type: "button", role: "tab", "aria-selected": mode === "preview", onClick: () => setMode("preview"), children: "Preview" })
648
+ ] }),
649
+ /* @__PURE__ */ jsxs("div", { className: "realiiz-md__panel", children: [
650
+ /* @__PURE__ */ jsxs("div", { className: "realiiz-md__head", children: [
651
+ mode === "edit" && /* @__PURE__ */ jsxs("span", { className: "realiiz-md__tools", role: "toolbar", "aria-label": "Formatting", children: [
652
+ FORMAT.map(btn),
653
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__sep" }),
654
+ BLOCK.map(btn),
655
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__sep" }),
656
+ INLINE.map(btn),
657
+ termTool && btn("term"),
658
+ inTable && /* @__PURE__ */ jsxs(Fragment, { children: [
659
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__sep" }),
660
+ TABLE_OPS.map((t) => /* @__PURE__ */ jsx("button", { type: "button", "data-tbl-op": t.op, "data-tip": t.tip, "aria-label": t.tip, onMouseDown: (e) => e.preventDefault(), onClick: () => runTable(t.op), children: t.label }, t.op))
661
+ ] }),
662
+ /* @__PURE__ */ jsxs("span", { className: "realiiz-md__insert", children: [
663
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__lab", children: "Insert" }),
664
+ INSERT.map(btn),
665
+ /* @__PURE__ */ jsx(TableGrid, { onPick: (r, c) => run(insertTable(r, c)) })
666
+ ] })
667
+ ] }),
668
+ /* @__PURE__ */ jsx("span", { className: "realiiz-md__spacer" }),
669
+ /* @__PURE__ */ jsx(
670
+ "button",
671
+ {
672
+ type: "button",
673
+ className: "realiiz-md__expand",
674
+ "data-tip": expanded ? "Back to the normal layout \u2014 or press Esc" : "Fill the window \u2014 Esc to come back",
675
+ "aria-label": expanded ? "Exit" : "Expand",
676
+ onClick: () => setExpanded((x) => !x),
677
+ children: expanded ? MD_ICONS.contract : MD_ICONS.expand
678
+ }
679
+ )
680
+ ] }),
681
+ mode === "edit" ? /* @__PURE__ */ jsx(
682
+ "textarea",
683
+ {
684
+ ref: ta,
685
+ id,
686
+ name,
687
+ className: "realiiz-form__control realiiz-form__control--body realiiz-md__ta",
688
+ style: expanded ? void 0 : { minHeight },
689
+ value,
690
+ placeholder,
691
+ onChange: (e) => {
692
+ onChange(e.target.value);
693
+ sync();
694
+ },
695
+ onSelect: sync,
696
+ onKeyUp: sync,
697
+ onClick: sync,
698
+ onKeyDown: onKey,
699
+ spellCheck: true
700
+ }
701
+ ) : /* @__PURE__ */ jsxs("div", { className: "realiiz-md__preview", style: expanded ? void 0 : { minHeight }, "aria-live": "polite", "aria-busy": loading, children: [
702
+ loading && /* @__PURE__ */ jsx("div", { className: "realiiz-md__loading", children: "Rendering\u2026" }),
703
+ !loading && preview && "error" in preview && /* @__PURE__ */ jsxs("div", { className: "realiiz-form__alert", role: "alert", children: [
704
+ "Couldn't render: ",
705
+ preview.error
706
+ ] }),
707
+ !loading && preview && "html" in preview && /* @__PURE__ */ jsx("div", { className: ["realiiz-prose", previewClassName].filter(Boolean).join(" "), dangerouslySetInnerHTML: { __html: preview.html } })
708
+ ] })
709
+ ] }),
710
+ note && /* @__PURE__ */ jsx("div", { className: "realiiz-form__helper realiiz-md__note", role: "status", children: note })
711
+ ] });
712
+ }
713
+ function commonPrefix(a, b) {
714
+ const n = Math.min(a.length, b.length);
715
+ let i = 0;
716
+ while (i < n && a[i] === b[i]) i++;
717
+ return i;
718
+ }
719
+ function commonSuffix(a, b) {
720
+ const n = Math.min(a.length, b.length);
721
+ let i = 0;
722
+ while (i < n && a[a.length - 1 - i] === b[b.length - 1 - i]) i++;
723
+ return i;
724
+ }
725
+
143
726
  // src/forms-ui/issues.ts
144
727
  function mapIssues(issues, specs) {
145
728
  const byName = new Map(specs.map((s) => [s.name, s]));
@@ -189,10 +772,13 @@ function toControl(spec, raw) {
189
772
  switch (spec.input) {
190
773
  case "boolean":
191
774
  return Boolean(raw);
192
- case "date":
193
- return raw instanceof Date ? raw.toISOString().slice(0, 10) : String(raw);
775
+ case "date": {
776
+ if (raw instanceof Date) return raw.toISOString().slice(0, 10);
777
+ const s = String(raw);
778
+ return /^\d{4}-\d{2}-\d{2}/.test(s) ? s.slice(0, 10) : s;
779
+ }
194
780
  case "tags":
195
- return Array.isArray(raw) ? raw.join(", ") : String(raw);
781
+ return Array.isArray(raw) ? raw.map(String) : String(raw).split(",").map((t) => t.trim()).filter(Boolean);
196
782
  default:
197
783
  return String(raw);
198
784
  }
@@ -213,6 +799,10 @@ function toFrontmatter(specs, state, hiddenValues) {
213
799
  frontmatter[spec.name] = PENDING_UPLOAD_PREFIX + v.name;
214
800
  continue;
215
801
  }
802
+ if (Array.isArray(v)) {
803
+ if (v.length || spec.required) frontmatter[spec.name] = v;
804
+ continue;
805
+ }
216
806
  const s = typeof v === "string" ? v.trim() : "";
217
807
  if (s === "") {
218
808
  if (spec.required) frontmatter[spec.name] = spec.input === "tags" ? [] : "";
@@ -233,13 +823,39 @@ function toFrontmatter(specs, state, hiddenValues) {
233
823
  }
234
824
  return { frontmatter, files };
235
825
  }
236
- function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content" }) {
826
+ var LEAVE_MESSAGE = "You have unsaved changes. Leave this page and lose them?";
827
+ function slugify(s) {
828
+ return s.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
829
+ }
830
+ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, onCancel, submitLabel = "Save", className, showBody = true, bodyLabel = "Content", renderPreview, previewClassName, termComponent, stickyBar = false, status, barExtra, footer, renderLabel, derive, guardUnsaved = true }) {
237
831
  const formId = useId();
238
832
  const specs = useMemo(() => walkSchema(entry.schema, entry), [entry]);
239
833
  const visible = useMemo(() => specs.filter((s) => s.role !== "hidden" && s.role !== "body"), [specs]);
240
834
  const bodySpec = specs.find((s) => s.role === "body");
241
- const [state, setState] = useState(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])));
835
+ const sections = useMemo(() => {
836
+ const groups = entry.groups ?? [];
837
+ const grouped = new Set(groups.flatMap((g) => g.fields));
838
+ const byName = new Map(visible.map((s) => [s.name, s]));
839
+ const main = { label: entry.singular ?? entry.label, fields: visible.filter((s) => !grouped.has(s.name)) };
840
+ const rest = groups.map((g) => ({ label: g.label, fields: g.fields.map((n) => byName.get(n)).filter((s) => Boolean(s)) })).filter((g) => g.fields.length > 0);
841
+ return [main, ...rest];
842
+ }, [visible, entry.groups, entry.label, entry.singular]);
843
+ const initialState = useMemo(() => Object.fromEntries(specs.map((s) => [s.name, toControl(s, initialValues[s.name])])), [specs, initialValues]);
844
+ const [state, setState] = useState(initialState);
845
+ const [deriving, setDeriving] = useState(() => Boolean(derive && !initialState[derive.target]));
846
+ const update = (name, v) => setState((st) => {
847
+ const next = { ...st, [name]: v };
848
+ if (derive && deriving && name === derive.source && typeof v === "string") {
849
+ next[derive.target] = (derive.transform ?? slugify)(v);
850
+ }
851
+ return next;
852
+ });
853
+ const onFieldChange = (name, v) => {
854
+ if (derive && deriving && name === derive.target) setDeriving(false);
855
+ update(name, v);
856
+ };
242
857
  const [body, setBody] = useState(initialBody);
858
+ const dirty = body !== initialBody || specs.some((s) => !sameValue(state[s.name], initialState[s.name]));
243
859
  const [issues, setIssues] = useState([]);
244
860
  const [attempted, setAttempted] = useState(false);
245
861
  const [submitting, setSubmitting] = useState(false);
@@ -262,6 +878,31 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
262
878
  live = false;
263
879
  };
264
880
  }, [visible]);
881
+ const leaving = useRef(false);
882
+ useEffect(() => {
883
+ if (!guardUnsaved || !dirty || submitting) return;
884
+ const onUnload = (e) => {
885
+ if (!leaving.current) {
886
+ e.preventDefault();
887
+ e.returnValue = LEAVE_MESSAGE;
888
+ }
889
+ };
890
+ const onClick = (e) => {
891
+ const a = e.target?.closest?.("a[href]");
892
+ if (!a || a.target === "_blank" || e.defaultPrevented || e.metaKey || e.ctrlKey) return;
893
+ if (a.origin !== window.location.origin) return;
894
+ if (!window.confirm(LEAVE_MESSAGE)) {
895
+ e.preventDefault();
896
+ e.stopPropagation();
897
+ } else leaving.current = true;
898
+ };
899
+ window.addEventListener("beforeunload", onUnload);
900
+ document.addEventListener("click", onClick, true);
901
+ return () => {
902
+ window.removeEventListener("beforeunload", onUnload);
903
+ document.removeEventListener("click", onClick, true);
904
+ };
905
+ }, [guardUnsaved, dirty, submitting]);
265
906
  const validate = useCallback(() => {
266
907
  const { frontmatter } = toFrontmatter(specs, state, hiddenValues);
267
908
  const result = entry.schema.safeParse(frontmatter);
@@ -290,9 +931,9 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
290
931
  /* @__PURE__ */ jsx("strong", { children: "This can't be saved yet." }),
291
932
  /* @__PURE__ */ jsx("ul", { children: formLevel.map((i, n) => /* @__PURE__ */ jsx("li", { children: i.message }, n)) })
292
933
  ] }),
293
- /* @__PURE__ */ jsxs("fieldset", { children: [
294
- /* @__PURE__ */ jsx("legend", { children: entry.label }),
295
- visible.map((s) => /* @__PURE__ */ jsx(
934
+ sections.map((sec) => /* @__PURE__ */ jsxs("fieldset", { children: [
935
+ /* @__PURE__ */ jsx("legend", { children: sec.label }),
936
+ sec.fields.map((s) => /* @__PURE__ */ jsx(
296
937
  Field,
297
938
  {
298
939
  formId,
@@ -301,36 +942,45 @@ function ContentForm({ entry, initialValues = {}, initialBody = "", onSubmit, on
301
942
  issues: byField.get(s.name) ?? [],
302
943
  current: typeof initialValues[s.name] === "string" ? initialValues[s.name] : void 0,
303
944
  options: optionsFor(s),
304
- onChange: (v) => setState((st) => ({ ...st, [s.name]: v })),
945
+ renderLabel,
946
+ onChange: (v) => onFieldChange(s.name, v),
305
947
  onBlur: () => {
306
948
  if (attempted) validate();
307
949
  }
308
950
  },
309
951
  s.name
310
952
  ))
311
- ] }),
953
+ ] }, sec.label)),
312
954
  (showBody || bodySpec) && /* @__PURE__ */ jsxs("fieldset", { children: [
313
955
  /* @__PURE__ */ jsx("legend", { children: bodySpec?.label ?? bodyLabel }),
314
- /* @__PURE__ */ jsx("label", { className: "realiiz-form__label", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
956
+ /* @__PURE__ */ jsx("label", { className: "realiiz-form__label realiiz-sr-only", htmlFor: `${formId}-body`, children: bodySpec?.label ?? bodyLabel }),
315
957
  /* @__PURE__ */ jsx(
316
- "textarea",
958
+ MarkdownEditor,
317
959
  {
318
960
  id: `${formId}-body`,
319
- name: "body",
320
- className: "realiiz-form__control realiiz-form__control--body",
321
961
  value: body,
962
+ onChange: setBody,
322
963
  placeholder: bodySpec?.placeholder ?? "Write in Markdown. Headings with ##, links as [text](url).",
323
- onChange: (e) => setBody(e.target.value)
964
+ renderPreview,
965
+ previewClassName,
966
+ termComponent
324
967
  }
325
968
  )
326
969
  ] }),
327
- /* @__PURE__ */ jsxs("div", { className: "realiiz-form__actions", children: [
970
+ footer && /* @__PURE__ */ jsx("div", { className: "realiiz-form__footer", children: footer }),
971
+ /* @__PURE__ */ jsxs("div", { className: stickyBar ? "realiiz-form__bar" : "realiiz-form__actions", "data-dirty": dirty || void 0, children: [
972
+ stickyBar && /* @__PURE__ */ jsx("span", { className: "realiiz-form__status", role: "status", children: status ?? (dirty ? "Unsaved changes" : "No changes yet") }),
973
+ stickyBar && barExtra,
328
974
  onCancel && /* @__PURE__ */ jsx("button", { type: "button", className: "realiiz-form__button", onClick: onCancel, children: "Cancel" }),
329
975
  /* @__PURE__ */ jsx("button", { type: "submit", className: "realiiz-form__button realiiz-form__button--primary", disabled: submitting, children: submitting ? `${submitLabel}\u2026` : submitLabel })
330
976
  ] })
331
977
  ] });
332
978
  }
979
+ function sameValue(a, b) {
980
+ if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i) => v === b[i]);
981
+ return a === b;
982
+ }
333
983
 
334
- export { ContentForm, PENDING_UPLOAD_PREFIX };
984
+ export { ChipsInput, ContentForm, LengthGauge, MarkdownEditor, PENDING_UPLOAD_PREFIX, TOOLS, slugify };
335
985
  //# sourceMappingURL=index.js.map
336
986
  //# sourceMappingURL=index.js.map