@jskit-ai/users-web 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  export default Object.freeze({
2
2
  packageVersion: 1,
3
3
  packageId: "@jskit-ai/users-web",
4
- version: "0.1.30",
4
+ version: "0.1.32",
5
+ kind: "runtime",
5
6
  description: "Users web module: workspace selector shell element plus workspace/profile/members UI elements.",
6
7
  dependsOn: [
7
8
  "@jskit-ai/http-runtime",
@@ -241,11 +242,11 @@ export default Object.freeze({
241
242
  "@uppy/dashboard": "^5.1.1",
242
243
  "@uppy/image-editor": "^4.2.0",
243
244
  "@uppy/xhr-upload": "^5.1.1",
244
- "@jskit-ai/http-runtime": "0.1.16",
245
- "@jskit-ai/realtime": "0.1.16",
246
- "@jskit-ai/kernel": "0.1.17",
247
- "@jskit-ai/shell-web": "0.1.16",
248
- "@jskit-ai/users-core": "0.1.24",
245
+ "@jskit-ai/http-runtime": "0.1.17",
246
+ "@jskit-ai/realtime": "0.1.17",
247
+ "@jskit-ai/kernel": "0.1.18",
248
+ "@jskit-ai/shell-web": "0.1.17",
249
+ "@jskit-ai/users-core": "0.1.27",
249
250
  "vuetify": "^4.0.0"
250
251
  },
251
252
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-web",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -9,8 +9,8 @@
9
9
  "./client": "./src/client/index.js",
10
10
  "./client/components/WorkspaceMembersClientElement": "./src/client/components/WorkspaceMembersClientElement.vue",
11
11
  "./client/composables/useAddEdit": "./src/client/composables/useAddEdit.js",
12
+ "./client/composables/useCrudSchemaForm": "./src/client/composables/useCrudSchemaForm.js",
12
13
  "./client/composables/useList": "./src/client/composables/useList.js",
13
- "./client/composables/useCommand": "./src/client/composables/useCommand.js",
14
14
  "./client/composables/useView": "./src/client/composables/useView.js",
15
15
  "./client/composables/usePagedCollection": "./src/client/composables/usePagedCollection.js",
16
16
  "./client/composables/useAccountSettingsRuntime": "./src/client/composables/useAccountSettingsRuntime.js",
@@ -21,11 +21,11 @@
21
21
  "dependencies": {
22
22
  "@tanstack/vue-query": "5.92.12",
23
23
  "@mdi/js": "^7.4.47",
24
- "@jskit-ai/users-core": "0.1.24",
25
- "@jskit-ai/realtime": "0.1.16",
26
- "@jskit-ai/http-runtime": "0.1.16",
27
- "@jskit-ai/kernel": "0.1.17",
28
- "@jskit-ai/shell-web": "0.1.16",
24
+ "@jskit-ai/users-core": "0.1.27",
25
+ "@jskit-ai/realtime": "0.1.17",
26
+ "@jskit-ai/http-runtime": "0.1.17",
27
+ "@jskit-ai/kernel": "0.1.18",
28
+ "@jskit-ai/shell-web": "0.1.17",
29
29
  "vuetify": "^4.0.0"
30
30
  }
31
31
  }
@@ -32,11 +32,6 @@ import { useWorkspaceRouteContext } from "../composables/useWorkspaceRouteContex
32
32
  import { useShellWebErrorRuntime } from "@jskit-ai/shell-web/client/error";
33
33
  import { createWorkspaceRealtimeMatcher } from "../support/realtimeWorkspace.js";
34
34
  import { buildWorkspaceQueryKey } from "../support/workspaceQueryKeys.js";
35
- import {
36
- WORKSPACE_SETTINGS_CHANGED_EVENT,
37
- WORKSPACE_MEMBERS_CHANGED_EVENT,
38
- WORKSPACE_INVITES_CHANGED_EVENT
39
- } from "@jskit-ai/users-core/shared/events/usersEvents";
40
35
  import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
41
36
 
42
37
  const forms = reactive({
@@ -274,7 +269,7 @@ const workspaceSettingsView = useView({
274
269
  buildWorkspaceQueryKey("settings", surfaceId, workspaceSlug),
275
270
  viewPermissions: ["workspace.members.invite"],
276
271
  realtime: {
277
- event: WORKSPACE_SETTINGS_CHANGED_EVENT,
272
+ event: "workspace.settings.changed",
278
273
  matches: matchesWorkspaceRealtime
279
274
  },
280
275
  fallbackLoadError: "Unable to load workspace settings."
@@ -295,7 +290,7 @@ const workspaceMembersList = useList({
295
290
  buildWorkspaceQueryKey("members", surfaceId, workspaceSlug),
296
291
  viewPermissions: ["workspace.members.view", "workspace.members.manage"],
297
292
  realtime: {
298
- event: WORKSPACE_MEMBERS_CHANGED_EVENT,
293
+ event: "workspace.members.changed",
299
294
  matches: matchesWorkspaceRealtime
300
295
  },
301
296
  selectItems: (payload) => normalizeMembers(payload?.members),
@@ -309,7 +304,7 @@ const workspaceInvitesList = useList({
309
304
  buildWorkspaceQueryKey("invites", surfaceId, workspaceSlug),
310
305
  viewPermissions: ["workspace.members.view", "workspace.members.manage"],
311
306
  realtime: {
312
- event: WORKSPACE_INVITES_CHANGED_EVENT,
307
+ event: "workspace.invites.changed",
313
308
  matches: matchesWorkspaceRealtime
314
309
  },
315
310
  selectItems: (payload) => normalizeInvites(payload?.invites),
@@ -170,7 +170,6 @@
170
170
  import { computed, reactive } from "vue";
171
171
  import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
172
172
  import { workspaceSettingsResource } from "@jskit-ai/users-core/shared/resources/workspaceSettingsResource";
173
- import { WORKSPACE_SETTINGS_CHANGED_EVENT } from "@jskit-ai/users-core/shared/events/usersEvents";
174
173
  import { USERS_ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/users-core/shared/support/usersVisibility";
175
174
  import {
176
175
  DEFAULT_WORKSPACE_DARK_PALETTE,
@@ -221,7 +220,7 @@ const addEdit = useAddEdit({
221
220
  "darkSurfaceVariantColor"
222
221
  ],
223
222
  realtime: {
224
- event: WORKSPACE_SETTINGS_CHANGED_EVENT,
223
+ event: "workspace.settings.changed",
225
224
  matches: matchesWorkspaceRealtime
226
225
  },
227
226
  model: workspaceSettingsForm,
@@ -12,11 +12,6 @@ import { useView } from "../composables/useView.js";
12
12
  import { usePaths } from "../composables/usePaths.js";
13
13
  import { useRealtimeQueryInvalidation } from "../composables/useRealtimeQueryInvalidation.js";
14
14
  import { useWorkspaceSurfaceId } from "../composables/useWorkspaceSurfaceId.js";
15
- import {
16
- WORKSPACE_SETTINGS_CHANGED_EVENT,
17
- WORKSPACES_CHANGED_EVENT,
18
- WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT
19
- } from "@jskit-ai/users-core/shared/events/usersEvents";
20
15
  import { USERS_ROUTE_VISIBILITY_PUBLIC } from "@jskit-ai/users-core/shared/support/usersVisibility";
21
16
  import { normalizePendingInvite } from "../composables/accountSettingsRuntimeHelpers.js";
22
17
 
@@ -55,7 +50,7 @@ const bootstrapView = useView({
55
50
  apiSuffix: "/bootstrap",
56
51
  queryKeyFactory: () => bootstrapQueryKey,
57
52
  realtime: {
58
- event: WORKSPACE_SETTINGS_CHANGED_EVENT
53
+ event: "workspace.settings.changed"
59
54
  },
60
55
  fallbackLoadError: "Unable to load workspaces.",
61
56
  model: bootstrapModel,
@@ -112,12 +107,12 @@ const createWorkspaceCommand = useCommand({
112
107
  });
113
108
 
114
109
  useRealtimeQueryInvalidation({
115
- event: WORKSPACES_CHANGED_EVENT,
110
+ event: "workspaces.changed",
116
111
  queryKey: bootstrapQueryKey
117
112
  });
118
113
 
119
114
  useRealtimeQueryInvalidation({
120
- event: WORKSPACE_PENDING_INVITATIONS_CHANGED_EVENT,
115
+ event: "workspace.invitations.pending.changed",
121
116
  queryKey: bootstrapQueryKey
122
117
  });
123
118
 
@@ -0,0 +1,120 @@
1
+ import { computed, unref } from "vue";
2
+ import { asPlainObject } from "./scopeHelpers.js";
3
+ import {
4
+ normalizeRouteParamName,
5
+ resolveRouteParamsSource,
6
+ resolveRoutePathnameSource,
7
+ resolveRouteTemplateLocation,
8
+ toRouteParamValue
9
+ } from "./routeTemplateHelpers.js";
10
+
11
+ function toResolvedRecordId({ routeParams, recordIdParam, routeRecordId }) {
12
+ const explicitRecordId = toRouteParamValue(
13
+ typeof routeRecordId === "function" ? routeRecordId() : unref(routeRecordId)
14
+ );
15
+ if (explicitRecordId) {
16
+ return explicitRecordId;
17
+ }
18
+
19
+ return toRouteParamValue(routeParams[recordIdParam]);
20
+ }
21
+
22
+ function resolveSavedRecordId(payload, saveRecordIdSelector) {
23
+ const sourcePayload = asPlainObject(payload);
24
+ if (typeof saveRecordIdSelector === "function") {
25
+ return toRouteParamValue(saveRecordIdSelector(sourcePayload));
26
+ }
27
+
28
+ return toRouteParamValue(sourcePayload.id);
29
+ }
30
+
31
+ function createAddEditUiRuntime({
32
+ recordIdParam = "recordId",
33
+ routeParams = null,
34
+ routePath = "",
35
+ routeRecordId = null,
36
+ apiUrlTemplate = "",
37
+ viewUrlTemplate = "",
38
+ listUrlTemplate = "",
39
+ saveRecordIdSelector = null
40
+ } = {}) {
41
+ const normalizedRecordIdParam = normalizeRouteParamName(recordIdParam, {
42
+ context: "useAddEdit recordIdParam"
43
+ });
44
+ const normalizedApiUrlTemplate = String(apiUrlTemplate || "").trim();
45
+ const normalizedViewUrlTemplate = String(viewUrlTemplate || "").trim();
46
+ const normalizedListUrlTemplate = String(listUrlTemplate || "").trim();
47
+
48
+ function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
49
+ const normalizedTemplate = String(urlTemplate || "").trim();
50
+ if (!normalizedTemplate) {
51
+ return "";
52
+ }
53
+
54
+ const currentRouteParams = resolveRouteParamsSource(routeParams);
55
+ const sourceParams = {
56
+ ...currentRouteParams,
57
+ ...asPlainObject(extraParams)
58
+ };
59
+ const resolvedRecordId = toRouteParamValue(sourceParams[normalizedRecordIdParam]) ||
60
+ toResolvedRecordId({
61
+ routeParams: currentRouteParams,
62
+ recordIdParam: normalizedRecordIdParam,
63
+ routeRecordId
64
+ });
65
+ sourceParams[normalizedRecordIdParam] = resolvedRecordId;
66
+
67
+ return resolveRouteTemplateLocation(normalizedTemplate, {
68
+ params: sourceParams,
69
+ currentPathname: resolveRoutePathnameSource(routePath)
70
+ });
71
+ }
72
+
73
+ const recordId = computed(() =>
74
+ toResolvedRecordId({
75
+ routeParams: resolveRouteParamsSource(routeParams),
76
+ recordIdParam: normalizedRecordIdParam,
77
+ routeRecordId
78
+ })
79
+ );
80
+
81
+ const apiSuffix = computed(() => resolveTemplatePath(normalizedApiUrlTemplate));
82
+ const listUrl = computed(() => resolveTemplatePath(normalizedListUrlTemplate));
83
+ const cancelUrl = computed(() => resolveTemplatePath(normalizedViewUrlTemplate) || listUrl.value);
84
+
85
+ function resolveParams(urlTemplate = "", extraParams = {}) {
86
+ return resolveTemplatePath(urlTemplate, extraParams);
87
+ }
88
+
89
+ function resolveViewUrl(recordIdLike = "") {
90
+ const targetRecordId = toRouteParamValue(recordIdLike);
91
+ if (!targetRecordId) {
92
+ return "";
93
+ }
94
+
95
+ return resolveTemplatePath(normalizedViewUrlTemplate, {
96
+ [normalizedRecordIdParam]: targetRecordId
97
+ });
98
+ }
99
+
100
+ function resolveSavedViewUrl(payload = {}) {
101
+ const targetRecordId = resolveSavedRecordId(payload, saveRecordIdSelector);
102
+ if (!targetRecordId) {
103
+ return "";
104
+ }
105
+
106
+ return resolveViewUrl(targetRecordId);
107
+ }
108
+
109
+ return Object.freeze({
110
+ recordId,
111
+ apiSuffix,
112
+ listUrl,
113
+ cancelUrl,
114
+ resolveParams,
115
+ resolveViewUrl,
116
+ resolveSavedViewUrl
117
+ });
118
+ }
119
+
120
+ export { createAddEditUiRuntime };
@@ -0,0 +1,152 @@
1
+ import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
2
+ import { asPlainObject } from "./scopeHelpers.js";
3
+
4
+ function normalizeCrudFormFields(fields = []) {
5
+ const normalizedFields = [];
6
+ const seenKeys = new Set();
7
+ for (const field of Array.isArray(fields) ? fields : []) {
8
+ const source = asPlainObject(field);
9
+ const key = String(source.key || "").trim();
10
+ if (!key || seenKeys.has(key)) {
11
+ continue;
12
+ }
13
+
14
+ seenKeys.add(key);
15
+ normalizedFields.push({
16
+ ...source,
17
+ key
18
+ });
19
+ }
20
+
21
+ return Object.freeze(normalizedFields);
22
+ }
23
+
24
+ function resolveFormFieldType(field = {}) {
25
+ return String(field.type || "").trim().toLowerCase();
26
+ }
27
+
28
+ function resolveFormFieldInitialValue(field = {}) {
29
+ if (Object.prototype.hasOwnProperty.call(field, "initialValue")) {
30
+ return field.initialValue;
31
+ }
32
+ if (Object.prototype.hasOwnProperty.call(field, "defaultValue")) {
33
+ return field.defaultValue;
34
+ }
35
+
36
+ const fieldType = resolveFormFieldType(field);
37
+ if (fieldType === "boolean") {
38
+ return false;
39
+ }
40
+
41
+ return "";
42
+ }
43
+
44
+ function createCrudFormModel(fields = []) {
45
+ const model = {};
46
+ for (const field of normalizeCrudFormFields(fields)) {
47
+ model[field.key] = resolveFormFieldInitialValue(field);
48
+ }
49
+
50
+ return model;
51
+ }
52
+
53
+ function buildCrudFormPayload(fields = [], model = {}) {
54
+ const payload = {};
55
+ const sourceModel = asPlainObject(model);
56
+
57
+ for (const field of normalizeCrudFormFields(fields)) {
58
+ const fieldKey = field.key;
59
+ const fieldType = resolveFormFieldType(field);
60
+ const rawValue = sourceModel[fieldKey];
61
+
62
+ if (fieldType === "boolean") {
63
+ payload[fieldKey] = Boolean(rawValue);
64
+ continue;
65
+ }
66
+
67
+ if (fieldType === "integer" || fieldType === "number") {
68
+ const normalizedValue = String(rawValue ?? "").trim();
69
+ if (!normalizedValue) {
70
+ continue;
71
+ }
72
+
73
+ const parsedNumber = Number(normalizedValue);
74
+ payload[fieldKey] = Number.isFinite(parsedNumber)
75
+ ? (fieldType === "integer" ? Math.trunc(parsedNumber) : parsedNumber)
76
+ : rawValue;
77
+ continue;
78
+ }
79
+
80
+ if (rawValue == null) {
81
+ continue;
82
+ }
83
+
84
+ payload[fieldKey] = rawValue;
85
+ }
86
+
87
+ return payload;
88
+ }
89
+
90
+ function applyCrudPayloadToForm(fields = [], model = {}, payload = {}) {
91
+ const targetModel = asPlainObject(model);
92
+ const sourcePayload = asPlainObject(payload);
93
+ for (const field of normalizeCrudFormFields(fields)) {
94
+ const fieldKey = field.key;
95
+ const fieldType = resolveFormFieldType(field);
96
+ const rawValue = sourcePayload[fieldKey];
97
+
98
+ if (fieldType === "boolean") {
99
+ targetModel[fieldKey] = Boolean(rawValue);
100
+ continue;
101
+ }
102
+
103
+ if (fieldType === "integer" || fieldType === "number") {
104
+ targetModel[fieldKey] = rawValue == null ? "" : String(rawValue);
105
+ continue;
106
+ }
107
+
108
+ targetModel[fieldKey] = rawValue == null ? "" : String(rawValue);
109
+ }
110
+ }
111
+
112
+ function resolveCrudFieldErrors(fieldErrors = {}, fieldKey = "") {
113
+ const key = String(fieldKey || "").trim();
114
+ if (!key) {
115
+ return [];
116
+ }
117
+
118
+ const source = asPlainObject(fieldErrors);
119
+ const message = String(source[key] || "").trim();
120
+ if (!message) {
121
+ return [];
122
+ }
123
+
124
+ return [message];
125
+ }
126
+
127
+ function parseCrudResourceOperationInput({
128
+ resource = null,
129
+ operationName = "",
130
+ rawPayload = {},
131
+ context = {}
132
+ } = {}) {
133
+ const normalizedOperationName = String(operationName || "").trim();
134
+ const operations = asPlainObject(asPlainObject(resource).operations);
135
+ const operation = asPlainObject(operations[normalizedOperationName]);
136
+
137
+ return validateOperationSection({
138
+ operation,
139
+ section: "bodyValidator",
140
+ value: rawPayload,
141
+ context
142
+ });
143
+ }
144
+
145
+ export {
146
+ normalizeCrudFormFields,
147
+ createCrudFormModel,
148
+ buildCrudFormPayload,
149
+ applyCrudPayloadToForm,
150
+ resolveCrudFieldErrors,
151
+ parseCrudResourceOperationInput
152
+ };
@@ -0,0 +1,111 @@
1
+ import { computed } from "vue";
2
+ import { asPlainObject } from "./scopeHelpers.js";
3
+ import {
4
+ normalizeRouteParamName,
5
+ toRouteParamValue,
6
+ resolveRouteParamsSource,
7
+ resolveRoutePathnameSource,
8
+ resolveRouteTemplateLocation
9
+ } from "./routeTemplateHelpers.js";
10
+
11
+ function resolveRecordId(record, recordIdSelector) {
12
+ const item = asPlainObject(record);
13
+ if (typeof recordIdSelector === "function") {
14
+ return toRouteParamValue(recordIdSelector(item));
15
+ }
16
+
17
+ return toRouteParamValue(item.id);
18
+ }
19
+
20
+ function createListUiRuntime({
21
+ items,
22
+ isInitialLoading,
23
+ recordIdParam = "recordId",
24
+ recordIdSelector = null,
25
+ routeParams = null,
26
+ routePath = "",
27
+ viewUrlTemplate = "",
28
+ editUrlTemplate = ""
29
+ } = {}) {
30
+ const normalizedRecordIdParam = normalizeRouteParamName(recordIdParam, {
31
+ context: "useList recordIdParam"
32
+ });
33
+ const normalizedViewUrlTemplate = String(viewUrlTemplate || "").trim();
34
+ const normalizedEditUrlTemplate = String(editUrlTemplate || "").trim();
35
+ const hasViewUrl = Boolean(normalizedViewUrlTemplate);
36
+ const hasEditUrl = Boolean(normalizedEditUrlTemplate);
37
+ const actionColumnCount = (hasViewUrl ? 1 : 0) + (hasEditUrl ? 1 : 0);
38
+ const normalizedItems = computed(() => (Array.isArray(items?.value) ? items.value : []));
39
+ const showListSkeleton = computed(() => Boolean(isInitialLoading?.value && normalizedItems.value.length < 1));
40
+
41
+ function resolveTemplatePath(urlTemplate = "", extraParams = {}) {
42
+ const normalizedTemplate = String(urlTemplate || "").trim();
43
+ if (!normalizedTemplate) {
44
+ return "";
45
+ }
46
+
47
+ return resolveRouteTemplateLocation(normalizedTemplate, {
48
+ params: {
49
+ ...resolveRouteParamsSource(routeParams),
50
+ ...asPlainObject(extraParams)
51
+ },
52
+ currentPathname: resolveRoutePathnameSource(routePath)
53
+ });
54
+ }
55
+
56
+ function resolveParams(urlTemplate = "", extraParams = {}) {
57
+ return resolveTemplatePath(urlTemplate, extraParams);
58
+ }
59
+
60
+ function resolveRowKey(record, index) {
61
+ const recordId = resolveRecordId(record, recordIdSelector);
62
+ if (recordId) {
63
+ return recordId;
64
+ }
65
+
66
+ return `row-${index}`;
67
+ }
68
+
69
+ function resolveViewUrl(record) {
70
+ if (!hasViewUrl) {
71
+ return "";
72
+ }
73
+
74
+ const recordId = resolveRecordId(record, recordIdSelector);
75
+ if (!recordId) {
76
+ return "";
77
+ }
78
+
79
+ return resolveTemplatePath(normalizedViewUrlTemplate, {
80
+ [normalizedRecordIdParam]: recordId
81
+ });
82
+ }
83
+
84
+ function resolveEditUrl(record) {
85
+ if (!hasEditUrl) {
86
+ return "";
87
+ }
88
+
89
+ const recordId = resolveRecordId(record, recordIdSelector);
90
+ if (!recordId) {
91
+ return "";
92
+ }
93
+
94
+ return resolveTemplatePath(normalizedEditUrlTemplate, {
95
+ [normalizedRecordIdParam]: recordId
96
+ });
97
+ }
98
+
99
+ return Object.freeze({
100
+ hasViewUrl,
101
+ hasEditUrl,
102
+ actionColumnCount,
103
+ showListSkeleton,
104
+ resolveParams,
105
+ resolveRowKey,
106
+ resolveViewUrl,
107
+ resolveEditUrl
108
+ });
109
+ }
110
+
111
+ export { createListUiRuntime };
@@ -0,0 +1,34 @@
1
+ import { useOperationScope } from "./internal/useOperationScope.js";
2
+
3
+ const USERS_OPERATION_ADAPTER_ID = "users-default";
4
+
5
+ function createUsersOperationAdapter() {
6
+ return Object.freeze({
7
+ id: USERS_OPERATION_ADAPTER_ID,
8
+ useOperationScope(options = {}) {
9
+ return useOperationScope(options);
10
+ }
11
+ });
12
+ }
13
+
14
+ const defaultUsersOperationAdapter = createUsersOperationAdapter();
15
+
16
+ function resolveOperationAdapter(adapter, { context = "users-web operation adapter" } = {}) {
17
+ if (adapter == null) {
18
+ return defaultUsersOperationAdapter;
19
+ }
20
+
21
+ if (!adapter || typeof adapter !== "object" || Array.isArray(adapter)) {
22
+ throw new TypeError(`${context} must be an object when provided.`);
23
+ }
24
+ if (typeof adapter.useOperationScope !== "function") {
25
+ throw new TypeError(`${context} must expose useOperationScope(options).`);
26
+ }
27
+
28
+ return adapter;
29
+ }
30
+
31
+ export {
32
+ createUsersOperationAdapter,
33
+ resolveOperationAdapter
34
+ };
@@ -0,0 +1,110 @@
1
+ import { unref } from "vue";
2
+ import { asPlainObject } from "./scopeHelpers.js";
3
+
4
+ const ROUTE_PARAM_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
5
+
6
+ function normalizeRouteParamName(value = "", { context = "users-web route param" } = {}) {
7
+ const normalizedValue = String(value || "").trim();
8
+ if (!normalizedValue) {
9
+ throw new TypeError(`${context} must be a non-empty route parameter name.`);
10
+ }
11
+ if (!ROUTE_PARAM_NAME_PATTERN.test(normalizedValue)) {
12
+ throw new TypeError(
13
+ `${context} "${normalizedValue}" is invalid. Use letters, numbers, and underscores only.`
14
+ );
15
+ }
16
+
17
+ return normalizedValue;
18
+ }
19
+
20
+ function toRouteParamValue(value) {
21
+ if (Array.isArray(value)) {
22
+ return toRouteParamValue(value[0]);
23
+ }
24
+ if (value == null) {
25
+ return "";
26
+ }
27
+
28
+ return String(value).trim();
29
+ }
30
+
31
+ function resolveRouteSourceValue(source = null) {
32
+ if (typeof source === "function") {
33
+ return source();
34
+ }
35
+
36
+ return unref(source);
37
+ }
38
+
39
+ function resolveRouteParamsSource(source = null) {
40
+ return asPlainObject(resolveRouteSourceValue(source));
41
+ }
42
+
43
+ function normalizeRoutePathname(value = "") {
44
+ const rawPathname = String(value || "").trim();
45
+ const sanitizedPathname = rawPathname.split(/[?#]/u, 1)[0] || "";
46
+ if (!sanitizedPathname) {
47
+ return "/";
48
+ }
49
+
50
+ return sanitizedPathname.startsWith("/") ? sanitizedPathname : `/${sanitizedPathname}`;
51
+ }
52
+
53
+ function resolveRoutePathnameSource(source = "") {
54
+ return normalizeRoutePathname(resolveRouteSourceValue(source));
55
+ }
56
+
57
+ function resolveRouteTemplatePath(routeTemplate = "", params = {}) {
58
+ const normalizedTemplate = String(routeTemplate || "").trim();
59
+ if (!normalizedTemplate) {
60
+ return "";
61
+ }
62
+
63
+ const source = asPlainObject(params);
64
+ const missingParams = [];
65
+ const resolvedPath = normalizedTemplate.replace(/:([A-Za-z][A-Za-z0-9_]*)/g, (_, key) => {
66
+ const value = toRouteParamValue(source[key]);
67
+ if (!value) {
68
+ missingParams.push(key);
69
+ return `:${key}`;
70
+ }
71
+
72
+ return encodeURIComponent(value);
73
+ });
74
+
75
+ if (missingParams.length > 0) {
76
+ return "";
77
+ }
78
+
79
+ return resolvedPath;
80
+ }
81
+
82
+ function resolveRouteTemplateLocation(routeTemplate = "", { params = {}, currentPathname = "/" } = {}) {
83
+ const resolvedTemplatePath = resolveRouteTemplatePath(routeTemplate, params);
84
+ if (!resolvedTemplatePath) {
85
+ return "";
86
+ }
87
+ if (resolvedTemplatePath.startsWith("/")) {
88
+ return resolvedTemplatePath;
89
+ }
90
+
91
+ const normalizedCurrentPathname = resolveRoutePathnameSource(currentPathname);
92
+ const basePathname = normalizedCurrentPathname.endsWith("/")
93
+ ? normalizedCurrentPathname
94
+ : `${normalizedCurrentPathname}/`;
95
+ const resolvedPathname = new URL(resolvedTemplatePath, `https://jskit.local${basePathname}`).pathname;
96
+ if (resolvedPathname.length > 1 && resolvedPathname.endsWith("/")) {
97
+ return resolvedPathname.slice(0, -1);
98
+ }
99
+
100
+ return resolvedPathname;
101
+ }
102
+
103
+ export {
104
+ normalizeRouteParamName,
105
+ toRouteParamValue,
106
+ resolveRouteParamsSource,
107
+ resolveRoutePathnameSource,
108
+ resolveRouteTemplatePath,
109
+ resolveRouteTemplateLocation
110
+ };