@jskit-ai/http-web 0.1.4 → 0.1.6

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 (25) hide show
  1. package/package.json +11 -37
  2. package/patterns/crud-screen-set/PATTERN.md +88 -0
  3. package/patterns/crud-screen-set/example/books/BookEditPage.vue +54 -0
  4. package/patterns/crud-screen-set/example/books/BookFormFields.vue +52 -0
  5. package/patterns/crud-screen-set/example/books/BookListPage.vue +54 -0
  6. package/patterns/crud-screen-set/example/books/BookNewPage.vue +45 -0
  7. package/patterns/crud-screen-set/example/books/BookViewPage.vue +50 -0
  8. package/patterns/crud-screen-set/example/books/formFields.js +31 -0
  9. package/patterns/crud-screen-set/example/books/listExtensions.js +9 -0
  10. package/src/client/components/CrudAddEditScreen.vue +29 -31
  11. package/src/client/components/CrudDeleteAction.vue +1 -2
  12. package/src/client/components/CrudListBulkActionSurface.vue +2 -3
  13. package/src/client/components/CrudListRecordActionMenu.vue +3 -9
  14. package/src/client/components/CrudListScreen.vue +80 -63
  15. package/src/client/components/CrudViewScreen.vue +32 -33
  16. package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -1
  17. package/src/client/composables/support/errorMessageHelpers.js +4 -2
  18. package/src/client/composables/useCrudDeleteAction.js +2 -2
  19. package/src/client/composables/useCrudListScreen.js +2 -2
  20. package/src/client/composables/useCrudViewScreen.js +2 -2
  21. package/test/crudScreenComponents.test.js +31 -14
  22. package/test/crudScreenPattern.test.js +60 -0
  23. package/test/errorMessageHelpers.test.js +13 -2
  24. package/test/useCrudAddEdit.test.js +20 -0
  25. package/test/useCrudDeleteAction.test.js +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/http-web",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -44,11 +44,11 @@
44
44
  "./client/support/contractGuards": "./src/client/support/contractGuards.js"
45
45
  },
46
46
  "dependencies": {
47
- "@jskit-ai/http-runtime": "0.1.157",
48
- "@jskit-ai/kernel": "0.1.159",
49
- "@jskit-ai/realtime": "0.1.156",
50
- "@jskit-ai/resource-crud-core": "0.1.101",
51
- "@jskit-ai/shell-web": "0.1.163",
47
+ "@jskit-ai/http-runtime": "0.1.159",
48
+ "@jskit-ai/kernel": "0.1.161",
49
+ "@jskit-ai/realtime": "0.1.158",
50
+ "@jskit-ai/resource-crud-core": "0.1.103",
51
+ "@jskit-ai/shell-web": "0.1.165",
52
52
  "@mdi/js": "^7.4.47"
53
53
  },
54
54
  "peerDependencies": {
@@ -64,11 +64,7 @@
64
64
  "provides": [
65
65
  "runtime.http-web"
66
66
  ],
67
- "requires": [
68
- "runtime.http-client",
69
- "runtime.web-error",
70
- "runtime.web-placement"
71
- ]
67
+ "requires": []
72
68
  },
73
69
  "runtime": {
74
70
  "server": {
@@ -111,40 +107,18 @@
111
107
  },
112
108
  {
113
109
  "subpath": "./client/components/CrudListScreen",
114
- "summary": "Exports the shared generated CRUD list screen."
110
+ "summary": "Exports the shared CRUD list screen."
115
111
  },
116
112
  {
117
113
  "subpath": "./client/components/CrudViewScreen",
118
- "summary": "Exports the shared generated CRUD view screen."
114
+ "summary": "Exports the shared CRUD view screen."
119
115
  },
120
116
  {
121
117
  "subpath": "./client/components/CrudAddEditScreen",
122
- "summary": "Exports the shared generated CRUD add/edit screen."
118
+ "summary": "Exports the shared CRUD add/edit screen."
123
119
  }
124
- ],
125
- "containerTokens": {
126
- "server": [],
127
- "client": []
128
- }
120
+ ]
129
121
  }
