@ghentcdh/json-forms-vue 1.1.1 → 2.0.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.
Files changed (65) hide show
  1. package/composables/useFormEvents.d.ts +4 -2
  2. package/composables/useHttpClient.d.ts +12 -0
  3. package/forms/FormComponent.properties.d.ts +16 -0
  4. package/forms/FormComponent.vue.d.ts +28 -1
  5. package/forms/errorMode.d.ts +1 -0
  6. package/forms/modal/FormModal.properties.d.ts +11 -0
  7. package/forms/modal/FormModal.vue.d.ts +83 -5
  8. package/forms/modal/FormModalService.d.ts +29 -1
  9. package/forms/renderers/array/ArrayRenderers.d.ts +1 -1
  10. package/forms/renderers/controls/{composable/UseControlBinding.d.ts → composables/useControlBinding.d.ts} +8 -1
  11. package/forms/renderers/controls/{composable/UseFetchOption.d.ts → composables/useFetchOption.d.ts} +4 -3
  12. package/forms/renderers/controls/composables/useReadonlyBinding.d.ts +135 -0
  13. package/forms/renderers/controls/{composable/UseSelectBinding.d.ts → composables/useSelectBinding.d.ts} +6 -0
  14. package/forms/renderers/controls/index.d.ts +2 -2
  15. package/forms/renderers/controls/readonly/ControlReadonlyRenderer.vue.d.ts +7 -0
  16. package/forms/renderers/controls/readonly/ReadonlyLabel.vue.d.ts +254 -0
  17. package/forms/renderers/controls/readonly/__tests__/formatDate.spec.d.ts +1 -0
  18. package/forms/renderers/controls/readonly/constants.d.ts +2 -0
  19. package/forms/renderers/controls/readonly/displayValue/BooleanValue.vue.d.ts +46 -0
  20. package/forms/renderers/controls/readonly/displayValue/DateValue.vue.d.ts +46 -0
  21. package/forms/renderers/controls/readonly/displayValue/LinkValue.vue.d.ts +46 -0
  22. package/forms/renderers/controls/readonly/displayValue/MarkdownValue.vue.d.ts +46 -0
  23. package/forms/renderers/controls/readonly/displayValue/NotSetValue.vue.d.ts +46 -0
  24. package/forms/renderers/controls/readonly/displayValue/NumberValue.vue.d.ts +46 -0
  25. package/forms/renderers/controls/readonly/displayValue/ObjectValue.vue.d.ts +46 -0
  26. package/forms/renderers/controls/readonly/displayValue/StringValue.vue.d.ts +46 -0
  27. package/forms/renderers/controls/readonly/displayValue/ViewDetailValue.vue.d.ts +46 -0
  28. package/forms/renderers/controls/readonly/displayValue/displayValue.properties.d.ts +23 -0
  29. package/forms/renderers/controls/readonly/displayValue/formatDate.d.ts +25 -0
  30. package/forms/renderers/controls/readonly/displayValue/index.d.ts +1 -0
  31. package/forms/renderers/{layout/LayoutRenders.d.ts → controls/readonly/index.d.ts} +4 -4
  32. package/forms/renderers/controls/readonly/useDisplayValue.d.ts +1 -0
  33. package/forms/renderers/controls/{composable/resource.d.ts → resource.d.ts} +88 -13
  34. package/forms/renderers/index.d.ts +42 -2
  35. package/forms/{renderes.d.ts → renderers/layout/LayoutRenderers.d.ts} +9 -8
  36. package/forms/renderers/layout/ReadOnlyLayoutRenderer.vue.d.ts +7 -0
  37. package/http-client.d.ts +14 -0
  38. package/index.d.ts +15 -8
  39. package/index.js +1702 -1077
  40. package/package.json +14 -22
  41. package/repository/{crud.repository.d.ts → CrudRepository.d.ts} +9 -7
  42. package/repository/index.d.ts +1 -1
  43. package/table/TableComponent.properties.d.ts +49 -0
  44. package/table/TableComponent.vue.d.ts +92 -0
  45. package/table/TableToolbar.vue.d.ts +36 -0
  46. package/table/cells/index.d.ts +10 -0
  47. package/table/filter/{table-filter.vue.d.ts → FilterRowInput.vue.d.ts} +11 -7
  48. package/table/filter/TableFilter.vue.d.ts +16 -0
  49. package/table/index.d.ts +6 -2
  50. package/testers/__tests__/jsonforms-testers.spec.d.ts +1 -0
  51. package/testers/__tests__/tester.spec.d.ts +1 -0
  52. package/testers/jsonforms-testers.d.ts +27 -0
  53. package/testers/tester.d.ts +16 -0
  54. package/view/modal/ViewModal.properties.d.ts +82 -0
  55. package/{forms/FormWithActions.vue.d.ts → view/modal/ViewModal.vue.d.ts} +150 -51
  56. package/form.store.d.ts +0 -8
  57. package/forms/FormWithActions.properties.d.ts +0 -52
  58. package/forms/FormWithTable.properties.d.ts +0 -65
  59. package/forms/FormWithTable.vue.d.ts +0 -90
  60. package/renderes/tester.d.ts +0 -11
  61. package/table/table.component.properties.d.ts +0 -34
  62. package/table/table.component.vue.d.ts +0 -60
  63. package/table/table.store.d.ts +0 -29
  64. /package/forms/renderers/controls/{composable/UseInput.d.ts → composables/useInput.d.ts} +0 -0
  65. /package/forms/{renderer-registry.d.ts → renderers/registry.d.ts} +0 -0
package/index.js CHANGED
@@ -1,399 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const vue = require("vue");
4
- const lodashEs = require("lodash-es");
5
- const jsonFormsCore = require("@ghentcdh/json-forms-core");
6
4
  const ui = require("@ghentcdh/ui");
7
- const core = require("@vueuse/core");
8
- const vueRouter = require("vue-router");
9
- const toolsVue = require("@ghentcdh/tools-vue");
10
- const zod = require("zod");
11
5
  const veeValidate = require("vee-validate");
12
- const core$1 = require("@jsonforms/core");
13
- const testers = require("@jsonforms/core/src/testers/testers");
14
- const axios = require("axios");
15
- const FormModalProperties = {
16
- /** Title displayed in the modal header. */
17
- modalTitle: { type: String, required: true },
18
- /** Label for the save button. */
19
- saveLabel: { type: String, default: "save" },
20
- /** Label for the cancel button. */
21
- cancelLabel: { type: String, default: "cancel" },
22
- /** JSON schema describing the shape of the form data. */
23
- schema: { type: Object, required: true },
24
- /** UI schema describing the layout and controls. */
25
- uiSchema: { type: Object, required: true },
26
- /** Modal width (`'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`). */
27
- modalSize: { type: String, default: "md" },
28
- /** Callback invoked when the modal closes (with result or `null` on cancel). */
29
- onClose: {
30
- type: Function,
31
- required: true
32
- },
33
- /** Callback for form events dispatched by custom renderers. */
34
- onEvents: {
35
- type: Function
36
- },
37
- /** Initial form data to populate the form with. */
38
- data: { type: Object, required: true },
39
- /** When validation errors are shown. */
40
- errorMode: {
41
- type: String,
42
- default: "onBlur"
43
- }
44
- };
45
- const FormModalEmits = [
46
- /** Emitted when the modal is closed (submit or cancel). */
47
- "closeModal",
48
- /** Emitted when a custom renderer dispatches a form event. */
49
- "events",
50
- /** Emitted when validation errors change. */
51
- "errors",
52
- /** Emitted when form validity changes. */
53
- "valid"
54
- ];
55
- const _hoisted_1$8 = { class: "" };
56
- const _hoisted_2$2 = { class: "flex gap-2 items-center mb-2" };
57
- const _hoisted_3$1 = { class: "flex gap-2" };
58
- const _sfc_main$h = /* @__PURE__ */ vue.defineComponent({
59
- __name: "table-filter",
60
- props: {
61
- layout: {},
62
- filters: {}
63
- },
64
- emits: ["changeFilters", "removeFilter"],
65
- setup(__props, { emit: __emit }) {
66
- const formData = vue.ref();
67
- const properties = __props;
68
- const emits = __emit;
69
- vue.watch(
70
- () => properties.filters,
71
- () => {
72
- formData.value = {};
73
- properties.filters.forEach((filter) => {
74
- formData.value[filter.key] = filter.value;
75
- });
76
- },
77
- { immediate: true }
78
- );
79
- const onResetFilters = () => {
80
- emits("changeFilters", {});
81
- };
82
- const removeFilter = (filter) => {
83
- formData.value[filter.key] = void 0;
84
- emits("changeFilters", formData.value);
85
- };
6
+ const zod = require("zod");
7
+ const jsonFormsCore = require("@ghentcdh/json-forms-core");
8
+ const _hoisted_1$k = { class: "text-xs bg-base-200 p-2 h-full border-b border-gray-200 max-w-96 last-of-type:border-b-0 text-left font-semibold text-text-accent flex items-center" };
9
+ const _hoisted_2$8 = { class: "h-full p-2 whitespace-pre-wrap break-words overflow-hidden min-w-0 text-sm" };
10
+ const _sfc_main$u = /* @__PURE__ */ vue.defineComponent({
11
+ __name: "ReadonlyLabel",
12
+ props: ui.ControlWrapperProperties,
13
+ setup(__props) {
86
14
  return (_ctx, _cache) => {
87
- return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$8, [
88
- vue.createElementVNode("div", _hoisted_2$2, [
89
- __props.filters.length ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
90
- key: 0,
91
- size: "xs",
92
- outline: true,
93
- onClick: onResetFilters
94
- }, {
95
- default: vue.withCtx(() => [..._cache[0] || (_cache[0] = [
96
- vue.createTextVNode(" Reset all filters ", -1)
97
- ])]),
98
- _: 1
99
- })) : vue.createCommentVNode("", true)
15
+ return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
16
+ vue.createElementVNode("div", _hoisted_1$k, [
17
+ vue.createElementVNode("span", null, vue.toDisplayString(_ctx.label), 1)
100
18
  ]),
101
- vue.createElementVNode("div", _hoisted_3$1, [
102
- (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.filters, (filter) => {
103
- return vue.openBlock(), vue.createBlock(vue.unref(ui.BtnBadge), {
104
- key: filter.key,
105
- icon: vue.unref(ui.IconEnum).Close,
106
- onClick: ($event) => removeFilter(filter)
107
- }, {
108
- default: vue.withCtx(() => [
109
- vue.createTextVNode(vue.toDisplayString(filter.label) + ": " + vue.toDisplayString(filter.value), 1)
110
- ]),
111
- _: 2
112
- }, 1032, ["icon", "onClick"]);
113
- }), 128))
114
- ])
115
- ]);
116
- };
117
- }
118
- });
119
- const TableComponentProperties = {
120
- id: { type: String, required: true },
121
- uiSchema: { type: Object, required: true },
122
- schema: { type: Object, required: true },
123
- filterUiSchema: { type: Object },
124
- filterSchema: { type: Object },
125
- uri: { type: String, required: true },
126
- reload: { type: Number },
127
- actions: { type: Array }
128
- };
129
- const TableComponentEmits = ["delete", "edit"];
130
- class TableStore {
131
- constructor() {
132
- this.route = vueRouter.useRoute();
133
- this.router = vueRouter.useRouter();
134
- this.requestData = vue.ref(jsonFormsCore.RequestSchema.parse(this.route.query));
135
- this._reload = vue.ref(Date.now());
136
- this.loading = vue.ref(true);
137
- this.uri = vue.ref("");
138
- this.data = core.computedAsync(async () => {
139
- this._reload.value;
140
- if (!this.uri.value) return null;
141
- this.loading.value = true;
142
- if (this.requestData.value.page < 1) {
143
- this.requestData.value.page = 1;
144
- }
145
- const response = await toolsVue.useApi().get(this.uri.value, {
146
- params: this.requestData.value
147
- }).catch((error) => {
148
- console.error(error);
149
- return { data: [], request: { totalPages: 1, page: 1 } };
150
- }).finally(() => this.loading.value = false);
151
- const data = response.data;
152
- if (data.request.totalPages < data.request.page) {
153
- this.updateRequest({ page: data.request.totalPages });
154
- }
155
- return data;
156
- });
157
- this.pageData = vue.computed(() => {
158
- const request = this.data.value?.request ?? {
159
- count: 0,
160
- pageSize: 1,
161
- page: 1
162
- };
163
- return {
164
- count: request.count,
165
- pageSize: request.pageSize,
166
- page: request.page
167
- };
168
- });
169
- this.tableData = vue.computed(() => {
170
- const d = this.data.value;
171
- if (!d) return [];
172
- if (this.loading.value) return [];
173
- return d.data ?? [];
174
- });
175
- this.init = (url) => {
176
- this.uri.value = url;
177
- };
178
- this.updateRequest = (data) => {
179
- this.requestData.value = { ...this.requestData.value, ...data };
180
- this.router.replace({
181
- query: {
182
- ...this.route.query,
183
- ...this.requestData.value
184
- }
185
- });
186
- };
187
- this.sort = (id) => {
188
- const sortDir = this.requestData.value.sort === id && this.requestData.value.sortDir === "asc" ? "desc" : "asc";
189
- this.updateRequest({ sort: id, sortDir });
190
- };
191
- this.updateFilters = (filters) => {
192
- const filter = [];
193
- Object.entries(filters).forEach(([key, value]) => {
194
- if (!value) return;
195
- const operator = value?.operator || "contains";
196
- filter.push(`${key}:${value}:${operator}`);
197
- });
198
- this.updateRequest({ filter });
199
- };
200
- this.sorting = vue.computed(() => {
201
- const requestData = this.requestData.value;
202
- return {
203
- sortColumn: requestData.sort,
204
- sortDirection: requestData.sortDir ?? "asc"
205
- };
206
- });
207
- this.filters = vue.computed(
208
- () => jsonFormsCore.extractFilters(this.requestData.value.filter)
209
- );
210
- }
211
- get httpRequest() {
212
- return toolsVue.useApi();
213
- }
214
- reload() {
215
- this._reload.value = Date.now();
216
- }
217
- updatePage(page) {
218
- this.updateRequest({ page });
219
- }
220
- }
221
- const tableCache = /* @__PURE__ */ new Map();
222
- const useTableStore = (name) => {
223
- const tableStore = tableCache.get(name);
224
- if (tableStore) {
225
- return tableStore;
226
- }
227
- const newTableStore = new TableStore();
228
- tableCache.set(name, newTableStore);
229
- return newTableStore;
230
- };
231
- const _hoisted_1$7 = {
232
- key: 0,
233
- class: "mb-2"
234
- };
235
- const _sfc_main$g = /* @__PURE__ */ vue.defineComponent({
236
- __name: "table.component",
237
- props: TableComponentProperties,
238
- emits: TableComponentEmits,
239
- setup(__props, { emit: __emit }) {
240
- const properties = __props;
241
- const emit = __emit;
242
- vue.watch(
243
- () => properties.reload,
244
- () => {
245
- store.reload();
246
- }
247
- );
248
- let store = useTableStore(properties.id);
249
- vue.watch(
250
- () => properties.uri,
251
- () => {
252
- store.init(properties.uri);
253
- },
254
- { immediate: true }
255
- );
256
- const edit = (data) => {
257
- emit("edit", data);
258
- };
259
- const deleteFn = (data) => {
260
- emit("delete", data);
261
- };
262
- const components = {
263
- TextCell: ui.TextCell,
264
- BooleanCell: ui.BooleanCell
265
- };
266
- const displayColumns = vue.computed(() => {
267
- return properties.uiSchema.elements.map((e) => {
268
- const element = e;
269
- const def = jsonFormsCore.findColumnDef(element, properties.schema);
270
- const type = lodashEs.isArray(def.type) ? def.type[0] : def.type;
271
- let component;
272
- if (element.options?.format && element.options.format in components) {
273
- component = components[element.options.format];
274
- } else {
275
- component = components[element.type];
276
- }
277
- if (!component) console.warn("No component found for type", element.type);
278
- return {
279
- ...def,
280
- type,
281
- component
282
- };
283
- });
284
- });
285
- const onChangeFilters = (filters) => {
286
- store.updateFilters(filters);
287
- };
288
- const onUpdatePage = (page) => {
289
- store.updatePage(page);
290
- };
291
- const onSort = (id) => {
292
- store.sort(id);
293
- };
294
- const sort = vue.computed(() => {
295
- return store.sorting.value ?? { orderBy: "", ascending: 1 };
296
- });
297
- return (_ctx, _cache) => {
298
- return vue.openBlock(), vue.createElementBlock("div", null, [
299
- _ctx.filterUiSchema && _ctx.filterSchema ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$7, [
300
- vue.createVNode(_sfc_main$h, {
301
- layout: { uiSchema: _ctx.filterUiSchema, schema: _ctx.filterSchema },
302
- filters: vue.unref(store).filters.value,
303
- onChangeFilters
304
- }, null, 8, ["layout", "filters"])
305
- ])) : vue.createCommentVNode("", true),
306
- vue.createElementVNode("div", null, [
307
- vue.createVNode(vue.unref(ui.Table), {
308
- "display-columns": displayColumns.value,
309
- sort: sort.value,
310
- page: vue.unref(store).pageData.value,
311
- loading: vue.unref(store).loading.value,
312
- data: vue.unref(store).tableData.value,
313
- actions: _ctx.actions,
314
- onUpdatePage,
315
- onDelete: deleteFn,
316
- onEdit: edit,
317
- onSort
318
- }, null, 8, ["display-columns", "sort", "page", "loading", "data", "actions"])
19
+ vue.createElementVNode("div", _hoisted_2$8, [
20
+ vue.renderSlot(_ctx.$slots, "default")
319
21
  ])
320
- ]);
22
+ ], 64);
321
23
  };
322
24
  }
323
25
  });
