@thebes/cadmea 1.1.0 → 1.2.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>"];
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
+ ];
79
122
  const RichTextEditor = lazy(() => import("../RichTextEditor-DcLqdFY7.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,118 @@ function CollectionEdit(props) {
187
286
  }
188
287
  })));
189
288
  }
190
- function renderInput(key, field, value, setField, ctx) {
191
- const Widget = ctx.fieldWidgets?.[key];
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)
223
383
  });
224
384
  }
225
385
  });
226
386
  default: return null;
227
387
  }
228
388
  }
229
- function renderUploadInput(key, field, value, setField, ctx) {
389
+ function UploadControl(props) {
230
390
  const [uploading, setUploading] = createSignal(false);
231
391
  const [uploadError, setUploadError] = createSignal();
232
- return ssr(_tmpl$16, escape(createComponent(Show, {
233
- when: value,
392
+ const value = () => props.fieldApi().state.value;
393
+ return ssr(_tmpl$19, escape(createComponent(Show, {
394
+ get when() {
395
+ return value();
396
+ },
234
397
  get children() {
235
- return ssr(_tmpl$14$1, escape(value));
398
+ return ssr(_tmpl$17, escape(value()));
236
399
  }
237
- })), ssrAttribute("id", escape(key, true), false), ssrAttribute("required", field.required && !value, true), ssrAttribute("disabled", uploading(), true), escape(createComponent(Show, {
400
+ })), 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
401
  get when() {
239
402
  return uploading();
240
403
  },
@@ -246,57 +409,175 @@ function renderUploadInput(key, field, value, setField, ctx) {
246
409
  return uploadError();
247
410
  },
248
411
  get children() {
249
- return ssr(_tmpl$15, escape(uploadError()));
412
+ return ssr(_tmpl$18, escape(uploadError()));
250
413
  }
251
414
  })));
252
415
  }
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))
416
+ function RelationshipField(props) {
417
+ const [query, setQuery] = createSignal("");
418
+ const [open, setOpen] = createSignal(false);
419
+ const [active, setActive] = createSignal(0);
420
+ const listId = `${props.name}-listbox`;
421
+ const isMulti = () => props.field.hasMany === true;
422
+ const value = () => props.fieldApi().state.value;
423
+ const selectedIds = () => {
424
+ const v = value();
425
+ if (isMulti()) return Array.isArray(v) ? v : [];
426
+ return v != null ? [v] : [];
427
+ };
428
+ const selectedOptions = () => props.options.filter((o) => selectedIds().includes(o.id));
429
+ const filtered = () => {
430
+ const q = query().toLowerCase();
431
+ return props.options.filter((o) => o.label.toLowerCase().includes(q) && (!isMulti() || !selectedIds().includes(o.id)));
432
+ };
433
+ const singleLabel = () => selectedOptions()[0]?.label ?? "";
434
+ return ssr(_tmpl$23, escape(createComponent(Show, {
435
+ get when() {
436
+ return isMulti() && selectedOptions().length > 0;
437
+ },
438
+ get children() {
439
+ return ssr(_tmpl$20, escape(createComponent(For, {
440
+ get each() {
441
+ return selectedOptions();
442
+ },
443
+ children: (option) => ssr(_tmpl$24, escape(option.label), `Remove ${escape(option.label, true)}`)
444
+ })));
445
+ }
446
+ })), 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, {
447
+ get when() {
448
+ return !isMulti() && value() != null && !props.field.required;
449
+ },
450
+ get children() {
451
+ return ssr(_tmpl$21);
452
+ }
453
+ })), escape(createComponent(Show, {
454
+ get when() {
455
+ return open() && filtered().length > 0;
456
+ },
457
+ get children() {
458
+ return ssr(_tmpl$22, ssrAttribute("id", escape(listId, true), false), escape(createComponent(For, {
459
+ get each() {
460
+ return filtered();
461
+ },
462
+ 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))
463
+ })));
464
+ }
259
465
  })));
