@jskit-ai/http-web 0.1.91 → 0.1.93

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 (51) hide show
  1. package/package.json +6 -6
  2. package/patterns/crud-screen-set/PATTERN.md +11 -1
  3. package/src/client/bulkActions.js +13 -40
  4. package/src/client/components/CrudAddEditScreen.vue +1 -9
  5. package/src/client/components/CrudListBulkActionSurface.vue +2 -5
  6. package/src/client/components/CrudListFilterControls.vue +175 -0
  7. package/src/client/components/CrudListFilterSurface.vue +5 -248
  8. package/src/client/components/CrudListScreen.vue +1 -28
  9. package/src/client/components/CrudViewScreen.vue +1 -9
  10. package/src/client/composables/crud/crudJsonApiTransportSupport.js +20 -42
  11. package/src/client/composables/crud/crudLookupFieldLabelSupport.js +2 -13
  12. package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -27
  13. package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -7
  14. package/src/client/composables/internal/crudListFilterLookupSupport.js +2 -1
  15. package/src/client/composables/internal/useOperationScope.js +5 -7
  16. package/src/client/composables/records/useAddEdit.js +12 -11
  17. package/src/client/composables/records/useCrudAddEdit.js +74 -22
  18. package/src/client/composables/records/useCrudList.js +0 -6
  19. package/src/client/composables/records/useCrudView.js +0 -6
  20. package/src/client/composables/records/useList.js +2 -8
  21. package/src/client/composables/records/useView.js +3 -8
  22. package/src/client/composables/runtime/addEditUiRuntime.js +12 -60
  23. package/src/client/composables/runtime/listUiRuntime.js +11 -36
  24. package/src/client/composables/runtime/useFieldErrorBag.js +3 -3
  25. package/src/client/composables/runtime/viewUiRuntime.js +14 -62
  26. package/src/client/composables/support/errorMessageHelpers.js +6 -48
  27. package/src/client/composables/support/resourceLoadStateHelpers.js +67 -156
  28. package/src/client/composables/support/routeTemplateHelpers.js +57 -0
  29. package/src/client/composables/useAccess.js +12 -17
  30. package/src/client/composables/useCommand.js +0 -1
  31. package/src/client/composables/useCrudDeleteAction.js +7 -5
  32. package/src/client/composables/useCrudListBulkActions.js +9 -21
  33. package/src/client/composables/useCrudListFilterLookups.js +1 -3
  34. package/src/client/composables/useCrudListParentTitle.js +0 -2
  35. package/src/client/composables/useCrudListRowActions.js +8 -24
  36. package/src/client/composables/useCrudListScreen.js +4 -5
  37. package/src/client/composables/useCrudViewScreen.js +2 -3
  38. package/src/client/composables/useRealtimeQueryInvalidation.js +3 -10
  39. package/src/client/composables/useScopeRuntime.js +2 -5
  40. package/src/client/rowActions.js +13 -40
  41. package/src/client/support/crudListActionSupport.js +54 -0
  42. package/test/crudAddEditActions.test.js +59 -19
  43. package/test/crudListFilterSurface.test.js +7 -4
  44. package/test/listUiRuntime.test.js +3 -1
  45. package/test/requestTransportOptions.test.js +24 -0
  46. package/test/resourceLoadStateHelpers.test.js +32 -0
  47. package/test/useAccess.test.js +62 -0
  48. package/test/useCrudDeleteAction.test.js +57 -2
  49. package/test/useCrudListScreen.test.js +45 -0
  50. package/test/useListRouteInitialization.test.js +5 -44
  51. package/src/client/composables/runtime/operationAdapters.js +0 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/http-web",
3
- "version": "0.1.91",
3
+ "version": "0.1.93",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test --test-concurrency=1"
@@ -44,14 +44,14 @@
44
44
  "./client/support/contractGuards": "./src/client/support/contractGuards.js"
45
45
  },
46
46
  "dependencies": {
47
- "@jskit-ai/resource-crud-core": "0.1.187",
47
+ "@jskit-ai/resource-crud-core": "0.1.188",
48
48
  "@mdi/js": "^7.4.47"
49
49
  },