324
- const createRepository = (formSchemaModel, httpRequest, options = {}) => {
325
- const notificationEntity = options.notification?.entityType || "entity";
326
- const notificationStore = options.notification?.notification ?? null;
327
- const getDataUri = (...suffix) => {
328
- return [formSchemaModel.uri, ...suffix].join("/");
329
- };
330
- const handleSuccess = (message) => {
331
- notificationStore?.success(message);
332
- };
333
- const handleError = (error, message) => {
334
- console.error(error);
335
- notificationStore?.error(message);
336
- throw new Error(error);
337
- };
338
- const create = (object, options2) => {
339
- return httpRequest.post(getDataUri(), object, options2).then((response) => {
340
- handleSuccess(`Created ${notificationEntity}`);
341
- return response.data;
342
- }).catch((response) => {
343
- handleError(response, `Failed to create ${notificationEntity}`);
344
- });
345
- };
346
- const patch = (id, object, options2) => {
347
- return httpRequest.patch(getDataUri(id), object, options2).then((response) => {
348
- handleSuccess(`Saved ${notificationEntity}`);
349
- return response.data;
350
- }).catch((response) => {
351
- handleError(response, `Failed to save ${notificationEntity}`);
352
- });
353
- };
354
- const get = (id, options2) => {
355
- return httpRequest.get(getDataUri(id), options2).then((response) => {
356
- return response.data;
357
- }).catch((response) => {
358
- handleError(response, "Failed to load data");
359
- });
360
- };
361
- const _delete = (id, options2) => {
362
- return httpRequest.delete(getDataUri(id), options2).then((response) => {
363
- handleSuccess(`${notificationEntity} deleted`);
364
- return response;
365
- }).catch((response) => {
366
- handleError(response, `Failed to delete ${notificationEntity}`);
367
- });
368
- };
369
- const createMulti = (objects, options2) => {
370
- return Promise.all(
371
- objects.map((object) => httpRequest.post(getDataUri(), object, options2))
372
- ).then((response) => {
373
- handleSuccess(`Created ${notificationEntity}`);
374
- return response.data;
375
- }).catch((response) => {
376
- handleError(response, `Failed to save ${notificationEntity}`);
377
- });
378
- };
379
- return { create, patch, createMulti, delete: _delete, get };
380
- };
381
- const createFormEvents = (dispatch) => ({
382
- dispatch
383
- });
384
- const FORM_EVENTS_KEY = /* @__PURE__ */ Symbol("json-forms:events");
385
- const provideFormEvents = (dispatch) => {
386
- const events = createFormEvents(dispatch);
387
- vue.provide(FORM_EVENTS_KEY, events);
388
- return events;
389
- };
390
- const useFormEvents = () => {
391
- return vue.inject(
392
- FORM_EVENTS_KEY,
393
- createFormEvents(() => {
394
- })
395
- );
396
- };
397
26
  const errorPatterns = [
398
27
  [/^Invalid input: expected \w+, received undefined$/, "This field is required"],
399
28
  [/^Invalid input: expected \w+, received null$/, "This field is required"],
@@ -435,20 +64,13 @@ const registerZodErrorMap = () => {
435
64
  return { message: `Must be at most ${i.maximum} characters` };
436
65
  }
437
66
  }
438
- return { message: issue.message };
67
+ return { message: issue.message ?? "" };
439
68
  }
440
69
  });
441
70
  };
442
- function findRenderer(registry, uischema, schema) {
443
- let best = null;
444
- for (const entry of registry) {
445
- const rank = entry.tester(uischema, schema);
446
- if (rank > -1 && (!best || rank > best.rank)) {
447
- best = { rank, renderer: entry.renderer };
448
- }
449
- }
450
- return best?.renderer ?? null;
451
- }
71
+ const ERROR_MODE_KEY = /* @__PURE__ */ Symbol("errorMode");
72
+ const FORM_SUBMITTED_KEY = /* @__PURE__ */ Symbol("formSubmitted");
73
+ const FORM_READONLY_KEY = /* @__PURE__ */ Symbol("formReadonly");
452
74
  const scopeToPath = (scope) => {
453
75
  if (!scope) return "";
454
76
  return scope.replace(/^#\//, "").split("/").filter((s) => s !== "properties").join(".");
@@ -457,86 +79,574 @@ const resolveSchema = (root, scope) => {
457
79
  const segments = scope.replace(/^#\//, "").split("/").filter(Boolean);
458
80
  return segments.reduce((acc, key) => acc?.[key], root);
459
81
  };
460
- const _hoisted_1$6 = {
82
+ const checkRequired = (rootSchema, scope, fieldName) => {
83
+ const segments = scope.replace(/^#\//, "").split("/");
84
+ const parentSegments = segments.slice(0, -2);
85
+ if (parentSegments.length === 0) {
86
+ const req2 = rootSchema?.required;
87
+ return Array.isArray(req2) && req2.includes(fieldName);
88
+ }
89
+ const parentScope = `#/${parentSegments.join("/")}`;
90
+ const parentSchema = resolveSchema(rootSchema, parentScope);
91
+ const req = parentSchema?.required;
92
+ return Array.isArray(req) && req.includes(fieldName);
93
+ };
94
+ const useInputProps = (uischema, schema, field, options = {}) => {
95
+ const rootSchema = vue.inject("rootSchema");
96
+ const path = scopeToPath(uischema.scope);
97
+ const { errorMessage, meta } = field;
98
+ const opts = uischema.options ?? {};
99
+ const labelFromScope = path.split(".").pop() ?? "";
100
+ const isRequired = rootSchema ? checkRequired(rootSchema, uischema.scope, labelFromScope) : false;
101
+ const s = schema ?? {};
102
+ const inferredType = (() => {
103
+ if (opts.format === "text") return "text";
104
+ if (s.format === "email") return "email";
105
+ if (s.format === "uri") return "url";
106
+ if (s.type === "number" || s.type === "integer") return "number";
107
+ return options.defaultType ?? "text";
108
+ })();
109
+ const styles = ui.mergeStyles(opts.styles);
110
+ const errorMode = vue.inject(ERROR_MODE_KEY, vue.ref("onBlur"));
111
+ const submitted = vue.inject(FORM_SUBMITTED_KEY, vue.ref(false));
112
+ const formReadonly = vue.inject(FORM_READONLY_KEY, vue.ref(false));
113
+ const shouldShowError = vue.computed(() => {
114
+ if (!errorMessage.value) return false;
115
+ switch (errorMode.value) {
116
+ case "always":
117
+ return true;
118
+ case "onChange":
119
+ return meta.dirty;
120
+ case "onSubmit":
121
+ return submitted.value;
122
+ case "onBlur":
123
+ default:
124
+ return meta.touched;
125
+ }
126
+ });
127
+ const width = opts.colspan || styles?.width === "full" ? "w-full" : opts.width ?? "min-w-input";
128
+ return vue.computed(() => ({
129
+ id: path,
130
+ placeholder: opts.placeholder,
131
+ description: s.description,
132
+ errors: shouldShowError.value ? opts.errorMessage ?? formatError(errorMessage.value) : void 0,
133
+ label: opts.label ?? labelFromScope.charAt(0).toUpperCase() + labelFromScope.slice(1),
134
+ visible: opts.visible ?? true,
135
+ required: isRequired,
136
+ enabled: opts.readonly !== true && !formReadonly.value,
137
+ isFocused: false,
138
+ isTouched: shouldShowError.value,
139
+ hideLabel: opts.hideLabel ?? false,
140
+ styles,
141
+ width,
142
+ type: inferredType,
143
+ ...options.overrides
144
+ }));
145
+ };
146
+ const useCustomControlBinding = ({
147
+ useProps,
148
+ setDefaultValue
149
+ } = {}) => {
150
+ return (uischema, schema, options = {}) => {
151
+ const { values: formValues } = veeValidate.useFormContext();
152
+ const pathPrefix = vue.inject("pathPrefix", "");
153
+ const scopePath = scopeToPath(uischema.scope);
154
+ const path = pathPrefix ? `${pathPrefix}.${scopePath}` : scopePath;
155
+ const field = veeValidate.useField(() => path);
156
+ setDefaultValue?.(field);
157
+ const wrapper = useInputProps(uischema, schema, field, options);
158
+ const customWrapper = useProps?.(uischema, schema, field, options) ?? {
159
+ value: {}
160
+ };
161
+ const onBlur = () => field.handleBlur(new Event("blur"));
162
+ const onChange = () => field.handleChange(field.value.value);
163
+ let initialized = false;
164
+ vue.watch(field.value, (val) => {
165
+ if (!initialized) {
166
+ initialized = true;
167
+ return;
168
+ }
169
+ field.handleChange(val);
170
+ });
171
+ return {
172
+ formValues,
173
+ uischema,
174
+ schema,
175
+ wrapper: vue.computed(() => ({ ...wrapper.value, ...customWrapper.value })),
176
+ value: field.value,
177
+ field,
178
+ onBlur,
179
+ onChange,
180
+ appliedOptions: vue.computed(
181
+ () => uischema.options ?? {}
182
+ )
183
+ };
184
+ };
185
+ };
186
+ const useControlBinding = (uischema, schema, options = {}) => {
187
+ return useCustomControlBinding()(uischema, schema, options);
188
+ };
189
+ const NOT_APPLICABLE = -1;
190
+ const rankWith = (rank, tester) => (uischema, schema, context) => tester(uischema, schema, context) ? rank : NOT_APPLICABLE;
191
+ const and = (...testers) => (uischema, schema, context) => testers.every((tester) => tester(uischema, schema, context));
192
+ const or = (...testers) => (uischema, schema, context) => testers.some((tester) => tester(uischema, schema, context));
193
+ const uiTypeIs = (expected) => (uischema) => !!uischema && uischema.type === expected;
194
+ const isControl = (uischema) => !!uischema && uischema.type === "Control";
195
+ const schemaTypeIs = (expected) => (uischema, schema) => isControl(uischema) && !!schema && schema.type === expected;
196
+ const optionIsIgnoreCase = (optionName, optionValue) => (uischema) => {
197
+ const options = uischema.options;
198
+ if (!options) return false;
199
+ const val = options[optionName];
200
+ return val?.toLowerCase() === optionValue?.toLowerCase();
201
+ };
202
+ const schemaFormatIsOneOf = (...formats) => (_uischema, schema) => {
203
+ const fmt = schema?.format;
204
+ return typeof fmt === "string" && formats.includes(fmt);
205
+ };
206
+ const anyOfTypeIs = (type) => (_uischema, schema) => {
207
+ const s = schema;
208
+ if (!Array.isArray(s?.anyOf)) return false;
209
+ return s.anyOf.some((sub) => sub?.type === type);
210
+ };
211
+ const isAutoCompleteControl = and(
212
+ // uiTypeIs('Control'),
213
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.autocomplete)
214
+ );
215
+ const isTextAreaControl = and(
216
+ uiTypeIs("Control"),
217
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.textArea)
218
+ );
219
+ const isStringFormat = and(
220
+ uiTypeIs("Control"),
221
+ or(
222
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.string),
223
+ schemaTypeIs("string"),
224
+ anyOfTypeIs("string")
225
+ )
226
+ );
227
+ const isMarkdownControl = and(
228
+ uiTypeIs("Control"),
229
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.markdown)
230
+ );
231
+ and(
232
+ uiTypeIs("Control"),
233
+ // schemaTypeIs('object'),
234
+ (uischema) => !uischema.options?.format
235
+ );
236
+ const isArrayRenderer = and(
237
+ schemaTypeIs("array")
238
+ // optionIsIgnoreCase('format', ControlType.array),
239
+ );
240
+ const isMultiselectControl = and(
241
+ uiTypeIs("Control"),
242
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.mutliSelect)
243
+ );
244
+ const isSelectControl = and(
245
+ uiTypeIs("Control"),
246
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.select)
247
+ );
248
+ const isBooleanControl = or(
249
+ and(uiTypeIs("Control"), schemaTypeIs("boolean")),
250
+ and(uiTypeIs("Control"), optionIsIgnoreCase("format", jsonFormsCore.ControlType.boolean))
251
+ );
252
+ const isNumberFormat = and(
253
+ uiTypeIs("Control"),
254
+ or(optionIsIgnoreCase("format", jsonFormsCore.ControlType.number), schemaTypeIs("number"))
255
+ );
256
+ const isIntegerFormat = and(
257
+ uiTypeIs("Control"),
258
+ or(
259
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.integer),
260
+ schemaTypeIs("integer")
261
+ )
262
+ );
263
+ and(
264
+ uiTypeIs("Control"),
265
+ or(
266
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.date),
267
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.dateTime),
268
+ schemaFormatIsOneOf("date", "date-time")
269
+ )
270
+ );
271
+ and(
272
+ uiTypeIs("Control"),
273
+ optionIsIgnoreCase("format", jsonFormsCore.ControlType.relation)
274
+ );
275
+ const EMPTY_VALUE = "—";
276
+ const DisplayValueProperties = {
277
+ direction: { type: String, default: "ltr" },
278
+ path: { type: String, required: true },
279
+ displayValue: { type: Object, required: true },
280
+ options: { type: Object, required: true },
281
+ value: { type: Object, required: true }
282
+ };
283
+ const _hoisted_1$j = { class: "text-gray-500 text-sm" };
284
+ const _sfc_main$t = /* @__PURE__ */ vue.defineComponent({
285
+ __name: "NotSetValue",
286
+ props: DisplayValueProperties,
287
+ setup(__props) {
288
+ return (_ctx, _cache) => {
289
+ return vue.openBlock(), vue.createElementBlock("p", _hoisted_1$j, [
290
+ vue.createTextVNode(vue.toDisplayString(vue.unref(EMPTY_VALUE)) + " · ", 1),
291
+ _cache[0] || (_cache[0] = vue.createElementVNode("small", null, "Not set", -1))
292
+ ]);
293
+ };
294
+ }
295
+ });
296
+ const _hoisted_1$i = {
297
+ key: 0,
298
+ class: "inline-flex items-center gap-1 text-success"
299
+ };
300
+ const _hoisted_2$7 = {
461
301
  key: 1,
462
- class: "text-error text-xs"
302
+ class: "inline-flex items-center gap-1 text-base-content/50"
463
303
  };
464
- const _sfc_main$f = /* @__PURE__ */ vue.defineComponent({
465
- __name: "Dispatch",
466
- props: {
467
- uischema: {},
468
- schema: {},
469
- pathPrefix: { default: void 0 }
470
- },
304
+ const _sfc_main$s = /* @__PURE__ */ vue.defineComponent({
305
+ __name: "BooleanValue",
306
+ props: DisplayValueProperties,
307
+ setup(__props) {
308
+ return (_ctx, _cache) => {
309
+ return _ctx.displayValue === true ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_1$i, [
310
+ vue.createVNode(vue.unref(ui.Icon), {
311
+ icon: vue.unref(ui.IconEnum).Check,
312
+ size: "sm"
313
+ }, null, 8, ["icon"]),
314
+ _cache[0] || (_cache[0] = vue.createElementVNode("span", null, "Yes", -1))
315
+ ])) : _ctx.displayValue === false ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_2$7, [
316
+ vue.createVNode(vue.unref(ui.Icon), {
317
+ icon: vue.unref(ui.IconEnum).Close,
318
+ size: "sm"
319
+ }, null, 8, ["icon"]),
320
+ _cache[1] || (_cache[1] = vue.createElementVNode("span", null, "No", -1))
321
+ ])) : (vue.openBlock(), vue.createBlock(_sfc_main$t, { key: 2 }));
322
+ };
323
+ }
324
+ });
325
+ const UNITS = [
326
+ ["year", 60 * 60 * 24 * 365],
327
+ ["month", 60 * 60 * 24 * 30],
328
+ ["day", 60 * 60 * 24],
329
+ ["hour", 60 * 60],
330
+ ["minute", 60],
331
+ ["second", 1]
332
+ ];
333
+ const relativeFromNow = (date, now, locale) => {
334
+ const rtf = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
335
+ const diffSeconds = Math.round((date.getTime() - now.getTime()) / 1e3);
336
+ const abs = Math.abs(diffSeconds);
337
+ for (const [unit, secondsInUnit] of UNITS) {
338
+ if (abs >= secondsInUnit || unit === "second") {
339
+ return rtf.format(Math.round(diffSeconds / secondsInUnit), unit);
340
+ }
341
+ }
342
+ return rtf.format(0, "second");
343
+ };
344
+ const formatDate = (value, {
345
+ withTime = true,
346
+ locale = "en-GB",
347
+ timeZone = "UTC"
348
+ } = {}) => {
349
+ const date = parseDate(value);
350
+ if (!date) return null;
351
+ const datePart = new Intl.DateTimeFormat(locale, {
352
+ day: "numeric",
353
+ month: "short",
354
+ year: "numeric",
355
+ timeZone
356
+ }).format(date);
357
+ let out = datePart;
358
+ if (withTime) {
359
+ const timePart = new Intl.DateTimeFormat(locale, {
360
+ hour: "2-digit",
361
+ minute: "2-digit",
362
+ timeZone
363
+ }).format(date);
364
+ out = `${datePart}, ${timePart}`;
365
+ }
366
+ return out;
367
+ };
368
+ const relativeDate = (value, { locale = "en-GB", now } = {}) => {
369
+ const date = parseDate(value);
370
+ if (!date) return;
371
+ return relativeFromNow(date, now ?? /* @__PURE__ */ new Date(), locale);
372
+ };
373
+ const parseDate = (value) => {
374
+ if (value == null || value === "") return null;
375
+ const date = value instanceof Date ? value : new Date(String(value));
376
+ if (Number.isNaN(date.getTime())) return null;
377
+ return date;
378
+ };
379
+ const isDate = (value) => {
380
+ return !!parseDate(value);
381
+ };
382
+ const _hoisted_1$h = ["dir"];
383
+ const _hoisted_2$6 = { class: "text-gray-500" };
384
+ const _sfc_main$r = /* @__PURE__ */ vue.defineComponent({
385
+ __name: "DateValue",
386
+ props: DisplayValueProperties,
387
+ setup(__props) {
388
+ return (_ctx, _cache) => {
389
+ return vue.openBlock(), vue.createElementBlock("p", { dir: _ctx.direction }, [
390
+ vue.createTextVNode(vue.toDisplayString(vue.unref(formatDate)(_ctx.displayValue)) + " · ", 1),
391
+ vue.createElementVNode("small", _hoisted_2$6, vue.toDisplayString(vue.unref(relativeDate)(_ctx.displayValue)), 1)
392
+ ], 8, _hoisted_1$h);
393
+ };
394
+ }
395
+ });
396
+ const _hoisted_1$g = ["href"];
397
+ const _sfc_main$q = /* @__PURE__ */ vue.defineComponent({
398
+ __name: "LinkValue",
399
+ props: DisplayValueProperties,
400
+ setup(__props) {
401
+ return (_ctx, _cache) => {
402
+ return vue.openBlock(), vue.createElementBlock("a", {
403
+ href: _ctx.displayValue,
404
+ target: "_blank",
405
+ rel: "noopener noreferrer"
406
+ }, vue.toDisplayString(_ctx.displayValue), 9, _hoisted_1$g);
407
+ };
408
+ }
409
+ });
410
+ const _hoisted_1$f = ["innerHTML"];
411
+ const _sfc_main$p = /* @__PURE__ */ vue.defineComponent({
412
+ __name: "MarkdownValue",
413
+ props: DisplayValueProperties,
414
+ setup(__props) {
415
+ const props = __props;
416
+ const renderedHtml = vue.computed(() => {
417
+ const raw = props.value;
418
+ if (!raw) return "";
419
+ return raw.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
420
+ });
421
+ return (_ctx, _cache) => {
422
+ return vue.openBlock(), vue.createElementBlock("div", {
423
+ class: "prose prose-sm max-w-none",
424
+ innerHTML: renderedHtml.value
425
+ }, null, 8, _hoisted_1$f);
426
+ };
427
+ }
428
+ });
429
+ const _hoisted_1$e = { key: 0 };
430
+ const _sfc_main$o = /* @__PURE__ */ vue.defineComponent({
431
+ __name: "NumberValue",
432
+ props: DisplayValueProperties,
433
+ setup(__props) {
434
+ return (_ctx, _cache) => {
435
+ return _ctx.displayValue ? (vue.openBlock(), vue.createElementBlock("p", _hoisted_1$e, vue.toDisplayString(_ctx.displayValue), 1)) : (vue.openBlock(), vue.createBlock(_sfc_main$t, { key: 1 }));
436
+ };
437
+ }
438
+ });
439
+ const _hoisted_1$d = {
440
+ class: "block overflow-auto text-sm",
441
+ style: { "max-height": "200px" }
442
+ };
443
+ const _sfc_main$n = /* @__PURE__ */ vue.defineComponent({
444
+ __name: "ObjectValue",
445
+ props: DisplayValueProperties,
446
+ setup(__props) {
447
+ return (_ctx, _cache) => {
448
+ return vue.openBlock(), vue.createElementBlock("pre", _hoisted_1$d, " " + vue.toDisplayString(_ctx.displayValue) + "\n\n\n ", 1);
449
+ };
450
+ }
451
+ });
452
+ const _hoisted_1$c = ["dir"];
453
+ const _sfc_main$m = /* @__PURE__ */ vue.defineComponent({
454
+ __name: "StringValue",
455
+ props: DisplayValueProperties,
456
+ setup(__props) {
457
+ return (_ctx, _cache) => {
458
+ return _ctx.displayValue ? (vue.openBlock(), vue.createElementBlock("p", {
459
+ key: 0,
460
+ dir: _ctx.direction
461
+ }, vue.toDisplayString(_ctx.displayValue), 9, _hoisted_1$c)) : (vue.openBlock(), vue.createBlock(_sfc_main$t, { key: 1 }));
462
+ };
463
+ }
464
+ });
465
+ const createFormEvents = (dispatch) => ({
466
+ dispatch
467
+ });
468
+ const FORM_EVENTS_KEY = /* @__PURE__ */ Symbol("json-forms:events");
469
+ const provideFormEvents = (dispatch) => {
470
+ const events = createFormEvents(dispatch);
471
+ vue.provide(FORM_EVENTS_KEY, events);
472
+ return events;
473
+ };
474
+ const useFormEvents = () => {
475
+ return vue.inject(
476
+ FORM_EVENTS_KEY,
477
+ createFormEvents(() => {
478
+ })
479
+ );
480
+ };
481
+ const _hoisted_1$b = { class: "px-2 flex gap-2 items-center" };
482
+ const _hoisted_2$5 = { class: "truncate" };
483
+ const _sfc_main$l = /* @__PURE__ */ vue.defineComponent({
484
+ __name: "ViewDetailValue",
485
+ props: DisplayValueProperties,
471
486
  setup(__props) {
472
487
  const props = __props;
473
- const registry = vue.inject("renderers");
474
- const rootSchema = vue.inject("rootSchema");
475
- const parentPrefix = vue.inject("pathPrefix", "");
476
- const effectivePrefix = props.pathPrefix ?? parentPrefix;
477
- if (props.pathPrefix !== void 0) {
478
- vue.provide("pathPrefix", effectivePrefix);
479
- }
480
- const resolved = vue.computed(() => {
481
- const u = props.uischema;
482
- if (!u.scope) return props.schema;
483
- const fromRoot = resolveSchema(rootSchema, u.scope);
484
- if (fromRoot) return fromRoot;
485
- return resolveSchema(props.schema, u.scope) ?? props.schema;
486
- });
487
- const renderer = vue.computed(
488
- () => findRenderer(registry, props.uischema, resolved.value)
489
- );
488
+ const formEvents = useFormEvents();
489
+ const view = () => {
490
+ formEvents.dispatch({
491
+ event: "view",
492
+ type: props.path,
493
+ data: props.value,
494
+ options: props.options
495
+ });
496
+ };
490
497
  return (_ctx, _cache) => {
491
- return renderer.value ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(renderer.value), {
492
- key: 0,
493
- uischema: __props.uischema,
494
- schema: resolved.value
495
- }, null, 8, ["uischema", "schema"])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, " No renderer for " + vue.toDisplayString(__props.uischema.scope ?? __props.uischema.type), 1));
498
+ return vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
499
+ color: "ghost",
500
+ class: "border-gray-300 text-primary h-8",
501
+ tooltip: "View",
502
+ onClick: view
503
+ }, {
504
+ default: vue.withCtx(() => [
505
+ vue.createElementVNode("span", _hoisted_1$b, [
506
+ vue.createElementVNode("span", _hoisted_2$5, vue.toDisplayString(_ctx.displayValue), 1),
507
+ vue.createVNode(vue.unref(ui.Icon), {
508
+ icon: vue.unref(ui.IconEnum).View,
509
+ size: "sm"
510
+ }, null, 8, ["icon"])
511
+ ])
512
+ ]),
513
+ _: 1
514
+ });
496
515
  };