260
466
  }
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);
467
+ function renderArray(form, ctx, name, field, label) {
468
+ return createComponent(form.Field, {
469
+ name,
470
+ mode: "array",
471
+ children: (fieldApi) => createComponent(BlockEditor, {
472
+ form,
473
+ ctx,
474
+ name,
475
+ field,
476
+ label,
477
+ fieldApi
478
+ })
479
+ });
480
+ }
481
+ function variantLabel(disc, variant) {
482
+ return disc.variantsAdmin?.[variant]?.label ?? humanize(variant);
483
+ }
484
+ function BlockEditor(props) {
485
+ const [collapsed, setCollapsed] = createSignal(/* @__PURE__ */ new Set());
486
+ const [menuOpen, setMenuOpen] = createSignal(false);
487
+ const disc = props.field.discriminator;
488
+ const variants = disc ? Object.keys(disc.variants) : [];
489
+ const items = () => Array.isArray(props.fieldApi().state.value) ? props.fieldApi().state.value : [];
490
+ function blockTitle(item) {
491
+ if (disc) {
492
+ const v = item[disc.key];
493
+ if (typeof v === "string") return variantLabel(disc, v);
494
+ }
495
+ return props.label;
270
496
  }
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;
497
+ function blockSummary(item) {
498
+ for (const [key, f] of fieldsForItem(props.field, item)) {
499
+ if (key === disc?.key) continue;
500
+ if ((f.type === "text" || f.type === "select") && item[key]) return String(item[key]);
501
+ }
502
+ return "";
278
503
  }
279
- return ssr(_tmpl$18, escape(createComponent(For, {
504
+ return ssr(_tmpl$28, escape(props.label), escape(createComponent(Show, {
505
+ get when() {
506
+ return props.field.required;
507
+ },
508
+ get children() {
509
+ return ssr(_tmpl$9$1);
510
+ }
511
+ })), escape(createComponent(Show, {
512
+ get when() {
513
+ return props.field.admin?.description;
514
+ },
515
+ get children() {
516
+ return ssr(_tmpl$0$1, escape(props.field.admin?.description));
517
+ }
518
+ })), escape(createComponent(Index, {
280
519
  get each() {
281
520
  return items();
282
521
  },
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));
522
+ children: (item, index) => {
523
+ const isCollapsed = () => collapsed().has(index);
524
+ return ssr(_tmpl$31, ssrAttribute("aria-expanded", !isCollapsed(), false), isCollapsed() ? "▸" : "▾", escape(blockTitle(item())), escape(createComponent(Show, {
525
+ get when() {
526
+ return isCollapsed() && blockSummary(item());
527
+ },
528
+ get children() {
529
+ return ssr(_tmpl$29, escape(blockSummary(item())));
530
+ }
531
+ })), ssrAttribute("disabled", index === 0, true), ssrAttribute("disabled", index === items().length - 1, true), escape(createComponent(Show, {
532
+ get when() {
533
+ return !isCollapsed();
534
+ },
535
+ get children() {
536
+ return ssr(_tmpl$30, escape(createComponent(For, {
537
+ get each() {
538
+ return fieldsForItem(props.field, item());
539
+ },
540
+ children: ([itemKey, itemField]) => renderField(props.form, props.ctx, `${props.name}[${index}].${itemKey}`, itemField, labelFor(itemKey, itemField))
541
+ })));
542
+ }
543
+ })));
544
+ }
545
+ })), escape(createComponent(Show, {
546
+ get when() {
547
+ return disc && variants.length > 0;
548
+ },
549
+ get fallback() {
550
+ return ssr(_tmpl$32, escape(props.label));
551
+ },
552
+ get children() {
553
+ return ssr(_tmpl$27, ssrAttribute("aria-expanded", escape(menuOpen(), true), false), escape(createComponent(Show, {
554
+ get when() {
555
+ return menuOpen();
556
+ },
557
+ get children() {
558
+ return ssr(_tmpl$26, escape(createComponent(For, {
559
+ each: variants,
560
+ children: (variant) => ssr(_tmpl$34, escape(createComponent(Show, {
561
+ get when() {
562
+ return disc?.variantsAdmin?.[variant]?.icon;
563
+ },
564
+ get children() {
565
+ return ssr(_tmpl$33, ssrAttribute("class", escape(disc?.variantsAdmin?.[variant]?.icon, true), false));
566
+ }
567
+ })), escape(variantLabel(disc, variant)))
568
+ })));
569
+ }
570
+ })));
571
+ }
572
+ })));
573
+ }
574
+ function fieldsForItem(field, item) {
575
+ const base = Object.entries(field.fields);
576
+ const discriminator = field.discriminator;
577
+ if (!discriminator) return base;
578
+ const variantValue = item[discriminator.key];
579
+ const variantFields = typeof variantValue === "string" ? discriminator.variants[variantValue] : void 0;
580
+ return variantFields ? [...base, ...Object.entries(variantFields)] : base;
300
581
  }
301
582
  function formatDateValue(value) {
302
583
  if (!value) return "—";
@@ -310,36 +591,39 @@ var _tmpl$$3 = ["<button type=\"button\" class=\"btn btn-outline btn-sm\">", "</
310
591
  ">",
311
592
  "</select><select aria-label=\"Sort direction\" class=\"select select-sm join-item\"",
312
593
  "><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>",
594
+ ], _tmpl$3$2 = [
595
+ "<table class=\"table hidden md:table\"><thead>",
596
+ "</thead><tbody>",
317
597
  "</tbody></table>"
318
- ], _tmpl$5$1 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$6$1 = [
598
+ ], _tmpl$4$2 = ["<div class=\"flex flex-col gap-2 md:hidden\">", "</div>"], _tmpl$5$1 = [
319
599
  "<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
600
  ">Prev</button><span class=\"text-sm opacity-70\">Page ",
321
601
  "</span><button type=\"button\" class=\"btn btn-sm\"",
322
602
  ">Next</button></div>"
323
- ], _tmpl$7 = [
603
+ ], _tmpl$6$1 = [
324
604
  "<div class=\"flex flex-col gap-3\"><div class=\"flex flex-wrap items-center justify-between gap-2\">",
325
605
  "",
326
606
  "</div>",
327
607
  "",
328
608
  "</div>"
329
- ], _tmpl$8 = [
609
+ ], _tmpl$7 = [
330
610
  "<option",
331
611
  ">",
332
612
  "</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 = [
613
+ ], _tmpl$8 = ["<p class=\"text-sm opacity-70\">No ", " yet.</p>"], _tmpl$9 = "<th></th>", _tmpl$0 = [
614
+ "<tr>",
615
+ "",
616
+ "</tr>"
617
+ ], _tmpl$1 = ["<th>", "</th>"], _tmpl$10 = ["<td><input type=\"checkbox\" class=\"checkbox checkbox-sm\"", "></td>"], _tmpl$11 = [
334
618
  "<tr",
335
619
  ">",
336
620
  "",
337
621
  "</tr>"
338
- ], _tmpl$11 = ["<td>", "</td>"], _tmpl$12 = ["<input type=\"checkbox\" class=\"checkbox checkbox-sm mt-1\"", ">"], _tmpl$13 = [
622
+ ], _tmpl$12 = ["<td>", "</td>"], _tmpl$13 = ["<input type=\"checkbox\" class=\"checkbox checkbox-sm mt-1\"", ">"], _tmpl$14 = [
339
623
  "<div class=\"card bg-base-200 cursor-pointer p-3\" role=\"button\" tabindex=\"0\"><div class=\"flex items-start gap-3\">",
340
624
  "<div class=\"flex flex-1 flex-col gap-1\">",
341
625
  "</div></div></div>"
342
- ], _tmpl$14 = [
626
+ ], _tmpl$15 = [
343
627
  "<div class=\"flex justify-between gap-2 text-sm\"><span class=\"opacity-60\">",
344
628
  "</span><span class=\"text-right\">",
345
629
  "</span></div>"
@@ -361,9 +645,23 @@ function rowId(row) {
361
645
  return typeof row.id === "number" ? row.id : void 0;
362
646
  }
363
647
  function CollectionList(props) {
364
- const columns = () => listableFields(props.config);
648
+ const columns = () => listableFields(props.config).map(([key]) => ({
649
+ id: key,
650
+ accessorFn: (row) => row[key],
651
+ header: key,
652
+ cell: (info) => formatCellValue(info.getValue())
653
+ }));
654
+ const table = createSolidTable({
655
+ get data() {
656
+ return props.rows;
657
+ },
658
+ get columns() {
659
+ return columns();
660
+ },
661
+ getCoreRowModel: getCoreRowModel()
662
+ });
365
663
  const [selectMode, setSelectMode] = createSignal(false);
366
- return ssr(_tmpl$7, escape(createComponent(Show, {
664
+ return ssr(_tmpl$6$1, escape(createComponent(Show, {
367
665
  get when() {
368
666
  return props.selectable;
369
667
  },
@@ -377,9 +675,9 @@ function CollectionList(props) {
377
675
  get children() {
378
676
  return ssr(_tmpl$2$2, ssrAttribute("value", escape(props.sortField ?? "", true), false), escape(createComponent(For, {
379
677
  get each() {
380
- return columns();
678
+ return table.getAllColumns();
381
679
  },
382
- children: ([key]) => ssr(_tmpl$8, ssrAttribute("value", escape(key, true), false), escape(key))
680
+ children: (column) => ssr(_tmpl$7, ssrAttribute("value", escape(column.id, true), false), escape(column.id))
383
681
  })), ssrAttribute("value", escape(props.sortDirection ?? "asc", true), false));
384
682
  }
385
683
  })), escape(createComponent(Show, {
@@ -387,54 +685,59 @@ function CollectionList(props) {
387
685
  return props.rows.length > 0;
388
686
  },
389
687
  get fallback() {
390
- return ssr(_tmpl$9, escape(props.config.slug));
688
+ return ssr(_tmpl$8, escape(props.config.slug));
391
689
  },
392
690
  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, {
691
+ return [ssr(_tmpl$3$2, escape(createComponent(For, {
401
692
  get each() {
402
- return columns();
693
+ return table.getHeaderGroups();
403
694
  },
404
- children: ([key]) => ssr(_tmpl$0, escape(key))
695
+ children: (headerGroup) => ssr(_tmpl$0, escape(createComponent(Show, {
696
+ get when() {
697
+ return selectMode();
698
+ },
699
+ get children() {
700
+ return ssr(_tmpl$9);
701
+ }
702
+ })), escape(createComponent(For, {
703
+ get each() {
704
+ return headerGroup.headers;
705
+ },
706
+ children: (header) => ssr(_tmpl$1, escape(flexRender(header.column.columnDef.header, header.getContext())))
707
+ })))
405
708
  })), escape(createComponent(For, {
406
709
  get each() {
407
- return props.rows;
710
+ return table.getRowModel().rows;
408
711
  },
409
- children: (row) => ssr(_tmpl$10, ssrAttribute("class", props.onRowClick || selectMode() ? "cursor-pointer hover" : escape(void 0, true), false), escape(createComponent(Show, {
712
+ children: (row) => ssr(_tmpl$11, ssrAttribute("class", props.onRowClick || selectMode() ? "cursor-pointer hover" : escape(void 0, true), false), escape(createComponent(Show, {
410
713
  get when() {
411
714
  return selectMode();
412
715
  },
413
716
  get children() {
414
- return ssr(_tmpl$1, ssrAttribute("checked", rowId(row) !== void 0 && (props.selectedIds?.has(rowId(row)) ?? false), true));
717
+ return ssr(_tmpl$10, ssrAttribute("checked", rowId(row.original) !== void 0 && (props.selectedIds?.has(rowId(row.original)) ?? false), true));
415
718
  }
416
719
  })), escape(createComponent(For, {
417
720
  get each() {
418
- return columns();
721
+ return row.getVisibleCells();
419
722
  },
420
- children: ([key]) => ssr(_tmpl$11, escape(formatCellValue(row[key])))
723
+ children: (cell) => ssr(_tmpl$12, escape(flexRender(cell.column.columnDef.cell, cell.getContext())))
421
724
  })))
422
- }))), ssr(_tmpl$5$1, escape(createComponent(For, {
725
+ }))), ssr(_tmpl$4$2, escape(createComponent(For, {
423
726
  get each() {
424
- return props.rows;
727
+ return table.getRowModel().rows;
425
728
  },
426
- children: (row) => ssr(_tmpl$13, escape(createComponent(Show, {
729
+ children: (row) => ssr(_tmpl$14, escape(createComponent(Show, {
427
730
  get when() {
428
731
  return selectMode();
429
732
  },
430
733
  get children() {
431
- return ssr(_tmpl$12, ssrAttribute("checked", rowId(row) !== void 0 && (props.selectedIds?.has(rowId(row)) ?? false), true));
734
+ return ssr(_tmpl$13, ssrAttribute("checked", rowId(row.original) !== void 0 && (props.selectedIds?.has(rowId(row.original)) ?? false), true));
432
735
  }
433
736
  })), escape(createComponent(For, {
434
737
  get each() {
435
- return columns();
738
+ return row.getVisibleCells();
436
739
  },
437
- children: ([key]) => ssr(_tmpl$14, escape(key), escape(formatCellValue(row[key])))
740
+ children: (cell) => ssr(_tmpl$15, escape(cell.column.id), escape(flexRender(cell.column.columnDef.cell, cell.getContext())))
438
741
  })))
439
742
  })))];
440
743
  }
@@ -443,7 +746,7 @@ function CollectionList(props) {
443
746
  return props.page !== void 0 && props.pageSize !== void 0;
444
747
  },
445
748
  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));
749
+ 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
750
  }
448
751
  })));
449
752
  }