@jskit-ai/http-web 0.1.92 → 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
@@ -220,6 +220,62 @@ function resolveRouteTemplateLocation(routeTemplate = "", { params = {}, current
220
220
  return resolvedPathname;
221
221
  }
222
222
 
223
+ function createRouteTemplateResolver({
224
+ recordIdParam,
225
+ routeParams = null,
226
+ routeParamNames = null,
227
+ routePath = "",
228
+ routeRecordId = null,
229
+ anchorMode = "at",
230
+ anchorOnTargetRecord = false,
231
+ fillRecordId = true
232
+ } = {}) {
233
+ function resolveCurrentRecordId(currentRouteParams = resolveRouteParamsSource(routeParams)) {
234
+ const explicitRecordId = toRouteParamValue(resolveRouteSourceValue(routeRecordId));
235
+ if (explicitRecordId) {
236
+ return explicitRecordId;
237
+ }
238
+
239
+ return toRouteParamValue(currentRouteParams[recordIdParam]);
240
+ }
241
+
242
+ function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
243
+ const normalizedTemplate = String(urlTemplate || "").trim();
244
+ if (!normalizedTemplate) {
245
+ return "";
246
+ }
247
+
248
+ const currentRouteParams = resolveRouteParamsSource(routeParams);
249
+ const sourceParams = {
250
+ ...currentRouteParams,
251
+ ...asPlainObject(extraParams)
252
+ };
253
+ const currentRecordId = resolveCurrentRecordId(currentRouteParams);
254
+ const targetRecordId = toRouteParamValue(sourceParams[recordIdParam]) || currentRecordId;
255
+ if (fillRecordId) {
256
+ sourceParams[recordIdParam] = targetRecordId;
257
+ }
258
+ const currentPathname = resolveScopedRoutePathname({
259
+ currentPathname: routePath,
260
+ params: currentRouteParams,
261
+ orderedParamNames: routeParamNames,
262
+ anchorParamName: recordIdParam,
263
+ anchorParamValue: anchorOnTargetRecord ? targetRecordId : currentRecordId,
264
+ anchorMode
265
+ });
266
+
267
+ return resolveRouteTemplateLocation(normalizedTemplate, {
268
+ params: sourceParams,
269
+ currentPathname
270
+ });
271
+ }
272
+
273
+ return Object.freeze({
274
+ resolveRecordId: () => resolveCurrentRecordId(),
275
+ resolveTemplatePath
276
+ });
277
+ }
278
+
223
279
  function extractRouteParamNames(pathTemplate = "") {
224
280
  const normalizedTemplate = String(pathTemplate || "").trim();
225
281
  if (!normalizedTemplate) {
@@ -280,6 +336,7 @@ export {
280
336
  resolveScopedRoutePathname,
281
337
  resolveRouteTemplatePath,
282
338
  resolveRouteTemplateLocation,
339
+ createRouteTemplateResolver,
283
340
  extractRouteParamNames,
284
341
  resolveRouteParamNamesInOrder
285
342
  };
@@ -1,6 +1,6 @@
1
1
  import { computed } from "vue";
2
2
  import { hasPermission, normalizePermissionList } from "../lib/permissions.js";
3
- import { resolveEnabledRef, resolveTextRef } from "./support/refValueHelpers.js";
3
+ import { resolveEnabledRef } from "./support/refValueHelpers.js";
4
4
  import {
5
5
  normalizeAccessMode,
6
6
  resolveAccessModeEnabled
@@ -19,8 +19,10 @@ function asPermissionList(value) {
19
19
  return [value];
20
20
  }
21
21
 
22
+ // Permissions come only from the web placement context, which the shell fills
23
+ // from its bootstrap. Until that context carries a `permissions` key, access is
24
+ // awaiting placement permissions and reports them as unavailable.
22
25
  function useAccess({
23
- scopeParamValue = "",
24
26
  enabled = true,
25
27
  access = "always",
26
28
  hasPermissionRequirements = false
@@ -30,9 +32,8 @@ function useAccess({
30
32
  hasPermissionRequirements: hasPermissionRequirements === true
31
33
  });
32
34
  const { context: placementContext } = useWebPlacementContext();
33
- const normalizedScopeParamValue = computed(() => resolveTextRef(scopeParamValue));
34
35
  const queryEnabled = computed(() => resolveEnabledRef(enabled) && accessRequired);
35
- const hasPlacementBootstrapPermissions = computed(() => {
36
+ const hasPlacementPermissions = computed(() => {
36
37
  const source = placementContext.value;
37
38
  if (!source || typeof source !== "object") {
38
39
  return false;
@@ -41,18 +42,18 @@ function useAccess({
41
42
  });
42
43
  const placementPermissions = computed(() => normalizePermissionList(placementContext.value?.permissions));
43
44
  const permissions = computed(() => {
44
- if (!queryEnabled.value || !hasPlacementBootstrapPermissions.value) {
45
+ if (!queryEnabled.value || !hasPlacementPermissions.value) {
45
46
  return [];
46
47
  }
47
48
  return placementPermissions.value;
48
49
  });
49
- const bootstrapError = computed(() => {
50
- if (!queryEnabled.value || hasPlacementBootstrapPermissions.value) {
50
+ const permissionsUnavailableError = computed(() => {
51
+ if (!queryEnabled.value || hasPlacementPermissions.value) {
51
52
  return "";
52
53
  }
53
54
  return "Permissions are unavailable in placement context.";
54
55
  });
55
- const isBootstrapping = computed(() => queryEnabled.value && !hasPlacementBootstrapPermissions.value);
56
+ const awaitingPlacementPermissions = computed(() => queryEnabled.value && !hasPlacementPermissions.value);
56
57
 
57
58
  function can(permission) {
58
59
  return hasPermission(permissions.value, permission);
@@ -88,21 +89,15 @@ function useAccess({
88
89
  return true;
89
90
  }
90
91
 
91
- async function refreshBootstrap() {
92
- return null;
93
- }
94
-
95
92
  return Object.freeze({
96
93
  accessMode: normalizedAccessMode,
97
94
  accessRequired,
98
- scopeParamValue: normalizedScopeParamValue,
99
95
  permissions,
100
- bootstrapError,
101
- isBootstrapping,
96
+ permissionsUnavailableError,
97
+ awaitingPlacementPermissions,
102
98
  can,
103
99
  canAny,
104
- canAll,
105
- refreshBootstrap
100
+ canAll
106
101
  });
107
102
  }
108
103
 
@@ -40,7 +40,6 @@ function useCommand({
40
40
  ownershipFilter,
41
41
  surfaceId,
42
42
  access,
43
- placementSource,
44
43
  apiSuffix,
45
44
  model,
46
45
  readEnabled: false,
@@ -1,4 +1,4 @@
1
- import { computed, proxyRefs, ref, unref } from "vue";
1
+ import { computed, nextTick, proxyRefs, ref, unref } from "vue";
2
2
  import { useRouter } from "vue-router";
3
3
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
4
  import { resolveCrudJsonApiTransport } from "./crud/crudJsonApiTransportSupport.js";
@@ -62,17 +62,19 @@ function useCrudDeleteAction({
62
62
  const deleteApiSuffix = computed(() => resolveDeleteApiSuffix(resolvedScreen, apiUrlTemplate));
63
63
 
64
64
  async function handleDeleteSuccess(_response, context = {}) {
65
- await context?.queryClient?.invalidateQueries?.({
66
- queryKey: ["crud", namespace]
67
- });
68
-
69
65
  const listLocation = resolveListLocation(resolvedScreen);
70
66
  if (!listLocation) {
71
67
  throw new Error("Deleted the record, but the generated list route is unavailable.");
72
68
  }
73
69
 
74
70
  isOpen.value = false;
71
+ // Leave the deleted record's view before invalidating: while its view query is
72
+ // still observed, invalidation would refetch it and report "Record not found.".
75
73
  await activeRouter.push(listLocation);
74
+ await nextTick();
75
+ await context?.queryClient?.invalidateQueries?.({
76
+ queryKey: ["crud", namespace]
77
+ });
76
78
  }
77
79
 
78
80
  function handleDeleteError(cause) {
@@ -1,18 +1,10 @@
1
1
  import { computed, ref } from "vue";
2
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
2
  import { defineCrudListBulkActions } from "../bulkActions.js";
4
-
5
- function normalizeSelectedId(value = "") {
6
- return normalizeText(value);
7
- }
8
-
9
- function resolveActionKey(actionOrKey = "") {
10
- return normalizeText(
11
- actionOrKey && typeof actionOrKey === "object"
12
- ? actionOrKey.key
13
- : actionOrKey
14
- );
15
- }
3
+ import {
4
+ normalizeListActionKey,
5
+ resolveListActionBaseContext,
6
+ resolveListRecordId
7
+ } from "../support/crudListActionSupport.js";
16
8
 
17
9
  function useCrudListBulkActions(actions = [], {
18
10
  resolveRecordId = null,
@@ -28,10 +20,7 @@ function useCrudListBulkActions(actions = [], {
28
20
  const hasActions = computed(() => normalizedActions.length > 0);
29
21
 
30
22
  function getRecordId(record = {}, index = 0) {
31
- const resolvedId = typeof resolveRecordId === "function"
32
- ? resolveRecordId(record, index)
33
- : record?.id ?? record?.attributes?.id ?? index;
34
- return normalizeSelectedId(resolvedId);
23
+ return resolveListRecordId(record, index, resolveRecordId);
35
24
  }
36
25
 
37
26
  function setRecordSelected(record = {}, index = 0, selected = true) {
@@ -78,12 +67,12 @@ function useCrudListBulkActions(actions = [], {
78
67
  }
79
68
 
80
69
  function findAction(actionOrKey = "") {
81
- const actionKey = resolveActionKey(actionOrKey);
70
+ const actionKey = normalizeListActionKey(actionOrKey);
82
71
  return normalizedActions.find((action) => action.key === actionKey) || null;
83
72
  }
84
73
 
85
74
  function isActionExecuting(actionOrKey = "") {
86
- const actionKey = resolveActionKey(actionOrKey);
75
+ const actionKey = normalizeListActionKey(actionOrKey);
87
76
  return Boolean(actionKey && executingActionKey.value === actionKey);
88
77
  }
89
78
 
@@ -110,9 +99,8 @@ function useCrudListBulkActions(actions = [], {
110
99
 
111
100
  executingActionKey.value = action.key;
112
101
  try {
113
- const baseContext = typeof resolveContext === "function" ? resolveContext() : {};
114
102
  return await action.run({
115
- ...(baseContext && typeof baseContext === "object" && !Array.isArray(baseContext) ? baseContext : {}),
103
+ ...resolveListActionBaseContext(resolveContext),
116
104
  action,
117
105
  ids: selectedIds.value.slice(),
118
106
  selectedIds: selectedIds.value.slice(),
@@ -21,7 +21,6 @@ function useCrudListFilterLookups(
21
21
  definitions = {},
22
22
  {
23
23
  values = {},
24
- adapter = null,
25
24
  recordIdParam = "recordId",
26
25
  queryKeyPrefix = [],
27
26
  placementSourcePrefix = "",
@@ -52,7 +51,6 @@ function useCrudListFilterLookups(
52
51
  });
53
52
 
54
53
  const runtime = useList({
55
- adapter: adapter || undefined,
56
54
  apiSuffix: filter.lookup.apiSuffix,
57
55
  ...(transport ? { transport } : {}),
58
56
  queryKeyFactory: (surfaceId = "", scopeParamValue = "") => [
@@ -106,7 +104,7 @@ function useCrudListFilterLookups(
106
104
  options,
107
105
  searchQuery: computed(() => String(runtime.searchQuery || "")),
108
106
  isLoading: computed(() => {
109
- return Boolean(runtime.isInitialLoading || runtime.isFetching || runtime.isRefetching || runtime.isSearchDebouncing);
107
+ return Boolean(runtime.isInitialLoading || runtime.isFetching || runtime.isSearchDebouncing);
110
108
  }),
111
109
  resolveLabel(value, fallback = filter.label || "Option") {
112
110
  return resolveLookupOptionLabel(options.value, cachedOptions.value, value, fallback);
@@ -27,7 +27,6 @@ function normalizeQueryKeyPrefix(value = []) {
27
27
  function useCrudListParentTitle({
28
28
  listRuntime = null,
29
29
  resource = {},
30
- adapter = null,
31
30
  recordIdParam = "recordId",
32
31
  queryKeyPrefix = ["http-web", "crud-list-parent-title"],
33
32
  placementSource = "http-web.crud-list-parent-title",
@@ -93,7 +92,6 @@ function useCrudListParentTitle({
93
92
  });
94
93
 
95
94
  const parentView = viewRuntimeFactory({
96
- adapter,
97
95
  apiUrlTemplate: normalizeText(initialParentDescriptor.apiUrlTemplate),
98
96
  transport: parentTransport,
99
97
  readEnabled: shouldLoadParentRecord,
@@ -1,18 +1,10 @@
1
1
  import { computed, ref } from "vue";
2
- import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
3
2
  import { defineCrudListRowActions } from "../rowActions.js";
4
-
5
- function normalizeRowActionKey(actionOrKey = "") {
6
- return normalizeText(
7
- actionOrKey && typeof actionOrKey === "object"
8
- ? actionOrKey.key
9
- : actionOrKey
10
- );
11
- }
12
-
13
- function normalizeRowId(value = "") {
14
- return normalizeText(value);
15
- }
3
+ import {
4
+ normalizeListActionKey,
5
+ resolveListActionBaseContext,
6
+ resolveListRecordId
7
+ } from "../support/crudListActionSupport.js";
16
8
 
17
9
  function createExecutionKey(actionKey = "", recordId = "", index = 0) {
18
10
  return [
@@ -21,10 +13,6 @@ function createExecutionKey(actionKey = "", recordId = "", index = 0) {
21
13
  ].join(":");
22
14
  }
23
15
 
24
- function asContextObject(value = null) {
25
- return value && typeof value === "object" && !Array.isArray(value) ? value : {};
26
- }
27
-
28
16
  function useCrudListRowActions(actions = [], {
29
17
  resolveRecordId = null,
30
18
  resolveContext = null
@@ -34,16 +22,12 @@ function useCrudListRowActions(actions = [], {
34
22
  const hasActions = computed(() => normalizedActions.length > 0);
35
23
 
36
24
  function getRecordId(record = {}, index = 0) {
37
- const resolvedId = typeof resolveRecordId === "function"
38
- ? resolveRecordId(record, index)
39
- : record?.id ?? record?.attributes?.id ?? index;
40
- return normalizeRowId(resolvedId);
25
+ return resolveListRecordId(record, index, resolveRecordId);
41
26
  }
42
27
 
43
28
  function createActionContext(action = {}, record = {}, index = 0) {
44
- const baseContext = typeof resolveContext === "function" ? resolveContext() : {};
45
29
  return {
46
- ...asContextObject(baseContext),
30
+ ...resolveListActionBaseContext(resolveContext),
47
31
  action,
48
32
  record,
49
33
  index,
@@ -52,7 +36,7 @@ function useCrudListRowActions(actions = [], {
52
36
  }
53
37
 
54
38
  function findAction(actionOrKey = "") {
55
- const actionKey = normalizeRowActionKey(actionOrKey);
39
+ const actionKey = normalizeListActionKey(actionOrKey);
56
40
  return normalizedActions.find((action) => action.key === actionKey) || null;
57
41
  }
58
42
 
@@ -4,6 +4,7 @@ import { useCrudListBulkActions } from "./useCrudListBulkActions.js";
4
4
  import { useCrudListFilters } from "./useCrudListFilters.js";
5
5
  import { useCrudListRowActions } from "./useCrudListRowActions.js";
6
6
  import { resolveCrudHttpClient } from "./crud/crudHttpClientSupport.js";
7
+ import { resolveRecordTitle as resolveCrudRecordTitle } from "./crud/crudLookupFieldLabelSupport.js";
7
8
 
8
9
  function buildCrudListActionContext(records, client) {
9
10
  return Object.freeze({
@@ -110,7 +111,6 @@ function createCrudListDisplayRow(record = {}, index = 0, records = {}) {
110
111
  }
111
112
 
112
113
  function useCrudListScreen({
113
- adapter = null,
114
114
  client = null,
115
115
  resource = null,
116
116
  resourceNamespace = "resource",
@@ -140,7 +140,6 @@ function useCrudListScreen({
140
140
  : [];
141
141
  const normalizedResourceNamespace = String(resourceNamespace || "resource").trim() || "resource";
142
142
  const records = useCrudList({
143
- adapter: adapter || undefined,
144
143
  client: crudHttpClient,
145
144
  resource,
146
145
  apiSuffix,
@@ -207,7 +206,7 @@ function useCrudListScreen({
207
206
  );
208
207
 
209
208
  function resolveRecordTitle(record) {
210
- return records.resolveRecordTitle(record, {
209
+ return resolveCrudRecordTitle(record, {
211
210
  fallbackKey: titleFallbackFieldKey,
212
211
  defaultValue: "Record"
213
212
  });
@@ -223,8 +222,8 @@ function useCrudListScreen({
223
222
  displayRows,
224
223
  selectableRows,
225
224
  listPrimaryAction,
226
- hasViewUrl: Boolean(viewUrlTemplate),
227
- hasEditUrl: Boolean(editUrlTemplate),
225
+ hasViewUrl: records.hasViewUrl,
226
+ hasEditUrl: records.hasEditUrl,
228
227
  resolveRecordTitle,
229
228
  formatListCardValue: formatCrudListCardValue
230
229
  });
@@ -1,9 +1,9 @@
1
1
  import { computed, unref } from "vue";
2
2
  import { useRoute } from "vue-router";
3
3
  import { useCrudView } from "./records/useCrudView.js";
4
+ import { resolveRecordTitle } from "./crud/crudLookupFieldLabelSupport.js";
4
5
 
5
6
  function useCrudViewScreen({
6
- adapter = null,
7
7
  resource = null,
8
8
  resourceNamespace = "resource",
9
9
  apiUrlTemplate = "",
@@ -30,7 +30,6 @@ function useCrudViewScreen({
30
30
  String(workspaceSlug || "")
31
31
  ];
32
32
  const view = useCrudView({
33
- adapter: adapter || undefined,
34
33
  resource,
35
34
  apiUrlTemplate,
36
35
  recordIdParam,
@@ -52,7 +51,7 @@ function useCrudViewScreen({
52
51
  : null
53
52
  });
54
53
  const recordTitle = computed(() =>
55
- view.resolveRecordTitle(view.record, {
54
+ resolveRecordTitle(view.record, {
56
55
  fallbackKey: titleFallbackFieldKey,
57
56
  defaultValue: "Record"
58
57
  })
@@ -2,6 +2,7 @@ import { computed, unref } from "vue";
2
2
  import { useQueryClient } from "@tanstack/vue-query";
3
3
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
4
4
  import { useRealtimeEvent } from "@jskit-ai/realtime/client/composables/useRealtimeEvent";
5
+ import { resolveEnabledRef } from "./support/refValueHelpers.js";
5
6
 
6
7
  function normalizeRealtimeOptions(value = {}) {
7
8
  if (value === null || value === undefined || value === false) {
@@ -39,14 +40,6 @@ function resolveOperationRealtimeOptions({
39
40
  });
40
41
  }
41
42
 
42
- function resolveEnabled(value) {
43
- if (typeof value === "undefined") {
44
- return true;
45
- }
46
-
47
- return Boolean(unref(value));
48
- }
49
-
50
43
  function toQueryKeyList(value) {
51
44
  const resolved = typeof value === "function" ? value() : unref(value);
52
45
  if (!Array.isArray(resolved) || resolved.length < 1) {
@@ -71,7 +64,7 @@ function useRealtimeQueryInvalidation({
71
64
  } = {}) {
72
65
  const queryClient = useQueryClient();
73
66
  const normalizedEvent = computed(() => normalizeText(unref(event)));
74
- const active = computed(() => resolveEnabled(enabled) && Boolean(normalizedEvent.value));
67
+ const active = computed(() => resolveEnabledRef(enabled) && Boolean(normalizedEvent.value));
75
68
 
76
69
  const listener = useRealtimeEvent({
77
70
  event: normalizedEvent,
@@ -131,7 +124,7 @@ function useOperationRealtime({
131
124
  const resolvedQueryKey = Object.hasOwn(source, "queryKey") ? source.queryKey : queryKey;
132
125
  const active = computed(() => {
133
126
  const sourceEnabled = Object.hasOwn(source, "enabled") ? source.enabled : true;
134
- return resolveEnabled(enabled) && resolveEnabled(sourceEnabled);
127
+ return resolveEnabledRef(enabled) && resolveEnabledRef(sourceEnabled);
135
128
  });
136
129
 
137
130
  const bindings = eventList.map((event) =>
@@ -25,8 +25,7 @@ function useScopeRuntime({
25
25
  ownershipFilter = ROUTE_VISIBILITY_WORKSPACE,
26
26
  surfaceId = "",
27
27
  accessMode = "auto",
28
- hasPermissionRequirements = false,
29
- placementSource = "http-web.scope-runtime"
28
+ hasPermissionRequirements = false
30
29
  } = {}) {
31
30
  const normalizedOwnershipFilter = normalizeOwnershipFilter(ownershipFilter);
32
31
  const normalizedAccessMode = ensureAccessModeCompatibility({
@@ -83,11 +82,9 @@ function useScopeRuntime({
83
82
  });
84
83
 
85
84
  const accessRuntime = useAccess({
86
- scopeParamValue,
87
85
  enabled: computed(() => accessRequired && hasRequiredRouteScope.value),
88
86
  access: normalizedAccessMode,
89
- hasPermissionRequirements,
90
- placementSource: String(placementSource || "http-web.scope-runtime")
87
+ hasPermissionRequirements
91
88
  });
92
89
 
93
90
  function resolveApiPath(apiSuffix = "", context = {}) {
@@ -1,47 +1,20 @@
1
1
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
-
3
- function normalizeCrudListRowAction(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
- run: typeof rawAction.run === "function" ? rawAction.run : null,
20
- visible: rawAction.visible,
21
- disabled: rawAction.disabled,
22
- loading: rawAction.loading
23
- });
24
- }
2
+ import { defineCrudListActions } from "./support/crudListActionSupport.js";
25
3
 
26
4
  function defineCrudListRowActions(actions = []) {
27
- if (!Array.isArray(actions)) {
28
- throw new TypeError("defineCrudListRowActions requires an array.");
29
- }
30
-
31
- const normalizedActions = [];
32
- const seenKeys = new Set();
33
-
34
- actions.forEach((rawAction, index) => {
35
- const action = normalizeCrudListRowAction(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: "defineCrudListRowActions",
7
+ createAction: (rawAction, { key, label }) => ({
8
+ key,
9
+ label,
10
+ icon: normalizeText(rawAction.icon),
11
+ color: normalizeText(rawAction.color, { fallback: "primary" }),
12
+ run: typeof rawAction.run === "function" ? rawAction.run : null,
13
+ visible: rawAction.visible,
14
+ disabled: rawAction.disabled,
15
+ loading: rawAction.loading
16
+ })
42
17
  });
43
-
44
- return Object.freeze(normalizedActions);
45
18
  }
46
19
 
47
20
  export { defineCrudListRowActions };
@@ -0,0 +1,54 @@
1
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
+
3
+ function defineCrudListActions(actions, { name, createAction }) {
4
+ if (!Array.isArray(actions)) {
5
+ throw new TypeError(`${name} requires an array.`);
6
+ }
7
+
8
+ const normalizedActions = [];
9
+ const seenKeys = new Set();
10
+
11
+ actions.forEach((rawAction, index) => {
12
+ if (!rawAction || typeof rawAction !== "object" || Array.isArray(rawAction)) {
13
+ return;
14
+ }
15
+
16
+ const key = normalizeText(rawAction.key || rawAction.id || `action-${index + 1}`);
17
+ const label = normalizeText(rawAction.label || rawAction.title);
18
+ if (!key || !label || seenKeys.has(key)) {
19
+ return;
20
+ }
21
+
22
+ seenKeys.add(key);
23
+ normalizedActions.push(Object.freeze(createAction(rawAction, { key, label })));
24
+ });
25
+
26
+ return Object.freeze(normalizedActions);
27
+ }
28
+
29
+ function normalizeListActionKey(actionOrKey = "") {
30
+ return normalizeText(
31
+ actionOrKey && typeof actionOrKey === "object"
32
+ ? actionOrKey.key
33
+ : actionOrKey
34
+ );
35
+ }
36
+
37
+ function resolveListRecordId(record = {}, index = 0, resolveRecordId = null) {
38
+ const resolvedId = typeof resolveRecordId === "function"
39
+ ? resolveRecordId(record, index)
40
+ : record?.id ?? record?.attributes?.id ?? index;
41
+ return normalizeText(resolvedId);
42
+ }
43
+
44
+ function resolveListActionBaseContext(resolveContext = null) {
45
+ const baseContext = typeof resolveContext === "function" ? resolveContext() : {};
46
+ return baseContext && typeof baseContext === "object" && !Array.isArray(baseContext) ? baseContext : {};
47
+ }
48
+
49
+ export {
50
+ defineCrudListActions,
51
+ normalizeListActionKey,
52
+ resolveListRecordId,
53
+ resolveListActionBaseContext
54
+ };