497
516
  }
498
517
  });
499
- const JsonFormComponentProperties = {
500
- /** Unique identifier used to namespace the form element. */
501
- id: { type: String, required: true },
502
- /** HTML name attribute for the form. */
503
- name: { type: String, default: "form" },
504
- /** JSON schema describing the shape of the form data. */
505
- schema: { type: Object, required: true },
506
- /** UI schema describing the layout and controls. */
507
- uiSchema: { type: Object, required: true },
508
- /** Disable all form controls. */
509
- disabled: { type: Boolean, default: false },
510
- /** Current form data object. */
511
- formData: { type: Object, default: () => ({}) },
512
- /** When validation errors are shown (`'onBlur'`, `'onChange'`, `'onSubmit'`, `'always'`). */
513
- errorMode: {
514
- type: String,
515
- default: "onChanges"
518
+ const isLink = (value) => value?.startsWith?.("http");
519
+ const getDirection = (options, formValues) => {
520
+ const pathPrefix = vue.inject("pathPrefix", "");
521
+ const opts = options ?? {};
522
+ if (!opts.directionField) return opts.direction ?? "ltr";
523
+ const key = pathPrefix ? `${pathPrefix}.${opts.directionField}` : opts.directionField;
524
+ const val = key.split(".").reduce((o, k) => o?.[k], formValues);
525
+ return val ?? "ltr";
526
+ };
527
+ const getSelectValue = (value, opts) => {
528
+ const labelKey = opts.labelKey ?? "label";
529
+ const valueKey = opts.valueKey ?? "value";
530
+ const resolvedKey = value?.[valueKey] ?? value;
531
+ if (typeof resolvedKey === "string" && opts.values)
532
+ return opts.values?.find((o) => o[valueKey] === resolvedKey)?.[labelKey] ?? resolvedKey;
533
+ const resolved = value && typeof value === "object" ? value?.[labelKey] ?? value[valueKey] : value;
534
+ if (!resolved) return null;
535
+ return resolved;
536
+ };
537
+ const getNestedValue = (value, opts, formValues) => {
538
+ if (opts.dataPath) {
539
+ const nested = opts.dataPath.split(".").reduce((o, k) => o?.[k], formValues);
540
+ if (opts.key && nested && typeof nested === "object")
541
+ return nested[opts.key] ?? null;
542
+ return nested ?? null;
516
543
  }
544
+ if (value !== null && typeof value === "object" && opts.key) {
545
+ return value[opts.key] ?? null;
546
+ }
547
+ return value ?? null;
517
548
  };
518
- const JsonFormComponentEmits = [
519
- /** Emitted when form data changes. */
520
- "change",
521
- /** Emitted on form submission. */
522
- "submit",
523
- /** Emitted when validation errors change. */
524
- "errors",
525
- /** Emitted when form validity changes. */
526
- "valid",
527
- /** Emitted when a custom renderer dispatches a form event. */
528
- "events"
529
- ];
530
- const ERROR_MODE_KEY = /* @__PURE__ */ Symbol("errorMode");
531
- const FORM_SUBMITTED_KEY = /* @__PURE__ */ Symbol("formSubmitted");
532
- const _hoisted_1$5 = { class: "flex-1" };
533
- const _hoisted_2$1 = { key: 0 };
534
- const _hoisted_3 = {
549
+ const getDisplayValue = (value, formValues, opts) => {
550
+ const format = opts.format ?? "date";
551
+ if (format === "select") return getSelectValue(value, opts);
552
+ if (format === "autocomplete") return getSelectValue(value, opts);
553
+ if (typeof value !== "object") {
554
+ return value ?? null;
555
+ }
556
+ return getNestedValue(value, opts, formValues);
557
+ };
558
+ const _getComponent = (value, options, schema, uiSchema) => {
559
+ if (isNumberFormat(uiSchema, schema)) return _sfc_main$o;
560
+ if (isIntegerFormat(uiSchema, schema)) return _sfc_main$o;
561
+ if (isMarkdownControl(uiSchema, schema)) return _sfc_main$p;
562
+ if (value === null || value === void 0) return _sfc_main$t;
563
+ if (options.resource) return _sfc_main$l;
564
+ if (isDate(value)) return _sfc_main$r;
565
+ if (isLink(value)) return _sfc_main$q;
566
+ if (typeof value === "boolean") return _sfc_main$s;
567
+ if (typeof value === "object") return _sfc_main$n;
568
+ return _sfc_main$m;
569
+ };
570
+ const getComponent = (path, uischema, schema, field, formValues) => {
571
+ return vue.computed(() => {
572
+ const value = field.value?.value;
573
+ const options = uischema.options ?? {};
574
+ const displayValue = getDisplayValue(value, formValues, options);
575
+ return {
576
+ component: _getComponent(value, options, schema, uischema),
577
+ value: {
578
+ value,
579
+ path,
580
+ displayValue,
581
+ options,
582
+ direction: getDirection(options, formValues)
583
+ }
584
+ };
585
+ });
586
+ };
587
+ const useCustomReadonlyControlBinding = ({
588
+ useProps,
589
+ setDefaultValue
590
+ } = {}) => {
591
+ return (uischema, schema, options = {}) => {
592
+ const { values: formValues } = veeValidate.useFormContext();
593
+ const pathPrefix = vue.inject("pathPrefix", "");
594
+ const scopePath = scopeToPath(uischema.scope);
595
+ const path = pathPrefix ? `${pathPrefix}.${scopePath}` : scopePath;
596
+ const field = veeValidate.useField(() => path);
597
+ const wrapper = useInputProps(uischema, schema, field, options);
598
+ const customWrapper = useProps?.(uischema, schema, field, options) ?? {
599
+ value: {}
600
+ };
601
+ return {
602
+ wrapper: vue.computed(() => ({ ...wrapper.value, ...customWrapper.value })),
603
+ appliedOptions: vue.computed(
604
+ () => uischema.options ?? {}
605
+ ),
606
+ displayWrapper: getComponent(path, uischema, schema, field, formValues)
607
+ };
608
+ };
609
+ };
610
+ const useReadonlyControlBinding = (uischema, schema, options = {}) => {
611
+ return useCustomReadonlyControlBinding()(uischema, schema, options);
612
+ };
613
+ const useDisplayValue = (value, formValues, opts) => {
614
+ const raw = value;
615
+ if (typeof raw !== "object") {
616
+ return raw ?? null;
617
+ }
618
+ if (opts.dataPath) {
619
+ const nested = opts.dataPath.split(".").reduce((o, k) => o?.[k], formValues);
620
+ if (opts.key && nested && typeof nested === "object")
621
+ return nested[opts.key] ?? null;
622
+ return nested ?? null;
623
+ }
624
+ if (raw !== null && typeof raw === "object" && opts.key) {
625
+ return raw[opts.key] ?? null;
626
+ }
627
+ return raw ?? null;
628
+ };
629
+ const HTTP_CLIENT_KEY = /* @__PURE__ */ Symbol("json-forms:http-client");
630
+ const provideHttpClient = (http) => {
631
+ vue.provide(HTTP_CLIENT_KEY, http);
632
+ };
633
+ const useHttpClient = () => {
634
+ const http = vue.inject(HTTP_CLIENT_KEY);
635
+ if (!http) {
636
+ throw new Error(
637
+ "[json-forms] No HttpClient provided. Pass an `http` prop to <JsonForm> or call provideHttpClient() in a parent component."
638
+ );
639
+ }
640
+ return http;
641
+ };
642
+ const _hoisted_1$a = { class: "flex-1" };
643
+ const _hoisted_2$4 = { key: 0 };
644
+ const _hoisted_3$2 = {
535
645
  key: 0,
536
646
  class: "text-sm text-base-content/50"
537
647
  };
538
- const _hoisted_4 = { key: 1 };
539
- const _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
648
+ const _hoisted_4$2 = { key: 1 };
649
+ const _sfc_main$k = /* @__PURE__ */ vue.defineComponent({
540
650
  __name: "ArrayRenderer",
541
651
  props: {
542
652
  uischema: {},
@@ -579,7 +689,7 @@ const _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
579
689
  vue.unref(layout) === "row" ? "flex-row items-center" : "flex-col"
580
690
  ])
581
691
  }, [
582
- (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(fields), (entry, index2) => {
692
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(fields), (entry, index) => {
583
693
  return vue.openBlock(), vue.createElementBlock("div", {
584
694
  key: entry.key,
585
695
  class: "flex-1"
@@ -590,29 +700,29 @@ const _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
590
700
  vue.unref(layout) === "row" ? "flex-col" : "flex-row items-center"
591
701
  ])
592
702
  }, [
593
- vue.createElementVNode("div", _hoisted_1$5, [
703
+ vue.createElementVNode("div", _hoisted_1$a, [
594
704
  (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(childElements.value, (child, ci) => {
595
- return vue.openBlock(), vue.createBlock(_sfc_main$f, {
705
+ return vue.openBlock(), vue.createBlock(_sfc_main$5, {
596
706
  key: ci,
597
707
  uischema: child,
598
708
  schema: vue.unref(itemSchema),
599
- "path-prefix": `${vue.unref(path)}[${index2}]`
709
+ "path-prefix": `${vue.unref(path)}[${index}]`
600
710
  }, null, 8, ["uischema", "schema", "path-prefix"]);
601
711
  }), 128))
602
712
  ]),
603
- showActions.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$1, [
713
+ showActions.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$4, [
604
714
  vue.unref(fields).length > 1 ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
605
715
  key: 0,
606
716
  icon: vue.unref(ui.IconEnum).Delete,
607
717
  outline: true,
608
- onClick: ($event) => vue.unref(remove)(index2)
718
+ onClick: ($event) => vue.unref(remove)(index)
609
719
  }, null, 8, ["icon", "onClick"])) : vue.createCommentVNode("", true)
610
720
  ])) : vue.createCommentVNode("", true)
611
721
  ], 2)
612
722
  ]);
613
723
  }), 128)),
614
- vue.unref(fields).length === 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3, " No data ")) : vue.createCommentVNode("", true),
615
- showActions.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_4, [
724
+ vue.unref(fields).length === 0 ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$2, " No data ")) : vue.createCommentVNode("", true),
725
+ showActions.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_4$2, [
616
726
  vue.createVNode(vue.unref(ui.Btn), {
617
727
  icon: vue.unref(ui.IconEnum).Plus,
618
728
  outline: true,
@@ -629,50 +739,21 @@ const _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
629
739
  };
630
740
  }
631
741
  });
632
- const isAutoCompleteControl = testers.and(
633
- // uiTypeIs('Control'),
634
- testers.optionIs("format", jsonFormsCore.ControlType.autocomplete)
635
- );
636
- const isTextAreaControl = testers.and(
637
- core$1.uiTypeIs("Control"),
638
- testers.optionIs("format", jsonFormsCore.ControlType.textArea)
639
- );
640
- const isStringFormat = testers.and(
641
- core$1.uiTypeIs("Control"),
642
- testers.or(testers.optionIs("format", jsonFormsCore.ControlType.string), testers.schemaTypeIs("string"))
643
- );
644
- const isMarkdownControl = testers.and(
645
- core$1.uiTypeIs("Control"),
646
- testers.optionIs("format", jsonFormsCore.ControlType.markdown)
647
- );
648
- const isArrayRenderer = testers.and(
649
- testers.schemaTypeIs("array")
650
- // optionIs('format', ControlType.array),
651
- );
652
- const isMultiselectControl = testers.and(
653
- core$1.uiTypeIs("Control"),
654
- testers.optionIs("format", jsonFormsCore.ControlType.mutliSelect)
655
- );
656
- const isSelectControl = testers.and(
657
- core$1.uiTypeIs("Control"),
658
- testers.optionIs("format", jsonFormsCore.ControlType.select)
659
- );
660
- const isBooleanControl = testers.or(
661
- testers.isBooleanControl,
662
- testers.and(core$1.uiTypeIs("Control"), testers.optionIs("format", jsonFormsCore.ControlType.boolean))
663
- );
664
- const isNumberFormat = testers.and(
665
- core$1.uiTypeIs("Control"),
666
- testers.or(testers.optionIs("format", jsonFormsCore.ControlType.number), testers.schemaTypeIs("number"))
667
- );
668
- const isIntegerFormat = testers.and(
669
- core$1.uiTypeIs("Control"),
670
- testers.or(testers.optionIs("format", jsonFormsCore.ControlType.integer), testers.schemaTypeIs("integer"))
671
- );
672
742
  const arrayRenderers = [
673
- { tester: core$1.rankWith(12, isArrayRenderer), renderer: _sfc_main$e }
743
+ { tester: rankWith(12, isArrayRenderer), renderer: _sfc_main$k }
674
744
  ];
