@jskit-ai/http-web 0.1.22 → 0.1.24

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/http-web",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -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.119",
47
+ "@jskit-ai/resource-crud-core": "0.1.121",
48
48
  "@mdi/js": "^7.4.47"
49
49
  },
50
50
  "peerDependencies": {
51
- "@jskit-ai/http-runtime": "0.1.175",
52
- "@jskit-ai/kernel": "0.1.177",
53
- "@jskit-ai/realtime": "0.1.174",
54
- "@jskit-ai/shell-web": "0.1.181",
51
+ "@jskit-ai/http-runtime": "0.1.177",
52
+ "@jskit-ai/kernel": "0.1.179",
53
+ "@jskit-ai/realtime": "0.1.176",
54
+ "@jskit-ai/shell-web": "0.1.183",
55
55
  "@tanstack/vue-query": "^5.90.5",
56
56
  "vue": "^3.5.13",
57
57
  "vue-router": "^5.0.4",
@@ -35,6 +35,7 @@ questionnaire.
35
35
 
36
36
  - Route pages stay thin and use `useCrudListScreen()`, `useCrudViewScreen()`,
37
37
  or `useCrudAddEditScreen()`.
38
+ - Route pages do not add a page header or standalone heading by default.
38
39
  - Transport and validators come from the shared resource contract.
39
40
  - Initial visible loading uses structure-matching skeletons; pending buttons use
40
41
  stable disabled labels, never spinners.
@@ -12,8 +12,6 @@ const routeRecordId = computed(() => String(route.params.bookId || "").trim());
12
12
 
13
13
  const screen = useCrudAddEditScreen({
14
14
  mode: "edit",
15
- title: "Edit book",
16
- subtitle: "Update this catalogue entry.",
17
15
  saveLabel: "Save changes",
18
16
  cancelTo: "/books/:bookId",
19
17
  preserveCancelQuery: true,
@@ -25,11 +25,7 @@ const screen = useCrudListScreen({
25
25
  <template>
26
26
  <CrudListScreen
27
27
  :screen="screen"
28
- title-label="Books"
29
- heading-title="My books"
30
- subtitle="Search and maintain your catalogue."
31
28
  create-label="Add book"
32
- empty-title="No books yet"
33
29
  empty-body="Add your first book to begin the catalogue."
34
30
  >
35
31
  <template #card-fields="{ record, formatListCardValue }">
@@ -7,8 +7,6 @@ import { bookFormFields } from "./formFields.js";
7
7
 
8
8
  const screen = useCrudAddEditScreen({
9
9
  mode: "new",
10
- title: "Add book",
11
- subtitle: "Add a book to your catalogue.",
12
10
  saveLabel: "Save book",
13
11
  cancelTo: "/books",
14
12
  resource: bookResource,
@@ -30,7 +30,6 @@ const deleteAction = useCrudDeleteAction({
30
30
  :screen="screen"
31
31
  resource-singular-title="Book"
32
32
  resource-plural-title="Books"
33
- description="Review this catalogue entry."
34
33
  >
35
34
  <template #actions>
36
35
  <CrudDeleteAction :action="deleteAction" resource-singular-title="Book" />
@@ -28,8 +28,6 @@ const formRuntime = computed(() => props.screen?.formRuntime || {});
28
28
  const addEdit = computed(() => props.screen?.addEdit || formRuntime.value?.addEdit || {});
29
29
  const formState = computed(() => props.screen?.formState || formRuntime.value?.form || {});
30
30
  const mode = computed(() => String(unref(props.screen?.mode) || "new").trim() || "new");
31
- const title = computed(() => String(unref(props.screen?.title) || "").trim());
32
- const subtitle = computed(() => String(unref(props.screen?.subtitle) || "").trim());
33
31
  const saveLabel = computed(() => String(unref(props.screen?.saveLabel) || "Save").trim() || "Save");
34
32
  const cancelTo = computed(() => unref(props.screen?.cancelTo) || "");
35
33
 
@@ -53,27 +51,20 @@ function resolveCancelTo(target = cancelTo.value) {
53
51
 
54
52
  <template>
55
53
  <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
- <p v-if="subtitle" class="text-body-2 text-medium-emphasis mb-0">{{ subtitle }}</p>
60
- </div>
61
- <div class="crud-add-edit-form__actions">
62
- <v-btn v-if="cancelTo" color="primary" variant="outlined" :to="resolveCancelTo(cancelTo)">Cancel</v-btn>
63
- <v-btn
64
- color="primary"
65
- variant="flat"
66
- :disabled="addEdit.isSubmitDisabled"
67
- @click="addEdit.submit"
68
- >
69
- {{ addEdit.isSaving ? "Saving…" : saveLabel }}
70
- </v-btn>
71
- </div>
72
- </header>
54
+ <div class="crud-add-edit-form__actions">
55
+ <v-btn v-if="cancelTo" color="primary" variant="outlined" :to="resolveCancelTo(cancelTo)">Cancel</v-btn>
56
+ <v-btn
57
+ color="primary"
58
+ variant="flat"
59
+ :disabled="addEdit.isSubmitDisabled"
60
+ @click="addEdit.submit"
61
+ >
62
+ {{ addEdit.isSaving ? "Saving…" : saveLabel }}
63
+ </v-btn>
64
+ </div>
73
65
 
74
66
  <v-sheet rounded="lg" border class="crud-add-edit-form__panel">
75
67
  <div v-if="addEdit.loadError" class="crud-add-edit-form__state">
76
- <h2 class="text-h6 mb-2">Unable to load form</h2>
77
68
  <p class="text-body-2 text-medium-emphasis mb-4">
78
69
  {{ addEdit.loadError }}
79
70
  </p>
@@ -89,7 +80,7 @@ function resolveCancelTo(target = cancelTo.value) {
89
80
  </div>
90
81
  <template v-else-if="formRuntime.showFormSkeleton">
91
82
  <div class="pa-4">
92
- <v-skeleton-loader type="heading, text@2, article" />
83
+ <v-skeleton-loader type="text@2, article" />
93
84
  </div>
94
85
  </template>
95
86
  <v-form v-else class="pa-4" @submit.prevent="addEdit.submit" novalidate>
@@ -114,7 +105,6 @@ function resolveCancelTo(target = cancelTo.value) {
114
105
 
115
106
  <style scoped>
116
107
  .crud-screen {
117
- --crud-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
118
108
  --crud-screen-state-padding: 2.5rem 1.25rem;
119
109
  }
120
110
 
@@ -122,25 +112,6 @@ function resolveCancelTo(target = cancelTo.value) {
122
112
  --crud-screen-state-padding: 2rem 1rem;
123
113
  }
124
114
 
125
- .crud-add-edit-form__header {
126
- align-items: flex-start;
127
- display: flex;
128
- gap: 1rem;
129
- justify-content: space-between;
130
- }
131
-
132
- .crud-add-edit-form__copy {
133
- min-width: 0;
134
- }
135
-
136
- .crud-add-edit-form__title {
137
- font-size: var(--crud-screen-title-size);
138
- font-weight: 650;
139
- letter-spacing: -0.02em;
140
- line-height: 1.15;
141
- margin: 0 0 0.35rem;
142
- }
143
-
144
115
  .crud-add-edit-form__actions {
145
116
  display: flex;
146
117
  flex-wrap: wrap;
@@ -164,10 +135,6 @@ function resolveCancelTo(target = cancelTo.value) {
164
135
  }
165
136
 
166
137
  @media (max-width: 960px) {
167
- .crud-add-edit-form__header {
168
- flex-direction: column;
169
- }
170
-
171
138
  .crud-add-edit-form__actions {
172
139
  width: 100%;
173
140
  }
@@ -10,34 +10,14 @@ const props = defineProps({
10
10
  type: Object,
11
11
  required: true
12
12
  },
13
- titleLabel: {
14
- type: String,
15
- default: "Records"
16
- },
17
- headingTitle: {
18
- type: String,
19
- default: ""
20
- },
21
- subtitle: {
22
- type: String,
23
- default: ""
24
- },
25
13
  createLabel: {
26
14
  type: String,
27
15
  default: "New record"
28
16
  },
29
- loadErrorTitle: {
30
- type: String,
31
- default: "Unable to load records"
32
- },
33
17
  loadErrorBody: {
34
18
  type: String,
35
19
  default: "Check the connection and try again."
36
20
  },
37
- emptyTitle: {
38
- type: String,
39
- default: "No records yet"
40
- },
41
21
  emptyBody: {
42
22
  type: String,
43
23
  default: "Create the first record to start using this workflow."
@@ -57,10 +37,6 @@ const hasBulkActions = computed(() => Boolean(unref(bulkActions.value?.hasAction
57
37
  const hasRowActions = computed(() => Boolean(unref(rowActions.value?.hasActions)));
58
38
  const hasViewUrl = computed(() => Boolean(props.screen?.hasViewUrl));
59
39
  const hasEditUrl = computed(() => Boolean(props.screen?.hasEditUrl));
60
- const resolvedHeadingTitle = computed(() => String(props.headingTitle || props.titleLabel || "").trim());
61
- const resolvedSubtitle = computed(() =>
62
- String(props.subtitle || `Search, review, and update ${props.titleLabel} from this screen.`).trim()
63
- );
64
40
 
65
41
  function resolveListRecordTitle(record) {
66
42
  if (typeof props.screen?.resolveRecordTitle === "function") {
@@ -127,32 +103,25 @@ function setSelectableRowsSelected(selected = true) {
127
103
 
128
104
  <template>
129
105
  <section class="crud-screen crud-screen--operator crud-list-element d-flex flex-column ga-4">
130
- <header class="crud-list-header">
131
- <div class="crud-list-header__copy">
132
- <p class="text-overline text-medium-emphasis mb-1">{{ titleLabel }}</p>
133
- <h1 class="crud-list-header__title">{{ resolvedHeadingTitle }}</h1>
134
- <p class="text-body-2 text-medium-emphasis mb-0">{{ resolvedSubtitle }}</p>
135
- </div>
136
- <div class="crud-list-header__actions">
137
- <v-btn
138
- color="primary"
139
- variant="tonal"
140
- :disabled="records.isFetching"
141
- @click="records.reload"
142
- >
143
- {{ records.isFetching ? "Refreshing…" : "Refresh" }}
144
- </v-btn>
145
- <v-btn
146
- v-if="listPrimaryAction"
147
- class="crud-list-header__primary-action"
148
- color="primary"
149
- variant="flat"
150
- :to="listPrimaryAction"
151
- >
152
- {{ createLabel }}
153
- </v-btn>
154
- </div>
155
- </header>
106
+ <div class="crud-list-actions">
107
+ <v-btn
108
+ color="primary"
109
+ variant="tonal"
110
+ :disabled="records.isFetching"
111
+ @click="records.reload"
112
+ >
113
+ {{ records.isFetching ? "Refreshing…" : "Refresh" }}
114
+ </v-btn>
115
+ <v-btn
116
+ v-if="listPrimaryAction"
117
+ class="crud-list-primary-action"
118
+ color="primary"
119
+ variant="flat"
120
+ :to="listPrimaryAction"
121
+ >
122
+ {{ createLabel }}
123
+ </v-btn>
124
+ </div>
156
125
 
157
126
  <v-sheet rounded="lg" border class="crud-list-panel">
158
127
  <div class="crud-list-toolbar">
@@ -182,7 +151,6 @@ function setSelectableRowsSelected(selected = true) {
182
151
  </template>
183
152
  <template v-else>
184
153
  <div v-if="records.loadError" class="crud-list-state">
185
- <h2 class="text-h6 mb-2">{{ loadErrorTitle }}</h2>
186
154
  <p class="text-body-2 text-medium-emphasis mb-4">{{ loadErrorBody }}</p>
187
155
  <v-btn
188
156
  color="primary"
@@ -195,7 +163,6 @@ function setSelectableRowsSelected(selected = true) {
195
163
  </div>
196
164
 
197
165
  <div v-else-if="displayRows.length < 1" class="crud-list-state">
198
- <h2 class="text-h6 mb-2">{{ emptyTitle }}</h2>
199
166
  <p class="text-body-2 text-medium-emphasis mb-4">{{ emptyBody }}</p>
200
167
  <v-btn v-if="listPrimaryAction" color="primary" variant="flat" :to="listPrimaryAction">
201
168
  {{ createLabel }}
@@ -354,7 +321,6 @@ function setSelectableRowsSelected(selected = true) {
354
321
 
355
322
  <style scoped>
356
323
  .crud-screen {
357
- --crud-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
358
324
  --crud-screen-state-padding: 2.5rem 1.25rem;
359
325
  }
360
326
 
@@ -362,26 +328,7 @@ function setSelectableRowsSelected(selected = true) {
362
328
  --crud-screen-state-padding: 2rem 1rem;
363
329
  }
364
330
 
365
- .crud-list-header {
366
- align-items: flex-start;
367
- display: flex;
368
- gap: 1rem;
369
- justify-content: space-between;
370
- }
371
-
372
- .crud-list-header__copy {
373
- min-width: 0;
374
- }
375
-
376
- .crud-list-header__title {
377
- font-size: var(--crud-screen-title-size);
378
- font-weight: 650;
379
- letter-spacing: -0.02em;
380
- line-height: 1.15;
381
- margin: 0 0 0.35rem;
382
- }
383
-
384
- .crud-list-header__actions {
331
+ .crud-list-actions {
385
332
  display: flex;
386
333
  flex-wrap: wrap;
387
334
  gap: 0.5rem;
@@ -495,11 +442,7 @@ function setSelectableRowsSelected(selected = true) {
495
442
  }
496
443
 
497
444
  @media (max-width: 960px) {
498
- .crud-list-header {
499
- flex-direction: column;
500
- }
501
-
502
- .crud-list-header__actions {
445
+ .crud-list-actions {
503
446
  width: 100%;
504
447
  }
505
448
 
@@ -507,7 +450,7 @@ function setSelectableRowsSelected(selected = true) {
507
450
  min-height: 48px;
508
451
  }
509
452
 
510
- .crud-list-header__primary-action {
453
+ .crud-list-primary-action {
511
454
  display: none;
512
455
  }
513
456
 
@@ -13,58 +13,38 @@ const props = defineProps({
13
13
  resourcePluralTitle: {
14
14
  type: String,
15
15
  default: "Records"
16
- },
17
- description: {
18
- type: String,
19
- default: ""
20
- },
21
- unavailableTitle: {
22
- type: String,
23
- default: "Record unavailable"
24
16
  }
25
17
  });
26
18
 
27
19
  const view = computed(() => props.screen?.view || {});
28
- const recordTitle = computed(() => unref(props.screen?.recordTitle) || props.resourceSingularTitle);
29
20
  const listLocation = computed(() => unref(props.screen?.listLocation) || null);
30
21
  const editLocation = computed(() => unref(props.screen?.editLocation) || null);
31
- const resolvedDescription = computed(() =>
32
- String(props.description || `Review this ${props.resourceSingularTitle} record.`).trim()
33
- );
34
22
  </script>
35
23
 
36
24
  <template>
37
25
  <section class="crud-screen crud-screen--operator crud-view-element d-flex flex-column ga-4">
38
- <header class="crud-view-header">
39
- <div class="crud-view-header__copy">
40
- <p class="text-overline text-medium-emphasis mb-1">{{ resourceSingularTitle }}</p>
41
- <h1 class="crud-view-header__title">{{ recordTitle }}</h1>
42
- <p class="text-body-2 text-medium-emphasis mb-0">{{ resolvedDescription }}</p>
43
- </div>
44
- <div class="crud-view-header__actions">
45
- <v-btn
46
- v-if="listLocation"
47
- color="primary"
48
- variant="outlined"
49
- :to="listLocation"
50
- >
51
- Back to {{ resourcePluralTitle }}
52
- </v-btn>
53
- <slot name="actions" :screen="screen" :view="view" />
54
- <v-btn
55
- v-if="editLocation"
56
- color="primary"
57
- variant="flat"
58
- :to="editLocation"
59
- >
60
- Edit
61
- </v-btn>
62
- </div>
63
- </header>
26
+ <div class="crud-view-actions">
27
+ <v-btn
28
+ v-if="listLocation"
29
+ color="primary"
30
+ variant="outlined"
31
+ :to="listLocation"
32
+ >
33
+ Back to {{ resourcePluralTitle }}
34
+ </v-btn>
35
+ <slot name="actions" :screen="screen" :view="view" />
36
+ <v-btn
37
+ v-if="editLocation"
38
+ color="primary"
39
+ variant="flat"
40
+ :to="editLocation"
41
+ >
42
+ Edit
43
+ </v-btn>
44
+ </div>
64
45
 
65
46
  <v-sheet rounded="lg" border class="crud-view-panel">
66
47
  <div v-if="view.loadError || view.isNotFound" class="crud-view-state">
67
- <h2 class="text-h6 mb-2">{{ unavailableTitle }}</h2>
68
48
  <p class="text-body-2 text-medium-emphasis mb-4">
69
49
  {{ view.loadError || `This ${resourceSingularTitle} could not be found.` }}
70
50
  </p>
@@ -114,7 +94,6 @@ const resolvedDescription = computed(() =>
114
94
 
115
95
  <style scoped>
116
96
  .crud-screen {
117
- --crud-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
118
97
  --crud-screen-state-padding: 2.5rem 1.25rem;
119
98
  }
120
99
 
@@ -122,27 +101,7 @@ const resolvedDescription = computed(() =>
122
101
  --crud-screen-state-padding: 2rem 1rem;
123
102
  }
124
103
 
125
- .crud-view-header {
126
- align-items: flex-start;
127
- display: flex;
128
- gap: 1rem;
129
- justify-content: space-between;
130
- }
131
-
132
- .crud-view-header__copy {
133
- min-width: 0;
134
- }
135
-
136
- .crud-view-header__title {
137
- font-size: var(--crud-screen-title-size);
138
- font-weight: 650;
139
- letter-spacing: -0.02em;
140
- line-height: 1.15;
141
- margin: 0 0 0.35rem;
142
- overflow-wrap: anywhere;
143
- }
144
-
145
- .crud-view-header__actions {
104
+ .crud-view-actions {
146
105
  display: flex;
147
106
  flex-wrap: wrap;
148
107
  gap: 0.5rem;
@@ -176,15 +135,11 @@ const resolvedDescription = computed(() =>
176
135
  }
177
136
 
178
137
  @media (max-width: 960px) {
179
- .crud-view-header {
180
- flex-direction: column;
181
- }
182
-
183
- .crud-view-header__actions {
138
+ .crud-view-actions {
184
139
  width: 100%;
185
140
  }
186
141
 
187
- .crud-view-header__actions :deep(.v-btn) {
142
+ .crud-view-actions :deep(.v-btn) {
188
143
  min-height: 48px;
189
144
  flex: 1 1 10rem;
190
145
  }