@thebes/cadmea 1.1.1 → 1.3.0

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,6 +1,8 @@
1
1
  import { createComponent, escape, ssr, ssrAttribute, ssrStyleProperty } from "solid-js/web";
2
- import { For, Show, Suspense, createEffect, createMemo, createSignal, lazy, onCleanup, onMount } from "solid-js";
3
- import { VISUAL_EDIT_MESSAGE } from "@thebes/cadmus/cms";
2
+ import { createForm } from "@tanstack/solid-form";
3
+ import { VISUAL_EDIT_MESSAGE, validateDocument } from "@thebes/cadmus/cms";
4
+ import { For, Index, Show, Suspense, createEffect, createMemo, createSignal, lazy, onCleanup, onMount } from "solid-js";
5
+ import { createSolidTable, flexRender, getCoreRowModel } from "@tanstack/solid-table";
4
6
  //#region src/CollectionEdit.tsx
5
7
  var _tmpl$$4 = ["<p class=\"text-sm text-error\" role=\"alert\">", "</p>"], _tmpl$2$3 = "<span class=\"loading loading-spinner loading-sm\"></span>", _tmpl$3$3 = [
6
8
  "<button type=\"button\" class=\"btn flex-1\"",
@@ -19,45 +21,50 @@ var _tmpl$$4 = ["<p class=\"text-sm text-error\" role=\"alert\">", "</p>"], _tmp
19
21
  "",
20
22
  "<div class=\"bg-base-100 sticky bottom-0 flex gap-2 border-t py-3\">",
21
23
  "</div></form>"
22
- ], _tmpl$7$1 = "<span class=\"text-error\"> *</span>", _tmpl$8$1 = [
23
- "<div class=\"form-control\"><label class=\"label\"",
24
+ ], _tmpl$7$1 = [
25
+ "<fieldset class=\"border-base-300 rounded-box border p-4\"><legend class=\"px-2 text-sm font-semibold\">",
26
+ "</legend>",
27
+ "</fieldset>"
28
+ ], _tmpl$8$1 = ["<div class=\"grid grid-cols-1 gap-4 md:grid-cols-2\">", "</div>"], _tmpl$9$1 = "<span class=\"text-error\"> *</span>", _tmpl$0$1 = ["<p class=\"text-base-content/60 mb-1 text-xs\">", "</p>"], _tmpl$1$1 = ["<p class=\"text-error mt-1 text-sm\" role=\"alert\">", "</p>"], _tmpl$10$1 = [
29
+ "<div class=\"",
30
+ "\"><label class=\"label\"",
24
31
  ">",
25
32
  "",
26
33
  "</label>",
34
+ "",
35
+ "",
27
36
  "</div>"
28
- ], _tmpl$9$1 = [
29
- "<button type=\"submit\" class=\"btn btn-primary flex-1\"",
30
- ">",
31
- "</button>"
32
- ], _tmpl$0$1 = [
37
+ ], _tmpl$11$1 = [
33
38
  "<input",
34
39
  " class=\"input\" type=\"text\"",
35
40
  "",
36
41
  ">"
37
- ], _tmpl$1$1 = [
42
+ ], _tmpl$12$1 = [
38
43
  "<select",
39
44
  " class=\"select\"",
40
45
  "",
46
+ "",
41
47
  ">",
42
48
  "</select>"
43
- ], _tmpl$10$1 = [
49
+ ], _tmpl$13$1 = [
44
50
  "<option",
45
51
  ">",
46
52
  "</option>"
47
- ], _tmpl$11$1 = [
53
+ ], _tmpl$14$1 = [
48
54
  "<input",
49
55
  " class=\"input\" type=\"number\"",
50
56
  "",
51
57
  ">"
52
- ], _tmpl$12$1 = [
58
+ ], _tmpl$15$1 = [
53
59
  "<input",
54
60
  " class=\"input\" type=\"text\" readonly",
55
61
  ">"
56
- ], _tmpl$13$1 = [
62
+ ], _tmpl$16 = [
57
63
  "<input",
58
64
  " class=\"checkbox\" type=\"checkbox\"",
65
+ "",
59
66
  ">"
60
- ], _tmpl$14$1 = ["<p class=\"text-sm opacity-70 break-all\">", "</p>"], _tmpl$15 = ["<p class=\"text-sm text-error\">", "</p>"], _tmpl$16 = [
67
+ ], _tmpl$17 = ["<p class=\"text-sm opacity-70 break-all\">", "</p>"], _tmpl$18 = ["<p class=\"text-sm text-error\">", "</p>"], _tmpl$19 = [
61
68
  "<div class=\"flex flex-col gap-2\">",
62
69
  "<input",
63
70
  " class=\"file-input\" type=\"file\"",
@@ -65,38 +72,115 @@ var _tmpl$$4 = ["<p class=\"text-sm text-error\" role=\"alert\">", "</p>"], _tmp
65
72
  ">",
66
73
  "",
67
74
  "</div>"
68
- ], _tmpl$17 = [
69
- "<select",
70
- " class=\"select\"",
75
+ ], _tmpl$20 = ["<div class=\"mb-1 flex flex-wrap gap-1\">", "</div>"], _tmpl$21 = "<button type=\"button\" aria-label=\"Clear\" class=\"absolute top-2 right-2 cursor-pointer opacity-60\">×</button>", _tmpl$22 = [
76
+ "<div",
77
+ " role=\"listbox\" class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex max-h-56 w-full flex-col overflow-auto border p-1 shadow\">",
78
+ "</div>"
79
+ ], _tmpl$23 = [
80
+ "<div class=\"relative\">",
81
+ "<input",
82
+ " type=\"text\" role=\"combobox\"",
83
+ " autocomplete=\"off\" class=\"input\"",
71
84
  "",
72
- "><option value>—</option>",
73
- "</select>"
74
- ], _tmpl$18 = [
85
+ ">",
86
+ "",
87
+ "</div>"
88
+ ], _tmpl$24 = [
89
+ "<span class=\"badge badge-primary gap-1\">",
90
+ "<button type=\"button\" aria-label=\"",
91
+ "\" class=\"cursor-pointer\">×</button></span>"
92
+ ], _tmpl$25 = [
93
+ "<button type=\"button\" role=\"option\"",
94
+ " class=\"",
95
+ "\">",
96
+ "</button>"
97
+ ], _tmpl$26 = ["<div role=\"menu\" class=\"bg-base-100 border-base-300 rounded-box absolute z-10 mt-1 flex flex-col border p-1 shadow\">", "</div>"], _tmpl$27 = [
98
+ "<div class=\"relative self-start\"><button type=\"button\" class=\"btn btn-outline btn-sm\" aria-haspopup=\"menu\"",
99
+ ">Add block</button>",
100
+ "</div>"
101
+ ], _tmpl$28 = [
102
+ "<div class=\"form-control md:col-span-2\"><div class=\"label font-medium\">",
103
+ "",
104
+ "</div>",
75
105
  "<div class=\"flex flex-col gap-3\">",
76
- "<button type=\"button\" class=\"btn btn-outline btn-sm self-start\">Add ",
77
- "</button></div>"
78
- ], _tmpl$19 = ["<div class=\"card bg-base-200 flex flex-col gap-2 p-3\">", "<button type=\"button\" class=\"btn btn-error btn-outline btn-sm self-start\">Remove</button></div>"];
79
- const RichTextEditor = lazy(() => import("../RichTextEditor-DcLqdFY7.js").then((mod) => ({ default: mod.RichTextEditor })));
106
+ "",
107
+ "</div></div>"
108
+ ], _tmpl$29 = ["<span class=\"text-base-content/60 truncate text-sm\">", "</span>"], _tmpl$30 = ["<div class=\"flex flex-col gap-2\">", "</div>"], _tmpl$31 = [
109
+ "<div class=\"card bg-base-200 flex flex-col gap-2 p-3\"><div class=\"flex items-center gap-2\"><button type=\"button\" class=\"btn btn-ghost btn-sm gap-2\"",
110
+ "><span aria-hidden=\"true\">",
111
+ "</span><span class=\"font-semibold\">",
112
+ "</span></button>",
113
+ "<div class=\"ml-auto flex gap-1\"><button type=\"button\" class=\"btn btn-ghost btn-xs\" aria-label=\"Move up\"",
114
+ ">↑</button><button type=\"button\" class=\"btn btn-ghost btn-xs\" aria-label=\"Move down\"",
115
+ ">↓</button><button type=\"button\" class=\"btn btn-ghost btn-xs\" aria-label=\"Duplicate\">⧉</button><button type=\"button\" class=\"btn btn-ghost btn-xs text-error\" aria-label=\"Remove\">Remove</button></div></div>",
116
+ "</div>"
117
+ ], _tmpl$32 = ["<button type=\"button\" class=\"btn btn-outline btn-sm self-start\">Add ", "</button>"], _tmpl$33 = ["<i", " aria-hidden=\"true\"></i>"], _tmpl$34 = [
118
+ "<button type=\"button\" role=\"menuitem\" class=\"flex items-center gap-2 rounded px-3 py-2 text-left\">",
119
+ "",
120
+ "</button>"
121
+ ];
122
+ const RichTextEditor = lazy(() => import("../RichTextEditor-CPQTvhQD.js").then((mod) => ({ default: mod.RichTextEditor })));
80
123
  function editableFields(config) {
81
124
  return Object.entries(config.fields).filter(([key]) => key !== "id");
82
125
  }