50
50
  "peerDependencies": {
51
- "@jskit-ai/http-runtime": "0.1.243",
52
- "@jskit-ai/kernel": "0.1.245",
53
- "@jskit-ai/realtime": "0.1.243",
54
- "@jskit-ai/shell-web": "0.1.250",
51
+ "@jskit-ai/http-runtime": "0.1.244",
52
+ "@jskit-ai/kernel": "0.1.246",
53
+ "@jskit-ai/realtime": "0.1.245",
54
+ "@jskit-ai/shell-web": "0.1.252",
55
55
  "@tanstack/vue-query": "^5.90.5",
56
56
  "vue": "^3.5.13",
57
57
  "vue-router": "^5.0.4",
@@ -76,9 +76,19 @@ id (or a distinct new-record marker) so form cache entries stay separate.
76
76
  The New/Edit examples supply it; list/view screen wrappers derive their
77
77
  standard keys from `resourceNamespace`.
78
78
 
79
+ Each form fact has one input. `resource`, `input`, `buildPayload`,
80
+ `mapPayloadToModel` and `createModel` are top-level `useCrudAddEditScreen()`
81
+ options; `addEditOptions` carries only request and route options for
82
+ `useAddEdit()` (such as `apiSuffix`, `queryKeyFactory`, `readEnabled`,
83
+ `writeMethod`, URL templates, permissions, `client`, `transport` and
84
+ `onSaveSuccess`) and rejects anything else. The save-error message is
85
+ `addEditOptions.fallbackSaveError` when given, otherwise the resource's
86
+ `messages.saveError`; the request error and its toast use the same string.
87
+
79
88
  Save navigation is configured in the top-level `saveSuccess` object. The default
80
89
  prefers a configured record-view URL, then the list. A create-and-list variation
81
- sets `navigateToView: false` and `listUrlTemplate` to its list route. Set both
90
+ sets `navigateToView: false` and `saveSuccess.listUrlTemplate` to its list
91
+ route; `addEditOptions.listUrlTemplate` does not drive save navigation. Set both
82
92
  `navigateToView` and `navigateToList` to `false` to stay on the form. A custom
83
93
  `addEditOptions.onSaveSuccess` replaces default invalidation and navigation;
84
94
  it is unnecessary for ordinary list navigation. The generic CRUD guide explains
@@ -1,47 +1,20 @@
1
1
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
-
3
- function normalizeCrudListBulkAction(rawAction = {}, index = 0) {
4
- if (!rawAction || typeof rawAction !== "object" || Array.isArray(rawAction)) {
5
- return null;
6
- }
7
-
8
- const key = normalizeText(rawAction.key || rawAction.id || `action-${index + 1}`);
9
- const label = normalizeText(rawAction.label || rawAction.title);
10
- if (!key || !label) {
11
- return null;
12
- }
13
-
14
- return Object.freeze({
15
- key,
16
- label,
17
- icon: normalizeText(rawAction.icon),
18
- color: normalizeText(rawAction.color, { fallback: "primary" }),
19
- variant: normalizeText(rawAction.variant, { fallback: "tonal" }),
20
- confirmLabel: normalizeText(rawAction.confirmLabel),
21
- run: typeof rawAction.run === "function" ? rawAction.run : null,
22
- disabled: rawAction.disabled
23
- });
24
- }
2
+ import { defineCrudListActions } from "./support/crudListActionSupport.js";
25
3
 
26
4
  function defineCrudListBulkActions(actions = []) {
27
- if (!Array.isArray(actions)) {
28
- throw new TypeError("defineCrudListBulkActions requires an array.");
29
- }
30
-
31
- const normalizedActions = [];
32
- const seenKeys = new Set();
33
-
34
- actions.forEach((rawAction, index) => {
35
- const action = normalizeCrudListBulkAction(rawAction, index);
36
- if (!action || seenKeys.has(action.key)) {
37
- return;
38
- }
39
-
40
- seenKeys.add(action.key);
41
- normalizedActions.push(action);
5
+ return defineCrudListActions(actions, {
6
+ name: "defineCrudListBulkActions",
7
+ createAction: (rawAction, { key, label }) => ({
8
+ key,
9
+ label,
10
+ icon: normalizeText(rawAction.icon),
11
+ color: normalizeText(rawAction.color, { fallback: "primary" }),
12
+ variant: normalizeText(rawAction.variant, { fallback: "tonal" }),
13
+ confirmLabel: normalizeText(rawAction.confirmLabel),
14
+ run: typeof rawAction.run === "function" ? rawAction.run : null,
15
+ disabled: rawAction.disabled
16
+ })
42
17
  });
43
-
44
- return Object.freeze(normalizedActions);
45
18
  }
46
19
 
47
20
  export { defineCrudListBulkActions };
@@ -116,14 +116,6 @@ function resolveCancelTo(target = cancelTo.value) {
116
116
  </template>
117
117
 
118
118
  <style scoped>
119
- .crud-screen {
120
- --crud-screen-state-padding: 2.5rem 1.25rem;
121
- }
122
-
123
- .crud-screen--operator {
124
- --crud-screen-state-padding: 2rem 1rem;
125
- }
126
-
127
119
  .crud-add-edit-form__actions {
128
120
  display: flex;
129
121
  flex-wrap: wrap;
@@ -138,7 +130,7 @@ function resolveCancelTo(target = cancelTo.value) {
138
130
  .crud-add-edit-form__state {
139
131
  margin-inline: auto;
140
132
  max-width: 30rem;
141
- padding: var(--crud-screen-state-padding);
133
+ padding: 2rem 1rem;
142
134
  text-align: center;
143
135
  }
144
136
 
@@ -13,11 +13,8 @@ const props = defineProps({
13
13
  }
14
14
  });
15
15
 
16
- const display = useDisplay();
17
- const isCompactLayout = computed(() => {
18
- const displayName = String(display?.name?.value || "").trim().toLowerCase();
19
- return displayName === "xs" || displayName === "sm";
20
- });
16
+ const { smAndDown: isCompactLayout } = useDisplay();
17
+
21
18
  const actions = computed(() => Array.isArray(props.runtime?.actions) ? props.runtime.actions : []);
22
19
  const selectedCount = computed(() => Number(props.runtime?.selectedCount?.value || 0));
23
20
  const shouldRender = computed(() =>
@@ -0,0 +1,175 @@
1
+ <script setup>
2
+ import { computed } from "vue";
3
+ import CrudListDateFilterControl from "./CrudListDateFilterControl.vue";
4
+
5
+ const props = defineProps({
6
+ filters: {
7
+ type: Array,
8
+ default: () => []
9
+ },
10
+ runtime: {
11
+ type: Object,
12
+ default: null
13
+ },
14
+ stacked: {
15
+ type: Boolean,
16
+ default: false
17
+ }
18
+ });
19
+
20
+ const runtimeValues = computed(() => props.runtime?.values || {});
21
+
22
+ function optionItems(filter = {}) {
23
+ return Array.isArray(filter.options) ? filter.options : [];
24
+ }
25
+
26
+ function placeholder(filter = {}, fallback = "") {
27
+ return String(filter?.ui?.placeholder || fallback || "").trim();
28
+ }
29
+ </script>
30
+
31
+ <template>
32
+ <div
33
+ class="crud-list-filter-surface__controls"
34
+ :class="{ 'crud-list-filter-surface__controls--stacked': stacked }"
35
+ >
36
+ <template v-for="filter in filters" :key="filter.key">
37
+ <v-switch
38
+ v-if="filter.type === 'flag'"
39
+ v-model="runtimeValues[filter.key]"
40
+ :label="filter.label"
41
+ color="primary"
42
+ density="comfortable"
43
+ hide-details
44
+ class="crud-list-filter-surface__control"
45
+ />
46
+ <v-select
47
+ v-else-if="filter.type === 'enum' || filter.type === 'enumMany' || filter.type === 'presence'"
48
+ v-model="runtimeValues[filter.key]"
49
+ :items="optionItems(filter)"
50
+ :label="filter.label"
51
+ :placeholder="placeholder(filter)"
52
+ :multiple="filter.type === 'enumMany'"
53
+ :chips="filter.type === 'enumMany'"
54
+ :closable-chips="filter.type === 'enumMany'"
55
+ clearable
56
+ item-title="label"
57
+ item-value="value"
58
+ variant="outlined"
59
+ density="comfortable"
60
+ hide-details="auto"
61
+ class="crud-list-filter-surface__control"
62
+ />
63
+ <v-combobox
64
+ v-else-if="filter.type === 'recordIdMany'"
65
+ v-model="runtimeValues[filter.key]"
66
+ :label="filter.label"
67
+ :placeholder="placeholder(filter, 'Enter ids')"
68
+ multiple
69
+ chips
70
+ closable-chips
71
+ clearable
72
+ variant="outlined"
73
+ density="comfortable"
74
+ hide-details="auto"
75
+ class="crud-list-filter-surface__control"
76
+ />
77
+ <v-text-field
78
+ v-else-if="filter.type === 'recordId'"
79
+ v-model="runtimeValues[filter.key]"
80
+ :label="filter.label"
81
+ :placeholder="placeholder(filter, 'Enter id')"
82
+ clearable
83
+ variant="outlined"
84
+ density="comfortable"
85
+ hide-details="auto"
86
+ class="crud-list-filter-surface__control"
87
+ />
88
+ <div
89
+ v-else-if="filter.type === 'dateRange'"
90
+ class="crud-list-filter-surface__range crud-list-filter-surface__range--date"
91
+ role="group"
92
+ :aria-label="`${filter.label} date range`"
93
+ :data-filter-key="filter.key"
94
+ data-filter-type="date-range"
95
+ >
96
+ <CrudListDateFilterControl
97
+ v-model="runtimeValues[filter.key].from"
98
+ :label="`${filter.label} from`"
99
+ :control-id="`${filter.key}-from`"
100
+ :placeholder="placeholder(filter, 'Select start date')"
101
+ />
102
+ <CrudListDateFilterControl
103
+ v-model="runtimeValues[filter.key].to"
104
+ :label="`${filter.label} to`"
105
+ :control-id="`${filter.key}-to`"
106
+ :placeholder="placeholder(filter, 'Select end date')"
107
+ />
108
+ </div>
109
+ <div v-else-if="filter.type === 'numberRange'" class="crud-list-filter-surface__range">
110
+ <v-text-field
111
+ v-model="runtimeValues[filter.key].min"
112
+ :label="`${filter.label} min`"
113
+ type="number"
114
+ variant="outlined"
115
+ density="comfortable"
116
+ hide-details="auto"
117
+ />
118
+ <v-text-field
119
+ v-model="runtimeValues[filter.key].max"
120
+ :label="`${filter.label} max`"
121
+ type="number"
122
+ variant="outlined"
123
+ density="comfortable"
124
+ hide-details="auto"
125
+ />
126
+ </div>
127
+ <CrudListDateFilterControl
128
+ v-else-if="filter.type === 'date'"
129
+ v-model="runtimeValues[filter.key]"
130
+ :label="filter.label"
131
+ :control-id="filter.key"
132
+ :placeholder="placeholder(filter, 'Select date')"
133
+ class="crud-list-filter-surface__control"
134
+ />
135
+ </template>
136
+ </div>
137
+ </template>
138
+
139
+ <style scoped>
140
+ .crud-list-filter-surface__controls {
141
+ display: grid;
142
+ gap: 0.75rem;
143
+ grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
144
+ }
145
+
146
+ .crud-list-filter-surface__controls--stacked {
147
+ grid-template-columns: 1fr;
148
+ }
149
+
150
+ .crud-list-filter-surface__control {
151
+ min-width: 0;
152
+ }
153
+
154
+ .crud-list-filter-surface__range {
155
+ display: grid;
156
+ gap: 0.75rem;
157
+ grid-template-columns: repeat(2, minmax(0, 1fr));
158
+ min-width: 0;
159
+ }
160
+
161
+ .crud-list-filter-surface__range--date {
162
+ grid-column: span 2;
163
+ grid-template-columns: repeat(2, minmax(14rem, 1fr));
164
+ }
165
+
166
+ .crud-list-filter-surface__controls--stacked .crud-list-filter-surface__range--date {
167
+ grid-column: auto;
168
+ }
169
+
170
+ @media (max-width: 640px) {
171
+ .crud-list-filter-surface__range {
172
+ grid-template-columns: 1fr;
173
+ }
174
+ }
175
+ </style>
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, ref } from "vue";
3
3
  import { useDisplay } from "vuetify";
4
- import CrudListDateFilterControl from "./CrudListDateFilterControl.vue";
4
+ import CrudListFilterControls from "./CrudListFilterControls.vue";
5
5
 
6
6
  const props = defineProps({
7
7
  filters: {
@@ -18,7 +18,8 @@ const props = defineProps({
18
18
  }
19
19
  });
20
20
 
21
- const display = useDisplay();
21
+ const { smAndDown: isCompactLayout } = useDisplay();
22
+
22
23
  const sheetOpen = ref(false);
23
24
 
24
25
  const filterEntries = computed(() =>
@@ -27,22 +28,9 @@ const filterEntries = computed(() =>
27
28
  : {})
28
29
  .filter((filter) => filter?.key && filter?.type)
29
30
  );
30
- const runtimeValues = computed(() => props.runtime?.values || {});
31
31
  const activeChips = computed(() => Array.isArray(props.runtime?.activeChips?.value) ? props.runtime.activeChips.value : []);
32
32
  const hasActiveFilters = computed(() => Boolean(props.runtime?.hasActiveFilters?.value));
33
33
  const shouldRender = computed(() => filterEntries.value.length > 0 && Boolean(props.runtime?.values));
34
- const isCompactLayout = computed(() => {
35
- const displayName = String(display?.name?.value || "").trim().toLowerCase();
36
- return displayName === "xs" || displayName === "sm";
37
- });
38
-
39
- function optionItems(filter = {}) {
40
- return Array.isArray(filter.options) ? filter.options : [];
41
- }
42
-
43
- function placeholder(filter = {}, fallback = "") {
44
- return String(filter?.ui?.placeholder || fallback || "").trim();
45
- }
46
34
 
47
35
  function clearChip(chip = {}) {
48
36
  props.runtime?.clearChip?.(chip);
@@ -85,108 +73,7 @@ function clearFilters() {
85
73
  </div>
86
74
 
87
75
  <v-sheet v-if="!isCompactLayout" rounded="lg" border class="crud-list-filter-surface__panel">
88
- <div class="crud-list-filter-surface__controls">
89
- <template v-for="filter in filterEntries" :key="filter.key">
90
- <v-switch
91
- v-if="filter.type === 'flag'"
92
- v-model="runtimeValues[filter.key]"
93
- :label="filter.label"
94
- color="primary"
95
- density="comfortable"
96
- hide-details
97
- class="crud-list-filter-surface__control"
98
- />
99
- <v-select
100
- v-else-if="filter.type === 'enum' || filter.type === 'enumMany' || filter.type === 'presence'"
101
- v-model="runtimeValues[filter.key]"
102
- :items="optionItems(filter)"
103
- :label="filter.label"
104
- :placeholder="placeholder(filter)"
105
- :multiple="filter.type === 'enumMany'"
106
- :chips="filter.type === 'enumMany'"
107
- :closable-chips="filter.type === 'enumMany'"
108
- clearable
109
- item-title="label"
110
- item-value="value"
111
- variant="outlined"
112
- density="comfortable"
113
- hide-details="auto"
114
- class="crud-list-filter-surface__control"
115
- />
116
- <v-combobox
117
- v-else-if="filter.type === 'recordIdMany'"
118
- v-model="runtimeValues[filter.key]"
119
- :label="filter.label"
120
- :placeholder="placeholder(filter, 'Enter ids')"
121
- multiple
122
- chips
123
- closable-chips
124
- clearable
125
- variant="outlined"
126
- density="comfortable"
127
- hide-details="auto"
128
- class="crud-list-filter-surface__control"
129
- />
130
- <v-text-field
131
- v-else-if="filter.type === 'recordId'"
132
- v-model="runtimeValues[filter.key]"
133
- :label="filter.label"
134
- :placeholder="placeholder(filter, 'Enter id')"
135
- clearable
136
- variant="outlined"
137
- density="comfortable"
138
- hide-details="auto"
139
- class="crud-list-filter-surface__control"
140
- />
141
- <div
142
- v-else-if="filter.type === 'dateRange'"
143
- class="crud-list-filter-surface__range crud-list-filter-surface__range--date"
144
- role="group"
145
- :aria-label="`${filter.label} date range`"
146
- :data-filter-key="filter.key"
147
- data-filter-type="date-range"
148
- >
149
- <CrudListDateFilterControl
150
- v-model="runtimeValues[filter.key].from"
151
- :label="`${filter.label} from`"
152
- :control-id="`${filter.key}-from`"
153
- :placeholder="placeholder(filter, 'Select start date')"
154
- />
155
- <CrudListDateFilterControl
156
- v-model="runtimeValues[filter.key].to"
157
- :label="`${filter.label} to`"
158
- :control-id="`${filter.key}-to`"
159
- :placeholder="placeholder(filter, 'Select end date')"
160
- />
161
- </div>
162
- <div v-else-if="filter.type === 'numberRange'" class="crud-list-filter-surface__range">
163
- <v-text-field
164
- v-model="runtimeValues[filter.key].min"
165
- :label="`${filter.label} min`"
166
- type="number"
167
- variant="outlined"
168
- density="comfortable"
169
- hide-details="auto"
170
- />
171
- <v-text-field
172
- v-model="runtimeValues[filter.key].max"
173
- :label="`${filter.label} max`"
174
- type="number"
175
- variant="outlined"
176
- density="comfortable"
177
- hide-details="auto"
178
- />
179
- </div>
180
- <CrudListDateFilterControl
181
- v-else-if="filter.type === 'date'"
182
- v-model="runtimeValues[filter.key]"
183
- :label="filter.label"
184
- :control-id="filter.key"
185
- :placeholder="placeholder(filter, 'Select date')"
186
- class="crud-list-filter-surface__control"
187
- />
188
- </template>
189
- </div>
76
+ <CrudListFilterControls :filters="filterEntries" :runtime="runtime" />
190
77
  </v-sheet>
191
78
 
192
79
  <v-dialog v-model="sheetOpen" max-width="640" location="bottom">
@@ -196,103 +83,7 @@ function clearFilters() {
196
83
  <v-btn variant="text" @click="sheetOpen = false">Close</v-btn>
197
84
  </v-card-title>
198
85
  <v-card-text>
199
- <div class="crud-list-filter-surface__controls crud-list-filter-surface__controls--stacked">
200
- <template v-for="filter in filterEntries" :key="filter.key">
201
- <v-switch
202
- v-if="filter.type === 'flag'"
203
- v-model="runtimeValues[filter.key]"
204
- :label="filter.label"
205
- color="primary"
206
- density="comfortable"
207
- hide-details
208
- />
209
- <v-select
210
- v-else-if="filter.type === 'enum' || filter.type === 'enumMany' || filter.type === 'presence'"
211
- v-model="runtimeValues[filter.key]"
212
- :items="optionItems(filter)"
213
- :label="filter.label"
214
- :placeholder="placeholder(filter)"
215
- :multiple="filter.type === 'enumMany'"
216
- :chips="filter.type === 'enumMany'"
217
- :closable-chips="filter.type === 'enumMany'"
218
- clearable
219
- item-title="label"
220
- item-value="value"
221
- variant="outlined"
222
- density="comfortable"
223
- hide-details="auto"
224
- />
225
- <v-combobox
226
- v-else-if="filter.type === 'recordIdMany'"
227
- v-model="runtimeValues[filter.key]"
228
- :label="filter.label"
229
- :placeholder="placeholder(filter, 'Enter ids')"
230
- multiple
231
- chips
232
- closable-chips
233
- clearable
234
- variant="outlined"
235
- density="comfortable"
236
- hide-details="auto"
237
- />
238
- <v-text-field
239
- v-else-if="filter.type === 'recordId'"
240
- v-model="runtimeValues[filter.key]"
241
- :label="filter.label"
242
- :placeholder="placeholder(filter, 'Enter id')"
243
- clearable
244
- variant="outlined"
245
- density="comfortable"
246
- hide-details="auto"
247
- />
248
- <div
249
- v-else-if="filter.type === 'dateRange'"
250
- class="crud-list-filter-surface__range crud-list-filter-surface__range--date"
251
- role="group"
252
- :aria-label="`${filter.label} date range`"
253
- :data-filter-key="filter.key"
254
- data-filter-type="date-range"
255
- >
256
- <CrudListDateFilterControl
257
- v-model="runtimeValues[filter.key].from"
258
- :label="`${filter.label} from`"
259
- :control-id="`${filter.key}-from`"
260
- :placeholder="placeholder(filter, 'Select start date')"
261
- />
262
- <CrudListDateFilterControl
263
- v-model="runtimeValues[filter.key].to"
264
- :label="`${filter.label} to`"
265
- :control-id="`${filter.key}-to`"
266
- :placeholder="placeholder(filter, 'Select end date')"
267
- />
268
- </div>
269
- <div v-else-if="filter.type === 'numberRange'" class="crud-list-filter-surface__range">
270
- <v-text-field
271
- v-model="runtimeValues[filter.key].min"
272
- :label="`${filter.label} min`"
273
- type="number"
274
- variant="outlined"
275
- density="comfortable"
276
- hide-details="auto"
277
- />
278
- <v-text-field
279
- v-model="runtimeValues[filter.key].max"
280
- :label="`${filter.label} max`"
281
- type="number"
282
- variant="outlined"
283
- density="comfortable"
284
- hide-details="auto"
285
- />
286
- </div>
287
- <CrudListDateFilterControl
288
- v-else-if="filter.type === 'date'"
289
- v-model="runtimeValues[filter.key]"
290
- :label="filter.label"
291
- :control-id="filter.key"
292
- :placeholder="placeholder(filter, 'Select date')"
293
- />
294
- </template>
295
- </div>
86
+ <CrudListFilterControls :filters="filterEntries" :runtime="runtime" stacked />
296
87
  </v-card-text>
297
88
  <v-card-actions class="justify-space-between">
298
89
  <v-btn variant="text" @click="clearFilters">Clear all</v-btn>
@@ -335,36 +126,6 @@ function clearFilters() {
335
126
  padding: 1rem;
336
127
  }
337
128
 
338
- .crud-list-filter-surface__controls {
339
- display: grid;
340
- gap: 0.75rem;
341
- grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
342
- }
343
-
344
- .crud-list-filter-surface__controls--stacked {
345
- grid-template-columns: 1fr;
346
- }
347
-
348
- .crud-list-filter-surface__control {
349
- min-width: 0;
350
- }
351
-
352
- .crud-list-filter-surface__range {
353
- display: grid;
354
- gap: 0.75rem;
355
- grid-template-columns: repeat(2, minmax(0, 1fr));
356
- min-width: 0;
357
- }
358
-
359
- .crud-list-filter-surface__range--date {
360
- grid-column: span 2;
361
- grid-template-columns: repeat(2, minmax(14rem, 1fr));
362
- }
363
-
364
- .crud-list-filter-surface__controls--stacked .crud-list-filter-surface__range--date {
365
- grid-column: auto;
366
- }
367
-
368
129
  .crud-list-filter-surface__sheet {
369
130
  margin: 0.75rem;
370
131
  }
@@ -380,9 +141,5 @@ function clearFilters() {
380
141
  align-items: stretch;
381
142
  flex-direction: column;
382
143
  }
383
-
384
- .crud-list-filter-surface__range {
385
- grid-template-columns: 1fr;
386
- }
387
144
  }
388
145
  </style>
@@ -320,14 +320,6 @@ function setSelectableRowsSelected(selected = true) {
320
320
  </template>
321
321
 
322
322
  <style scoped>
323
- .crud-screen {
324
- --crud-screen-state-padding: 2.5rem 1.25rem;
325
- }
326
-
327
- .crud-screen--operator {
328
- --crud-screen-state-padding: 2rem 1rem;
329
- }
330
-
331
323
  .crud-list-actions {
332
324
  display: flex;
333
325
  flex-wrap: wrap;
@@ -350,7 +342,7 @@ function setSelectableRowsSelected(selected = true) {
350
342
  .crud-list-state {
351
343
  margin-inline: auto;
352
344
  max-width: 30rem;
353
- padding: var(--crud-screen-state-padding);
345
+ padding: 2rem 1rem;
354
346
  text-align: center;
355
347
  }
356
348
 
@@ -391,25 +383,6 @@ function setSelectableRowsSelected(selected = true) {
391
383
  margin-top: 0.85rem;
392
384
  }
393
385
 
394
- .crud-list-card__field {
395
- display: grid;
396
- gap: 0.15rem;
397
- }
398
-
399
- .crud-list-card__field-label {
400
- color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity));
401
- font-size: 0.72rem;
402
- letter-spacing: 0.04em;
403
- line-height: 1.2;
404
- text-transform: uppercase;
405
- }
406
-
407
- .crud-list-card__field-value {
408
- font-size: 0.95rem;
409
- line-height: 1.35;
410
- overflow-wrap: anywhere;
411
- }
412
-
413
386
  .crud-list-table {
414
387
  display: none;
415
388
  overflow-x: auto;