130
- },
131
- "mutations": {
132
- "dependencies": {
133
- "runtime": {
134
- "@jskit-ai/http-runtime": "0.1.157",
135
- "@jskit-ai/kernel": "0.1.159",
136
- "@jskit-ai/realtime": "0.1.156",
137
- "@jskit-ai/resource-crud-core": "0.1.101",
138
- "@jskit-ai/shell-web": "0.1.163",
139
- "@mdi/js": "^7.4.47"
140
- },
141
- "dev": {}
142
- },
143
- "packageJson": {
144
- "scripts": {}
145
- },
146
- "procfile": {},
147
- "files": []
148
122
  }
149
123
  }
150
124
  }
@@ -0,0 +1,88 @@
1
+ ---
2
+ id: crud/crud-screen-set
3
+ title: CRUD list, view, create, and edit screens
4
+ summary: Build a routed CRUD user interface as thin application pages over JSKIT's shared screen components and composables.
5
+ keywords: actions, add, crud, delete, edit, filters, list, material, routes, view, vue
6
+ requires: @jskit-ai/http-web, @jskit-ai/resource-crud-core
7
+ ---
8
+
9
+ # CRUD list, view, create, and edit screens
10
+
11
+ ## Use when
12
+
13
+ Use this pattern when a shared CRUD resource should have normal list, detail,
14
+ create, edit, and delete routes. The shared JSKIT screens own responsive chrome,
15
+ loading skeletons, load-error retry states, mutation feedback, route-aware data
16
+ operations, and common extensions. Application pages supply product copy,
17
+ fields, routes, and optional actions.
18
+
19
+ ## Do not use when
20
+
21
+ Do not use this pattern for a one-off command, an embedded read-only value, or a
22
+ domain whose operations are not CRUD. Do not replace the shared screens simply
23
+ to add filters, row actions, bulk actions, permission gating, includes, or
24
+ supporting detail content; those are public extension seams.
25
+
26
+ ## Product decisions
27
+
28
+ Decide which operations exist, the route hierarchy, record title, visible
29
+ fields, create/edit participation, destructive confirmation language, access
30
+ surface, filters, and domain actions. The resource contract and product intent
31
+ are authoritative. Do not derive these decisions from a live table or a field
32
+ questionnaire.
33
+
34
+ ## Invariants
35
+
36
+ - Route pages stay thin and use `useCrudListScreen()`, `useCrudViewScreen()`,
37
+ or `useCrudAddEditScreen()`.
38
+ - Transport and validators come from the shared resource contract.
39
+ - Initial visible loading uses structure-matching skeletons; pending buttons use
40
+ stable disabled labels, never spinners.
41
+ - Resource-load errors stay inside the screen with retry. Command success and
42
+ failure use JSKIT's shared toast/snackbar feedback path.
43
+ - URLs are route templates or route objects resolved through the screen runtime.
44
+ - Product field components are ordinary application source.
45
+ - Query keys and placement sources describe the resource, not an authoring tool.
46
+ - No generated-file markers, overwrite contracts, receipts, or provenance remain.
47
+
48
+ ## Framework APIs
49
+
50
+ Use `CrudListScreen`, `CrudViewScreen`, `CrudAddEditScreen`, and
51
+ `CrudDeleteAction` with their matching composables from `@jskit-ai/http-web`.
52
+ Use `defineCrudListFilters()`, `defineCrudListRowActions()`, and
53
+ `defineCrudListBulkActions()` for optional list extensions. The resource comes
54
+ from `defineCrudResource()` in `@jskit-ai/resource-crud-core`.
55
+
56
+ ## Example files
57
+
58
+ `example/books/` contains concrete list, view, new, and edit pages for the
59
+ owner-scoped `bookResource` pattern. `BookFormFields.vue` and `formFields.js`
60
+ show the small product-owned form seam. `listExtensions.js` makes optional
61
+ filters and actions explicit without adding empty screen machinery elsewhere.
62
+
63
+ ## Variation points
64
+
65
+ Change routes, labels, visible fields, field components, filters, query params,
66
+ realtime events, delete availability, and action definitions. Use the screen
67
+ slots for domain-specific content. Use a lower-level request composable only
68
+ when the wire contract genuinely is not CRUD.
69
+
70
+ ## Verification
71
+
72
+ - Mount each route with a representative resource response.
73
+ - Verify skeleton, empty, success, local load-error, and retry states.
74
+ - Verify create, edit, validation failure, delete confirmation, and navigation.
75
+ - Verify filters and actions when declared.
76
+ - Exercise compact, medium, and expanded widths with Playwright.
77
+ - Confirm keyboard access, 48 CSS-pixel compact targets, no horizontal overflow,
78
+ and toast-based mutation feedback.
79
+
80
+ ## Avoid
81
+
82
+ - route-local `fetch()` or duplicated request serializers
83
+ - copying shared screen chrome into every route
84
+ - spinner or circular-progress loading states
85
+ - transient in-page command-error banners that push the layout
86
+ - generator-branded CSS classes, cache keys, or placement ids
87
+ - comments or metadata reserving fields for a future authoring command
88
+ - a second source of truth for resource field validation
@@ -0,0 +1,54 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import { useRoute } from "vue-router";
4
+ import CrudAddEditScreen from "@jskit-ai/http-web/client/components/CrudAddEditScreen";
5
+ import { useCrudAddEditScreen } from "@jskit-ai/http-web/client/composables/useCrudAddEditScreen";
6
+ import { bookResource } from "@local/books/shared";
7
+ import BookFormFields from "./BookFormFields.vue";
8
+ import { bookFormFields } from "./formFields.js";
9
+
10
+ const route = useRoute();
11
+ const routeRecordId = computed(() => String(route.params.bookId || "").trim());
12
+
13
+ const screen = useCrudAddEditScreen({
14
+ mode: "edit",
15
+ title: "Edit book",
16
+ subtitle: "Update this catalogue entry.",
17
+ saveLabel: "Save changes",
18
+ cancelTo: "/books/:bookId",
19
+ preserveCancelQuery: true,
20
+ resource: bookResource,
21
+ operationName: "patch",
22
+ formFields: bookFormFields,
23
+ addEditOptions: {
24
+ apiUrlTemplate: "/books/:bookId",
25
+ placementSource: "crud.books.edit",
26
+ writeMethod: "PATCH",
27
+ requestRecoveryLabel: "Book",
28
+ fallbackLoadError: "Unable to load this book.",
29
+ fallbackSaveError: "Unable to save this book.",
30
+ recordIdParam: "bookId",
31
+ routeRecordId,
32
+ viewUrlTemplate: "/books/:bookId",
33
+ listUrlTemplate: "/books"
34
+ },
35
+ saveSuccess: {
36
+ invalidateQueryKey: ["crud", "books"],
37
+ listUrlTemplate: "/books"
38
+ }
39
+ });
40
+ </script>
41
+
42
+ <template>
43
+ <CrudAddEditScreen :screen="screen">
44
+ <template #fields="fieldRuntime">
45
+ <BookFormFields
46
+ v-model:title="fieldRuntime.formState.title"
47
+ v-model:author="fieldRuntime.formState.author"
48
+ v-model:notes="fieldRuntime.formState.notes"
49
+ :readonly="fieldRuntime.addEdit.isFieldLocked"
50
+ :resolve-field-errors="fieldRuntime.resolveFieldErrors"
51
+ />
52
+ </template>
53
+ </CrudAddEditScreen>
54
+ </template>
@@ -0,0 +1,52 @@
1
+ <script setup>
2
+ const title = defineModel("title", { type: String, default: "" });
3
+ const author = defineModel("author", { type: String, default: "" });
4
+ const notes = defineModel("notes", { type: String, default: "" });
5
+
6
+ defineProps({
7
+ readonly: {
8
+ type: Boolean,
9
+ default: false
10
+ },
11
+ resolveFieldErrors: {
12
+ type: Function,
13
+ required: true
14
+ }
15
+ });
16
+ </script>
17
+
18
+ <template>
19
+ <v-col cols="12" md="6">
20
+ <v-text-field
21
+ v-model="title"
22
+ label="Title"
23
+ variant="outlined"
24
+ density="comfortable"
25
+ maxlength="255"
26
+ :readonly="readonly"
27
+ :error-messages="resolveFieldErrors('title')"
28
+ />
29
+ </v-col>
30
+ <v-col cols="12" md="6">
31
+ <v-text-field
32
+ v-model="author"
33
+ label="Author"
34
+ variant="outlined"
35
+ density="comfortable"
36
+ maxlength="255"
37
+ :readonly="readonly"
38
+ :error-messages="resolveFieldErrors('author')"
39
+ />
40
+ </v-col>
41
+ <v-col cols="12">
42
+ <v-textarea
43
+ v-model="notes"
44
+ label="Notes"
45
+ variant="outlined"
46
+ density="comfortable"
47
+ auto-grow
48
+ :readonly="readonly"
49
+ :error-messages="resolveFieldErrors('notes')"
50
+ />
51
+ </v-col>
52
+ </template>
@@ -0,0 +1,54 @@
1
+ <script setup>
2
+ import CrudListScreen from "@jskit-ai/http-web/client/components/CrudListScreen";
3
+ import { useCrudListScreen } from "@jskit-ai/http-web/client/composables/useCrudListScreen";
4
+ import { bookResource } from "@local/books/shared";
5
+ import { listBulkActions, listFilters, listRowActions } from "./listExtensions.js";
6
+
7
+ const screen = useCrudListScreen({
8
+ resource: bookResource,
9
+ resourceNamespace: "books",
10
+ apiSuffix: "/books",
11
+ recordIdParam: "bookId",
12
+ recordIdSelector: (book = {}) => book.id,
13
+ titleFallbackFieldKey: "title",
14
+ viewUrlTemplate: "/books/:bookId",
15
+ editUrlTemplate: "/books/:bookId/edit",
16
+ newUrlTemplate: "/books/new",
17
+ listFilters,
18
+ listBulkActions,
19
+ listRowActions,
20
+ requestRecoveryLabel: "Books",
21
+ fallbackLoadError: "Unable to load books."
22
+ });
23
+ </script>
24
+
25
+ <template>
26
+ <CrudListScreen
27
+ :screen="screen"
28
+ title-label="Books"
29
+ heading-title="My books"
30
+ subtitle="Search and maintain your catalogue."
31
+ create-label="Add book"
32
+ empty-title="No books yet"
33
+ empty-body="Add your first book to begin the catalogue."
34
+ >
35
+ <template #card-fields="{ record, formatListCardValue }">
36
+ <dl class="d-grid ga-2 mb-0">
37
+ <div>
38
+ <dt class="text-caption text-medium-emphasis">Author</dt>
39
+ <dd class="text-body-2 mb-0">{{ formatListCardValue(record.author) }}</dd>
40
+ </div>
41
+ </dl>
42
+ </template>
43
+
44
+ <template #table-header>
45
+ <th>Title</th>
46
+ <th>Author</th>
47
+ </template>
48
+
49
+ <template #table-row="{ record }">
50
+ <td>{{ record.title }}</td>
51
+ <td>{{ record.author }}</td>
52
+ </template>
53
+ </CrudListScreen>
54
+ </template>
@@ -0,0 +1,45 @@
1
+ <script setup>
2
+ import CrudAddEditScreen from "@jskit-ai/http-web/client/components/CrudAddEditScreen";
3
+ import { useCrudAddEditScreen } from "@jskit-ai/http-web/client/composables/useCrudAddEditScreen";
4
+ import { bookResource } from "@local/books/shared";
5
+ import BookFormFields from "./BookFormFields.vue";
6
+ import { bookFormFields } from "./formFields.js";
7
+
8
+ const screen = useCrudAddEditScreen({
9
+ mode: "new",
10
+ title: "Add book",
11
+ subtitle: "Add a book to your catalogue.",
12
+ saveLabel: "Save book",
13
+ cancelTo: "/books",
14
+ resource: bookResource,
15
+ operationName: "create",
16
+ formFields: bookFormFields,
17
+ addEditOptions: {
18
+ apiSuffix: "/books",
19
+ placementSource: "crud.books.new",
20
+ readEnabled: false,
21
+ writeMethod: "POST",
22
+ recordIdParam: "bookId",
23
+ viewUrlTemplate: "/books/:bookId",
24
+ listUrlTemplate: "/books"
25
+ },
26
+ saveSuccess: {
27
+ invalidateQueryKey: ["crud", "books"],
28
+ listUrlTemplate: "/books"
29
+ }
30
+ });
31
+ </script>
32
+
33
+ <template>
34
+ <CrudAddEditScreen :screen="screen">
35
+ <template #fields="fieldRuntime">
36
+ <BookFormFields
37
+ v-model:title="fieldRuntime.formState.title"
38
+ v-model:author="fieldRuntime.formState.author"
39
+ v-model:notes="fieldRuntime.formState.notes"
40
+ :readonly="fieldRuntime.addEdit.isFieldLocked"
41
+ :resolve-field-errors="fieldRuntime.resolveFieldErrors"
42
+ />
43
+ </template>
44
+ </CrudAddEditScreen>
45
+ </template>
@@ -0,0 +1,50 @@
1
+ <script setup>
2
+ import CrudDeleteAction from "@jskit-ai/http-web/client/components/CrudDeleteAction";
3
+ import CrudViewScreen from "@jskit-ai/http-web/client/components/CrudViewScreen";
4
+ import { useCrudDeleteAction } from "@jskit-ai/http-web/client/composables/useCrudDeleteAction";
5
+ import { useCrudViewScreen } from "@jskit-ai/http-web/client/composables/useCrudViewScreen";
6
+ import { bookResource } from "@local/books/shared";
7
+
8
+ const screen = useCrudViewScreen({
9
+ resource: bookResource,
10
+ resourceNamespace: "books",
11
+ apiUrlTemplate: "/books/:bookId",
12
+ recordIdParam: "bookId",
13
+ titleFallbackFieldKey: "title",
14
+ listUrlTemplate: "/books",
15
+ editUrlTemplate: "/books/:bookId/edit",
16
+ requestRecoveryLabel: "Book",
17
+ fallbackLoadError: "Unable to load this book."
18
+ });
19
+
20
+ const deleteAction = useCrudDeleteAction({
21
+ screen,
22
+ resource: bookResource,
23
+ resourceNamespace: "books",
24
+ apiUrlTemplate: "/books/:bookId"
25
+ });
26
+ </script>
27
+
28
+ <template>
29
+ <CrudViewScreen
30
+ :screen="screen"
31
+ resource-singular-title="Book"
32
+ resource-plural-title="Books"
33
+ description="Review this catalogue entry."
34
+ >
35
+ <template #actions>
36
+ <CrudDeleteAction :action="deleteAction" resource-singular-title="Book" />
37
+ </template>
38
+
39
+ <template #fields="{ view }">
40
+ <v-col cols="12" md="6">
41
+ <div class="text-caption text-medium-emphasis">Author</div>
42
+ <div class="text-body-1">{{ view.record?.author }}</div>
43
+ </v-col>
44
+ <v-col cols="12">
45
+ <div class="text-caption text-medium-emphasis">Notes</div>
46
+ <div class="text-body-1 text-pre-wrap">{{ view.record?.notes || "No notes" }}</div>
47
+ </v-col>
48
+ </template>
49
+ </CrudViewScreen>
50
+ </template>
@@ -0,0 +1,31 @@
1
+ const bookFormFields = Object.freeze([
2
+ Object.freeze({
3
+ key: "title",
4
+ label: "Title",
5
+ type: "string",
6
+ nullable: false,
7
+ inputType: "text",
8
+ component: "text",
9
+ maxLength: 255
10
+ }),
11
+ Object.freeze({
12
+ key: "author",
13
+ label: "Author",
14
+ type: "string",
15
+ nullable: false,
16
+ inputType: "text",
17
+ component: "text",
18
+ maxLength: 255
19
+ }),
20
+ Object.freeze({
21
+ key: "notes",
22
+ label: "Notes",
23
+ type: "string",
24
+ nullable: true,
25
+ inputType: "textarea",
26
+ component: "textarea",
27
+ maxLength: 65535
28
+ })
29
+ ]);
30
+
31
+ export { bookFormFields };
@@ -0,0 +1,9 @@
1
+ import { defineCrudListBulkActions } from "@jskit-ai/http-web/client/bulkActions";
2
+ import { defineCrudListFilters } from "@jskit-ai/http-web/client/filters";
3
+ import { defineCrudListRowActions } from "@jskit-ai/http-web/client/rowActions";
4
+
5
+ const listFilters = defineCrudListFilters({});
6
+ const listBulkActions = defineCrudListBulkActions([]);
7
+ const listRowActions = defineCrudListRowActions([]);
8
+
9
+ export { listBulkActions, listFilters, listRowActions };
@@ -52,28 +52,27 @@ function resolveCancelTo(target = cancelTo.value) {
52
52
  </script>
53
53
 
54
54
  <template>
55
- <section class="generated-ui-screen generated-ui-screen--operator ui-generator-add-edit-form d-flex flex-column ga-4">
56
- <header class="ui-generator-add-edit-form__header">
57
- <div class="ui-generator-add-edit-form__copy">
58
- <h1 class="ui-generator-add-edit-form__title">{{ title }}</h1>
55
+ <section class="crud-screen crud-screen--operator crud-add-edit-form d-flex flex-column ga-4">
56
+ <header class="crud-add-edit-form__header">
57
+ <div class="crud-add-edit-form__copy">
58
+ <h1 class="crud-add-edit-form__title">{{ title }}</h1>
59
59
  <p v-if="subtitle" class="text-body-2 text-medium-emphasis mb-0">{{ subtitle }}</p>
60
60
  </div>
61
- <div class="ui-generator-add-edit-form__actions">
61
+ <div class="crud-add-edit-form__actions">
62
62
  <v-btn v-if="cancelTo" color="primary" variant="outlined" :to="resolveCancelTo(cancelTo)">Cancel</v-btn>
63
63
  <v-btn
64
64
  color="primary"
65
65
  variant="flat"
66
- :loading="addEdit.isSaving"
67
66
  :disabled="addEdit.isSubmitDisabled"
68
67
  @click="addEdit.submit"
69
68
  >
70
- {{ saveLabel }}
69
+ {{ addEdit.isSaving ? "Saving…" : saveLabel }}
71
70
  </v-btn>
72
71
  </div>
73
72
  </header>
74
73
 
75
- <v-sheet rounded="lg" border class="ui-generator-add-edit-form__panel">
76
- <div v-if="addEdit.loadError" class="ui-generator-add-edit-form__state">
74
+ <v-sheet rounded="lg" border class="crud-add-edit-form__panel">
75
+ <div v-if="addEdit.loadError" class="crud-add-edit-form__state">
77
76
  <h2 class="text-h6 mb-2">Unable to load form</h2>
78
77
  <p class="text-body-2 text-medium-emphasis mb-4">
79
78
  {{ addEdit.loadError }}
@@ -82,10 +81,10 @@ function resolveCancelTo(target = cancelTo.value) {
82
81
  v-if="addEdit.canRetryLoad"
83
82
  color="primary"
84
83
  variant="tonal"
85
- :loading="addEdit.isFetching"
84
+ :disabled="addEdit.isFetching"
86
85
  @click="addEdit.refresh"
87
86
  >
88
- Retry
87
+ {{ addEdit.isFetching ? "Retrying…" : "Retry" }}
89
88
  </v-btn>
90
89
  </div>
91
90
  <template v-else-if="formRuntime.showFormSkeleton">
@@ -94,8 +93,7 @@ function resolveCancelTo(target = cancelTo.value) {
94
93
  </div>
95
94
  </template>
96
95
  <v-form v-else class="pa-4" @submit.prevent="addEdit.submit" novalidate>
97
- <v-progress-linear v-if="addEdit.isRefetching" indeterminate class="mb-4" />
98
- <v-row class="ui-generator-add-edit-form__fields">
96
+ <v-row class="crud-add-edit-form__fields">
99
97
  <slot
100
98
  name="fields"
101
99
  :mode="mode"
@@ -115,71 +113,71 @@ function resolveCancelTo(target = cancelTo.value) {
115
113
  </template>
116
114
 
117
115
  <style scoped>
118
- .generated-ui-screen {
119
- --generated-ui-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
120
- --generated-ui-screen-state-padding: 2.5rem 1.25rem;
116
+ .crud-screen {
117
+ --crud-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
118
+ --crud-screen-state-padding: 2.5rem 1.25rem;
121
119
  }
122
120
 
123
- .generated-ui-screen--operator {
124
- --generated-ui-screen-state-padding: 2rem 1rem;
121
+ .crud-screen--operator {
122
+ --crud-screen-state-padding: 2rem 1rem;
125
123
  }
126
124
 
127
- .ui-generator-add-edit-form__header {
125
+ .crud-add-edit-form__header {
128
126
  align-items: flex-start;
129
127
  display: flex;
130
128
  gap: 1rem;
131
129
  justify-content: space-between;
132
130
  }
133
131
 
134
- .ui-generator-add-edit-form__copy {
132
+ .crud-add-edit-form__copy {
135
133
  min-width: 0;
136
134
  }
137
135
 
138
- .ui-generator-add-edit-form__title {
139
- font-size: var(--generated-ui-screen-title-size);
136
+ .crud-add-edit-form__title {
137
+ font-size: var(--crud-screen-title-size);
140
138
  font-weight: 650;
141
139
  letter-spacing: -0.02em;
142
140
  line-height: 1.15;
143
141
  margin: 0 0 0.35rem;
144
142
  }
145
143
 
146
- .ui-generator-add-edit-form__actions {
144
+ .crud-add-edit-form__actions {
147
145
  display: flex;
148
146
  flex-wrap: wrap;
149
147
  gap: 0.5rem;
150
148
  justify-content: flex-end;
151
149
  }
152
150
 
153
- .ui-generator-add-edit-form__panel {
151
+ .crud-add-edit-form__panel {
154
152
  overflow: hidden;
155
153
  }
156
154
 
157
- .ui-generator-add-edit-form__state {
155
+ .crud-add-edit-form__state {
158
156
  margin-inline: auto;
159
157
  max-width: 30rem;
160
- padding: var(--generated-ui-screen-state-padding);
158
+ padding: var(--crud-screen-state-padding);
161
159
  text-align: center;
162
160
  }
163
161
 
164
- .ui-generator-add-edit-form__fields :deep(.v-col) {
162
+ .crud-add-edit-form__fields :deep(.v-col) {
165
163
  min-width: 0;
166
164
  }
167
165
 
168
166
  @media (max-width: 960px) {
169
- .ui-generator-add-edit-form__header {
167
+ .crud-add-edit-form__header {
170
168
  flex-direction: column;
171
169
  }
172
170
 
173
- .ui-generator-add-edit-form__actions {
171
+ .crud-add-edit-form__actions {
174
172
  width: 100%;
175
173
  }
176
174
 
177
- .ui-generator-add-edit-form__actions :deep(.v-btn) {
175
+ .crud-add-edit-form__actions :deep(.v-btn) {
178
176
  min-height: 48px;
179
177
  flex: 1 1 10rem;
180
178
  }
181
179
 
182
- .ui-generator-add-edit-form__state :deep(.v-btn) {
180
+ .crud-add-edit-form__state :deep(.v-btn) {
183
181
  min-height: 48px;
184
182
  }
185
183
  }
@@ -81,11 +81,10 @@ function handleDialogModelUpdate(isOpen) {
81
81
  <v-btn
82
82
  color="error"
83
83
  variant="flat"
84
- :loading="action.isDeleting"
85
84
  :disabled="!action.canDelete"
86
85
  @click="action.confirm"
87
86
  >
88
- Delete
87
+ {{ action.isDeleting ? "Deleting…" : "Delete" }}
89
88
  </v-btn>
90
89
  </v-card-actions>
91
90
  </v-card>
@@ -61,10 +61,9 @@ function isActionExecuting(action = {}) {
61
61
  :variant="action.variant"
62
62
  :prepend-icon="action.icon || undefined"
63
63
  :disabled="isActionDisabled(action)"
64
- :loading="isActionExecuting(action)"
65
64
  @click="execute(action)"
66
65
  >
67
- {{ action.label }}
66
+ {{ isActionExecuting(action) ? `${action.label}…` : action.label }}
68
67
  </v-btn>
69
68
  </div>
70
69
 
@@ -76,7 +75,7 @@ function isActionExecuting(action = {}) {
76
75
  <v-list-item
77
76
  v-for="action in actions"
78
77
  :key="action.key"
79
- :title="action.label"
78
+ :title="isActionExecuting(action) ? `${action.label}…` : action.label"
80
79
  :prepend-icon="action.icon || undefined"
81
80
  :disabled="isActionDisabled(action)"
82
81
  @click="execute(action)"