126
+ function humanize(key) {
127
+ const spaced = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
128
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1).toLowerCase();
129
+ }
130
+ function labelFor(key, field) {
131
+ return field.admin?.label ?? humanize(key);
132
+ }
133
+ function groupFields(entries) {
134
+ const groups = [];
135
+ const byName = /* @__PURE__ */ new Map();
136
+ for (const entry of entries) {
137
+ const name = entry[1].admin?.group;
138
+ let group = byName.get(name);
139
+ if (!group) {
140
+ group = {
141
+ name,
142
+ fields: []
143
+ };
144
+ byName.set(name, group);
145
+ groups.push(group);
146
+ }
147
+ group.fields.push(entry);
148
+ }
149
+ return groups;
150
+ }
83
151
  function CollectionEdit(props) {
84
- const initialSnapshot = JSON.stringify(props.initialValues ?? {});
85
- const [values, setValues] = createSignal(props.initialValues ?? {});
86
- createEffect(() => {
87
- props.onDirtyChange?.(JSON.stringify(values()) !== initialSnapshot);
88
- });
89
- function setField(key, value) {
90
- setValues((prev) => ({
91
- ...prev,
92
- [key]: value
93
- }));
152
+ const operation = props.initialValues?.id != null ? "update" : "create";
153
+ async function validateForm(value) {
154
+ const violations = await validateDocument(props.config, value, { operation });
155
+ const fields = {};
156
+ for (const v of violations) if (v.severity === "error" && !(v.path in fields)) fields[v.path] = v.message;
157
+ return Object.keys(fields).length > 0 ? { fields } : void 0;
158
+ }
159
+ const form = createForm(() => ({
160
+ defaultValues: props.initialValues ?? {},
161
+ validators: { onSubmitAsync: ({ value }) => validateForm(value) },
162
+ onSubmit: async ({ value }) => {
163
+ await props.onSubmit(editablePayload(value));
164
+ }
165
+ }));
166
+ const isDefaultValue = form.useStore((s) => s.isDefaultValue);
167
+ createEffect(() => props.onDirtyChange?.(!isDefaultValue()));
168
+ const formValues = form.useStore((s) => s.values);
169
+ function editablePayload(value) {
170
+ return Object.fromEntries(Object.entries(value).filter(([key]) => props.config.fields[key]?.type !== "date"));
94
171
  }
95
172
  const ctx = {
96
- onUploadFile: props.onUploadFile,
97
- relationshipOptions: props.relationshipOptions,
98
- fieldWidgets: props.fieldWidgets
173
+ get onUploadFile() {
174
+ return props.onUploadFile;
175
+ },
176
+ get relationshipOptions() {
177
+ return props.relationshipOptions;
178
+ },
179
+ get fieldWidgets() {
180
+ return props.fieldWidgets;
181
+ }
99
182
  };
183
+ const fieldGroups = groupFields(editableFields(props.config));
100
184
  const versioned = () => props.config.versions?.drafts && props.draftActions;
101
185
  return ssr(_tmpl$6$2, escape(createComponent(Show, {
102
186
  get when() {
@@ -106,17 +190,32 @@ function CollectionEdit(props) {
106
190
  return ssr(_tmpl$$4, escape(props.error));
107
191
  }
108
192
  })), escape(createComponent(For, {
109
- get each() {
110
- return editableFields(props.config);
111
- },
112
- children: ([key, field]) => ssr(_tmpl$8$1, ssrAttribute("for", escape(key, true), false), escape(key), escape(createComponent(Show, {
193
+ each: fieldGroups,
194
+ children: (group) => createComponent(Show, {
113
195
  get when() {
114
- return field.required;
196
+ return group.name;
197
+ },
198
+ get fallback() {
199
+ return createComponent(FieldsGrid, {
200
+ form,
201
+ ctx,
202
+ get fields() {
203
+ return group.fields;
204
+ },
205
+ values: formValues
206
+ });
115
207
  },
116
208
  get children() {
117
- return ssr(_tmpl$7$1);
209
+ return ssr(_tmpl$7$1, escape(group.name), escape(createComponent(FieldsGrid, {
210
+ form,
211
+ ctx,
212
+ get fields() {
213
+ return group.fields;
214
+ },
215
+ values: formValues
216
+ })));
118
217
  }
119
- })), escape(renderInput(key, field, values()[key], setField, ctx)))
218
+ })
120
219
  })), escape(createComponent(Show, {
121
220
  get when() {
122
221
  return versioned();
@@ -127,7 +226,7 @@ function CollectionEdit(props) {
127
226
  return props.capabilities?.canUpdate !== false;
128
227
  },
129
228
  get children() {
130
- return ssr(_tmpl$9$1, ssrAttribute("disabled", props.saving, true), escape(createComponent(Show, {
229
+ return ssr(_tmpl$4$3, ssrAttribute("disabled", props.saving, true), escape(createComponent(Show, {
131
230
  get when() {
132
231
  return props.saving;
133
232
  },
@@ -187,54 +286,121 @@ function CollectionEdit(props) {
187
286
  }
188
287
  })));
189
288
  }
190
- function renderInput(key, field, value, setField, ctx) {
191
- const Widget = ctx.fieldWidgets?.[key] ?? ctx.fieldWidgets?.[key.slice(key.lastIndexOf(".") + 1)];
289
+ function FieldsGrid(props) {
290
+ return ssr(_tmpl$8$1, escape(createComponent(For, {
291
+ get each() {
292
+ return props.fields;
293
+ },
294
+ children: ([key, field]) => createComponent(Show, {
295
+ get when() {
296
+ return !field.admin?.condition || field.admin.condition(props.values());
297
+ },
298
+ get children() {
299
+ return renderField(props.form, props.ctx, key, field, labelFor(key, field));
300
+ }
301
+ })
302
+ })));
303
+ }
304
+ function renderField(form, ctx, name, field, label) {
305
+ if (field.type === "array") return renderArray(form, ctx, name, field, label);
306
+ const spanClass = field.admin?.width === "half" ? "md:col-span-1" : "md:col-span-2";
307
+ return createComponent(form.Field, {
308
+ name,
309
+ children: (fieldApi) => ssr(_tmpl$10$1, `form-control ${escape(spanClass, true)}`, ssrAttribute("for", escape(name, true), false), escape(label), escape(createComponent(Show, {
310
+ get when() {
311
+ return field.required;
312
+ },
313
+ get children() {
314
+ return ssr(_tmpl$9$1);
315
+ }
316
+ })), escape(createComponent(Show, {
317
+ get when() {
318
+ return field.admin?.description;
319
+ },
320
+ get children() {
321
+ return ssr(_tmpl$0$1, escape(field.admin?.description));
322
+ }
323
+ })), escape(renderControl(ctx, name, field, fieldApi)), escape(createComponent(Show, {
324
+ get when() {
325
+ return (fieldApi().state.meta.errors?.length ?? 0) > 0;
326
+ },
327
+ get children() {
328
+ return ssr(_tmpl$1$1, escape(fieldApi().state.meta.errors.filter(Boolean).join(", ")));
329
+ }
330
+ })))
331
+ });
332
+ }
333
+ function renderControl(ctx, name, field, fieldApi) {
334
+ const Widget = ctx.fieldWidgets?.[name] ?? ctx.fieldWidgets?.[name.slice(name.lastIndexOf(".") + 1)];
192
335
  if (Widget) return createComponent(Widget, {
193
- fieldKey: key,
194
- value,
195
- setValue: (v) => setField(key, v),
336
+ fieldKey: name,
337
+ get value() {
338
+ return fieldApi().state.value;
339
+ },
340
+ setValue: (v) => fieldApi().handleChange(v),
196
341
  get onUploadFile() {
197
342
  return ctx.onUploadFile;
198
343
  }
199
344
  });
345
+ const readOnly = field.admin?.readOnly;
346
+ const change = (v) => fieldApi().handleChange(v);
200
347
  switch (field.type) {
201
- case "text": return ssr(_tmpl$0$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(value ?? "", true), false), ssrAttribute("required", field.required, true));
202
- case "select": return ssr(_tmpl$1$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(value ?? "", true), false), ssrAttribute("required", field.required, true), escape(createComponent(For, {
348
+ case "text": return ssr(_tmpl$11$1, ssrAttribute("id", escape(name, true), false), ssrAttribute("placeholder", escape(field.admin?.placeholder, true), false) + ssrAttribute("readonly", escape(readOnly, true), false) + ssrAttribute("value", escape(fieldApi().state.value ?? "", true), false), ssrAttribute("required", field.required, true));
349
+ case "select": return ssr(_tmpl$12$1, ssrAttribute("id", escape(name, true), false), ssrAttribute("value", escape(fieldApi().state.value ?? "", true), false), ssrAttribute("required", field.required, true), ssrAttribute("disabled", readOnly, true), escape(createComponent(For, {
203
350
  get each() {
204
351
  return field.options;
205
352
  },
206
- children: (option) => ssr(_tmpl$10$1, ssrAttribute("value", escape(option, true), false), escape(option))
353
+ children: (option) => ssr(_tmpl$13$1, ssrAttribute("value", escape(option, true), false), escape(option))
207
354
  })));
208
- case "number": return ssr(_tmpl$11$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(value ?? "", true), false), ssrAttribute("required", field.required, true));
209
- case "date": return ssr(_tmpl$12$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", escape(formatDateValue(value), true), false));
210
- case "checkbox": return ssr(_tmpl$13$1, ssrAttribute("id", escape(key, true), false), ssrAttribute("checked", value ?? false, true));
211
- case "upload": return renderUploadInput(key, field, value, setField, ctx);
212
- case "relationship": return renderRelationshipInput(key, field, value, setField, ctx);
213
- case "array": return renderArrayInput(key, field, value, setField, ctx);
355
+ case "number": return ssr(_tmpl$14$1, ssrAttribute("id", escape(name, true), false), ssrAttribute("placeholder", escape(field.admin?.placeholder, true), false) + ssrAttribute("readonly", escape(readOnly, true), false) + ssrAttribute("value", escape(fieldApi().state.value ?? "", true), false), ssrAttribute("required", field.required, true));
356
+ case "date": return ssr(_tmpl$15$1, ssrAttribute("id", escape(name, true), false), ssrAttribute("value", escape(formatDateValue(fieldApi().state.value), true), false));
357
+ case "checkbox": return ssr(_tmpl$16, ssrAttribute("id", escape(name, true), false), ssrAttribute("disabled", readOnly, true), ssrAttribute("checked", fieldApi().state.value ?? false, true));
358
+ case "upload": return createComponent(UploadControl, {
359
+ name,
360
+ field,
361
+ fieldApi,
362
+ ctx
363
+ });
364
+ case "relationship": return createComponent(RelationshipField, {
365
+ name,
366
+ field,
367
+ fieldApi,
368
+ get options() {
369
+ return ctx.relationshipOptions?.[field.relationTo] ?? [];
370
+ }
371
+ });
214
372
  case "richText": return createComponent(Suspense, {
215
373
  get fallback() {
216
374
  return ssr(_tmpl$2$3);
217
375
  },
218
376
  get children() {
219
377
  return createComponent(RichTextEditor, {
220
- id: key,
221
- content: value,
222
- onChange: (doc) => setField(key, doc)
378
+ id: name,
379
+ get content() {
380
+ return fieldApi().state.value;
381
+ },
382
+ onChange: (doc) => change(doc),
383
+ get onUploadFile() {
384
+ return ctx.onUploadFile;
385
+ }
223
386
  });
224
387
  }
225
388
  });
226
389
  default: return null;
227
390
  }
228
391
  }
229
- function renderUploadInput(key, field, value, setField, ctx) {
392
+ function UploadControl(props) {
230
393
  const [uploading, setUploading] = createSignal(false);
231
394
  const [uploadError, setUploadError] = createSignal();
232
- return ssr(_tmpl$16, escape(createComponent(Show, {
233
- when: value,
395
+ const value = () => props.fieldApi().state.value;
396
+ return ssr(_tmpl$19, escape(createComponent(Show, {
397
+ get when() {
398
+ return value();
399
+ },
234
400
  get children() {
235
- return ssr(_tmpl$14$1, escape(value));
401
+ return ssr(_tmpl$17, escape(value()));
236
402
  }
237
- })), ssrAttribute("id", escape(key, true), false), ssrAttribute("required", field.required && !value, true), ssrAttribute("disabled", uploading(), true), escape(createComponent(Show, {
403
+ })), ssrAttribute("id", escape(props.name, true), false), ssrAttribute("required", props.field.required && !value(), true), ssrAttribute("disabled", uploading() || props.field.admin?.readOnly, true), escape(createComponent(Show, {
238
404
  get when() {
239
405
  return uploading();
240
406
  },
@@ -246,57 +412,175 @@ function renderUploadInput(key, field, value, setField, ctx) {
246
412
  return uploadError();
247
413
  },
248
414
  get children() {
249
- return ssr(_tmpl$15, escape(uploadError()));
415
+ return ssr(_tmpl$18, escape(uploadError()));
250
416
  }
251
417
  })));
252
418
  }
253
- function renderRelationshipInput(key, field, value, setField, ctx) {
254
- if (field.hasMany) return null;
255
- const options = ctx.relationshipOptions?.[field.relationTo] ?? [];
256
- return ssr(_tmpl$17, ssrAttribute("id", escape(key, true), false), ssrAttribute("value", value != null ? escape(String(value), true) : "", false), ssrAttribute("required", field.required, true), escape(createComponent(For, {
257
- each: options,
258
- children: (option) => ssr(_tmpl$10$1, ssrAttribute("value", escape(option.id, true), false), escape(option.label))
419
+ function RelationshipField(props) {
420
+ const [query, setQuery] = createSignal("");
421
+ const [open, setOpen] = createSignal(false);
422
+ const [active, setActive] = createSignal(0);
423
+ const listId = `${props.name}-listbox`;
424
+ const isMulti = () => props.field.hasMany === true;
425
+ const value = () => props.fieldApi().state.value;
426
+ const selectedIds = () => {
427
+ const v = value();
428
+ if (isMulti()) return Array.isArray(v) ? v : [];
429
+ return v != null ? [v] : [];
430
+ };
431
+ const selectedOptions = () => props.options.filter((o) => selectedIds().includes(o.id));
432
+ const filtered = () => {
433
+ const q = query().toLowerCase();
434
+ return props.options.filter((o) => o.label.toLowerCase().includes(q) && (!isMulti() || !selectedIds().includes(o.id)));
435
+ };
436
+ const singleLabel = () => selectedOptions()[0]?.label ?? "";
437
+ return ssr(_tmpl$23, escape(createComponent(Show, {
438
+ get when() {
439
+ return isMulti() && selectedOptions().length > 0;
440
+ },
441
+ get children() {
442
+ return ssr(_tmpl$20, escape(createComponent(For, {
443
+ get each() {
444
+ return selectedOptions();
445
+ },
446
+ children: (option) => ssr(_tmpl$24, escape(option.label), `Remove ${escape(option.label, true)}`)
447
+ })));
448
+ }
449
+ })), ssrAttribute("id", escape(props.name, true), false), ssrAttribute("aria-expanded", escape(open(), true), false) + ssrAttribute("aria-controls", escape(listId, true), false), ssrAttribute("required", props.field.required && selectedIds().length === 0, true), ssrAttribute("disabled", props.field.admin?.readOnly, true) + ssrAttribute("placeholder", escape(props.field.admin?.placeholder ?? "Search…", true), false) + ssrAttribute("value", open() || isMulti() ? escape(query(), true) : escape(singleLabel(), true), false), escape(createComponent(Show, {
450
+ get when() {
451
+ return !isMulti() && value() != null && !props.field.required;
452
+ },
453
+ get children() {
454
+ return ssr(_tmpl$21);
455
+ }
456
+ })), escape(createComponent(Show, {
457
+ get when() {
458
+ return open() && filtered().length > 0;
459
+ },
460
+ get children() {
461
+ return ssr(_tmpl$22, ssrAttribute("id", escape(listId, true), false), escape(createComponent(For, {
462
+ get each() {
463
+ return filtered();
464
+ },
465
+ children: (option, i) => ssr(_tmpl$25, ssrAttribute("aria-selected", escape(selectedIds().includes(option.id), true), false), `rounded px-3 py-2 text-left ${i() === active() ? "bg-base-200" : ""}`, escape(option.label))
466
+ })));
467
+ }
259
468
  })));
260
469
  }
261
- function renderArrayInput(key, field, value, setField, ctx) {
262
- const items = () => Array.isArray(value) ? value : [];
263
- function updateItem(index, itemKey, itemValue) {
264
- const next = items().slice();
265
- next[index] = {
266
- ...next[index],
267
- [itemKey]: itemValue
268
- };
269
- setField(key, next);
470
+ function renderArray(form, ctx, name, field, label) {
471
+ return createComponent(form.Field, {
472
+ name,
473
+ mode: "array",
474
+ children: (fieldApi) => createComponent(BlockEditor, {
475
+ form,
476
+ ctx,
477
+ name,
478
+ field,
479
+ label,
480
+ fieldApi
481
+ })
482
+ });
483
+ }
484
+ function variantLabel(disc, variant) {
485
+ return disc.variantsAdmin?.[variant]?.label ?? humanize(variant);
486
+ }
487
+ function BlockEditor(props) {
488
+ const [collapsed, setCollapsed] = createSignal(/* @__PURE__ */ new Set());
489
+ const [menuOpen, setMenuOpen] = createSignal(false);
490
+ const disc = props.field.discriminator;
491
+ const variants = disc ? Object.keys(disc.variants) : [];
492
+ const items = () => Array.isArray(props.fieldApi().state.value) ? props.fieldApi().state.value : [];
493
+ function blockTitle(item) {
494
+ if (disc) {
495
+ const v = item[disc.key];
496
+ if (typeof v === "string") return variantLabel(disc, v);
497
+ }
498
+ return props.label;
270
499
  }
271
- function fieldsForItem(item) {
272
- const base = Object.entries(field.fields);
273
- const discriminator = field.discriminator;
274
- if (!discriminator) return base;
275
- const variantValue = item[discriminator.key];
276
- const variantFields = typeof variantValue === "string" ? discriminator.variants[variantValue] : void 0;
277
- return variantFields ? [...base, ...Object.entries(variantFields)] : base;
500
+ function blockSummary(item) {
501
+ for (const [key, f] of fieldsForItem(props.field, item)) {
502
+ if (key === disc?.key) continue;
503
+ if ((f.type === "text" || f.type === "select") && item[key]) return String(item[key]);
504
+ }
505
+ return "";
278
506
  }
279
- return ssr(_tmpl$18, escape(createComponent(For, {
507
+ return ssr(_tmpl$28, escape(props.label), escape(createComponent(Show, {
508
+ get when() {
509
+ return props.field.required;
510
+ },
511
+ get children() {
512
+ return ssr(_tmpl$9$1);
513
+ }
514
+ })), escape(createComponent(Show, {
515
+ get when() {
516
+ return props.field.admin?.description;
517
+ },
518
+ get children() {
519
+ return ssr(_tmpl$0$1, escape(props.field.admin?.description));
520
+ }
521
+ })), escape(createComponent(Index, {
280
522
  get each() {
281
523
  return items();
282
524
  },
283
- children: (item, index) => ssr(_tmpl$19, escape(createComponent(For, {
284
- get each() {
285
- return fieldsForItem(item);
286
- },
287
- children: ([itemKey, itemField]) => {
288
- const inputId = `${key}.${index()}.${itemKey}`;
289
- return ssr(_tmpl$8$1, ssrAttribute("for", escape(inputId, true), false), escape(itemKey), escape(createComponent(Show, {
290
- get when() {
291
- return itemField.required;
292
- },
293
- get children() {
294
- return ssr(_tmpl$7$1);
295
- }
296
- })), escape(renderInput(inputId, itemField, item[itemKey], (_, v) => updateItem(index(), itemKey, v), ctx)));
297
- }
298
- })))
299
- })), escape(key));
525
+ children: (item, index) => {
526
+ const isCollapsed = () => collapsed().has(index);
527
+ return ssr(_tmpl$31, ssrAttribute("aria-expanded", !isCollapsed(), false), isCollapsed() ? "▸" : "▾", escape(blockTitle(item())), escape(createComponent(Show, {
528
+ get when() {
529
+ return isCollapsed() && blockSummary(item());
530
+ },
531
+ get children() {
532
+ return ssr(_tmpl$29, escape(blockSummary(item())));
533
+ }
534
+ })), ssrAttribute("disabled", index === 0, true), ssrAttribute("disabled", index === items().length - 1, true), escape(createComponent(Show, {
535
+ get when() {
536
+ return !isCollapsed();
537
+ },
538
+ get children() {
539
+ return ssr(_tmpl$30, escape(createComponent(For, {
540
+ get each() {
541
+ return fieldsForItem(props.field, item());
542
+ },
543
+ children: ([itemKey, itemField]) => renderField(props.form, props.ctx, `${props.name}[${index}].${itemKey}`, itemField, labelFor(itemKey, itemField))
544
+ })));
545
+ }
546
+ })));
547
+ }
548
+ })), escape(createComponent(Show, {
549
+ get when() {
550
+ return disc && variants.length > 0;
551
+ },
552
+ get fallback() {
553
+ return ssr(_tmpl$32, escape(props.label));
554
+ },
555
+ get children() {
556
+ return ssr(_tmpl$27, ssrAttribute("aria-expanded", escape(menuOpen(), true), false), escape(createComponent(Show, {
557
+ get when() {
558
+ return menuOpen();
559
+ },
560
+ get children() {
561
+ return ssr(_tmpl$26, escape(createComponent(For, {
562
+ each: variants,
563
+ children: (variant) => ssr(_tmpl$34, escape(createComponent(Show, {
564
+ get when() {
565
+ return disc?.variantsAdmin?.[variant]?.icon;
566
+ },
567
+ get children() {
568
+ return ssr(_tmpl$33, ssrAttribute("class", escape(disc?.variantsAdmin?.[variant]?.icon, true), false));
569
+ }
570
+ })), escape(variantLabel(disc, variant)))
571
+ })));
572
+ }
573
+ })));
574
+ }
575
+ })));
576
+ }
577
+ function fieldsForItem(field, item) {
578
+ const base = Object.entries(field.fields);
579
+ const discriminator = field.discriminator;
580
+ if (!discriminator) return base;
581
+ const variantValue = item[discriminator.key];
582
+ const variantFields = typeof variantValue === "string" ? discriminator.variants[variantValue] : void 0;
583
+ return variantFields ? [...base, ...Object.entries(variantFields)] : base;
300
584
  }
301
585
  function formatDateValue(value) {
302
586
  if (!value) return "—";
@@ -310,36 +594,39 @@ var _tmpl$$3 = ["<button type=\"button\" class=\"btn btn-outline btn-sm\">", "</
310
594
  ">",
311
595
  "</select><select aria-label=\"Sort direction\" class=\"select select-sm join-item\"",
312
596
  "><option value=\"asc\">Ascending</option><option value=\"desc\">Descending</option></select></div>"
313
- ], _tmpl$3$2 = "<th></th>", _tmpl$4$2 = [
314
- "<table class=\"table hidden md:table\"><thead><tr>",
315
- "",
316
- "</tr></thead><tbody>",
597
+ ], _tmpl$3$2 = [
598
+ "<table class=\"table hidden md:table\"><thead>",
599
+ "</thead><tbody>",
317
600
  "</tbody></table>"
318
- ], _tmpl$5$1 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$6$1 = [
601
+ ], _tmpl$4$2 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$5$1 = [
319
602
  "<div class=\"bg-base-100 sticky bottom-0 flex items-center justify-between gap-2 border-t py-2\"><button type=\"button\" class=\"btn btn-sm\"",
320
603
  ">Prev</button><span class=\"text-sm opacity-70\">Page ",
321
604
  "</span><button type=\"button\" class=\"btn btn-sm\"",
322
605
  ">Next</button></div>"
323
- ], _tmpl$7 = [
606
+ ], _tmpl$6$1 = [
324
607
  "<div class=\"flex flex-col gap-3\"><div class=\"flex flex-wrap items-center justify-between gap-2\">",
325
608
  "",
326
609
  "</div>",
327
610
  "",
328
611
  "</div>"
329
- ], _tmpl$8 = [
612
+ ], _tmpl$7 = [
330
613
  "<option",
331
614
  ">",
332
615
  "</option>"
333
- ], _tmpl$9 = ["<p class=\"text-sm opacity-70\">No ", " yet.</p>"], _tmpl$0 = ["<th>", "</th>"], _tmpl$1 = ["<td><input type=\"checkbox\" class=\"checkbox checkbox-sm\"", "></td>"], _tmpl$10 = [
616
+ ], _tmpl$8 = ["<p class=\"text-sm opacity-70\">No ", " yet.</p>"], _tmpl$9 = "<th></th>", _tmpl$0 = [
617
+ "<tr>",
618
+ "",
619
+ "</tr>"
620
+ ], _tmpl$1 = ["<th>", "</th>"], _tmpl$10 = ["<td><input type=\"checkbox\" class=\"checkbox checkbox-sm\"", "></td>"], _tmpl$11 = [
334
621
  "<tr",
335
622
  ">",
336
623
  "",
337
624
  "</tr>"
338
- ], _tmpl$11 = ["<td>", "</td>"], _tmpl$12 = ["<input type=\"checkbox\" class=\"checkbox checkbox-sm mt-1\"", ">"], _tmpl$13 = [
625
+ ], _tmpl$12 = ["<td>", "</td>"], _tmpl$13 = ["<input type=\"checkbox\" class=\"checkbox checkbox-sm mt-1\"", ">"], _tmpl$14 = [
339
626
  "<div class=\"card bg-base-200 cursor-pointer p-3\" role=\"button\" tabindex=\"0\"><div class=\"flex items-start gap-3\">",
340
627
  "<div class=\"flex flex-1 flex-col gap-1\">",
341
628
  "</div></div></div>"
342
- ], _tmpl$14 = [
629
+ ], _tmpl$15 = [
343
630
  "<div class=\"flex justify-between gap-2 text-sm\"><span class=\"opacity-60\">",
344
631
  "</span><span class=\"text-right\">",
345
632
  "</span></div>"
@@ -361,9 +648,23 @@ function rowId(row) {
361
648
  return typeof row.id === "number" ? row.id : void 0;
362
649
  }
363
650
  function CollectionList(props) {
364
- const columns = () => listableFields(props.config);
651
+ const columns = () => listableFields(props.config).map(([key]) => ({
652
+ id: key,
653
+ accessorFn: (row) => row[key],
654
+ header: key,
655
+ cell: (info) => formatCellValue(info.getValue())
656
+ }));
657
+ const table = createSolidTable({
658
+ get data() {
659
+ return props.rows;
660
+ },
661
+ get columns() {
662
+ return columns();
663
+ },
664
+ getCoreRowModel: getCoreRowModel()
665
+ });
365
666
  const [selectMode, setSelectMode] = createSignal(false);
366
- return ssr(_tmpl$7, escape(createComponent(Show, {
667
+ return ssr(_tmpl$6$1, escape(createComponent(Show, {
367
668
  get when() {
368
669
  return props.selectable;
369
670
  },
@@ -377,9 +678,9 @@ function CollectionList(props) {
377
678
  get children() {
378
679
  return ssr(_tmpl$2$2, ssrAttribute("value", escape(props.sortField ?? "", true), false), escape(createComponent(For, {
379
680
  get each() {
380
- return columns();
681
+ return table.getAllColumns();
381
682
  },
382
- children: ([key]) => ssr(_tmpl$8, ssrAttribute("value", escape(key, true), false), escape(key))
683
+ children: (column) => ssr(_tmpl$7, ssrAttribute("value", escape(column.id, true), false), escape(column.id))
383
684
  })), ssrAttribute("value", escape(props.sortDirection ?? "asc", true), false));
384
685
  }
385
686
  })), escape(createComponent(Show, {
@@ -387,54 +688,59 @@ function CollectionList(props) {
387
688
  return props.rows.length > 0;
388
689
  },
389
690
  get fallback() {
390
- return ssr(_tmpl$9, escape(props.config.slug));
691
+ return ssr(_tmpl$8, escape(props.config.slug));
391
692
  },
392
693
  get children() {
393
- return [ssr(_tmpl$4$2, escape(createComponent(Show, {
394
- get when() {
395
- return selectMode();
396
- },
397
- get children() {
398
- return ssr(_tmpl$3$2);
399
- }
400
- })), escape(createComponent(For, {
694
+ return [ssr(_tmpl$3$2, escape(createComponent(For, {
401
695
  get each() {
402
- return columns();
696
+ return table.getHeaderGroups();
403
697
  },
404
- children: ([key]) => ssr(_tmpl$0, escape(key))
698
+ children: (headerGroup) => ssr(_tmpl$0, escape(createComponent(Show, {
699
+ get when() {
700
+ return selectMode();
701
+ },
702
+ get children() {
703
+ return ssr(_tmpl$9);
704
+ }
705
+ })), escape(createComponent(For, {
706
+ get each() {
707
+ return headerGroup.headers;
708
+ },
709
+ children: (header) => ssr(_tmpl$1, escape(flexRender(header.column.columnDef.header, header.getContext())))
710
+ })))
405
711
  })), escape(createComponent(For, {
406
712
  get each() {
407
- return props.rows;
713
+ return table.getRowModel().rows;
408
714
  },
409
- children: (row) => ssr(_tmpl$10, ssrAttribute("class", props.onRowClick || selectMode() ? "cursor-pointer hover" : escape(void 0, true), false), escape(createComponent(Show, {
715
+ children: (row) => ssr(_tmpl$11, ssrAttribute("class", props.onRowClick || selectMode() ? "cursor-pointer hover" : escape(void 0, true), false), escape(createComponent(Show, {
410
716
  get when() {
411
717
  return selectMode();
412
718
  },
413
719
  get children() {
414
- return ssr(_tmpl$1, ssrAttribute("checked", rowId(row) !== void 0 && (props.selectedIds?.has(rowId(row)) ?? false), true));
720
+ return ssr(_tmpl$10, ssrAttribute("checked", rowId(row.original) !== void 0 && (props.selectedIds?.has(rowId(row.original)) ?? false), true));
415
721
  }
416
722
  })), escape(createComponent(For, {
417
723
  get each() {
418
- return columns();
724
+ return row.getVisibleCells();
419
725
  },
420
- children: ([key]) => ssr(_tmpl$11, escape(formatCellValue(row[key])))
726
+ children: (cell) => ssr(_tmpl$12, escape(flexRender(cell.column.columnDef.cell, cell.getContext())))
421
727
  })))
422
- }))), ssr(_tmpl$5$1, escape(createComponent(For, {
728
+ }))), ssr(_tmpl$4$2, escape(createComponent(For, {
423
729
  get each() {
424
- return props.rows;
730
+ return table.getRowModel().rows;
425
731
  },
426
- children: (row) => ssr(_tmpl$13, escape(createComponent(Show, {
732
+ children: (row) => ssr(_tmpl$14, escape(createComponent(Show, {
427
733
  get when() {
428
734
  return selectMode();
429
735
  },
430
736
  get children() {
431
- return ssr(_tmpl$12, ssrAttribute("checked", rowId(row) !== void 0 && (props.selectedIds?.has(rowId(row)) ?? false), true));
737
+ return ssr(_tmpl$13, ssrAttribute("checked", rowId(row.original) !== void 0 && (props.selectedIds?.has(rowId(row.original)) ?? false), true));
432
738
  }
433
739
  })), escape(createComponent(For, {
434
740
  get each() {
435
- return columns();
741
+ return row.getVisibleCells();
436
742
  },
437
- children: ([key]) => ssr(_tmpl$14, escape(key), escape(formatCellValue(row[key])))
743
+ children: (cell) => ssr(_tmpl$15, escape(cell.column.id), escape(flexRender(cell.column.columnDef.cell, cell.getContext())))
438
744
  })))
439
745
  })))];
440
746
  }
@@ -443,7 +749,7 @@ function CollectionList(props) {
443
749
  return props.page !== void 0 && props.pageSize !== void 0;
444
750
  },
445
751
  get children() {
446
- return ssr(_tmpl$6$1, ssrAttribute("disabled", (props.page ?? 1) <= 1, true), escape(props.page), ssrAttribute("disabled", props.totalCount !== void 0 ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount : props.rows.length < (props.pageSize ?? 0), true));
752
+ return ssr(_tmpl$5$1, ssrAttribute("disabled", (props.page ?? 1) <= 1, true), escape(props.page), ssrAttribute("disabled", props.totalCount !== void 0 ? (props.page ?? 1) * (props.pageSize ?? 0) >= props.totalCount : props.rows.length < (props.pageSize ?? 0), true));
447
753
  }
448
754
  })));
449
755
  }