@jskit-ai/shell-web 0.1.250 → 0.1.252
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/fixtures/adaptive-shell/src/main.js +6 -3
- package/package.json +2 -2
- package/patterns/application-shell/PATTERN.md +18 -4
- package/patterns/application-shell/example/src/pages/home/index.vue +15 -5
- package/patterns/application-shell/example/tests/e2e/adaptive-shell.spec.ts +1 -1
- package/src/architecture/inspectVueArchitecture.js +1092 -336
- package/src/client/asyncModuleRecovery/runtime.js +116 -0
- package/src/client/components/ShellLayout.vue +18 -288
- package/src/client/components/ShellMenuLinkItem.vue +3 -21
- package/src/client/components/ShellOutlet.vue +2 -24
- package/src/client/components/ShellRouteTransition.vue +16 -114
- package/src/client/components/ShellSurfaceAwareMenuLinkItem.vue +5 -100
- package/src/client/components/ShellTabLinkItem.vue +5 -101
- package/src/client/composables/useShellNavigationLink.js +85 -0
- package/src/client/composables/useShellPullToRefresh.js +260 -0
- package/src/client/error/index.js +1 -1
- package/src/client/error/inject.js +4 -1
- package/src/client/error/policy.js +4 -13
- package/src/client/error/presenters.js +4 -9
- package/src/client/error/store.js +1 -1
- package/src/client/navigation/linkResolver.js +5 -27
- package/src/client/navigation/usePaths.js +1 -21
- package/src/client/placement/index.js +2 -0
- package/src/client/placement/inject.js +4 -1
- package/src/client/placement/runtime.js +2 -4
- package/src/client/placement/surfaceContext.js +11 -8
- package/src/client/placement/validators.js +18 -65
- package/src/client/providers/ShellWebClientProvider.js +100 -356
- package/src/client/requestRecovery/runtime.js +29 -77
- package/src/client/runtime/bootstrapRuntime.js +5 -4
- package/src/client/runtime/refreshRuntime.js +122 -0
- package/src/client/support/gestureTargets.js +26 -0
- package/src/client/support/menuLinkTarget.js +35 -19
- package/src/client/support/routeParams.js +26 -0
- package/src/client/support/routeSwipeGesture.js +88 -0
- package/src/client/support/routeTransitionKey.js +1 -3
- package/src/client/support/shellLayoutClass.js +32 -0
- package/test/bootstrapRuntime.test.js +98 -0
- package/test/errorActions.test.js +2 -1
- package/test/errorRuntime.test.js +30 -0
- package/test/linkResolver.test.js +23 -1
- package/test/menuLinkTarget.test.js +77 -0
- package/test/routeSwipeGesture.test.js +49 -0
- package/test/settingsPlacementContract.test.js +16 -6
- package/test/shellGestureSupport.test.js +46 -0
- package/test/surfaceContext.test.js +53 -0
- package/test/useShellPullToRefresh.test.js +123 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { computed, onBeforeUnmount, onMounted, ref, unref } from "vue";
|
|
2
|
+
import { isPrimaryTouchPointer, isShellGestureIgnoredTarget } from "../support/gestureTargets.js";
|
|
3
|
+
|
|
4
|
+
const PULL_REFRESH_TRIGGER_DISTANCE = 72;
|
|
5
|
+
const PULL_REFRESH_MAX_DISTANCE = 112;
|
|
6
|
+
const PULL_REFRESH_IGNORED_SELECTORS = Object.freeze(["[data-shell-pull-refresh-ignore]"]);
|
|
7
|
+
|
|
8
|
+
function isAtPageTop() {
|
|
9
|
+
if (typeof window !== "object" || typeof document !== "object") {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const documentScrollTop = Number(document.documentElement?.scrollTop || 0);
|
|
14
|
+
const bodyScrollTop = Number(document.body?.scrollTop || 0);
|
|
15
|
+
return Math.max(Number(window.scrollY || 0), documentScrollTop, bodyScrollTop) <= 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Compact-layout pull-to-refresh. Pointer events start the gesture where the
|
|
19
|
+
// browser supports them; touch events carry it on after the browser cancels the
|
|
20
|
+
// pointer to scroll, and start it where pointer events are missing.
|
|
21
|
+
function useShellPullToRefresh({ enabled, refreshRuntime = null } = {}) {
|
|
22
|
+
const pullDistance = ref(0);
|
|
23
|
+
const pullRefreshing = ref(false);
|
|
24
|
+
let activePull = null;
|
|
25
|
+
|
|
26
|
+
const pullProgress = computed(() =>
|
|
27
|
+
Math.min(100, Math.round((pullDistance.value / PULL_REFRESH_TRIGGER_DISTANCE) * 100))
|
|
28
|
+
);
|
|
29
|
+
const pullIndicatorVisible = computed(() =>
|
|
30
|
+
Boolean(unref(enabled) && (pullDistance.value > 0 || pullRefreshing.value))
|
|
31
|
+
);
|
|
32
|
+
const pullRefreshLabel = computed(() => {
|
|
33
|
+
if (pullRefreshing.value) {
|
|
34
|
+
return "Refreshing";
|
|
35
|
+
}
|
|
36
|
+
return pullProgress.value >= 100 ? "Release to refresh" : "Pull to refresh";
|
|
37
|
+
});
|
|
38
|
+
const pullRefreshStyle = computed(() => ({
|
|
39
|
+
"--shell-pull-refresh-distance": `${Math.round(Math.min(pullDistance.value, PULL_REFRESH_MAX_DISTANCE))}px`
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
onMounted(() => {
|
|
43
|
+
if (typeof window !== "object") {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
window.addEventListener("pointerdown", handlePullPointerDown, { capture: true, passive: true });
|
|
47
|
+
window.addEventListener("pointermove", handlePullPointerMove, { capture: true, passive: false });
|
|
48
|
+
window.addEventListener("pointerup", handlePullPointerEnd, { capture: true, passive: true });
|
|
49
|
+
window.addEventListener("pointercancel", handlePullPointerCancel, { capture: true, passive: true });
|
|
50
|
+
window.addEventListener("touchstart", handlePullTouchStart, { capture: true, passive: true });
|
|
51
|
+
window.addEventListener("touchmove", handlePullTouchMove, { capture: true, passive: false });
|
|
52
|
+
window.addEventListener("touchend", handlePullTouchEnd, { capture: true, passive: true });
|
|
53
|
+
window.addEventListener("touchcancel", handlePullTouchCancel, { capture: true, passive: true });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
onBeforeUnmount(() => {
|
|
57
|
+
if (typeof window !== "object") {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
window.removeEventListener("pointerdown", handlePullPointerDown, { capture: true });
|
|
61
|
+
window.removeEventListener("pointermove", handlePullPointerMove, { capture: true });
|
|
62
|
+
window.removeEventListener("pointerup", handlePullPointerEnd, { capture: true });
|
|
63
|
+
window.removeEventListener("pointercancel", handlePullPointerCancel, { capture: true });
|
|
64
|
+
window.removeEventListener("touchstart", handlePullTouchStart, { capture: true });
|
|
65
|
+
window.removeEventListener("touchmove", handlePullTouchMove, { capture: true });
|
|
66
|
+
window.removeEventListener("touchend", handlePullTouchEnd, { capture: true });
|
|
67
|
+
window.removeEventListener("touchcancel", handlePullTouchCancel, { capture: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
function canRefresh() {
|
|
71
|
+
return Boolean(refreshRuntime && typeof refreshRuntime.refresh === "function");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function canStartFromTarget(target) {
|
|
75
|
+
return Boolean(
|
|
76
|
+
unref(enabled) &&
|
|
77
|
+
canRefresh() &&
|
|
78
|
+
!pullRefreshing.value &&
|
|
79
|
+
isAtPageTop() &&
|
|
80
|
+
!isShellGestureIgnoredTarget(target, { extraSelectors: PULL_REFRESH_IGNORED_SELECTORS })
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function handlePullPointerDown(event) {
|
|
85
|
+
if (!isPrimaryTouchPointer(event) || !canStartFromTarget(event.target)) {
|
|
86
|
+
activePull = null;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
activePull = {
|
|
91
|
+
pointerId: event.pointerId,
|
|
92
|
+
touchIdentifier: null,
|
|
93
|
+
startX: event.clientX,
|
|
94
|
+
startY: event.clientY
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handlePullPointerMove(event) {
|
|
99
|
+
if (!activePull || event.pointerId !== activePull.pointerId) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
updatePullGesture(event.clientX, event.clientY, event);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function handlePullPointerEnd(event) {
|
|
107
|
+
if (!activePull || event.pointerId !== activePull.pointerId) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
finishPullGesture();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// The browser cancels the pointer when it takes over scrolling; touch events
|
|
115
|
+
// keep following the same finger.
|
|
116
|
+
function handlePullPointerCancel(event) {
|
|
117
|
+
if (!activePull || event.pointerId !== activePull.pointerId) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
activePull.pointerId = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function handlePullTouchStart(event) {
|
|
125
|
+
if (activePull || event?.touches?.length !== 1 || !canStartFromTarget(event.target)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const touch = event.touches[0];
|
|
130
|
+
activePull = {
|
|
131
|
+
pointerId: null,
|
|
132
|
+
touchIdentifier: touch.identifier,
|
|
133
|
+
startX: touch.clientX,
|
|
134
|
+
startY: touch.clientY
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handlePullTouchMove(event) {
|
|
139
|
+
const touch = findActiveTouch(event.touches);
|
|
140
|
+
if (!activePull || !touch) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
updatePullGesture(touch.clientX, touch.clientY, event);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handlePullTouchEnd(event) {
|
|
148
|
+
if (!activePull || !touchListIncludesActiveTouch(event.changedTouches)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
finishPullGesture();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function handlePullTouchCancel(event) {
|
|
156
|
+
if (activePull && touchListIncludesActiveTouch(event.changedTouches)) {
|
|
157
|
+
cancelPullRefresh();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function updatePullGesture(clientX, clientY, event) {
|
|
162
|
+
if (!activePull) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const deltaX = clientX - activePull.startX;
|
|
167
|
+
const deltaY = clientY - activePull.startY;
|
|
168
|
+
const absX = Math.abs(deltaX);
|
|
169
|
+
|
|
170
|
+
if (deltaY < -4 || (absX > 24 && absX > deltaY * 1.15)) {
|
|
171
|
+
cancelPullRefresh();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (deltaY <= 6 || !isAtPageTop()) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (event?.cancelable) {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
}
|
|
182
|
+
pullDistance.value = Math.min(PULL_REFRESH_MAX_DISTANCE, Math.round(deltaY * 0.55));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function finishPullGesture() {
|
|
186
|
+
const shouldRefresh = pullDistance.value >= PULL_REFRESH_TRIGGER_DISTANCE;
|
|
187
|
+
activePull = null;
|
|
188
|
+
|
|
189
|
+
if (!shouldRefresh) {
|
|
190
|
+
pullDistance.value = 0;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
void refreshFromPullGesture();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function cancelPullRefresh() {
|
|
198
|
+
activePull = null;
|
|
199
|
+
if (!pullRefreshing.value) {
|
|
200
|
+
pullDistance.value = 0;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function refreshFromPullGesture() {
|
|
205
|
+
if (!canRefresh() || pullRefreshing.value) {
|
|
206
|
+
pullDistance.value = 0;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
pullRefreshing.value = true;
|
|
211
|
+
pullDistance.value = PULL_REFRESH_TRIGGER_DISTANCE;
|
|
212
|
+
try {
|
|
213
|
+
await refreshRuntime.refresh("pull-to-refresh");
|
|
214
|
+
} finally {
|
|
215
|
+
pullRefreshing.value = false;
|
|
216
|
+
pullDistance.value = 0;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function findActiveTouch(touchList) {
|
|
221
|
+
if (!activePull || !touchList || touchList.length < 1) {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (activePull.touchIdentifier === null && touchList.length === 1) {
|
|
226
|
+
return touchList[0];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
for (const touch of touchList) {
|
|
230
|
+
if (touch.identifier === activePull.touchIdentifier) {
|
|
231
|
+
return touch;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function touchListIncludesActiveTouch(touchList) {
|
|
239
|
+
if (!activePull) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (activePull.touchIdentifier === null) {
|
|
244
|
+
return !touchList || touchList.length <= 1;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return Boolean(findActiveTouch(touchList));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
pullDistance,
|
|
252
|
+
pullRefreshing,
|
|
253
|
+
pullProgress,
|
|
254
|
+
pullIndicatorVisible,
|
|
255
|
+
pullRefreshLabel,
|
|
256
|
+
pullRefreshStyle
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export { useShellPullToRefresh };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { createDefaultErrorPolicy } from "./policy.js";
|
|
2
|
-
export { useShellWebErrorRuntime } from "./inject.js";
|
|
2
|
+
export { SHELL_WEB_ERROR_RUNTIME_KEY, useShellWebErrorRuntime } from "./inject.js";
|
|
3
3
|
export { useShellErrorPresentationStore } from "../stores/useShellErrorPresentationStore.js";
|
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
inject
|
|
3
3
|
} from "vue";
|
|
4
4
|
|
|
5
|
+
const SHELL_WEB_ERROR_RUNTIME_KEY = "jskit.shell-web.runtime.web-error.client";
|
|
6
|
+
|
|
5
7
|
const EMPTY_ERROR_RUNTIME = Object.freeze({
|
|
6
8
|
report() {
|
|
7
9
|
return Object.freeze({
|
|
@@ -49,7 +51,7 @@ const EMPTY_ERROR_RUNTIME = Object.freeze({
|
|
|
49
51
|
});
|
|
50
52
|
|
|
51
53
|
function useShellWebErrorRuntime({ required = false } = {}) {
|
|
52
|
-
const runtime = inject(
|
|
54
|
+
const runtime = inject(SHELL_WEB_ERROR_RUNTIME_KEY, null);
|
|
53
55
|
if (runtime && typeof runtime.report === "function") {
|
|
54
56
|
return runtime;
|
|
55
57
|
}
|
|
@@ -62,6 +64,7 @@ function useShellWebErrorRuntime({ required = false } = {}) {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
export {
|
|
67
|
+
SHELL_WEB_ERROR_RUNTIME_KEY,
|
|
65
68
|
EMPTY_ERROR_RUNTIME,
|
|
66
69
|
useShellWebErrorRuntime
|
|
67
70
|
};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
normalizeAction,
|
|
3
|
-
normalizeAdditionalActions,
|
|
4
2
|
normalizeChannel,
|
|
5
3
|
normalizeErrorIntent,
|
|
6
|
-
normalizeSeverity
|
|
7
|
-
normalizeText
|
|
4
|
+
normalizeSeverity
|
|
8
5
|
} from "./normalize.js";
|
|
9
6
|
|
|
10
7
|
function createDefaultErrorPolicy({
|
|
@@ -46,17 +43,11 @@ function createDefaultErrorPolicy({
|
|
|
46
43
|
}
|
|
47
44
|
}
|
|
48
45
|
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
// The runtime owns every other default, including message, actions,
|
|
47
|
+
// presenter, dedupe and persistence.
|
|
51
48
|
return Object.freeze({
|
|
52
49
|
channel,
|
|
53
|
-
|
|
54
|
-
severity: normalizeSeverity(event.severity, normalizedDefaultSeverity),
|
|
55
|
-
presenterId: normalizeText(event.presenterId),
|
|
56
|
-
action: normalizeAction(event.action),
|
|
57
|
-
additionalActions: normalizeAdditionalActions(event.additionalActions),
|
|
58
|
-
persist: channel !== "snackbar",
|
|
59
|
-
dedupeKey: normalizeText(event.dedupeKey)
|
|
50
|
+
severity: normalizeSeverity(event.severity, normalizedDefaultSeverity)
|
|
60
51
|
});
|
|
61
52
|
};
|
|
62
53
|
}
|
|
@@ -3,8 +3,7 @@ import { normalizeText } from "./normalize.js";
|
|
|
3
3
|
function createStoreBackedPresenter({
|
|
4
4
|
id,
|
|
5
5
|
channel,
|
|
6
|
-
store
|
|
7
|
-
defaultPersist = false
|
|
6
|
+
store
|
|
8
7
|
} = {}) {
|
|
9
8
|
const normalizedId = normalizeText(id);
|
|
10
9
|
const normalizedChannel = normalizeText(channel).toLowerCase();
|
|
@@ -26,7 +25,6 @@ function createStoreBackedPresenter({
|
|
|
26
25
|
present(payload = {}) {
|
|
27
26
|
return store.present(normalizedChannel, {
|
|
28
27
|
...payload,
|
|
29
|
-
persist: typeof payload.persist === "boolean" ? payload.persist : defaultPersist,
|
|
30
28
|
presenterId: normalizedId
|
|
31
29
|
});
|
|
32
30
|
},
|
|
@@ -40,8 +38,7 @@ function createMaterialSnackbarPresenter({ store } = {}) {
|
|
|
40
38
|
return createStoreBackedPresenter({
|
|
41
39
|
id: "material.snackbar",
|
|
42
40
|
channel: "snackbar",
|
|
43
|
-
store
|
|
44
|
-
defaultPersist: false
|
|
41
|
+
store
|
|
45
42
|
});
|
|
46
43
|
}
|
|
47
44
|
|
|
@@ -49,8 +46,7 @@ function createMaterialBannerPresenter({ store } = {}) {
|
|
|
49
46
|
return createStoreBackedPresenter({
|
|
50
47
|
id: "material.banner",
|
|
51
48
|
channel: "banner",
|
|
52
|
-
store
|
|
53
|
-
defaultPersist: true
|
|
49
|
+
store
|
|
54
50
|
});
|
|
55
51
|
}
|
|
56
52
|
|
|
@@ -58,8 +54,7 @@ function createMaterialDialogPresenter({ store } = {}) {
|
|
|
58
54
|
return createStoreBackedPresenter({
|
|
59
55
|
id: "material.dialog",
|
|
60
56
|
channel: "dialog",
|
|
61
|
-
store
|
|
62
|
-
defaultPersist: true
|
|
57
|
+
store
|
|
63
58
|
});
|
|
64
59
|
}
|
|
65
60
|
|
|
@@ -80,7 +80,7 @@ function createErrorPresentationStore({
|
|
|
80
80
|
channel: normalizedChannel,
|
|
81
81
|
message: normalizeText(payload.message, "Request failed."),
|
|
82
82
|
severity: normalizeSeverity(payload.severity, "error"),
|
|
83
|
-
persist:
|
|
83
|
+
persist: payload.persist === true,
|
|
84
84
|
action: normalizeAction(payload.action),
|
|
85
85
|
additionalActions: normalizeAdditionalActions(payload.additionalActions),
|
|
86
86
|
presenterId: normalizeText(payload.presenterId),
|
|
@@ -6,26 +6,19 @@ import {
|
|
|
6
6
|
resolveSurfaceDefinitionFromPlacementContext,
|
|
7
7
|
resolveSurfaceRootPathFromPlacementContext
|
|
8
8
|
} from "../placement/surfaceContext.js";
|
|
9
|
-
|
|
10
|
-
function normalizeParamsMap(params = null) {
|
|
11
|
-
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
12
|
-
return {};
|
|
13
|
-
}
|
|
14
|
-
return params;
|
|
15
|
-
}
|
|
9
|
+
import { normalizeRouteParams } from "../support/routeParams.js";
|
|
16
10
|
|
|
17
11
|
function materializeSurfaceRouteBase(routeBaseTemplate = "/", { params = {}, strictParams = true, surface = "" } = {}) {
|
|
18
|
-
const normalizedParams =
|
|
12
|
+
const normalizedParams = normalizeRouteParams(params);
|
|
19
13
|
const missingParams = new Set();
|
|
20
14
|
const outputPath = String(routeBaseTemplate || "/").replace(/:([A-Za-z0-9_]+)/g, (_full, rawName) => {
|
|
21
15
|
const paramName = String(rawName || "").trim();
|
|
22
16
|
const paramValue = normalizedParams[paramName];
|
|
23
|
-
|
|
24
|
-
if (!normalizedValue) {
|
|
17
|
+
if (!paramValue) {
|
|
25
18
|
missingParams.add(paramName);
|
|
26
19
|
return `:${paramName}`;
|
|
27
20
|
}
|
|
28
|
-
return encodeURIComponent(
|
|
21
|
+
return encodeURIComponent(paramValue);
|
|
29
22
|
});
|
|
30
23
|
|
|
31
24
|
if (strictParams && missingParams.size > 0) {
|
|
@@ -94,23 +87,8 @@ function useShellLinkResolver({ surface = "" } = {}) {
|
|
|
94
87
|
});
|
|
95
88
|
}
|
|
96
89
|
|
|
97
|
-
function toSurface(relativePath = "/", options = {}) {
|
|
98
|
-
return resolve(relativePath, options);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function toWorkspace(relativePath = "/", options = {}) {
|
|
102
|
-
return resolve(relativePath, options);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function toAuto(relativePath = "/", options = {}) {
|
|
106
|
-
return resolve(relativePath, options);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
90
|
return Object.freeze({
|
|
110
|
-
resolve
|
|
111
|
-
toSurface,
|
|
112
|
-
toWorkspace,
|
|
113
|
-
toAuto
|
|
91
|
+
resolve
|
|
114
92
|
});
|
|
115
93
|
}
|
|
116
94
|
|
|
@@ -4,6 +4,7 @@ import { resolveScopedApiBasePath } from "@jskit-ai/kernel/shared/surface";
|
|
|
4
4
|
import { resolveSurfaceDefinitionFromPlacementContext } from "../placement/index.js";
|
|
5
5
|
import { useShellLinkResolver } from "./linkResolver.js";
|
|
6
6
|
import { useSurfaceRouteContext } from "./useSurfaceRouteContext.js";
|
|
7
|
+
import { normalizeRouteParams } from "../support/routeParams.js";
|
|
7
8
|
|
|
8
9
|
function normalizePathSuffix(value = "") {
|
|
9
10
|
const raw = normalizeText(unref(value));
|
|
@@ -32,27 +33,6 @@ function resolveDefaultSurfaceIdFromPlacementContext(placementContext = null) {
|
|
|
32
33
|
return resolveSurfaceId(placementContext?.surfaceConfig?.defaultSurfaceId, "");
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
function normalizeRouteParams(params = null) {
|
|
36
|
-
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
37
|
-
return {};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const output = {};
|
|
41
|
-
for (const [rawKey, rawValue] of Object.entries(params)) {
|
|
42
|
-
const key = normalizeText(rawKey);
|
|
43
|
-
if (!key) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const value = Array.isArray(rawValue) ? rawValue[0] : rawValue;
|
|
48
|
-
const normalizedValue = normalizeText(value);
|
|
49
|
-
if (normalizedValue) {
|
|
50
|
-
output[key] = normalizedValue;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return output;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
36
|
function resolveRouteParams(baseParams = {}, overrideParams = null) {
|
|
57
37
|
return {
|
|
58
38
|
...normalizeRouteParams(baseParams),
|
|
@@ -8,6 +8,7 @@ export {
|
|
|
8
8
|
} from "./validators.js";
|
|
9
9
|
|
|
10
10
|
export {
|
|
11
|
+
SHELL_WEB_PLACEMENT_RUNTIME_KEY,
|
|
11
12
|
useWebPlacementContext
|
|
12
13
|
} from "./inject.js";
|
|
13
14
|
|
|
@@ -17,6 +18,7 @@ export {
|
|
|
17
18
|
|
|
18
19
|
export {
|
|
19
20
|
readPlacementSurfaceConfig,
|
|
21
|
+
resolveAllowedOriginsFromPlacementContext,
|
|
20
22
|
resolveSurfaceDefinitionFromPlacementContext,
|
|
21
23
|
joinSurfacePath,
|
|
22
24
|
resolveSurfaceIdFromPlacementPathname,
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
shallowRef
|
|
6
6
|
} from "vue";
|
|
7
7
|
|
|
8
|
+
const SHELL_WEB_PLACEMENT_RUNTIME_KEY = "jskit.shell-web.runtime.web-placement.client";
|
|
9
|
+
|
|
8
10
|
const EMPTY_WEB_PLACEMENT_RUNTIME = Object.freeze({
|
|
9
11
|
getContext() {
|
|
10
12
|
return Object.freeze({});
|
|
@@ -36,7 +38,7 @@ const EMPTY_WEB_PLACEMENT_CONTEXT = Object.freeze({
|
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
function useWebPlacementRuntime({ required = false } = {}) {
|
|
39
|
-
const runtime = inject(
|
|
41
|
+
const runtime = inject(SHELL_WEB_PLACEMENT_RUNTIME_KEY, null);
|
|
40
42
|
if (runtime && typeof runtime.getPlacements === "function") {
|
|
41
43
|
return runtime;
|
|
42
44
|
}
|
|
@@ -96,6 +98,7 @@ function useWebPlacementContext({ required = false } = {}) {
|
|
|
96
98
|
}
|
|
97
99
|
|
|
98
100
|
export {
|
|
101
|
+
SHELL_WEB_PLACEMENT_RUNTIME_KEY,
|
|
99
102
|
EMPTY_WEB_PLACEMENT_RUNTIME,
|
|
100
103
|
EMPTY_WEB_PLACEMENT_CONTEXT,
|
|
101
104
|
useWebPlacementRuntime,
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_DEBUG_DEPTH, explodePayload } from "./debug.js";
|
|
|
2
2
|
import { createListenerSubscription } from "@jskit-ai/kernel/shared/support/listenerSet";
|
|
3
3
|
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
4
4
|
import {
|
|
5
|
+
WEB_PLACEMENT_SURFACE_ANY,
|
|
5
6
|
normalizePlacementLayoutClass,
|
|
6
7
|
normalizePlacementTopologyDefinition
|
|
7
8
|
} from "@jskit-ai/kernel/shared/support/shellLayoutTargets";
|
|
@@ -24,7 +25,6 @@ function ensureArray(value) {
|
|
|
24
25
|
|
|
25
26
|
const PLACEMENT_DEBUG_PREFIX = "[placement-debug]";
|
|
26
27
|
const PLACEMENT_DEBUG_FLAG = "__JSKIT_PLACEMENT_DEBUG__";
|
|
27
|
-
const WEB_PLACEMENT_SURFACE_ANY = "*";
|
|
28
28
|
const NOOP = () => {};
|
|
29
29
|
|
|
30
30
|
function isPlacementDebugEnabled() {
|
|
@@ -480,9 +480,7 @@ function createWebPlacementRuntime({ components, contextContributors = [], logge
|
|
|
480
480
|
if (!renderablePlacement) {
|
|
481
481
|
continue;
|
|
482
482
|
}
|
|
483
|
-
const placementSurfaces =
|
|
484
|
-
? renderablePlacement.surfaces
|
|
485
|
-
: [WEB_PLACEMENT_SURFACE_ANY];
|
|
483
|
+
const placementSurfaces = renderablePlacement.surfaces;
|
|
486
484
|
|
|
487
485
|
if (!matchesSurface(placementSurfaces, normalizedSurface)) {
|
|
488
486
|
debugLog("getPlacements:skip-surfaces", {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import { isRecord } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
8
|
import { normalizePathname } from "@jskit-ai/kernel/shared/surface/paths";
|
|
9
9
|
import { isExternalLinkTarget, splitPathQueryHash } from "@jskit-ai/kernel/shared/support/linkPath";
|
|
10
|
+
import { resolveAllowedOriginsFromSurfaceDefinitions } from "@jskit-ai/kernel/shared/support/returnToPath";
|
|
10
11
|
|
|
11
12
|
const EMPTY_SURFACE_CONFIG = Object.freeze({
|
|
12
13
|
tenancyMode: "",
|
|
@@ -119,6 +120,14 @@ function readPlacementSurfaceConfig(contextValue = null) {
|
|
|
119
120
|
return normalizeSurfaceConfig(contextRecord.surfaceConfig);
|
|
120
121
|
}
|
|
121
122
|
|
|
123
|
+
// Origins a return-to URL may point at: this page plus every surface origin.
|
|
124
|
+
function resolveAllowedOriginsFromPlacementContext(contextValue = null) {
|
|
125
|
+
const seedOrigins = typeof window === "object" && window?.location?.origin ? [window.location.origin] : [];
|
|
126
|
+
return resolveAllowedOriginsFromSurfaceDefinitions(readPlacementSurfaceConfig(contextValue).surfacesById, {
|
|
127
|
+
seedOrigins
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
122
131
|
function resolveSurfaceDefinitionFromPlacementContext(contextValue = null, surfaceId = "") {
|
|
123
132
|
const surfaceConfig = readPlacementSurfaceConfig(contextValue);
|
|
124
133
|
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
@@ -253,14 +262,7 @@ function createPlacementSurfacePathHelpers(surfaceConfig = EMPTY_SURFACE_CONFIG)
|
|
|
253
262
|
|
|
254
263
|
return createSurfacePathHelpers({
|
|
255
264
|
defaultSurfaceId: resolvedDefaultSurfaceId,
|
|
256
|
-
|
|
257
|
-
resolveSurfaceRouteBase(surfaceId) {
|
|
258
|
-
const normalizedSurfaceId = normalizeSurfaceId(surfaceId);
|
|
259
|
-
return String(surfacesById[normalizedSurfaceId]?.routeBase || "/").trim() || "/";
|
|
260
|
-
},
|
|
261
|
-
listSurfaceDefinitions() {
|
|
262
|
-
return Object.values(surfacesById);
|
|
263
|
-
}
|
|
265
|
+
surfaceDefinitions: Object.values(surfacesById)
|
|
264
266
|
});
|
|
265
267
|
}
|
|
266
268
|
|
|
@@ -280,6 +282,7 @@ export {
|
|
|
280
282
|
EMPTY_SURFACE_CONFIG,
|
|
281
283
|
buildSurfaceConfigContext,
|
|
282
284
|
readPlacementSurfaceConfig,
|
|
285
|
+
resolveAllowedOriginsFromPlacementContext,
|
|
283
286
|
resolveSurfaceDefinitionFromPlacementContext,
|
|
284
287
|
joinSurfacePath,
|
|
285
288
|
resolveSurfaceIdFromPlacementPathname,
|