@jskit-ai/crud-ui-generator 0.1.49 → 0.1.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,9 @@
1
+ import { GENERATED_UI_NAVIGATION_ROLE_OPTION } from "@jskit-ai/kernel/shared/support/generatedUiContract";
2
+
1
3
  export default Object.freeze({
2
4
  packageVersion: 1,
3
5
  packageId: "@jskit-ai/crud-ui-generator",
4
- version: "0.1.49",
6
+ version: "0.1.50",
5
7
  kind: "generator",
6
8
  description: "Generate CRUD route trees from resource validators at an explicit route root relative to src/pages/.",
7
9
  options: {
@@ -66,6 +68,7 @@ export default Object.freeze({
66
68
  promptLabel: "Link placement",
67
69
  promptHint: "Optional semantic target override for the generated list-page link placement (format: area.slot)."
68
70
  },
71
+ "navigation-role": GENERATED_UI_NAVIGATION_ROLE_OPTION,
69
72
  namespace: {
70
73
  required: false,
71
74
  inputType: "text",
@@ -110,6 +113,7 @@ export default Object.freeze({
110
113
  "display-fields",
111
114
  "id-param",
112
115
  "parent-title",
116
+ "navigation-role",
113
117
  "link-placement",
114
118
  "namespace",
115
119
  "force"
@@ -171,7 +175,7 @@ export default Object.freeze({
171
175
  mutations: {
172
176
  dependencies: {
173
177
  runtime: {
174
- "@jskit-ai/users-web": "0.1.81"
178
+ "@jskit-ai/users-web": "0.1.82"
175
179
  },
176
180
  dev: {}
177
181
  },
@@ -195,6 +199,28 @@ export default Object.freeze({
195
199
  in: ["list"]
196
200
  }
197
201
  },
202
+ {
203
+ from: "templates/src/pages/admin/ui-generator/listFilters.js",
204
+ to: "src/pages/${option:target-root|trim}/listFilters.js",
205
+ reason: "Install generated client-side list filter definitions.",
206
+ category: "crud-ui-generator",
207
+ id: "crud-ui-page-list-filters-${option:target-root|snake}",
208
+ when: {
209
+ option: "operations",
210
+ in: ["list"]
211
+ }
212
+ },
213
+ {
214
+ from: "templates/src/pages/admin/ui-generator/listBulkActions.js",
215
+ to: "src/pages/${option:target-root|trim}/listBulkActions.js",
216
+ reason: "Install generated client-side list bulk action definitions.",
217
+ category: "crud-ui-generator",
218
+ id: "crud-ui-page-list-bulk-actions-${option:target-root|snake}",
219
+ when: {
220
+ option: "operations",
221
+ in: ["list"]
222
+ }
223
+ },
198
224
  {
199
225
  from: "templates/src/pages/admin/ui-generator/ViewElement.vue",
200
226
  to: "src/pages/${option:target-root|trim}/[${option:id-param|trim}]/index.vue",
@@ -355,8 +381,7 @@ export default Object.freeze({
355
381
  file: "src/placement.js",
356
382
  position: "bottom",
357
383
  skipIfContains: "__JSKIT_UI_MENU_MARKER__",
358
- value:
359
- "\n// __JSKIT_UI_MENU_MARKER__\n{\n addPlacement({\n id: \"__JSKIT_UI_MENU_PLACEMENT_ID__\",\n target: \"__JSKIT_UI_MENU_PLACEMENT_TARGET__\",\n__JSKIT_UI_MENU_OWNER_LINE__ kind: \"link\",\n surfaces: [\"__JSKIT_UI_SURFACE_ID__\"],\n order: 155,\n props: {\n label: \"__JSKIT_UI_MENU_LABEL__\",\n icon: \"__JSKIT_UI_MENU_ICON__\",\n surface: \"__JSKIT_UI_SURFACE_ID__\",\n scopedSuffix: \"__JSKIT_UI_MENU_WORKSPACE_SUFFIX__\",\n unscopedSuffix: \"__JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__\",\n__JSKIT_UI_MENU_TO_PROP_LINE__ },\n__JSKIT_UI_MENU_WHEN_LINE__ });\n}\n",
384
+ value: "__JSKIT_UI_MENU_APPEND_BLOCK__",
360
385
  reason: "Append generated CRUD list-page placement.",
361
386
  category: "crud-ui-generator",
362
387
  id: "crud-ui-placement-menu",
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-ui-generator",
3
- "version": "0.1.49",
3
+ "version": "0.1.50",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/crud-core": "0.1.74",
10
- "@jskit-ai/kernel": "0.1.66",
11
- "@jskit-ai/resource-crud-core": "0.1.11"
9
+ "@jskit-ai/crud-core": "0.1.75",
10
+ "@jskit-ai/kernel": "0.1.67",
11
+ "@jskit-ai/resource-crud-core": "0.1.12"
12
12
  },
13
13
  "exports": {
14
14
  "./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
@@ -7,6 +7,10 @@ import {
7
7
  resolvePageTargetDetails
8
8
  } from "@jskit-ai/kernel/server/support";
9
9
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
10
+ import {
11
+ resolveGeneratedUiNavigationRoleLinkPlacement,
12
+ shouldCreateGeneratedUiNavigationLink
13
+ } from "@jskit-ai/kernel/shared/support/generatedUiContract";
10
14
  import {
11
15
  loadResourceDefinition,
12
16
  requireOperation,
@@ -22,6 +26,7 @@ import {
22
26
  resolveNearestParentRouteParamKey,
23
27
  buildListHeaderColumns,
24
28
  buildListRowColumns,
29
+ buildListCardFields,
25
30
  buildViewColumns,
26
31
  buildFormColumns,
27
32
  resolveRecordIdFieldKey,
@@ -157,6 +162,18 @@ function toTitleFromKebab(value = "", fallback = "") {
157
162
  return wordsToTitle(words);
158
163
  }
159
164
 
165
+ function buildCrudListCopy(resourceLabels = {}) {
166
+ const pluralTitle = normalizeText(resourceLabels.pluralTitle) || "Records";
167
+ const singularTitle = normalizeText(resourceLabels.singularTitle) || "Record";
168
+ return Object.freeze({
169
+ loadErrorTitle: `Unable to load ${pluralTitle}`,
170
+ loadErrorBody: "Check the connection and try again.",
171
+ emptyTitle: `No ${pluralTitle} yet`,
172
+ emptyBody: `Create the first ${singularTitle} to start using this workflow.`,
173
+ createLabel: `New ${singularTitle}`
174
+ });
175
+ }
176
+
160
177
  function normalizeRelativeAppPath(value = "") {
161
178
  return String(value || "")
162
179
  .replaceAll("\\", "/")
@@ -242,6 +259,20 @@ function parseParentTitleOption(options) {
242
259
  return parentTitleMode;
243
260
  }
244
261
 
262
+ function shouldCreateNavigationLink(options = {}, inferenceContext = {}) {
263
+ return shouldCreateGeneratedUiNavigationLink(options, {
264
+ dynamicRoutePolicy: "any",
265
+ routePath: inferenceContext?.routePath
266
+ });
267
+ }
268
+
269
+ function resolveNavigationRoleLinkPlacement(options = {}, inferenceContext = {}) {
270
+ return resolveGeneratedUiNavigationRoleLinkPlacement(options, {
271
+ dynamicRoutePolicy: "any",
272
+ routePath: inferenceContext?.routePath
273
+ });
274
+ }
275
+
245
276
  function validateDisplayFieldsForOperation(selectedFieldKeys, fields, operationName) {
246
277
  const selectedFields = Array.isArray(selectedFieldKeys) ? selectedFieldKeys : [];
247
278
  if (selectedFields.length < 1) {
@@ -303,6 +334,58 @@ function hasLookupFormFields(fields = []) {
303
334
  return (Array.isArray(fields) ? fields : []).some((field) => normalizeText(field?.component).toLowerCase() === "lookup");
304
335
  }
305
336
 
337
+ function hasLookupDisplayFields(fields = []) {
338
+ return (Array.isArray(fields) ? fields : []).some((field) => normalizeText(field?.relation?.kind).toLowerCase() === "lookup");
339
+ }
340
+
341
+ function buildListCardSlotProps(fields = []) {
342
+ const normalizedFields = Array.isArray(fields) ? fields : [];
343
+ if (normalizedFields.length < 1) {
344
+ return "{}";
345
+ }
346
+
347
+ const props = ["record"];
348
+ if (hasLookupDisplayFields(normalizedFields)) {
349
+ props.push("records");
350
+ }
351
+ props.push("formatListCardValue");
352
+ return `{ ${props.join(", ")} }`;
353
+ }
354
+
355
+ function buildListRowSlotProps(fields = []) {
356
+ const normalizedFields = Array.isArray(fields) ? fields : [];
357
+ if (normalizedFields.length < 1) {
358
+ return "{}";
359
+ }
360
+
361
+ const props = ["record"];
362
+ if (hasLookupDisplayFields(normalizedFields)) {
363
+ props.push("records");
364
+ }
365
+ return `{ ${props.join(", ")} }`;
366
+ }
367
+
368
+ function buildCrudFieldsSlotProps(fields = [], { includeMode = false } = {}) {
369
+ const props = [];
370
+ if (includeMode) {
371
+ props.push("mode");
372
+ }
373
+
374
+ props.push("formState", "addEdit", "resolveFieldErrors");
375
+ if (hasLookupFormFields(fields)) {
376
+ props.push(
377
+ "resolveLookupItems: fieldLookupItems",
378
+ "resolveLookupLoading: fieldLookupLoading",
379
+ "resolveLookupSearch: fieldLookupSearch",
380
+ "setLookupSearch: setFieldLookupSearch"
381
+ );
382
+ }
383
+
384
+ return `{
385
+ ${props.join(",\n ")}
386
+ }`;
387
+ }
388
+
306
389
  function buildLookupImportLine(fields = []) {
307
390
  return hasLookupFormFields(fields)
308
391
  ? 'import { createCrudLookupFieldRuntime } from "@jskit-ai/users-web/client/composables/crudLookupFieldRuntime";'
@@ -339,16 +422,17 @@ const {
339
422
  `;
340
423
  }
341
424
 
342
- function buildLookupFormProps(fields = []) {
425
+ function buildLookupFormProps(fields = [], { sourcePrefix = "" } = {}) {
343
426
  if (!hasLookupFormFields(fields)) {
344
427
  return "";
345
428
  }
346
429
 
430
+ const prefix = String(sourcePrefix || "");
347
431
  return `
348
- :resolve-lookup-items="resolveLookupItems"
349
- :resolve-lookup-loading="resolveLookupLoading"
350
- :resolve-lookup-search="resolveLookupSearch"
351
- :set-lookup-search="setLookupSearch"`;
432
+ :resolve-lookup-items="${prefix}resolveLookupItems"
433
+ :resolve-lookup-loading="${prefix}resolveLookupLoading"
434
+ :resolve-lookup-search="${prefix}resolveLookupSearch"
435
+ :set-lookup-search="${prefix}setLookupSearch"`;
352
436
  }
353
437
 
354
438
  function buildLookupFormPropDefinitions({ createFields = [], editFields = [] } = {}) {
@@ -468,6 +552,16 @@ function resolveTargetRootRelativeRoutePath(pageTarget = {}) {
468
552
  return visibleRouteSegments.length > 0 ? `/${visibleRouteSegments.join("/")}` : "/";
469
553
  }
470
554
 
555
+ function resolveNavigationInferenceRoutePath(pageTarget = {}) {
556
+ const visibleRouteSegments = Array.isArray(pageTarget?.visibleRouteSegments)
557
+ ? pageTarget.visibleRouteSegments.map((entry) => normalizeText(entry)).filter(Boolean)
558
+ : [];
559
+ if (visibleRouteSegments.length > 0) {
560
+ return `/${visibleRouteSegments.join("/")}`;
561
+ }
562
+ return String(pageTarget?.routeUrlSuffix || "");
563
+ }
564
+
471
565
  function resolveMenuToPropLine(linkTo = "") {
472
566
  if (!linkTo) {
473
567
  return "";
@@ -483,6 +577,44 @@ function resolveMenuOwnerLine(owner = "") {
483
577
  return ` owner: ${JSON.stringify(normalizedOwner)},\n`;
484
578
  }
485
579
 
580
+ function buildMenuAppendBlock({
581
+ hasListOperation = false,
582
+ menuMarker = "",
583
+ pageLinkTarget = null,
584
+ pageTarget = {}
585
+ } = {}) {
586
+ const placementId = String(pageLinkTarget?.pageTarget?.placementId || "");
587
+ const placementTarget = String(pageLinkTarget?.placementTarget?.id || "");
588
+ if (!hasListOperation || !placementId || !placementTarget) {
589
+ return "";
590
+ }
591
+
592
+ const surfaceId = String(pageTarget?.surfaceId || "");
593
+ const routeUrlSuffix = String(pageLinkTarget?.pageTarget?.routeUrlSuffix || "");
594
+ const menuToPropLine = resolveMenuToPropLine(pageLinkTarget?.linkTo || "");
595
+ const whenLine = String(pageLinkTarget?.whenLine || "");
596
+
597
+ return `
598
+ // ${menuMarker}
599
+ {
600
+ addPlacement({
601
+ id: ${JSON.stringify(placementId)},
602
+ target: ${JSON.stringify(placementTarget)},
603
+ ${resolveMenuOwnerLine(pageLinkTarget?.placementTarget?.owner || "")} kind: "link",
604
+ surfaces: [${JSON.stringify(surfaceId)}],
605
+ order: 155,
606
+ props: {
607
+ label: ${JSON.stringify(pageTarget?.defaultName || "")},
608
+ icon: ${JSON.stringify(DEFAULT_GENERATED_LINK_ICON)},
609
+ surface: ${JSON.stringify(surfaceId)},
610
+ scopedSuffix: ${JSON.stringify(routeUrlSuffix)},
611
+ unscopedSuffix: ${JSON.stringify(routeUrlSuffix)},
612
+ ${menuToPropLine} },
613
+ ${whenLine} });
614
+ }
615
+ `;
616
+ }
617
+
486
618
  function resolveCrudRelativePath(namespace = "") {
487
619
  return `/${requireCrudNamespace(namespace, {
488
620
  context: "crud-ui-generator resource namespace"
@@ -509,7 +641,7 @@ function buildListHeadingTitleSetup({
509
641
  }
510
642
 
511
643
  return `const parentTitle = useCrudListParentTitle({
512
- listRuntime: records,
644
+ listRuntime,
513
645
  resource: uiResource,
514
646
  adapter: UI_OPERATION_ADAPTER || undefined,
515
647
  recordIdParam: UI_RECORD_ID_PARAM,
@@ -651,13 +783,20 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
651
783
  const viewTitleFallbackFieldKey = hasViewOperation
652
784
  ? resolveViewTitleFallbackFieldKey(viewFieldsAll, { recordIdFieldKey })
653
785
  : "";
786
+ const listTitleFallbackFieldKey = hasListOperation
787
+ ? resolveViewTitleFallbackFieldKey(listFieldsAll, { recordIdFieldKey: listRecordIdFieldKey })
788
+ : "";
654
789
 
655
- const pageLinkTarget = hasListOperation
790
+ const pageLinkTarget = hasListOperation && shouldCreateNavigationLink(options, {
791
+ routePath: resolveNavigationInferenceRoutePath(pageTarget)
792
+ })
656
793
  ? await resolvePageLinkTargetDetails({
657
794
  appRoot,
658
795
  pageTarget,
659
796
  targetFile: listTargetFile,
660
- placement: options?.["link-placement"],
797
+ placement: resolveNavigationRoleLinkPlacement(options, {
798
+ routePath: resolveNavigationInferenceRoutePath(pageTarget)
799
+ }),
661
800
  context: "crud-ui-generator"
662
801
  })
663
802
  : null;
@@ -666,6 +805,8 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
666
805
  : "";
667
806
  const createFormColumns = buildFormColumns(createFields);
668
807
  const editFormColumns = buildFormColumns(editFields);
808
+ const sharedFormFields = Object.freeze([...createFields, ...editFields]);
809
+ const listCopy = buildCrudListCopy(resourceLabels);
669
810
 
670
811
  return {
671
812
  __JSKIT_UI_RESOURCE_IMPORT_PATH__: `/${normalizeRelativeAppPath(options?.["resource-file"])}`,
@@ -674,6 +815,11 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
674
815
  __JSKIT_UI_RESOURCE_NAMESPACE__: resourceNamespace,
675
816
  __JSKIT_UI_RESOURCE_SINGULAR_TITLE__: resourceLabels.singularTitle,
676
817
  __JSKIT_UI_RESOURCE_PLURAL_TITLE__: resourceLabels.pluralTitle,
818
+ __JSKIT_UI_LIST_LOAD_ERROR_TITLE__: listCopy.loadErrorTitle,
819
+ __JSKIT_UI_LIST_LOAD_ERROR_BODY__: listCopy.loadErrorBody,
820
+ __JSKIT_UI_LIST_EMPTY_TITLE__: listCopy.emptyTitle,
821
+ __JSKIT_UI_LIST_EMPTY_BODY__: listCopy.emptyBody,
822
+ __JSKIT_UI_LIST_CREATE_LABEL__: listCopy.createLabel,
677
823
  __JSKIT_UI_ROUTE_TITLE__: pageTarget.defaultName,
678
824
  __JSKIT_UI_PARENT_TITLE_MODE__: parentTitleMode,
679
825
  __JSKIT_UI_LIST_PARENT_TITLE_IMPORT_LINE__: buildListParentTitleImportLine(parentTitleMode),
@@ -687,6 +833,10 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
687
833
  __JSKIT_UI_SURFACE_ID__: pageTarget.surfaceId,
688
834
  __JSKIT_UI_LIST_HEADER_COLUMNS__: buildListHeaderColumns(listFields),
689
835
  __JSKIT_UI_LIST_ROW_COLUMNS__: buildListRowColumns(listFields),
836
+ __JSKIT_UI_LIST_CARD_FIELDS__: buildListCardFields(listFields),
837
+ __JSKIT_UI_LIST_CARD_SLOT_PROPS__: buildListCardSlotProps(listFields),
838
+ __JSKIT_UI_LIST_ROW_SLOT_PROPS__: buildListRowSlotProps(listFields),
839
+ __JSKIT_UI_LIST_TITLE_FALLBACK_FIELD_KEY__: JSON.stringify(listTitleFallbackFieldKey),
690
840
  __JSKIT_UI_LIST_REALTIME_EVENTS__: JSON.stringify(listRealtimeEvents),
691
841
  __JSKIT_UI_LIST_RECORD_ID_EXPR__: resolveRecordIdExpression(recordIdFields),
692
842
  __JSKIT_UI_VIEW_COLUMNS__: buildViewColumns(viewFields),
@@ -709,6 +859,9 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
709
859
  __JSKIT_UI_EDIT_FORM_COLUMNS__: editFormColumns,
710
860
  __JSKIT_UI_CREATE_FORM_COLUMNS_DIRECT__: rewriteGeneratedBlockIndent(createFormColumns, { trimPrefix: " " }),
711
861
  __JSKIT_UI_EDIT_FORM_COLUMNS_DIRECT__: rewriteGeneratedBlockIndent(editFormColumns, { trimPrefix: " " }),
862
+ __JSKIT_UI_CREATE_FORM_SLOT_PROPS__: buildCrudFieldsSlotProps(createFields),
863
+ __JSKIT_UI_EDIT_FORM_SLOT_PROPS__: buildCrudFieldsSlotProps(editFields),
864
+ __JSKIT_UI_SHARED_FORM_SLOT_PROPS__: buildCrudFieldsSlotProps(sharedFormFields, { includeMode: true }),
712
865
  __JSKIT_UI_CREATE_FORM_FIELDS__: JSON.stringify(createFields),
713
866
  __JSKIT_UI_EDIT_FORM_FIELDS__: JSON.stringify(editFields),
714
867
  __JSKIT_UI_CREATE_FORM_FIELD_PUSH_LINES__: renderObjectPushLines("UI_CREATE_FORM_FIELDS", createFields),
@@ -727,6 +880,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
727
880
  }),
728
881
  __JSKIT_UI_CREATE_LOOKUP_FORM_PROPS__: buildLookupFormProps(createFields),
729
882
  __JSKIT_UI_EDIT_LOOKUP_FORM_PROPS__: buildLookupFormProps(editFields),
883
+ __JSKIT_UI_SHARED_LOOKUP_FORM_PROPS__: buildLookupFormProps(sharedFormFields, { sourcePrefix: "props." }),
730
884
  __JSKIT_UI_FORM_LOOKUP_PROP_DEFS__: buildLookupFormPropDefinitions({
731
885
  createFields,
732
886
  editFields
@@ -740,7 +894,13 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
740
894
  __JSKIT_UI_MENU_NON_WORKSPACE_SUFFIX__: String(pageLinkTarget?.pageTarget?.routeUrlSuffix || ""),
741
895
  __JSKIT_UI_MENU_WHEN_LINE__: String(pageLinkTarget?.whenLine || ""),
742
896
  __JSKIT_UI_MENU_TO_PROP_LINE__: resolveMenuToPropLine(pageLinkTarget?.linkTo || ""),
743
- __JSKIT_UI_MENU_LABEL__: pageTarget.defaultName
897
+ __JSKIT_UI_MENU_LABEL__: pageTarget.defaultName,
898
+ __JSKIT_UI_MENU_APPEND_BLOCK__: buildMenuAppendBlock({
899
+ hasListOperation,
900
+ menuMarker,
901
+ pageLinkTarget,
902
+ pageTarget
903
+ })
744
904
  };
745
905
  }
746
906
 
@@ -835,7 +835,7 @@ function renderTemplateJsStringLiteral(value) {
835
835
 
836
836
  function buildListHeaderColumns(fields = []) {
837
837
  return (Array.isArray(fields) ? fields : [])
838
- .map((field) => ` <th>${escapeHtml(field.label)}</th>`)
838
+ .map((field) => ` <th>${escapeHtml(field.label)}</th>`)
839
839
  .join("\n");
840
840
  }
841
841
 
@@ -888,10 +888,25 @@ function buildListRowColumns(fields = []) {
888
888
  return (Array.isArray(fields) ? fields : [])
889
889
  .map((field) => {
890
890
  if (isLookupField(field)) {
891
- return ` <td>{{ records.resolveFieldDisplay(record, ${toLookupDisplayFieldExpression(field)}) }}</td>`;
891
+ return ` <td>{{ records.resolveFieldDisplay(record, ${toLookupDisplayFieldExpression(field)}) }}</td>`;
892
892
  }
893
893
 
894
- return ` <td>{{ ${toAccessorExpression("record", field.key)} }}</td>`;
894
+ return ` <td>{{ ${toAccessorExpression("record", field.key)} }}</td>`;
895
+ })
896
+ .join("\n");
897
+ }
898
+
899
+ function buildListCardFields(fields = []) {
900
+ return (Array.isArray(fields) ? fields : [])
901
+ .map((field) => {
902
+ const valueExpression = isLookupField(field)
903
+ ? `records.resolveFieldDisplay(record, ${toLookupDisplayFieldExpression(field)})`
904
+ : toAccessorExpression("record", field.key);
905
+
906
+ return ` <div class="ui-generator-list-card__field">
907
+ <span class="ui-generator-list-card__field-label">${escapeHtml(field.label)}</span>
908
+ <span class="ui-generator-list-card__field-value">{{ formatListCardValue(${valueExpression}) }}</span>
909
+ </div>`;
895
910
  })
896
911
  .join("\n");
897
912
  }
@@ -903,10 +918,10 @@ function buildViewColumns(fields = []) {
903
918
  ? `view.resolveFieldDisplay(view.record, ${toLookupDisplayFieldExpression(field)})`
904
919
  : toOptionalAccessorExpression("view.record", field.key);
905
920
 
906
- return ` <v-col cols="12" md="6">
907
- <div class="text-caption text-medium-emphasis">${escapeHtml(field.label)}</div>
908
- <div class="text-body-1">{{ ${valueExpression} }}</div>
909
- </v-col>`;
921
+ return ` <v-col cols="12" md="6">
922
+ <div class="text-caption text-medium-emphasis">${escapeHtml(field.label)}</div>
923
+ <div class="text-body-1">{{ ${valueExpression} }}</div>
924
+ </v-col>`;
910
925
  })
911
926
  .join("\n");
912
927
  }
@@ -929,34 +944,34 @@ function buildFormColumns(fields = []) {
929
944
  const fieldErrorExpression = `resolveFieldErrors(${fieldKeyLiteral})`;
930
945
  const component = normalizeText(field?.component).toLowerCase();
931
946
  if (component === "switch") {
932
- return ` <v-col cols="12" md="6">
933
- <v-switch
934
- v-model="${formAccessor}"
935
- label="${label}"
936
- color="primary"
937
- hide-details="auto"
938
- :disabled="addEdit.isFieldLocked"
939
- :error-messages="${fieldErrorExpression}"
940
- />
941
- </v-col>`;
947
+ return ` <v-col cols="12" md="6">
948
+ <v-switch
949
+ v-model="${formAccessor}"
950
+ label="${label}"
951
+ color="primary"
952
+ hide-details="auto"
953
+ :disabled="addEdit.isFieldLocked"
954
+ :error-messages="${fieldErrorExpression}"
955
+ />
956
+ </v-col>`;
942
957
  }
943
958
 
944
959
  if (component === "select") {
945
960
  const selectOptions = Array.isArray(field?.options) ? field.options : [];
946
- return ` <v-col cols="12" md="6">
947
- <v-select
948
- v-model="${formAccessor}"
949
- label="${label}"
950
- variant="outlined"
951
- density="comfortable"
952
- :items="${serializeTemplateBindingValue(selectOptions)}"
953
- item-title="label"
954
- item-value="value"
955
- :disabled="addEdit.isFieldLocked"
956
- :clearable="${field.nullable === true ? "true" : "false"}"
957
- :error-messages="${fieldErrorExpression}"
958
- />
959
- </v-col>`;
961
+ return ` <v-col cols="12" md="6">
962
+ <v-select
963
+ v-model="${formAccessor}"
964
+ label="${label}"
965
+ variant="outlined"
966
+ density="comfortable"
967
+ :items="${serializeTemplateBindingValue(selectOptions)}"
968
+ item-title="label"
969
+ item-value="value"
970
+ :disabled="addEdit.isFieldLocked"
971
+ :clearable="${field.nullable === true ? "true" : "false"}"
972
+ :error-messages="${fieldErrorExpression}"
973
+ />
974
+ </v-col>`;
960
975
  }
961
976
 
962
977
  if (component === "lookup") {
@@ -964,53 +979,53 @@ function buildFormColumns(fields = []) {
964
979
  const useAutocomplete = lookupFormControl !== "select";
965
980
  const lookupComponentTag = useAutocomplete ? "v-autocomplete" : "v-select";
966
981
  const lookupAttributeLines = [
967
- ` :items="resolveLookupItems(${fieldKeyLiteral}, { selectedValue: ${formAccessor}, selectedRecord: addEdit.resource.data })"`
982
+ ` :items="fieldLookupItems(${fieldKeyLiteral}, { selectedValue: ${formAccessor}, selectedRecord: addEdit.resource.data })"`
968
983
  ];
969
984
  if (useAutocomplete) {
970
985
  lookupAttributeLines.push(
971
- ` :search="resolveLookupSearch(${fieldKeyLiteral})"`,
972
- ` @update:search="setLookupSearch(${fieldKeyLiteral}, $event)"`
986
+ ` :search="fieldLookupSearch(${fieldKeyLiteral})"`,
987
+ ` @update:search="setFieldLookupSearch(${fieldKeyLiteral}, $event)"`
973
988
  );
974
989
  }
975
990
  lookupAttributeLines.push(
976
- ` item-title="label"`,
977
- ` item-value="value"`
991
+ ` item-title="label"`,
992
+ ` item-value="value"`
978
993
  );
979
994
  if (useAutocomplete) {
980
- lookupAttributeLines.push(" no-filter");
995
+ lookupAttributeLines.push(" no-filter");
981
996
  }
982
- return ` <v-col cols="12" md="6">
983
- <${lookupComponentTag}
984
- v-model="${formAccessor}"
985
- label="${label}"
986
- variant="outlined"
987
- density="comfortable"
988
- autocomplete="off"
997
+ return ` <v-col cols="12" md="6">
998
+ <${lookupComponentTag}
999
+ v-model="${formAccessor}"
1000
+ label="${label}"
1001
+ variant="outlined"
1002
+ density="comfortable"
1003
+ autocomplete="off"
989
1004
  ${lookupAttributeLines.join("\n")}
990
- :loading="resolveLookupLoading(${fieldKeyLiteral})"
991
- :disabled="addEdit.isFieldLocked"
992
- :clearable="${field.nullable === true ? "true" : "false"}"
993
- :error-messages="${fieldErrorExpression}"
994
- />
995
- </v-col>`;
1005
+ :loading="fieldLookupLoading(${fieldKeyLiteral})"
1006
+ :disabled="addEdit.isFieldLocked"
1007
+ :clearable="${field.nullable === true ? "true" : "false"}"
1008
+ :error-messages="${fieldErrorExpression}"
1009
+ />
1010
+ </v-col>`;
996
1011
  }
997
1012
 
998
1013
  const inputType = normalizeText(field?.inputType) || "text";
999
1014
  const maxLength = Number.isInteger(field?.maxLength) && field.maxLength > 0
1000
1015
  ? String(field.maxLength)
1001
1016
  : "undefined";
1002
- return ` <v-col cols="12" md="6">
1003
- <v-text-field
1004
- v-model="${formAccessor}"
1005
- label="${label}"
1006
- type="${escapeHtml(inputType)}"
1007
- variant="outlined"
1008
- density="comfortable"
1009
- :maxlength="${maxLength}"
1010
- :readonly="addEdit.isFieldLocked"
1011
- :error-messages="${fieldErrorExpression}"
1012
- />
1013
- </v-col>`;
1017
+ return ` <v-col cols="12" md="6">
1018
+ <v-text-field
1019
+ v-model="${formAccessor}"
1020
+ label="${label}"
1021
+ type="${escapeHtml(inputType)}"
1022
+ variant="outlined"
1023
+ density="comfortable"
1024
+ :maxlength="${maxLength}"
1025
+ :readonly="addEdit.isFieldLocked"
1026
+ :error-messages="${fieldErrorExpression}"
1027
+ />
1028
+ </v-col>`;
1014
1029
  })
1015
1030
  .filter(Boolean)
1016
1031
  .join("\n");
@@ -1069,6 +1084,7 @@ export {
1069
1084
  resolveNearestParentRouteParamKey,
1070
1085
  buildListHeaderColumns,
1071
1086
  buildListRowColumns,
1087
+ buildListCardFields,
1072
1088
  buildViewColumns,
1073
1089
  buildFormColumns,
1074
1090
  resolveRecordIdFieldKey,