675
- const MethodSchema = zod.z.enum(["get", "post", "delete", "put", "patch"]);
745
+ const MethodSchema = zod.z.enum([
746
+ "get",
747
+ "post",
748
+ "delete",
749
+ "put",
750
+ "patch",
751
+ "GET",
752
+ "POST",
753
+ "DELETE",
754
+ "PUT",
755
+ "PATCH"
756
+ ]);
676
757
  const OperationSchema = zod.z.object({
677
758
  uri: zod.z.string(),
678
759
  method: MethodSchema
@@ -693,22 +774,24 @@ const OperationMap = {
693
774
  findAll: "get",
694
775
  findOne: "get",
695
776
  lookup: "get",
696
- update: "get"
777
+ update: "patch"
697
778
  };
779
+ const schemaDef = zod.z.object({
780
+ ui: zod.z.any().optional(),
781
+ data: zod.z.any()
782
+ });
698
783
  const ResourceSchema = zod.z.object({
699
784
  id: zod.z.string(),
700
785
  uri: zod.z.string(),
701
786
  operations: Operations,
702
- schema: zod.z.object({
703
- ui: zod.z.any().optional(),
704
- data: zod.z.any()
787
+ schema: schemaDef.optional(),
788
+ schemas: zod.z.object({
789
+ form: schemaDef.optional()
705
790
  }).optional()
706
791
  }).transform((data) => {
707
- const schema = data.schema;
708
- if (schema) {
709
- if (!schema.ui) {
710
- schema.ui = jsonFormsCore.uiFromJsonSchema(schema.data);
711
- }
792
+ const schema = data.schema ?? data.schemas?.form ?? { ui: void 0, data: void 0 };
793
+ if (!schema.ui && schema.data) {
794
+ schema.ui = jsonFormsCore.uiFromJsonSchema(schema.data);
712
795
  }
713
796
  const operations = {};
714
797
  for (const k in OperationMap) {
@@ -716,14 +799,14 @@ const ResourceSchema = zod.z.object({
716
799
  const defaultOperation = OperationMap[key];
717
800
  const operation = data.operations[key];
718
801
  const mapResourceSchema = () => {
719
- if (lodashEs.isUndefined(operation) || operation === false) return null;
802
+ if (operation === void 0 || operation === false) return null;
720
803
  if (operation === true)
721
804
  return { uri: data.uri, method: defaultOperation };
722
805
  if (typeof operation === "string")
723
806
  return { uri: operation, method: "get" };
724
807
  return {
725
808
  uri: operation.uri,
726
- method: operation.method ?? defaultOperation
809
+ method: operation.method?.toLowerCase() ?? defaultOperation
727
810
  };
728
811
  };
729
812
  operations[key] = mapResourceSchema();
@@ -734,58 +817,72 @@ const ResourceSchema = zod.z.object({
734
817
  operations
735
818
  };
736
819
  });
737
- const getResourceSchema = async (resourceUri, skipAuth) => {
738
- const fetch = skipAuth ? axios : toolsVue.useApi();
739
- return fetch.get(resourceUri).then((response) => {
820
+ const getResourceSchema = async (resourceUri, http) => {
821
+ return http.get(resourceUri).then((response) => {
740
822
  const resource = ResourceSchema.safeParse(response.data);
741
823
  if (!resource.success)
742
824
  throw new Error(`Invalid resource schema: ${resource.error}`);
743
825
  return resource.data;
744
826
  });
745
827
  };
746
- const useRemoteOption = (options) => {
828
+ const resolvePlaceholders = (uri, formValues, searchTerm) => {
829
+ const resolved = uri.replace(/\{form\.([^}]+)\}/g, (_, key) => {
830
+ const value = key.split(".").reduce((o, k) => o?.[k], formValues);
831
+ return encodeURIComponent(value ?? "");
832
+ });
833
+ if (resolved.includes("{q}"))
834
+ return resolved.replace("{q}", encodeURIComponent(searchTerm));
835
+ if (resolved.includes("{text}"))
836
+ return resolved.replace("{text}", searchTerm);
837
+ return `${resolved}${encodeURIComponent(searchTerm)}`;
838
+ };
839
+ const useRemoteOption = (options, http, formValues) => {
747
840
  return {
748
841
  fetchOptions: (searchTerm, signal) => {
749
- const fetch = options.skipAuth ? axios : toolsVue.useApi();
750
- return fetch.get(`${options.uri}${searchTerm}`, { signal }).then((data) => data.data[options.dataField ?? "data"]);
842
+ const uri = resolvePlaceholders(options.uri, formValues, searchTerm);
843
+ return http.get(uri, { signal }).then((data) => {
844
+ const body = data.data;
845
+ if (Array.isArray(body)) return body;
846
+ return body[options.dataField ?? "data"];
847
+ });
751
848
  }
752
849
  };
753
850
  };
754
- const useResourceOptions = async (options) => {
755
- const resource = await getResourceSchema(
756
- options.resource,
757
- options.skipAuth ?? false
758
- );
759
- const fetch = options.skipAuth ? axios : toolsVue.useApi();
851
+ const useResourceOptions = async (options, http, formValues) => {
852
+ const resource = await getResourceSchema(options.resource, http);
760
853
  const lookup = resource.operations.lookup;
761
854
  return {
762
855
  fetchOptions: (searchTerm, signal) => {
763
- const uri = lookup.uri.replace("{text}", searchTerm);
856
+ const uri = resolvePlaceholders(lookup.uri, formValues, searchTerm);
764
857
  const method = lookup.method;
765
- return fetch[method](uri, { signal }).then(
766
- (data) => data.data[options.dataField ?? "data"]
767
- );
858
+ return http[method](uri, { signal }).then((data) => data.data[options.dataField ?? "data"]);
768
859
  },
769
- enableCreate: !!resource.operations.create,
860
+ enableCreate: !!resource.operations.create && resource.schema.ui,
770
861
  form: resource.operations.create ? {
771
862
  ui_schema: resource.schema.ui,
772
863
  json_schema: resource.schema.data,
773
864
  title: `Create new ${resource.id}`,
774
865
  create: async (data) => {
775
866
  const create = resource.operations.create;
776
- return fetch[create.method](create.uri, data).then(
777
- (result) => result.data
778
- );
867
+ return http[create.method](create.uri, data).then((result) => result.data);
779
868
  }
780
869
  } : null
781
870
  };
782
871
  };
783
- const useFetchOptions = async (options) => {
872
+ const useFetchOptions = async (options, http, formValues = {}) => {
784
873
  let config = {};
785
- if (options.uri)
786
- config = useRemoteOption(options);
787
- if (options.resource)
788
- config = await useResourceOptions(options);
874
+ if ("uri" in options && options.uri)
875
+ config = useRemoteOption(
876
+ options,
877
+ http,
878
+ formValues
879
+ );
880
+ if ("resource" in options && options.resource)
881
+ config = await useResourceOptions(
882
+ options,
883
+ http,
884
+ formValues
885
+ );
789
886
  return {
790
887
  fetchOptions: null,
791
888
  labelKey: options.labelKey,
@@ -795,116 +892,41 @@ const useFetchOptions = async (options) => {
795
892
  ...config
796
893
  };
797
894
  };
798
- const checkRequired = (rootSchema, scope, fieldName) => {
799
- const segments = scope.replace(/^#\//, "").split("/");
800
- const parentSegments = segments.slice(0, -2);
801
- if (parentSegments.length === 0) {
802
- const req2 = rootSchema?.required;
803
- return Array.isArray(req2) && req2.includes(fieldName);
804
- }
805
- const parentScope = `#/${parentSegments.join("/")}`;
806
- const parentSchema = resolveSchema(rootSchema, parentScope);
807
- const req = parentSchema?.required;
808
- return Array.isArray(req) && req.includes(fieldName);
809
- };
810
- const useInputProps = (uischema, schema, field, options = {}) => {
811
- const rootSchema = vue.inject("rootSchema");
812
- const path = scopeToPath(uischema.scope);
813
- const { errorMessage, meta } = field;
814
- const opts = uischema.options ?? {};
815
- const labelFromScope = path.split(".").pop() ?? "";
816
- const isRequired = rootSchema ? checkRequired(rootSchema, uischema.scope, labelFromScope) : false;
817
- const s = schema ?? {};
818
- const inferredType = (() => {
819
- if (opts.format === "text") return "text";
820
- if (s.format === "email") return "email";
821
- if (s.format === "uri") return "url";
822
- if (s.type === "number" || s.type === "integer") return "number";
823
- return options.defaultType ?? "text";
824
- })();
825
- const styles = ui.mergeStyles(opts.styles);
826
- const errorMode = vue.inject(ERROR_MODE_KEY, vue.ref("onBlur"));
827
- const submitted = vue.inject(FORM_SUBMITTED_KEY, vue.ref(false));
828
- const shouldShowError = vue.computed(() => {
829
- if (!errorMessage.value) return false;
830
- switch (errorMode.value) {
831
- case "always":
832
- return true;
833
- case "onChange":
834
- return meta.dirty;
835
- case "onSubmit":
836
- return submitted.value;
837
- case "onBlur":
838
- default:
839
- return meta.touched;
840
- }
841
- });
842
- const width = opts.colspan || styles?.width === "full" ? "w-full" : opts.width ?? "min-w-input";
843
- return vue.computed(() => ({
844
- id: path,
845
- placeholder: opts.placeholder,
846
- description: s.description,
847
- errors: shouldShowError.value ? opts.errorMessage ?? formatError(errorMessage.value) : void 0,
848
- label: opts.label ?? labelFromScope.charAt(0).toUpperCase() + labelFromScope.slice(1),
849
- visible: opts.visible ?? true,
850
- required: isRequired,
851
- enabled: opts.readonly !== true,
852
- isFocused: false,
853
- isTouched: shouldShowError.value,
854
- hideLabel: opts.hideLabel ?? false,
855
- styles,
856
- width,
857
- type: inferredType,
858
- ...options.overrides
859
- }));
860
- };
861
- const useCustomControlBinding = ({
862
- useProps,
863
- setDefaultValue
864
- } = {}) => {
865
- return (uischema, schema, options = {}) => {
866
- const pathPrefix = vue.inject("pathPrefix", "");
867
- const scopePath = scopeToPath(uischema.scope);
868
- const path = pathPrefix ? `${pathPrefix}.${scopePath}` : scopePath;
869
- const field = veeValidate.useField(() => path);
870
- setDefaultValue?.(field);
871
- const wrapper = useInputProps(uischema, schema, field, options);
872
- const customWrapper = useProps?.(uischema, schema, field, options) ?? {
873
- value: {}
874
- };
875
- const onBlur = () => field.handleBlur(new Event("blur"));
876
- const onChange = () => field.handleChange(field.value.value);
877
- let initialized = false;
878
- vue.watch(field.value, (val) => {
879
- if (!initialized) {
880
- initialized = true;
881
- return;
882
- }
883
- field.handleChange(val);
884
- });
885
- return {
886
- wrapper: vue.computed(() => ({ ...wrapper.value, ...customWrapper.value })),
887
- value: field.value,
888
- field,
889
- onBlur,
890
- onChange,
891
- appliedOptions: vue.computed(
892
- () => uischema.options ?? {}
893
- )
894
- };
895
- };
896
- };
897
- const useControlBinding = (uischema, schema, options = {}) => {
898
- return useCustomControlBinding()(uischema, schema, options);
895
+ const loadDisplayValue = (options, value) => {
896
+ const values = options.values;
897
+ if (!values) return value;
898
+ const valueKey = options.valueKey;
899
+ const id = value?.[valueKey] ?? value;
900
+ const findValue = values.find((o) => o[valueKey] === id);
901
+ return findValue;
899
902
  };
900
903
  const useSelectInput = (...fields) => (uischema, schema, field) => {
901
904
  const opts = uischema.options ?? {};
902
905
  return vue.computed(() => {
903
- return lodashEs.pick(opts, fields);
906
+ const options = Object.fromEntries(
907
+ fields.filter((f) => f in opts).map((f) => [f, opts[f]])
908
+ );
909
+ if (!options.labelKey) options.labelKey = "label";
910
+ if (!options.valueKey) options.valueKey = "value";
911
+ options.displayValue = loadDisplayValue(
912
+ options,
913
+ field.value.value
914
+ );
915
+ return options;
904
916
  });
905
917
  };
906
918
  const useSelectBinding = useCustomControlBinding({
907
- useProps: useSelectInput("options", "labelKey", "valueKey")
919
+ useProps: useSelectInput(
920
+ "options",
921
+ "values",
922
+ "uri",
923
+ "resource",
924
+ "dataField",
925
+ "labelKey",
926
+ "valueKey",
927
+ "clearable",
928
+ "storeValue"
929
+ )
908
930
  });
909
931
  const useAutocompleteBinding = useCustomControlBinding({
910
932
  useProps: useSelectInput(
@@ -918,7 +940,58 @@ const useAutocompleteBinding = useCustomControlBinding({
918
940
  "skipAuth"
919
941
  )
920
942
  });
921
- const _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
943
+ const FormModalProperties = {
944
+ /** Title displayed in the modal header. */
945
+ modalTitle: { type: String, required: true },
946
+ /** Label for the save button. */
947
+ saveLabel: { type: String, default: "Save" },
948
+ /** Label for the cancel button. */
949
+ cancelLabel: { type: String, default: "Cancel" },
950
+ /** JSON schema describing the shape of the form data. */
951
+ schema: { type: Object, required: true },
952
+ /** UI schema describing the layout and controls. */
953
+ uiSchema: { type: Object, required: true },
954
+ /** Modal width (`'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`). */
955
+ modalSize: { type: String, default: "md" },
956
+ /** Callback invoked when the modal closes (with result or `null` on cancel). */
957
+ onClose: {
958
+ type: Function,
959
+ required: true
960
+ },
961
+ /** Callback for form events dispatched by custom renderers. */
962
+ onEvents: {
963
+ type: Function
964
+ },
965
+ /** Initial form data to populate the form with. */
966
+ data: { type: Object, required: true },
967
+ /** When validation errors are shown. */
968
+ errorMode: {
969
+ type: String,
970
+ default: "onBlur"
971
+ },
972
+ /** HTTP client passed through to the inner JsonForm for remote renderers (e.g. autocomplete). */
973
+ http: {
974
+ type: Object,
975
+ default: null
976
+ },
977
+ /** Custom renderer registry passed to the inner JsonForm. */
978
+ renderers: {
979
+ type: Array,
980
+ default: null
981
+ }
982
+ };
983
+ const FormModalEmits = [
984
+ /** Emitted when the modal is closed (submit or cancel). */
985
+ "closeModal",
986
+ /** Emitted when a custom renderer dispatches a form event. */
987
+ "events",
988
+ /** Emitted when validation errors change. */
989
+ "errors",
990
+ /** Emitted when form validity changes. */
991
+ "valid"
992
+ ];
993
+ const _hoisted_1$9 = { class: "overflow-auto" };
994
+ const _sfc_main$j = /* @__PURE__ */ vue.defineComponent({
922
995
  __name: "FormModal",
923
996
  props: /* @__PURE__ */ vue.mergeModels(FormModalProperties, {
924
997
  "modelValue": {},
@@ -927,7 +1000,7 @@ const _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
927
1000
  emits: /* @__PURE__ */ vue.mergeModels(FormModalEmits, ["update:modelValue"]),
928
1001
  setup(__props, { emit: __emit }) {
929
1002
  const properties = __props;
930
- const id = `modal_${Math.floor(Math.random() * 1e3)}`;
1003
+ const id = `edit_${Math.floor(Math.random() * 1e3)}`;
931
1004
  const formRef = vue.ref();
932
1005
  const valid = vue.ref(false);
933
1006
  const formData = vue.useModel(__props, "modelValue");
@@ -949,7 +1022,7 @@ const _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
949
1022
  };
950
1023
  const onErrors = (errors) => {
951
1024
  emits("errors", errors);
952
- valid.value = lodashEs.isEmpty(errors);
1025
+ valid.value = !errors || (Array.isArray(errors) ? errors.length === 0 : Object.keys(errors).length === 0);
953
1026
  };
954
1027
  vue.watch(valid, (newValid, oldValid) => {
955
1028
  if (newValid !== oldValid) {
@@ -964,43 +1037,215 @@ const _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
964
1037
  onCloseModal: onCancel
965
1038
  }), {
966
1039
  content: vue.withCtx(() => [
967
- vue.renderSlot(_ctx.$slots, "content-before"),
968
- vue.createVNode(_sfc_main$2, {
969
- id: `modal-${id}`,
970
- ref_key: "formRef",
971
- ref: formRef,
972
- "form-data": formData.value,
973
- schema: _ctx.schema,
974
- "ui-schema": _ctx.uiSchema,
975
- "error-mode": _ctx.errorMode,
976
- onErrors,
977
- onChange,
978
- onEvents: _cache[0] || (_cache[0] = ($event) => emits("events", $event))
979
- }, null, 8, ["id", "form-data", "schema", "ui-schema", "error-mode"]),
980
- vue.renderSlot(_ctx.$slots, "content-after")
1040
+ vue.createElementVNode("div", _hoisted_1$9, [
1041
+ vue.renderSlot(_ctx.$slots, "content-before"),
1042
+ vue.createVNode(_sfc_main$4, {
1043
+ id: `modal-${id}`,
1044
+ ref_key: "formRef",
1045
+ ref: formRef,
1046
+ "form-data": formData.value,
1047
+ schema: _ctx.schema,
1048
+ "ui-schema": _ctx.uiSchema,
1049
+ "error-mode": _ctx.errorMode,
1050
+ http: properties.http,
1051
+ renderers: properties.renderers,
1052
+ onErrors,
1053
+ onChange,
1054
+ onEvents: _cache[0] || (_cache[0] = ($event) => emits("events", $event))
1055
+ }, null, 8, ["id", "form-data", "schema", "ui-schema", "error-mode", "http", "renderers"]),
1056
+ vue.renderSlot(_ctx.$slots, "content-after")
1057
+ ])
981
1058
  ]),
982
1059
  actions: vue.withCtx(() => [
983
1060
  vue.createVNode(vue.unref(ui.Btn), {
984
1061
  color: vue.unref(ui.Color).secondary,
985
1062
  outline: true,
986
- "aria-label": "Cancel",
1063
+ "aria-label": _ctx.cancelLabel,
987
1064
  onClick: onCancel
988
1065
  }, {
989
- default: vue.withCtx(() => [..._cache[1] || (_cache[1] = [
990
- vue.createTextVNode(" Cancel ", -1)
991
- ])]),
1066
+ default: vue.withCtx(() => [
1067
+ vue.createTextVNode(vue.toDisplayString(_ctx.cancelLabel), 1)
1068
+ ]),
992
1069
  _: 1
993
- }, 8, ["color"]),
1070
+ }, 8, ["color", "aria-label"]),
994
1071
  vue.createVNode(vue.unref(ui.Btn), {
995
1072
  disabled: !valid.value,
996
- "aria-label": "Save",
1073
+ "aria-label": _ctx.saveLabel,
997
1074
  onClick: onSubmit
998
1075
  }, {
999
- default: vue.withCtx(() => [..._cache[2] || (_cache[2] = [
1000
- vue.createTextVNode(" Save ", -1)
1001
- ])]),
1076
+ default: vue.withCtx(() => [
1077
+ vue.createTextVNode(vue.toDisplayString(_ctx.saveLabel), 1)
1078
+ ]),
1079
+ _: 1
1080
+ }, 8, ["disabled", "aria-label"])
1081
+ ]),
1082
+ _: 3
1083
+ }, 16, ["width"]);
1084
+ };
1085
+ }
1086
+ });
1087
+ const ViewModalProperties = {
1088
+ /** Title displayed in the modal header. */
1089
+ modalTitle: { type: String, required: true },
1090
+ /** Label for the close button. */
1091
+ closeLabel: { type: String, default: "Close" },
1092
+ /** Label for the edit button. Only rendered when `canEdit` is true. */
1093
+ editLabel: { type: String, default: "Edit" },
1094
+ /** Label for the delete button. Only rendered when `canDelete` is true. */
1095
+ deleteLabel: { type: String, default: "Delete" },
1096
+ /** JSON schema describing the shape of the form data. */
1097
+ schema: { type: Object, required: true },
1098
+ /** UI schema describing the layout and controls. */
1099
+ uiSchema: { type: Object, required: true },
1100
+ /** Modal width (`'xs'`, `'sm'`, `'md'`, `'lg'`, `'xl'`). */
1101
+ modalSize: { type: String, default: "md" },
1102
+ /** Callback invoked when the modal closes (with result or `null` on cancel). */
1103
+ onClose: {
1104
+ type: Function,
1105
+ default: () => {
1106
+ }
1107
+ },
1108
+ /**
1109
+ * Show the Edit button.
1110
+ * The caller wires the action by listening to the `edit` event via `onEdit`
1111
+ * in the props object — Vue's v-bind spread in modalWrapper converts onXxx
1112
+ * keys into event handlers automatically.
1113
+ */
1114
+ canEdit: { type: Boolean, default: false },
1115
+ /**
1116
+ * Show the Delete button.
1117
+ * The caller wires the action by listening to the `delete` event via `onDelete`
1118
+ * in the props object.
1119
+ */
1120
+ canDelete: { type: Boolean, default: false },
1121
+ /** Initial form data to populate the form with. */
1122
+ data: { type: Object, required: true },
1123
+ /** Custom renderer registry passed to the inner JsonForm. */
1124
+ renderers: {
1125
+ type: Array,
1126
+ default: null
1127
+ }
1128
+ };
1129
+ const ViewModalEmits = [
1130
+ /** Emitted when the modal is closed. */
1131
+ "closeModal",
1132
+ /** Emitted when the Edit button is clicked. Payload: current form data. */
1133
+ "edit",
1134
+ /** Emitted when the Delete button is clicked. Payload: current form data. */
1135
+ "delete",
1136
+ /** Emitted when the view event is fired from the form. Payload: payload of the form event */
1137
+ "view"
1138
+ ];
1139
+ const _hoisted_1$8 = { class: "text-gray-500 text-xs mb-2" };
1140
+ const _hoisted_2$3 = { class: "overflow-y-auto" };
1141
+ const _sfc_main$i = /* @__PURE__ */ vue.defineComponent({
1142
+ __name: "ViewModal",
1143
+ props: /* @__PURE__ */ vue.mergeModels(ViewModalProperties, {
1144
+ "modelValue": {},
1145
+ "modelModifiers": {}
1146
+ }),
1147
+ emits: /* @__PURE__ */ vue.mergeModels(ViewModalEmits, ["update:modelValue"]),
1148
+ setup(__props, { emit: __emit }) {
1149
+ const properties = __props;
1150
+ const id = `view_${Math.floor(Math.random() * 1e3)}`;
1151
+ const valid = vue.ref(false);
1152
+ const formData = vue.useModel(__props, "modelValue");
1153
+ const emits = __emit;
1154
+ if (properties.data) {
1155
+ formData.value = properties.data;
1156
+ }
1157
+ vue.provide(
1158
+ "renderers",
1159
+ properties.renderers?.length ? [...customRenderers, ...properties.renderers] : customRenderers
1160
+ );
1161
+ vue.provide("readonlyRenderers", properties.renderers ?? []);
1162
+ vue.provide("rootSchema", properties.schema);
1163
+ vue.provide("styles", ui.myStyles);
1164
+ const onCancel = () => {
1165
+ formData.value = {};
1166
+ emits("closeModal", null);
1167
+ };
1168
+ const onEditClick = () => {
1169
+ const data = formData.value;
1170
+ emits("edit", data);
1171
+ emits("closeModal", null);
1172
+ };
1173
+ const onDeleteClick = () => {
1174
+ const data = formData.value;
1175
+ emits("delete", data);
1176
+ emits("closeModal", null);
1177
+ };
1178
+ vue.watch(valid, (newValid, oldValid) => {
1179
+ if (newValid !== oldValid) {
1180
+ emits("valid", newValid);
1181
+ }
1182
+ });
1183
+ const handleEvent = (event) => {
1184
+ if (event.event !== "view") return;
1185
+ emits("view", event);
1186
+ };
1187
+ return (_ctx, _cache) => {
1188
+ return vue.openBlock(), vue.createBlock(vue.unref(ui.Modal), vue.mergeProps(properties, {
1189
+ open: true,
1190
+ "disable-close": false,
1191
+ width: _ctx.modalSize,
1192
+ onCloseModal: onCancel
1193
+ }), {
1194
+ title: vue.withCtx(() => [
1195
+ vue.createElementVNode("h3", null, vue.toDisplayString(_ctx.modalTitle), 1),
1196
+ vue.createElementVNode("div", _hoisted_1$8, vue.toDisplayString(formData.value.id), 1)
1197
+ ]),
1198
+ content: vue.withCtx(() => [
1199
+ vue.renderSlot(_ctx.$slots, "content-before"),
1200
+ vue.createElementVNode("div", _hoisted_2$3, [
1201
+ vue.createVNode(_sfc_main$4, {
1202
+ id: `modal-${id}`,
1203
+ ref: "formRef",
1204
+ "form-data": formData.value,
1205
+ schema: _ctx.schema,
1206
+ readonly: true,
1207
+ "ui-schema": _ctx.uiSchema,
1208
+ renderers: properties.renderers,
1209
+ onEvents: handleEvent
1210
+ }, null, 8, ["id", "form-data", "schema", "ui-schema", "renderers"])
1211
+ ]),
1212
+ vue.renderSlot(_ctx.$slots, "content-after")
1213
+ ]),
1214
+ actions: vue.withCtx(() => [
1215
+ _ctx.canEdit ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
1216
+ key: 0,
1217
+ "aria-label": _ctx.editLabel,
1218
+ onClick: onEditClick
1219
+ }, {
1220
+ default: vue.withCtx(() => [
1221
+ vue.createTextVNode(vue.toDisplayString(_ctx.editLabel), 1)
1222
+ ]),
1223
+ _: 1
1224
+ }, 8, ["aria-label"])) : vue.createCommentVNode("", true),
1225
+ vue.createVNode(vue.unref(ui.Btn), {
1226
+ color: vue.unref(ui.Color).secondary,
1227
+ outline: true,
1228
+ "aria-label": _ctx.closeLabel,
1229
+ onClick: onCancel
1230
+ }, {
1231
+ default: vue.withCtx(() => [
1232
+ vue.createTextVNode(vue.toDisplayString(_ctx.closeLabel), 1)
1233
+ ]),
1002
1234
  _: 1
1003
- }, 8, ["disabled"])
1235
+ }, 8, ["color", "aria-label"]),
1236
+ _ctx.canDelete ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
1237
+ key: 1,
1238
+ color: vue.unref(ui.Color).error,
1239
+ outline: true,
1240
+ "aria-label": _ctx.deleteLabel,
1241
+ icon: vue.unref(ui.IconEnum).Delete,
1242
+ onClick: onDeleteClick
1243
+ }, {
1244
+ default: vue.withCtx(() => [
1245
+ vue.createTextVNode(vue.toDisplayString(_ctx.deleteLabel), 1)
1246
+ ]),
1247
+ _: 1
1248
+ }, 8, ["color", "aria-label", "icon"])) : vue.createCommentVNode("", true)
1004
1249
  ]),
1005
1250
  _: 3
1006
1251
  }, 16, ["width"]);
@@ -1015,10 +1260,12 @@ class JsonFormModalService {
1015
1260
  uiSchema,
1016
1261
  modalSize,
1017
1262
  onClose,
1018
- onEvents
1263
+ onEvents,
1264
+ http,
1265
+ renderers
1019
1266
  }) {
1020
1267
  ui.ModalService.openModal({
1021
- component: _sfc_main$d,
1268
+ component: _sfc_main$j,
1022
1269
  props: {
1023
1270
  schema,
1024
1271
  uiSchema,
@@ -1026,12 +1273,49 @@ class JsonFormModalService {
1026
1273
  data: initialData ?? {},
1027
1274
  modalTitle,
1028
1275
  onClose,
1029
- onEvents
1276
+ onEvents,
1277
+ http,
1278
+ renderers
1279
+ }
1280
+ });
1281
+ }
1282
+ static openViewModal({
1283
+ data,
1284
+ modalTitle,
1285
+ schema,
1286
+ uiSchema,
1287
+ modalSize,
1288
+ onClose,
1289
+ onEdit,
1290
+ onDelete,
1291
+ renderers,
1292
+ onView
1293
+ }) {
1294
+ ui.ModalService.openModal({
1295
+ component: _sfc_main$i,
1296
+ props: {
1297
+ schema,
1298
+ uiSchema,
1299
+ modalSize,
1300
+ data,
1301
+ modalTitle,
1302
+ onClose: onClose ?? (() => {
1303
+ }),
1304
+ // Boolean props drive button visibility — no `on` prefix so Vue passes
1305
+ // them as regular props, not event listeners.
1306
+ canEdit: !!onEdit,
1307
+ canDelete: !!onDelete,
1308
+ // `onEdit`/`onDelete` are intercepted by Vue's v-bind spread as event
1309
+ // handlers for ViewModal's declared 'edit'/'delete' emits.
1310
+ onEdit,
1311
+ onDelete,
1312
+ renderers,
1313
+ onView
1030
1314
  }
1031
1315
  });
1032
1316
  }
1033
1317
  }
1034
- const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1318
+ const _sfc_main$h = /* @__PURE__ */ vue.defineComponent({
1035
1319
  __name: "AutocompleteControlRenderer",
1036
1320
  props: {
1037
1321
  uischema: {},
@@ -1047,12 +1331,16 @@ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1047
1331
  onChange: onFieldChange,
1048
1332
  appliedOptions
1049
1333
  } = useAutocompleteBinding(props.uischema, props.schema);
1050
- const fetchOptions = core.computedAsync(async () => {
1051
- const config = await useFetchOptions(
1052
- appliedOptions.value
1053
- );
1054
- return config;
1055
- });
1334
+ const http = useHttpClient();
1335
+ const { values: formValues } = veeValidate.useFormContext();
1336
+ const fetchOptions = vue.ref(null);
1337
+ vue.watch(
1338
+ [appliedOptions, formValues],
1339
+ async ([opts]) => {
1340
+ fetchOptions.value = await useFetchOptions(opts, http, formValues);
1341
+ },
1342
+ { immediate: true, deep: true }
1343
+ );
1056
1344
  const onChange = (val) => {
1057
1345
  setValue(val);
1058
1346
  onFieldChange();
@@ -1065,12 +1353,17 @@ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1065
1353
  return;
1066
1354
  }
1067
1355
  const { valueKey, labelKey } = fetchOptions.value;
1356
+ const opts = appliedOptions.value;
1357
+ if (opts.storeValue && valueKey && valueKey in result) {
1358
+ field.setValue(result[valueKey]);
1359
+ return;
1360
+ }
1068
1361
  const keys = [valueKey, labelKey].filter(Boolean);
1069
1362
  if (keys.length === 0) {
1070
1363
  field.setValue(result);
1071
1364
  return;
1072
1365
  }
1073
- const stripped = lodashEs.pick(result, keys);
1366
+ const stripped = Object.fromEntries(keys.filter((k) => k in result).map((k) => [k, result[k]]));
1074
1367
  field.setValue(stripped);
1075
1368
  };
1076
1369
  const onCreate = () => {
@@ -1081,6 +1374,7 @@ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1081
1374
  schema: form.json_schema,
1082
1375
  uiSchema: form.ui_schema,
1083
1376
  modalTitle: `Create new ${wrapper.value.label}`,
1377
+ http,
1084
1378
  onClose: (result) => {
1085
1379
  if (!result || !result.valid) return;
1086
1380
  form.create(result.data).then((res) => {
@@ -1098,12 +1392,12 @@ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1098
1392
  });
1099
1393
  };
1100
1394
  return (_ctx, _cache) => {
1101
- return vue.unref(fetchOptions) ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Autocomplete), vue.mergeProps({ key: 0 }, vue.unref(wrapper), {
1395
+ return fetchOptions.value ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Autocomplete), vue.mergeProps({ key: 0 }, vue.unref(wrapper), {
1102
1396
  "model-value": vue.unref(value),
1103
- "fetch-options": vue.unref(fetchOptions).fetchOptions,
1104
- "label-key": vue.unref(fetchOptions).labelKey,
1105
- "value-key": vue.unref(fetchOptions).valueKey,
1106
- "enable-create": vue.unref(fetchOptions).enableCreate,
1397
+ "fetch-options": fetchOptions.value.fetchOptions,
1398
+ "label-key": fetchOptions.value.labelKey,
1399
+ "value-key": fetchOptions.value.valueKey,
1400
+ "enable-create": fetchOptions.value.enableCreate,
1107
1401
  onChange,
1108
1402
  onBlur: vue.unref(onBlur),
1109
1403
  onCreate
@@ -1111,7 +1405,7 @@ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1111
1405
  };
1112
1406
  }
1113
1407
  });
1114
- const _sfc_main$b = /* @__PURE__ */ vue.defineComponent({
1408
+ const _sfc_main$g = /* @__PURE__ */ vue.defineComponent({
1115
1409
  __name: "BooleanControlRenderer",
1116
1410
  props: {
1117
1411
  uischema: {},
@@ -1138,7 +1432,7 @@ const _sfc_main$b = /* @__PURE__ */ vue.defineComponent({
1138
1432
  };
1139
1433
  }
1140
1434
  });
1141
- const _sfc_main$a = /* @__PURE__ */ vue.defineComponent({
1435
+ const _sfc_main$f = /* @__PURE__ */ vue.defineComponent({
1142
1436
  __name: "MarkdownControlRenderer",
1143
1437
  props: {
1144
1438
  uischema: {},
@@ -1166,7 +1460,7 @@ const _sfc_main$a = /* @__PURE__ */ vue.defineComponent({
1166
1460
  };
1167
1461
  }
1168
1462
  });
1169
- const _sfc_main$9 = /* @__PURE__ */ vue.defineComponent({
1463
+ const _sfc_main$e = /* @__PURE__ */ vue.defineComponent({
1170
1464
  __name: "MultiSelectControlRenderer",
1171
1465
  props: {
1172
1466
  uischema: {},
@@ -1179,10 +1473,14 @@ const _sfc_main$9 = /* @__PURE__ */ vue.defineComponent({
1179
1473
  value,
1180
1474
  field,
1181
1475
  onBlur,
1182
- onChange: onFieldChange
1476
+ onChange: onFieldChange,
1477
+ appliedOptions
1183
1478
  } = useSelectBinding(props.uischema, props.schema);
1184
1479
  const onChange = (val) => {
1185
- field.setValue(val);
1480
+ const opts = appliedOptions.value;
1481
+ const valueKey = opts.valueKey ?? "value";
1482
+ const stored = opts.storeValue && Array.isArray(val) ? val.map((item) => item && typeof item === "object" ? item[valueKey] : item) : val;
1483
+ field.setValue(stored);
1186
1484
  onFieldChange();
1187
1485
  };
1188
1486
  return (_ctx, _cache) => {
@@ -1194,7 +1492,7 @@ const _sfc_main$9 = /* @__PURE__ */ vue.defineComponent({
1194
1492
  };
1195
1493
  }
1196
1494
  });
1197
- const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
1495
+ const _sfc_main$d = /* @__PURE__ */ vue.defineComponent({
1198
1496
  __name: "NumberControlRenderer",
1199
1497
  props: {
1200
1498
  uischema: {},
@@ -1218,7 +1516,7 @@ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
1218
1516
  };
1219
1517
  }
1220
1518
  });
1221
- const _sfc_main$7 = /* @__PURE__ */ vue.defineComponent({
1519
+ const _sfc_main$c = /* @__PURE__ */ vue.defineComponent({
1222
1520
  __name: "SelectControlRenderer",
1223
1521
  props: {
1224
1522
  uischema: {},
@@ -1234,24 +1532,60 @@ const _sfc_main$7 = /* @__PURE__ */ vue.defineComponent({
1234
1532
  onChange: onFieldChange,
1235
1533
  appliedOptions
1236
1534
  } = useSelectBinding(props.uischema, props.schema);
1535
+ const http = useHttpClient();
1536
+ const { values: formValues } = veeValidate.useFormContext();
1537
+ const valueKey = vue.computed(
1538
+ () => appliedOptions.value.valueKey ?? "value"
1539
+ );
1540
+ const isRemote = vue.computed(
1541
+ () => !!appliedOptions.value.uri || !!appliedOptions.value.resource
1542
+ );
1543
+ const remoteOptions = vue.ref([]);
1544
+ vue.watch(
1545
+ [appliedOptions, formValues],
1546
+ async ([opts]) => {
1547
+ if (!isRemote.value) return;
1548
+ const fetcher = await useFetchOptions(
1549
+ opts,
1550
+ http,
1551
+ formValues
1552
+ );
1553
+ if (!fetcher.fetchOptions) return;
1554
+ try {
1555
+ const results = await fetcher.fetchOptions(
1556
+ "",
1557
+ new AbortController().signal
1558
+ );
1559
+ remoteOptions.value = Array.isArray(results) ? results : [];
1560
+ } catch {
1561
+ remoteOptions.value = [];
1562
+ }
1563
+ },
1564
+ { immediate: true, deep: true }
1565
+ );
1237
1566
  const selectOptions = vue.computed(() => {
1238
- return appliedOptions.options ?? [];
1567
+ if (isRemote.value) return remoteOptions.value;
1568
+ const opts = appliedOptions.value;
1569
+ return opts.options ?? opts.values ?? [];
1239
1570
  });
1240
1571
  const onChange = (val) => {
1241
- field.setValue(val);
1572
+ const opts = appliedOptions.value;
1573
+ const stored = opts.storeValue && val && typeof val === "object" ? val[valueKey.value] : val;
1574
+ field.setValue(stored);
1242
1575
  onFieldChange();
1243
1576
  };
1244
1577
  return (_ctx, _cache) => {
1245
1578
  return vue.openBlock(), vue.createBlock(vue.unref(ui.SelectComponent), vue.mergeProps(vue.unref(wrapper), {
1246
1579
  "model-value": vue.unref(value),
1247
1580
  options: selectOptions.value,
1581
+ clearable: vue.unref(appliedOptions).clearable ?? true,
1248
1582
  onChange,
1249
1583
  onBlur: vue.unref(onBlur)
1250
- }), null, 16, ["model-value", "options", "onBlur"]);
1584
+ }), null, 16, ["model-value", "options", "clearable", "onBlur"]);
1251
1585
  };
1252
1586
  }
1253
1587
  });
1254
- const _sfc_main$6 = /* @__PURE__ */ vue.defineComponent({
1588
+ const _sfc_main$b = /* @__PURE__ */ vue.defineComponent({
1255
1589
  __name: "StringControlRenderer",
1256
1590
  props: {
1257
1591
  uischema: {},
@@ -1273,7 +1607,7 @@ const _sfc_main$6 = /* @__PURE__ */ vue.defineComponent({
1273
1607
  };
1274
1608
  }
1275
1609
  });
1276
- const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
1610
+ const _sfc_main$a = /* @__PURE__ */ vue.defineComponent({
1277
1611
  __name: "TextAreaControlRenderer",
1278
1612
  props: {
1279
1613
  uischema: {},
@@ -1281,47 +1615,87 @@ const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
1281
1615
  },
1282
1616
  setup(__props) {
1283
1617
  const props = __props;
1284
- const { wrapper, value, onBlur, onChange } = useControlBinding(
1618
+ const { wrapper, value, onBlur, onChange, appliedOptions } = useControlBinding(
1285
1619
  props.uischema,
1286
1620
  props.schema
1287
1621
  );
1622
+ const pathPrefix = vue.inject("pathPrefix", "");
1623
+ const opts = props.uischema.options ?? {};
1624
+ const { values: formValues } = veeValidate.useFormContext();
1625
+ const dir = vue.computed(() => {
1626
+ if (!opts.directionField) return opts.direction ?? "ltr";
1627
+ const key = pathPrefix ? `${pathPrefix}.${opts.directionField}` : opts.directionField;
1628
+ const val = key.split(".").reduce((o, k) => o?.[k], formValues);
1629
+ return val ?? "ltr";
1630
+ });
1288
1631
  return (_ctx, _cache) => {
1289
1632
  return vue.openBlock(), vue.createBlock(vue.unref(ui.Textarea), vue.mergeProps(vue.unref(wrapper), {
1290
1633
  modelValue: vue.unref(value),
1291
1634
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => vue.isRef(value) ? value.value = $event : null),
1635
+ dir: dir.value,
1636
+ height: vue.unref(appliedOptions).height,
1637
+ "min-height": vue.unref(appliedOptions).minHeight,
1292
1638
  onBlur: vue.unref(onBlur),
1293
1639
  onChange: vue.unref(onChange)
1294
- }), null, 16, ["modelValue", "onBlur", "onChange"]);
1640
+ }), null, 16, ["modelValue", "dir", "height", "min-height", "onBlur", "onChange"]);
1295
1641
  };
1296
1642
  }
1297
1643
  });
1298
- const index = [
1299
- { tester: core$1.rankWith(10, isStringFormat), renderer: _sfc_main$6 },
1644
+ const controlRenderers = [
1645
+ { tester: rankWith(10, isStringFormat), renderer: _sfc_main$b },
1300
1646
  {
1301
- tester: core$1.rankWith(11, isTextAreaControl),
1302
- renderer: _sfc_main$5
1647
+ tester: rankWith(11, isTextAreaControl),
1648
+ renderer: _sfc_main$a
1303
1649
  },
1304
1650
  {
1305
- tester: core$1.rankWith(11, isMarkdownControl),
1306
- renderer: _sfc_main$a
1651
+ tester: rankWith(11, isMarkdownControl),
1652
+ renderer: _sfc_main$f
1307
1653
  },
1308
- { tester: core$1.rankWith(11, isBooleanControl), renderer: _sfc_main$b },
1309
- { tester: core$1.rankWith(11, isSelectControl), renderer: _sfc_main$7 },
1654
+ { tester: rankWith(11, isBooleanControl), renderer: _sfc_main$g },
1655
+ { tester: rankWith(11, isSelectControl), renderer: _sfc_main$c },
1310
1656
  {
1311
- tester: core$1.rankWith(11, isMultiselectControl),
1312
- renderer: _sfc_main$9
1657
+ tester: rankWith(11, isMultiselectControl),
1658
+ renderer: _sfc_main$e
1313
1659
  },
1314
1660
  {
1315
- tester: core$1.rankWith(12, isAutoCompleteControl),
1316
- renderer: _sfc_main$c
1661
+ tester: rankWith(12, isAutoCompleteControl),
1662
+ renderer: _sfc_main$h
1317
1663
  },
1318
1664
  {
1319
- tester: core$1.rankWith(12, isNumberFormat),
1320
- renderer: _sfc_main$8
1665
+ tester: rankWith(12, isNumberFormat),
1666
+ renderer: _sfc_main$d
1321
1667
  },
1322
1668
  {
1323
- tester: core$1.rankWith(12, isIntegerFormat),
1324
- renderer: _sfc_main$8
1669
+ tester: rankWith(12, isIntegerFormat),
1670
+ renderer: _sfc_main$d
1671
+ }
1672
+ ];
1673
+ const _sfc_main$9 = /* @__PURE__ */ vue.defineComponent({
1674
+ __name: "ControlReadonlyRenderer",
1675
+ props: {
1676
+ uischema: {},
1677
+ schema: {}
1678
+ },
1679
+ setup(__props) {
1680
+ const props = __props;
1681
+ const { wrapper, displayWrapper } = useReadonlyControlBinding(
1682
+ props.uischema,
1683
+ props.schema
1684
+ );
1685
+ return (_ctx, _cache) => {
1686
+ return vue.openBlock(), vue.createBlock(_sfc_main$u, vue.normalizeProps(vue.guardReactiveProps(vue.unref(wrapper))), {
1687
+ default: vue.withCtx(() => [
1688
+ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(vue.unref(displayWrapper).component), vue.normalizeProps(vue.guardReactiveProps(vue.unref(displayWrapper).value)), null, 16))
1689
+ ]),
1690
+ _: 1
1691
+ }, 16);
1692
+ };
1693
+ }
1694
+ });
1695
+ const readonlyControlRenderers = [
1696
+ {
1697
+ tester: rankWith(10, uiTypeIs("Control")),
1698
+ renderer: _sfc_main$9
1325
1699
  }
1326
1700
  ];
1327
1701
  const COLSPAN = {
@@ -1338,8 +1712,8 @@ const COLSPAN = {
1338
1712
  11: "col-span-11",
1339
1713
  12: "col-span-12"
1340
1714
  };
1341
- const _hoisted_1$4 = { class: "flex flex-col gap-4" };
1342
- const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
1715
+ const _hoisted_1$7 = { class: "flex flex-col gap-4" };
1716
+ const _sfc_main$8 = /* @__PURE__ */ vue.defineComponent({
1343
1717
  __name: "CollapseLayoutRenderer",
1344
1718
  props: {
1345
1719
  uischema: {},
@@ -1359,13 +1733,13 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
1359
1733
  return (_ctx, _cache) => {
1360
1734
  return vue.openBlock(), vue.createBlock(vue.unref(ui.Collapse), { title: title.value }, {
1361
1735
  default: vue.withCtx(() => [
1362
- vue.createElementVNode("div", _hoisted_1$4, [
1736
+ vue.createElementVNode("div", _hoisted_1$7, [
1363
1737
  (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.uischema.elements, (child, i) => {
1364
1738
  return vue.openBlock(), vue.createElementBlock("div", {
1365
1739
  key: i,
1366
1740
  class: vue.normalizeClass(vue.unref(COLSPAN)[child.options?.colspan ?? 12])
1367
1741
  }, [
1368
- vue.createVNode(_sfc_main$f, {
1742
+ vue.createVNode(_sfc_main$5, {
1369
1743
  uischema: child,
1370
1744
  schema: __props.schema
1371
1745
  }, null, 8, ["uischema", "schema"])
@@ -1378,11 +1752,11 @@ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
1378
1752
  };
1379
1753
  }
1380
1754
  });
1381
- const _hoisted_1$3 = {
1755
+ const _hoisted_1$6 = {
1382
1756
  key: 1,
1383
1757
  class: "flex flex-col gap-3"
1384
1758
  };
1385
- const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
1759
+ const _sfc_main$7 = /* @__PURE__ */ vue.defineComponent({
1386
1760
  __name: "LayoutRenderer",
1387
1761
  props: {
1388
1762
  uischema: {},
@@ -1391,8 +1765,11 @@ const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
1391
1765
  setup(__props) {
1392
1766
  const props = __props;
1393
1767
  const LAYOUT = {
1394
- GridLayout: "grid grid-cols-12 gap-3",
1395
- HorizontalLayout: "flex flex-row gap-3",
1768
+ // Stack on narrow viewports (e.g. small modals), switch to the 12-column
1769
+ // grid at md+. Children keep their `col-span-*`; with a single column the
1770
+ // span clamps to full width, so fields stack cleanly.
1771
+ GridLayout: "grid grid-cols-1 gap-x-3 md:grid-cols-12",
1772
+ HorizontalLayout: "flex flex-col gap-y-3 md:flex-row",
1396
1773
  VerticalLayout: "flex flex-col gap-3"
1397
1774
  };
1398
1775
  const getLayout = vue.computed(() => LAYOUT[props.uischema.type]);
@@ -1407,31 +1784,175 @@ const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
1407
1784
  key: i,
1408
1785
  class: vue.normalizeClass(vue.unref(COLSPAN)[child.options?.colspan ?? 12])
1409
1786
  }, [
1410
- vue.createVNode(_sfc_main$f, {
1787
+ vue.createVNode(_sfc_main$5, {
1411
1788
  uischema: child,
1412
1789
  schema: __props.schema
1413
1790
  }, null, 8, ["uischema", "schema"])
1414
1791
  ], 2);
1415
1792
  }), 128))
1416
- ], 2)) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$3, " No Applicable Layout found "));
1793
+ ], 2)) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, " No Applicable Layout found "));
1794
+ };
1795
+ }
1796
+ });
1797
+ const _hoisted_1$5 = {
1798
+ class: "grid items-center overflow-hidden",
1799
+ style: { "grid-template-columns": "minmax(150px, auto) minmax(0, 1fr)" }
1800
+ };
1801
+ const _sfc_main$6 = /* @__PURE__ */ vue.defineComponent({
1802
+ __name: "ReadOnlyLayoutRenderer",
1803
+ props: {
1804
+ uischema: {},
1805
+ schema: {}
1806
+ },
1807
+ setup(__props) {
1808
+ return (_ctx, _cache) => {
1809
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$5, [
1810
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.uischema.elements, (child, i) => {
1811
+ return vue.openBlock(), vue.createBlock(_sfc_main$5, {
1812
+ key: i,
1813
+ uischema: child,
1814
+ schema: __props.schema
1815
+ }, null, 8, ["uischema", "schema"]);
1816
+ }), 128))
1817
+ ]);
1818
+ };
1819
+ }
1820
+ });
1821
+ const isLayoutType = or(
1822
+ uiTypeIs("GridLayout"),
1823
+ uiTypeIs("HorizontalLayout"),
1824
+ uiTypeIs("VerticalLayout")
1825
+ );
1826
+ const layoutRenderers = [
1827
+ { tester: rankWith(10, isLayoutType), renderer: _sfc_main$7 },
1828
+ {
1829
+ tester: rankWith(10, uiTypeIs("CollapseLayout")),
1830
+ renderer: _sfc_main$8
1831
+ }
1832
+ ];
1833
+ const readonlyLayoutRenderers = [
1834
+ { tester: rankWith(10, isLayoutType), renderer: _sfc_main$6 },
1835
+ {
1836
+ tester: rankWith(10, uiTypeIs("CollapseLayout")),
1837
+ renderer: _sfc_main$8
1838
+ }
1839
+ ];
1840
+ const customRenderers = [
1841
+ layoutRenderers,
1842
+ controlRenderers,
1843
+ arrayRenderers
1844
+ ].flat();
1845
+ const readonlyRenderers = [
1846
+ readonlyLayoutRenderers,
1847
+ readonlyControlRenderers,
1848
+ arrayRenderers
1849
+ ].flat();
1850
+ function findRenderer(registry, uischema, schema) {
1851
+ let best = null;
1852
+ for (const entry of registry) {
1853
+ const rank = entry.tester(uischema, schema);
1854
+ if (rank > -1 && (!best || rank > best.rank)) {
1855
+ best = { rank, renderer: entry.renderer };
1856
+ }
1857
+ }
1858
+ return best?.renderer ?? null;
1859
+ }
1860
+ const _hoisted_1$4 = {
1861
+ key: 1,
1862
+ class: "text-error text-xs"
1863
+ };
1864
+ const _sfc_main$5 = /* @__PURE__ */ vue.defineComponent({
1865
+ __name: "Dispatch",
1866
+ props: {
1867
+ uischema: {},
1868
+ schema: {},
1869
+ pathPrefix: { default: void 0 }
1870
+ },
1871
+ setup(__props) {
1872
+ const props = __props;
1873
+ const editableRegistry = vue.inject("renderers");
1874
+ const extraReadonlyRenderers = vue.inject("readonlyRenderers", []);
1875
+ const formReadonly = vue.inject(FORM_READONLY_KEY, vue.ref(false));
1876
+ const effectiveReadonlyRenderers = vue.computed(
1877
+ () => extraReadonlyRenderers.length ? [...readonlyRenderers, ...extraReadonlyRenderers] : readonlyRenderers
1878
+ );
1879
+ const registry = vue.computed(
1880
+ () => formReadonly.value ? effectiveReadonlyRenderers.value : editableRegistry
1881
+ );
1882
+ const rootSchema = vue.inject("rootSchema");
1883
+ const parentPrefix = vue.inject("pathPrefix", "");
1884
+ const effectivePrefix = props.pathPrefix ?? parentPrefix;
1885
+ if (props.pathPrefix !== void 0) {
1886
+ vue.provide("pathPrefix", effectivePrefix);
1887
+ }
1888
+ const resolved = vue.computed(() => {
1889
+ const u = props.uischema;
1890
+ if (!u.scope) return props.schema;
1891
+ const fromRoot = resolveSchema(rootSchema, u.scope);
1892
+ if (fromRoot) return fromRoot;
1893
+ return resolveSchema(props.schema, u.scope) ?? props.schema;
1894
+ });
1895
+ const renderer = vue.computed(
1896
+ () => findRenderer(
1897
+ registry.value,
1898
+ props.uischema,
1899
+ resolved.value
1900
+ )
1901
+ );
1902
+ return (_ctx, _cache) => {
1903
+ return renderer.value ? (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(renderer.value), {
1904
+ key: 0,
1905
+ uischema: __props.uischema,
1906
+ schema: resolved.value
1907
+ }, null, 8, ["uischema", "schema"])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$4, " No renderer for " + vue.toDisplayString(__props.uischema.scope) + " type: " + vue.toDisplayString(__props.uischema.type), 1));
1417
1908
  };
1418
1909
  }
1419
1910
  });
1420
- const isLayoutType = core$1.or(
1421
- core$1.uiTypeIs("GridLayout"),
1422
- core$1.uiTypeIs("HorizontalLayout"),
1423
- core$1.uiTypeIs("VerticalLayout")
1424
- );
1425
- const layoutRenderes = [
1426
- { tester: core$1.rankWith(10, isLayoutType), renderer: _sfc_main$3 },
1427
- {
1428
- tester: core$1.rankWith(10, core$1.uiTypeIs("CollapseLayout")),
1429
- renderer: _sfc_main$4
1911
+ const JsonFormComponentProperties = {
1912
+ /** Unique identifier used to namespace the form element. */
1913
+ id: { type: String, required: true },
1914
+ /** HTML name attribute for the form. */
1915
+ name: { type: String, default: "form" },
1916
+ /** JSON schema describing the shape of the form data. */
1917
+ schema: { type: Object, required: true },
1918
+ /** UI schema describing the layout and controls. */
1919
+ uiSchema: { type: Object, required: true },
1920
+ /** Disable all form controls. */
1921
+ disabled: { type: Boolean, default: false },
1922
+ /** Make all form controls readonly. */
1923
+ readonly: { type: Boolean, default: false },
1924
+ /** Current form data object. */
1925
+ formData: { type: Object, default: () => ({}) },
1926
+ /** When validation errors are shown (`'onBlur'`, `'onChange'`, `'onSubmit'`, `'always'`). */
1927
+ errorMode: {
1928
+ type: String,
1929
+ default: "onChanges"
1930
+ },
1931
+ /** HTTP client used by renderers that fetch remote data (e.g. autocomplete). */
1932
+ http: {
1933
+ type: Object,
1934
+ default: null
1935
+ },
1936
+ /** Custom renderer registry. Overrides the default `customRenderers` when provided. */
1937
+ renderers: {
1938
+ type: Array,
1939
+ default: null
1430
1940
  }
1941
+ };
1942
+ const JsonFormComponentEmits = [
1943
+ /** Emitted when form data changes. */
1944
+ "change",
1945
+ /** Emitted on form submission. */
1946
+ "submit",
1947
+ /** Emitted when validation errors change. */
1948
+ "errors",
1949
+ /** Emitted when form validity changes. */
1950
+ "valid",
1951
+ /** Emitted when a custom renderer dispatches a form event. */
1952
+ "events"
1431
1953
  ];
1432
- const customRenderes = [layoutRenderes, index, arrayRenderers].flat();
1433
- const _hoisted_1$2 = ["id"];
1434
- const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
1954
+ const _hoisted_1$3 = ["id"];
1955
+ const _sfc_main$4 = /* @__PURE__ */ vue.defineComponent({
1435
1956
  __name: "FormComponent",
1436
1957
  props: JsonFormComponentProperties,
1437
1958
  emits: JsonFormComponentEmits,
@@ -1452,12 +1973,17 @@ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
1452
1973
  validationSchema: zodSchema,
1453
1974
  initialValues: properties.formData
1454
1975
  });
1455
- vue.provide("renderers", customRenderes);
1976
+ vue.provide(
1977
+ "renderers",
1978
+ properties.renderers?.length ? [...customRenderers, ...properties.renderers] : customRenderers
1979
+ );
1980
+ vue.provide("readonlyRenderers", properties.renderers ?? []);
1456
1981
  vue.provide("rootSchema", properties.schema);
1457
1982
  vue.provide("styles", ui.myStyles);
1458
1983
  const submitted = vue.ref(false);
1459
1984
  vue.provide(ERROR_MODE_KEY, vue.toRef(properties, "errorMode"));
1460
1985
  vue.provide(FORM_SUBMITTED_KEY, submitted);
1986
+ vue.provide(FORM_READONLY_KEY, vue.toRef(properties, "readonly"));
1461
1987
  vue.onMounted(async () => {
1462
1988
  const result = await validate();
1463
1989
  emits("valid", result.valid);
@@ -1465,6 +1991,9 @@ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
1465
1991
  provideFormEvents((payload) => {
1466
1992
  emits("events", payload);
1467
1993
  });
1994
+ if (properties.http) {
1995
+ provideHttpClient(properties.http);
1996
+ }
1468
1997
  let syncing = false;
1469
1998
  vue.watch(
1470
1999
  () => properties.formData,
@@ -1513,380 +2042,476 @@ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
1513
2042
  id: _ctx.id,
1514
2043
  onSubmit: vue.withModifiers(onSubmit, ["prevent"])
1515
2044
  }, [
1516
- vue.createVNode(_sfc_main$f, {
2045
+ vue.createVNode(_sfc_main$5, {
1517
2046
  uischema: _ctx.uiSchema,
1518
2047
  schema: _ctx.schema
1519
2048
  }, null, 8, ["uischema", "schema"])
1520
- ], 40, _hoisted_1$2);
2049
+ ], 40, _hoisted_1$3);
1521
2050
  };
1522
2051
  }
1523
2052
  });
1524
- const FormWithActionsProperties = {
1525
- /** Unique identifier used to namespace the inner form element. */
1526
- id: { type: String, required: true },
1527
- /** Title shown when creating a new record. */
1528
- createTitle: { type: String, required: true },
1529
- /** Title shown when editing an existing record. Falls back to `createTitle` when omitted. */
1530
- updateTitle: { type: String },
1531
- /** JSON schema describing the shape of the form data. */
1532
- schema: { type: Object },
1533
- /** UI schema describing the layout and controls. */
1534
- uiSchema: { type: Object },
1535
- /** REST endpoint used by `FormStore` to persist data. When omitted the form emits `submit` instead. */
1536
- uri: { type: String },
1537
- /** Enable vertical scrolling inside the form area. */
1538
- scrollable: { type: Boolean, default: false },
1539
- /** Stretch the collapse wrapper to full height. */
1540
- fullHeight: { type: Boolean, default: false },
1541
- /** Two-way bound form data object. */
1542
- modelValue: { type: Object, default: () => ({}) },
1543
- /** When validation errors are shown (`'onBlur'`, `'onChange'`, `'onSubmit'`, `'always'`). */
1544
- errorMode: {
1545
- type: String,
1546
- default: "onBlur"
1547
- }
1548
- };
1549
- const FormWithActionsEmits = [
1550
- /** Emitted when `modelValue` changes. */
1551
- "update:modelValue",
1552
- /** Emitted after a successful `FormStore.save()`. */
1553
- "success",
1554
- /** Emitted on submit when no `uri` is provided. */
1555
- "submit",
1556
- /** Emitted whenever form validity changes. */
1557
- "valid",
1558
- /** Emitted when a custom renderer dispatches a form event. */
1559
- "events",
1560
- /** Emitted when validation errors change. */
1561
- "errors",
1562
- /** Emitted when the user cancels editing an existing record. */
1563
- "cancel"
1564
- ];
1565
- class FormStore {
1566
- constructor(uri) {
1567
- this.uri = uri;
1568
- }
1569
- async delete(data) {
1570
- return toolsVue.useApi().delete(`${this.uri}/${data.id}`).then(() => {
1571
- ui.NotificationService.success("Data deleted");
1572
- }).catch((error) => {
1573
- console.error(error);
1574
- ui.NotificationService.error("Error deleting data");
2053
+ const createRepository = (formSchemaModel, httpRequest, options = {}) => {
2054
+ const notificationEntity = options.notification?.entityType || "entity";
2055
+ const notificationStore = options.notification?.notification ?? null;
2056
+ const getDataUri = (...suffix) => {
2057
+ return [formSchemaModel.uri, ...suffix].join("/");
2058
+ };
2059
+ const handleSuccess = (message) => {
2060
+ notificationStore?.success(message);
2061
+ };
2062
+ const handleError = (error, message) => {
2063
+ console.error(error);
2064
+ notificationStore?.error(message);
2065
+ throw new Error(error);
2066
+ };
2067
+ const create = (object, options2) => {
2068
+ return httpRequest.post(getDataUri(), object, options2).then((response) => {
2069
+ handleSuccess(`Created ${notificationEntity}`);
2070
+ return response.data;
2071
+ }).catch((response) => {
2072
+ handleError(response, `Failed to create ${notificationEntity}`);
1575
2073
  });
1576
- }
1577
- async save(id, data) {
1578
- if (!this.uri) return;
1579
- const promise = id ? toolsVue.useApi().patch(`${this.uri}/${id}`, data) : toolsVue.useApi().post(this.uri, data);
1580
- return promise.then((response) => {
1581
- ui.NotificationService.success("Data saved");
2074
+ };
2075
+ const patch = (id, object, options2) => {
2076
+ return httpRequest.patch(getDataUri(id), object, options2).then((response) => {
2077
+ handleSuccess(`Saved ${notificationEntity}`);
2078
+ return response.data;
2079
+ }).catch((response) => {
2080
+ handleError(response, `Failed to save ${notificationEntity}`);
2081
+ });
2082
+ };
2083
+ const get = (id, options2) => {
2084
+ return httpRequest.get(getDataUri(id), options2).then((response) => {
1582
2085
  return response.data;
1583
- }).catch((error) => {
1584
- console.error(error);
1585
- ui.NotificationService.error("Error saving data");
2086
+ }).catch((response) => {
2087
+ handleError(response, "Failed to load data");
2088
+ });
2089
+ };
2090
+ const _delete = (id, options2) => {
2091
+ return httpRequest.delete(getDataUri(id), options2).then((response) => {
2092
+ handleSuccess(`${notificationEntity} deleted`);
2093
+ return response;
2094
+ }).catch((response) => {
2095
+ handleError(response, `Failed to delete ${notificationEntity}`);
2096
+ });
2097
+ };
2098
+ const createMulti = (objects, options2) => {
2099
+ return Promise.all(
2100
+ objects.map((object) => httpRequest.post(getDataUri(), object, options2))
2101
+ ).then((responses) => {
2102
+ handleSuccess(`Created ${notificationEntity}`);
2103
+ return responses.map((r) => r.data);
2104
+ }).catch((response) => {
2105
+ handleError(response, `Failed to save ${notificationEntity}`);
1586
2106
  });
2107
+ };
2108
+ return { create, patch, createMulti, delete: _delete, get };
2109
+ };
2110
+ const TableComponentProperties = {
2111
+ id: { type: String, required: true },
2112
+ uiSchema: { type: Object, required: true },
2113
+ schema: { type: Object, required: true },
2114
+ reload: { type: Number },
2115
+ loading: { type: Boolean, default: false },
2116
+ multiselect: { type: Boolean, default: false },
2117
+ actions: { type: Array },
2118
+ data: { type: Array },
2119
+ page: { type: Object },
2120
+ sort: { type: Object },
2121
+ cellRenderers: { type: Array },
2122
+ hidePagination: { type: Boolean, default: false }
2123
+ };
2124
+ const TableComponentEmits = [
2125
+ "updatePage",
2126
+ "updatePageSize",
2127
+ "sort",
2128
+ "selectionChange"
2129
+ ];
2130
+ const cellTypeIs = (type, rank) => (element) => element.type === type ? rank : -1;
2131
+ const cellFormatIs = (format, rank) => (element) => element.options?.format === format ? rank : -1;
2132
+ const findCellRenderer = (registry, element) => {
2133
+ let best;
2134
+ for (const entry of registry) {
2135
+ const rank = entry.tester(element);
2136
+ if (rank > -1 && (!best || rank > best.rank)) {
2137
+ best = { rank, renderer: entry.renderer };
2138
+ }
1587
2139
  }
1588
- }
1589
- const _hoisted_1$1 = { class: "flex justify-end gap-2 p-2 mt-2 border-t border-gray-300 z-[30] shrink-0" };
1590
- const _hoisted_2 = { class: "flex justify-end gap-2" };
1591
- const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
1592
- __name: "FormWithActions",
1593
- props: FormWithActionsProperties,
1594
- emits: FormWithActionsEmits,
2140
+ return best?.renderer;
2141
+ };
2142
+ const defaultCellRenderers = [
2143
+ { tester: cellTypeIs("TextCell", 10), renderer: ui.TextCell },
2144
+ { tester: cellTypeIs("BooleanCell", 10), renderer: ui.BooleanCell }
2145
+ ];
2146
+ const _sfc_main$3 = /* @__PURE__ */ vue.defineComponent({
2147
+ __name: "TableComponent",
2148
+ props: TableComponentProperties,
2149
+ emits: TableComponentEmits,
1595
2150
  setup(__props, { emit: __emit }) {
1596
2151
  const properties = __props;
1597
2152
  const emits = __emit;
1598
- const formRef = vue.ref();
1599
- const formData = vue.ref(properties.modelValue);
1600
- const initialFormData = vue.ref(structuredClone(vue.toRaw(properties.modelValue)));
1601
- const recordId = vue.ref(properties.modelValue?.id ?? null);
1602
- const valid = vue.ref(false);
1603
- vue.watch(
1604
- () => properties.modelValue,
1605
- (newValue) => {
1606
- if (JSON.stringify(vue.toRaw(newValue)) === JSON.stringify(vue.toRaw(formData.value))) return;
1607
- recordId.value = newValue?.id ?? null;
1608
- initialFormData.value = structuredClone(vue.toRaw(newValue));
1609
- formData.value = newValue;
1610
- }
1611
- );
1612
- const store = vue.computed(
1613
- () => properties.uri ? new FormStore(properties.uri) : null
2153
+ const allRenderers = vue.computed(() => [
2154
+ ...properties.cellRenderers ?? [],
2155
+ ...defaultCellRenderers
2156
+ ]);
2157
+ const displayColumns = vue.computed(
2158
+ () => properties.uiSchema.elements.map((e) => {
2159
+ const element = e;
2160
+ const def = jsonFormsCore.findColumnDef(element, properties.schema);
2161
+ const type = Array.isArray(def.type) ? def.type[0] : def.type;
2162
+ const component = findCellRenderer(allRenderers.value, element);
2163
+ if (!component)
2164
+ console.warn("No cell renderer found for", element.type, element.options?.format);
2165
+ return {
2166
+ ...def,
2167
+ label: e.options?.label ?? def.id,
2168
+ type,
2169
+ component
2170
+ };
2171
+ })
1614
2172
  );
1615
- const updateValue = (data) => {
1616
- formData.value = data;
1617
- emits("update:modelValue", data);
1618
- };
1619
- const save = () => {
1620
- formRef.value?.markSubmitted();
1621
- if (!valid.value) return;
1622
- if (store.value) {
1623
- store.value.save(recordId.value, formData.value).then((response) => {
1624
- emits("success", response);
1625
- });
1626
- } else {
1627
- emits("submit", formData.value);
1628
- }
1629
- };
1630
- const clear = () => {
1631
- formData.value = { id: null };
1632
- emits("update:modelValue", formData.value);
2173
+ return (_ctx, _cache) => {
2174
+ return vue.openBlock(), vue.createBlock(vue.unref(ui.Table), vue.mergeProps(properties, {
2175
+ "display-columns": displayColumns.value,
2176
+ page: _ctx.hidePagination ? void 0 : _ctx.page,
2177
+ onSort: _cache[0] || (_cache[0] = (id) => emits("sort", id)),
2178
+ onUpdatePage: _cache[1] || (_cache[1] = (page) => emits("updatePage", page)),
2179
+ onUpdatePageSize: _cache[2] || (_cache[2] = (size) => emits("updatePageSize", size)),
2180
+ onSelectionChange: _cache[3] || (_cache[3] = (e) => emits("selectionChange", e))
2181
+ }), null, 16, ["display-columns", "page"]);
1633
2182
  };
1634
- const cancel = () => {
1635
- formData.value = structuredClone(vue.toRaw(initialFormData.value));
1636
- emits("update:modelValue", formData.value);
1637
- emits("cancel");
2183
+ }
2184
+ });
2185
+ const _hoisted_1$2 = { class: "flex gap-2 items-center" };
2186
+ const _hoisted_2$2 = {
2187
+ key: 1,
2188
+ class: "flex-1 min-w-0"
2189
+ };
2190
+ const _sfc_main$2 = /* @__PURE__ */ vue.defineComponent({
2191
+ __name: "FilterRowInput",
2192
+ props: {
2193
+ modelValue: {},
2194
+ fields: {}
2195
+ },
2196
+ emits: ["update:modelValue", "remove"],
2197
+ setup(__props, { emit: __emit }) {
2198
+ const props = __props;
2199
+ const emit = __emit;
2200
+ const noValue = vue.computed(() => jsonFormsCore.OperatorNoValue.has(props.modelValue.operator));
2201
+ const update = (key, value) => {
2202
+ emit("update:modelValue", { ...props.modelValue, [key]: value });
1638
2203
  };
1639
- const title = vue.computed(() => {
1640
- if (!properties.updateTitle) return properties.createTitle;
1641
- return recordId.value ? properties.updateTitle : properties.createTitle;
1642
- });
1643
- const onErrors = (errors) => {
1644
- emits("errors", errors);
1645
- valid.value = lodashEs.isEmpty(errors);
2204
+ const onOperatorChange = (op) => {
2205
+ emit("update:modelValue", {
2206
+ ...props.modelValue,
2207
+ operator: op,
2208
+ value: jsonFormsCore.OperatorNoValue.has(op) ? "" : props.modelValue.value
2209
+ });
1646
2210
  };
1647
- vue.watch(valid, (newValid, oldValid) => {
1648
- if (newValid !== oldValid) {
1649
- emits("valid", newValid);
1650
- }
1651
- });
1652
2211
  return (_ctx, _cache) => {
1653
- return vue.openBlock(), vue.createBlock(vue.unref(ui.Collapse), {
1654
- title: title.value,
1655
- "height-full": _ctx.fullHeight,
1656
- scrollable: true
1657
- }, {
1658
- default: vue.withCtx(() => [
1659
- vue.createElementVNode("div", {
1660
- class: vue.normalizeClass(["flex flex-col", { "overflow-hidden h-full": _ctx.scrollable }])
1661
- }, [
1662
- vue.createElementVNode("div", {
1663
- class: vue.normalizeClass(["flex-1", { "overflow-y-auto overflow-x-hidden": _ctx.scrollable }])
1664
- }, [
1665
- vue.createVNode(_sfc_main$2, {
1666
- id: `form_${_ctx.id}`,
1667
- ref_key: "formRef",
1668
- ref: formRef,
1669
- "form-data": formData.value,
1670
- schema: _ctx.schema,
1671
- "ui-schema": _ctx.uiSchema,
1672
- "error-mode": _ctx.errorMode,
1673
- onChange: updateValue,
1674
- onSubmit: save,
1675
- onErrors,
1676
- onEvents: _cache[0] || (_cache[0] = ($event) => emits("events", $event))
1677
- }, null, 8, ["id", "form-data", "schema", "ui-schema", "error-mode"])
1678
- ], 2),
1679
- vue.createElementVNode("div", _hoisted_1$1, [
1680
- vue.createElementVNode("div", _hoisted_2, [
1681
- vue.renderSlot(_ctx.$slots, "actions"),
1682
- recordId.value ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
1683
- key: 0,
1684
- "aria-label": "Cancel",
1685
- outline: true,
1686
- onClick: cancel
1687
- }, {
1688
- default: vue.withCtx(() => [..._cache[1] || (_cache[1] = [
1689
- vue.createTextVNode(" Cancel ", -1)
1690
- ])]),
1691
- _: 1
1692
- })) : (vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
1693
- key: 1,
1694
- "aria-label": "Clear",
1695
- outline: true,
1696
- onClick: clear
1697
- }, {
1698
- default: vue.withCtx(() => [..._cache[2] || (_cache[2] = [
1699
- vue.createTextVNode(" Clear ", -1)
1700
- ])]),
1701
- _: 1
1702
- })),
1703
- vue.createVNode(vue.unref(ui.Btn), {
1704
- "aria-label": "Save",
1705
- color: vue.unref(ui.Color).primary,
1706
- disabled: !valid.value,
1707
- onClick: save
1708
- }, {
1709
- default: vue.withCtx(() => [..._cache[3] || (_cache[3] = [
1710
- vue.createTextVNode(" Save ", -1)
1711
- ])]),
1712
- _: 1
1713
- }, 8, ["color", "disabled"])
1714
- ])
1715
- ])
1716
- ], 2)
1717
- ]),
1718
- _: 3
1719
- }, 8, ["title", "height-full"]);
2212
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2, [
2213
+ vue.createVNode(vue.unref(ui.SelectComponent), {
2214
+ size: "sm",
2215
+ value: __props.modelValue.key,
2216
+ options: __props.fields,
2217
+ clearable: false,
2218
+ onChange: _cache[0] || (_cache[0] = ($event) => update("key", $event.value))
2219
+ }, null, 8, ["value", "options"]),
2220
+ vue.createVNode(vue.unref(ui.SelectComponent), {
2221
+ size: "sm",
2222
+ value: __props.modelValue.operator,
2223
+ options: vue.unref(jsonFormsCore.OperatorOptions),
2224
+ clearable: false,
2225
+ onChange: _cache[1] || (_cache[1] = ($event) => onOperatorChange($event.value))
2226
+ }, null, 8, ["value", "options"]),
2227
+ !noValue.value ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Input), {
2228
+ key: 0,
2229
+ size: "sm",
2230
+ placeholder: "Enter a value",
2231
+ value: __props.modelValue.value,
2232
+ clearable: true,
2233
+ onInput: _cache[2] || (_cache[2] = ($event) => update("value", $event.target.value))
2234
+ }, null, 8, ["value"])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_2$2)),
2235
+ vue.createVNode(vue.unref(ui.Btn), {
2236
+ icon: vue.unref(ui.IconEnum).Delete,
2237
+ size: "xs",
2238
+ outline: true,
2239
+ color: "error",
2240
+ "no-border": true,
2241
+ tooltip: "Remove filter",
2242
+ onClick: _cache[3] || (_cache[3] = ($event) => _ctx.$emit("remove"))
2243
+ }, null, 8, ["icon"])
2244
+ ]);
1720
2245
  };
1721
2246
  }
1722
2247
  });
1723
- const FormWithTableProperties = {
1724
- /** Unique identifier used to namespace the inner table element. */
1725
- id: { type: String, required: true },
1726
- /** Heading displayed above the table. */
1727
- tableTitle: { type: String, required: true },
1728
- /** Title shown in the modal when creating a new record. */
1729
- createTitle: { type: String, required: true },
1730
- /** Title shown in the modal when editing a record. Falls back to `createTitle` when omitted. */
1731
- updateTitle: { type: String },
1732
- /** Override URI used to fetch table data. Defaults to `uri` when omitted. */
1733
- dataUri: { type: String },
1734
- /** Custom row actions rendered in the table. */
1735
- tableActions: { type: Array },
1736
- /** JSON Forms layout for the create/edit modal form. */
1737
- form: { type: Object },
1738
- /** JSON Forms layout for the table. */
1739
- table: { type: Object },
1740
- /** JSON Forms layout for the table filter. */
1741
- filter: { type: Object },
1742
- /** REST endpoint used by `FormStore` for CRUD operations. */
1743
- uri: { type: String },
1744
- /** Default data pre-filled when creating a new record. */
1745
- initialData: { type: Object, default: () => ({}) },
1746
- /** When validation errors are shown in the modal form. */
1747
- errorMode: {
1748
- type: String,
1749
- default: "onBlur"
1750
- }
2248
+ const _hoisted_1$1 = { class: "px-2 flex gap-2 items-center" };
2249
+ const _hoisted_2$1 = {
2250
+ key: 0,
2251
+ class: "badge badge-sm bg-base-300 text-base-700"
1751
2252
  };
1752
- const FormWithTableEmits = [
1753
- /** Emitted when a row is selected for editing (listener-based routing). */
1754
- "editData",
1755
- /** Emitted after a record is saved through the modal. */
1756
- "save",
1757
- /** Emitted after a record is deleted. */
1758
- "delete",
1759
- /** Emitted when a custom renderer dispatches a form event. */
1760
- "events",
1761
- /** Emitted when a custom edit handler is registered. */
1762
- "custom:edit",
1763
- /** Emitted when a custom create handler is registered. */
1764
- "custom:create"
1765
- ];
1766
- const _hoisted_1 = { class: "flex justify-between items-center mb-2" };
1767
- const _sfc_main = /* @__PURE__ */ vue.defineComponent({
1768
- __name: "FormWithTable",
1769
- props: FormWithTableProperties,
1770
- emits: FormWithTableEmits,
2253
+ const _hoisted_3$1 = {
2254
+ key: 0,
2255
+ class: "absolute left-1/2 -translate-x-1/2 top-full mt-1 z-50 min-w-[560px] border border-base-200 rounded-xl bg-base-100 shadow-lg p-4"
2256
+ };
2257
+ const _hoisted_4$1 = { class: "flex flex-col gap-2" };
2258
+ const _hoisted_5$1 = { class: "flex items-center justify-between" };
2259
+ const _hoisted_6$1 = { class: "flex gap-2" };
2260
+ const _sfc_main$1 = /* @__PURE__ */ vue.defineComponent({
2261
+ __name: "TableFilter",
2262
+ props: {
2263
+ filters: {},
2264
+ filterSchema: {}
2265
+ },
2266
+ emits: ["changeFilters", "close"],
1771
2267
  setup(__props, { emit: __emit }) {
1772
- const properties = __props;
2268
+ const showFilters = vue.ref(false);
2269
+ const appliedCount = vue.ref(0);
2270
+ const containerRef = vue.ref(null);
2271
+ const onClickOutside = (event) => {
2272
+ const target = event.target;
2273
+ if (containerRef.value?.contains(target)) return;
2274
+ if (target.closest?.("[data-select-listbox]")) return;
2275
+ showFilters.value = false;
2276
+ };
2277
+ vue.onMounted(() => document.addEventListener("mousedown", onClickOutside));
2278
+ vue.onBeforeUnmount(
2279
+ () => document.removeEventListener("mousedown", onClickOutside)
2280
+ );
2281
+ const props = __props;
1773
2282
  const emit = __emit;
1774
- const reload = vue.ref(0);
1775
- const resolvedForm = vue.computed(() => properties.form);
1776
- const resolvedTable = vue.computed(() => properties.table);
1777
- const resolvedFilter = vue.computed(() => properties.filter);
1778
- const resolvedUri = vue.computed(() => properties.uri);
1779
- let store = new FormStore(resolvedUri.value ?? "");
1780
- vue.watch(resolvedUri, (uri) => {
1781
- store = new FormStore(uri ?? "");
2283
+ const fields = vue.computed(() => {
2284
+ const properties = props.filterSchema?.properties;
2285
+ if (!properties) return [];
2286
+ return Object.entries(properties).map(([key, schema]) => ({
2287
+ value: key,
2288
+ label: schema.title ?? key
2289
+ }));
1782
2290
  });
1783
- const hasEdit = ui.hasCustomEventListener("editData");
1784
- const customEdit = ui.hasCustomEventListener("custom:edit");
1785
- const customCreate = ui.hasCustomEventListener("custom:create");
1786
- const edit = (data) => {
1787
- if (customEdit) {
1788
- emit("custom:edit", data);
1789
- return;
1790
- }
1791
- if (hasEdit) {
1792
- emit("editData", data);
1793
- return;
1794
- }
1795
- openModal(data);
2291
+ const defaultField = vue.computed(() => fields.value[0]?.value ?? "");
2292
+ const rows = vue.ref([]);
2293
+ const emptyRow = () => ({
2294
+ key: defaultField.value,
2295
+ value: "",
2296
+ operator: "contains"
2297
+ });
2298
+ vue.watch(
2299
+ () => props.filters,
2300
+ (newFilters) => {
2301
+ const parsed = jsonFormsCore.extractFilters(newFilters ?? []);
2302
+ rows.value = parsed.length > 0 ? parsed : [emptyRow()];
2303
+ appliedCount.value = parsed.length;
2304
+ },
2305
+ { immediate: true }
2306
+ );
2307
+ const addRow = () => {
2308
+ rows.value = [
2309
+ ...rows.value,
2310
+ { key: defaultField.value, value: "", operator: "contains" }
2311
+ ];
1796
2312
  };
1797
- const create = () => {
1798
- if (customCreate) {
1799
- emit("custom:create");
1800
- return;
1801
- }
1802
- openModal();
1803
- };
1804
- const deleteFn = (data) => {
1805
- ui.ModalService.showConfirm({
1806
- title: "Delete record",
1807
- message: "Are you sure to delete, the data will be lost?",
1808
- onClose: (result) => {
1809
- if (result.confirmed) {
1810
- store.delete(data).then(() => {
1811
- reload.value = Date.now();
1812
- emit("delete", data);
1813
- });
1814
- }
1815
- }
1816
- });
2313
+ const removeRow = (index) => {
2314
+ const updated = rows.value.filter((_, i) => i !== index);
2315
+ rows.value = updated.length > 0 ? updated : [emptyRow()];
1817
2316
  };
1818
- const openModal = (formData) => {
1819
- if (!resolvedForm.value) return;
1820
- const isUpdate = !!formData?.id;
1821
- JsonFormModalService.openModal({
1822
- schema: resolvedForm.value.schema,
1823
- uiSchema: resolvedForm.value.uiSchema,
1824
- modalSize: resolvedForm.value.modalSize,
1825
- initialData: formData ?? properties.initialData,
1826
- modalTitle: (isUpdate ? properties.updateTitle ?? properties.createTitle : properties.createTitle) ?? "",
1827
- onClose: (result) => {
1828
- if (result && result.valid) {
1829
- store.save(formData?.id, result.data).then(() => {
1830
- reload.value = Date.now();
1831
- emit("save", { id: formData?.id, data: result.data });
1832
- });
1833
- }
1834
- },
1835
- onEvents: (payload) => emit("events", payload)
1836
- });
2317
+ const onApply = () => {
2318
+ const serialized = rows.value.filter((r) => r.key && r.value).map(jsonFormsCore.filterToString);
2319
+ appliedCount.value = serialized.length;
2320
+ emit("changeFilters", serialized);
2321
+ showFilters.value = false;
2322
+ };
2323
+ const onReset = () => {
2324
+ rows.value = [emptyRow()];
2325
+ appliedCount.value = 0;
2326
+ emit("changeFilters", []);
2327
+ showFilters.value = false;
1837
2328
  };
1838
2329
  return (_ctx, _cache) => {
1839
- return vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [
1840
- vue.createElementVNode("div", _hoisted_1, [
1841
- vue.createElementVNode("h1", null, vue.toDisplayString(_ctx.tableTitle), 1),
1842
- vue.createElementVNode("div", null, [
2330
+ return vue.openBlock(), vue.createElementBlock("div", {
2331
+ ref_key: "containerRef",
2332
+ ref: containerRef,
2333
+ class: "relative inline-flex"
2334
+ }, [
2335
+ vue.createVNode(vue.unref(ui.Btn), {
2336
+ size: "xs",
2337
+ color: "ghost",
2338
+ class: "border-gray-300 text-base-content/50 h-8",
2339
+ onClick: _cache[0] || (_cache[0] = ($event) => showFilters.value = !showFilters.value)
2340
+ }, {
2341
+ default: vue.withCtx(() => [
2342
+ vue.createElementVNode("span", _hoisted_1$1, [
2343
+ vue.createVNode(vue.unref(ui.Icon), {
2344
+ icon: vue.unref(ui.IconEnum).Filter,
2345
+ size: "sm",
2346
+ class: "text-base-500"
2347
+ }, null, 8, ["icon"]),
2348
+ _cache[1] || (_cache[1] = vue.createTextVNode(" Filters ", -1)),
2349
+ appliedCount.value ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_2$1, vue.toDisplayString(appliedCount.value), 1)) : vue.createCommentVNode("", true)
2350
+ ])
2351
+ ]),
2352
+ _: 1
2353
+ }),
2354
+ showFilters.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3$1, [
2355
+ vue.createElementVNode("div", _hoisted_4$1, [
2356
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(rows.value, (row, index) => {
2357
+ return vue.openBlock(), vue.createBlock(_sfc_main$2, {
2358
+ key: index,
2359
+ modelValue: rows.value[index],
2360
+ "onUpdate:modelValue": ($event) => rows.value[index] = $event,
2361
+ fields: fields.value,
2362
+ onRemove: ($event) => removeRow(index)
2363
+ }, null, 8, ["modelValue", "onUpdate:modelValue", "fields", "onRemove"]);
2364
+ }), 128))
2365
+ ]),
2366
+ _cache[5] || (_cache[5] = vue.createElementVNode("div", { class: "divider my-3" }, null, -1)),
2367
+ vue.createElementVNode("div", _hoisted_5$1, [
1843
2368
  vue.createVNode(vue.unref(ui.Btn), {
1844
2369
  icon: vue.unref(ui.IconEnum).Plus,
1845
- outline: true,
1846
- onClick: create
2370
+ color: "ghost",
2371
+ onClick: addRow
1847
2372
  }, {
1848
- default: vue.withCtx(() => [..._cache[0] || (_cache[0] = [
1849
- vue.createTextVNode(" Add new record ", -1)
2373
+ default: vue.withCtx(() => [..._cache[2] || (_cache[2] = [
2374
+ vue.createTextVNode(" Add filter ", -1)
1850
2375
  ])]),
1851
2376
  _: 1
1852
- }, 8, ["icon"])
2377
+ }, 8, ["icon"]),
2378
+ vue.createElementVNode("div", _hoisted_6$1, [
2379
+ vue.createVNode(vue.unref(ui.Btn), {
2380
+ color: "ghost",
2381
+ onClick: onReset
2382
+ }, {
2383
+ default: vue.withCtx(() => [..._cache[3] || (_cache[3] = [
2384
+ vue.createTextVNode(" Reset ", -1)
2385
+ ])]),
2386
+ _: 1
2387
+ }),
2388
+ vue.createVNode(vue.unref(ui.Btn), { onClick: onApply }, {
2389
+ default: vue.withCtx(() => [..._cache[4] || (_cache[4] = [
2390
+ vue.createTextVNode(" Apply ", -1)
2391
+ ])]),
2392
+ _: 1
2393
+ })
2394
+ ])
1853
2395
  ])
2396
+ ])) : vue.createCommentVNode("", true)
2397
+ ], 512);
2398
+ };
2399
+ }
2400
+ });
2401
+ const _hoisted_1 = { class: "navbar bg-base-100" };
2402
+ const _hoisted_2 = { class: "navbar-start" };
2403
+ const _hoisted_3 = { key: 0 };
2404
+ const _hoisted_4 = { class: "navbar-center flex gap-2 items-center" };
2405
+ const _hoisted_5 = {
2406
+ key: 1,
2407
+ class: "flex gap-2"
2408
+ };
2409
+ const _hoisted_6 = { key: 0 };
2410
+ const _hoisted_7 = { class: "navbar-end" };
2411
+ const _hoisted_8 = { key: 0 };
2412
+ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
2413
+ __name: "TableToolbar",
2414
+ props: {
2415
+ filterSchema: {},
2416
+ filters: {},
2417
+ search: {},
2418
+ actions: {}
2419
+ },
2420
+ emits: ["updateSearch", "updateFilters", "action"],
2421
+ setup(__props, { emit: __emit }) {
2422
+ const props = __props;
2423
+ const emit = __emit;
2424
+ const searchQuery = vue.ref(props.search ?? "");
2425
+ let searchTimeout = null;
2426
+ const onSearchInput = (value) => {
2427
+ searchQuery.value = value;
2428
+ if (searchTimeout) clearTimeout(searchTimeout);
2429
+ searchTimeout = setTimeout(() => emit("updateSearch", value), 300);
2430
+ };
2431
+ const onChangeFilters = (filters) => {
2432
+ emit("updateFilters", filters);
2433
+ };
2434
+ return (_ctx, _cache) => {
2435
+ return vue.openBlock(), vue.createElementBlock("div", _hoisted_1, [
2436
+ vue.createElementVNode("div", _hoisted_2, [
2437
+ _ctx.$slots.left ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_3, [
2438
+ vue.renderSlot(_ctx.$slots, "left")
2439
+ ])) : vue.createCommentVNode("", true)
1854
2440
  ]),
1855
- resolvedTable.value ? (vue.openBlock(), vue.createBlock(vue.unref(ui.Card), { key: 0 }, {
1856
- default: vue.withCtx(() => [
1857
- resolvedUri.value ? (vue.openBlock(), vue.createBlock(vue.unref(_sfc_main$g), {
1858
- key: 0,
1859
- id: `form_table_${_ctx.id}`,
1860
- "ui-schema": resolvedTable.value.uiSchema,
1861
- schema: resolvedTable.value.schema,
1862
- "filter-ui-schema": resolvedFilter.value?.uiSchema,
1863
- "filter-schema": resolvedFilter.value?.schema,
1864
- uri: _ctx.dataUri ?? resolvedUri.value,
1865
- reload: reload.value,
1866
- actions: _ctx.tableActions,
1867
- onEdit: edit,
1868
- onDelete: deleteFn
1869
- }, null, 8, ["id", "ui-schema", "schema", "filter-ui-schema", "filter-schema", "uri", "reload", "actions"])) : vue.createCommentVNode("", true)
1870
- ]),
1871
- _: 1
1872
- })) : vue.createCommentVNode("", true)
1873
- ], 64);
2441
+ vue.createElementVNode("div", _hoisted_4, [
2442
+ vue.createVNode(vue.unref(ui.Input), {
2443
+ placeholder: "Search...",
2444
+ value: searchQuery.value,
2445
+ size: "sm",
2446
+ width: "w-48",
2447
+ clearable: true,
2448
+ onInput: _cache[0] || (_cache[0] = ($event) => onSearchInput($event.target.value))
2449
+ }, null, 8, ["value"]),
2450
+ __props.filterSchema ? (vue.openBlock(), vue.createBlock(_sfc_main$1, {
2451
+ key: 0,
2452
+ filters: __props.filters ?? [],
2453
+ "filter-schema": __props.filterSchema,
2454
+ onChangeFilters
2455
+ }, null, 8, ["filters", "filter-schema"])) : vue.createCommentVNode("", true),
2456
+ __props.actions ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5, [
2457
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(__props.actions, (action) => {
2458
+ return vue.openBlock(), vue.createBlock(vue.unref(ui.Btn), {
2459
+ key: action.label ?? action.tooltip,
2460
+ size: "xs",
2461
+ color: "ghost",
2462
+ class: "border-gray-300 text-base-content/50 h-8",
2463
+ icon: action.icon,
2464
+ tooltip: action.tooltip,
2465
+ onClick: action.action
2466
+ }, {
2467
+ default: vue.withCtx(() => [
2468
+ action.label ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_6, vue.toDisplayString(action.label), 1)) : vue.createCommentVNode("", true)
2469
+ ]),
2470
+ _: 2
2471
+ }, 1032, ["icon", "tooltip", "onClick"]);
2472
+ }), 128))
2473
+ ])) : vue.createCommentVNode("", true)
2474
+ ]),
2475
+ vue.createElementVNode("div", _hoisted_7, [
2476
+ _ctx.$slots.right ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8, [
2477
+ vue.renderSlot(_ctx.$slots, "right")
2478
+ ])) : vue.createCommentVNode("", true)
2479
+ ])
2480
+ ]);
1874
2481
  };
1875
2482
  }
1876
2483
  });
2484
+ exports.FilterRowInput = _sfc_main$2;
1877
2485
  exports.FormModalEmits = FormModalEmits;
1878
2486
  exports.FormModalProperties = FormModalProperties;
1879
- exports.JsonForm = _sfc_main$2;
1880
- exports.JsonFormModal = _sfc_main$d;
2487
+ exports.JsonForm = _sfc_main$4;
2488
+ exports.JsonFormModal = _sfc_main$j;
1881
2489
  exports.JsonFormModalService = JsonFormModalService;
1882
- exports.JsonFormWithActions = _sfc_main$1;
1883
- exports.JsonFormWithTable = _sfc_main;
1884
- exports.TableComponent = _sfc_main$g;
2490
+ exports.ReadonlyLabel = _sfc_main$u;
2491
+ exports.TableComponent = _sfc_main$3;
1885
2492
  exports.TableComponentEmits = TableComponentEmits;
1886
2493
  exports.TableComponentProperties = TableComponentProperties;
2494
+ exports.TableFilter = _sfc_main$1;
2495
+ exports.TableToolbar = _sfc_main;
2496
+ exports.ViewDetailValue = _sfc_main$l;
2497
+ exports.cellFormatIs = cellFormatIs;
2498
+ exports.cellTypeIs = cellTypeIs;
1887
2499
  exports.createRepository = createRepository;
2500
+ exports.customRenderers = customRenderers;
2501
+ exports.defaultCellRenderers = defaultCellRenderers;
2502
+ exports.findCellRenderer = findCellRenderer;
1888
2503
  exports.formatError = formatError;
2504
+ exports.getNestedValue = getNestedValue;
2505
+ exports.isLink = isLink;
2506
+ exports.optionIsIgnoreCase = optionIsIgnoreCase;
1889
2507
  exports.provideFormEvents = provideFormEvents;
2508
+ exports.provideHttpClient = provideHttpClient;
2509
+ exports.readonlyRenderers = readonlyRenderers;
1890
2510
  exports.registerZodErrorMap = registerZodErrorMap;
2511
+ exports.useControlBinding = useControlBinding;
2512
+ exports.useCustomControlBinding = useCustomControlBinding;
2513
+ exports.useCustomReadonlyControlBinding = useCustomReadonlyControlBinding;
2514
+ exports.useDisplayValue = useDisplayValue;
1891
2515
  exports.useFormEvents = useFormEvents;
1892
- exports.veeRenderers = customRenderes;
2516
+ exports.useHttpClient = useHttpClient;
2517
+ exports.useReadonlyControlBinding = useReadonlyControlBinding;