wevu 7.3.0 → 7.4.0

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 (53) hide show
  1. package/dist/dev/package.mjs +1 -1
  2. package/dist/dev/router/initialNavigation.mjs +4 -1
  3. package/dist/dev/router/initialNavigation.mjs.map +1 -1
  4. package/dist/dev/router/scrollRestoration/adapters.d.mts +8 -0
  5. package/dist/dev/router/scrollRestoration/adapters.mjs +86 -0
  6. package/dist/dev/router/scrollRestoration/adapters.mjs.map +1 -0
  7. package/dist/dev/router/scrollRestoration/controller.d.mts +8 -0
  8. package/dist/dev/router/scrollRestoration/controller.mjs +148 -0
  9. package/dist/dev/router/scrollRestoration/controller.mjs.map +1 -0
  10. package/dist/dev/router/scrollRestoration/host.d.mts +3 -0
  11. package/dist/dev/router/scrollRestoration/host.mjs +76 -0
  12. package/dist/dev/router/scrollRestoration/host.mjs.map +1 -0
  13. package/dist/dev/router/scrollRestoration/index.d.mts +9 -0
  14. package/dist/dev/router/scrollRestoration/index.mjs +156 -0
  15. package/dist/dev/router/scrollRestoration/index.mjs.map +1 -0
  16. package/dist/dev/router/scrollRestoration/types.d.mts +61 -0
  17. package/dist/dev/router.d.mts +5 -1
  18. package/dist/dev/router.mjs +4 -1
  19. package/dist/dev/runtime/hooks/base.mjs +1 -13
  20. package/dist/dev/runtime/hooks/base.mjs.map +1 -1
  21. package/dist/dev/runtime/hooks/pageScroll.mjs +41 -0
  22. package/dist/dev/runtime/hooks/pageScroll.mjs.map +1 -0
  23. package/dist/dev/runtime/hooks/register.mjs +2 -1
  24. package/dist/dev/runtime/hooks/register.mjs.map +1 -1
  25. package/dist/dev/runtime/register/component/lifecycle/optionalHooks.mjs +2 -5
  26. package/dist/dev/runtime/register/component/lifecycle/optionalHooks.mjs.map +1 -1
  27. package/dist/dev/runtime/register/component/lifecycle/platform.mjs +8 -20
  28. package/dist/dev/runtime/register/component/lifecycle/platform.mjs.map +1 -1
  29. package/dist/dev/runtime/register/component/registerDefinition.mjs +3 -0
  30. package/dist/dev/runtime/register/component/registerDefinition.mjs.map +1 -1
  31. package/dist/dev/runtime/register/runtimeInstance/setupPhase.mjs +7 -2
  32. package/dist/dev/runtime/register/runtimeInstance/setupPhase.mjs.map +1 -1
  33. package/dist/package.mjs +1 -1
  34. package/dist/router/initialNavigation.mjs +1 -1
  35. package/dist/router/scrollRestoration/adapters.d.mts +7 -0
  36. package/dist/router/scrollRestoration/adapters.mjs +1 -0
  37. package/dist/router/scrollRestoration/controller.d.mts +7 -0
  38. package/dist/router/scrollRestoration/controller.mjs +1 -0
  39. package/dist/router/scrollRestoration/host.d.mts +3 -0
  40. package/dist/router/scrollRestoration/host.mjs +1 -0
  41. package/dist/router/scrollRestoration/index.d.mts +8 -0
  42. package/dist/router/scrollRestoration/index.mjs +1 -0
  43. package/dist/router/scrollRestoration/types.d.mts +60 -0
  44. package/dist/router.d.mts +5 -1
  45. package/dist/router.mjs +1 -1
  46. package/dist/runtime/hooks/base.mjs +1 -1
  47. package/dist/runtime/hooks/pageScroll.mjs +1 -0
  48. package/dist/runtime/hooks/register.mjs +1 -1
  49. package/dist/runtime/register/component/lifecycle/optionalHooks.mjs +1 -1
  50. package/dist/runtime/register/component/lifecycle/platform.mjs +1 -1
  51. package/dist/runtime/register/component/registerDefinition.mjs +1 -1
  52. package/dist/runtime/register/runtimeInstance/setupPhase.mjs +1 -1
  53. package/package.json +7 -7
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "7.3.0";
2
+ var version = "7.4.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };
@@ -30,6 +30,9 @@ function getInitialNavigationTimeout() {
30
30
  function isNavigationFailureResult(result) {
31
31
  return Boolean(result && typeof result === "object" && result.__wevuNavigationFailure);
32
32
  }
33
+ function getInitialNavigationPromise(instance) {
34
+ return initialNavigationControllers.get(instance)?.promise;
35
+ }
33
36
  function ensureInitialNavigation(instance, query, options = {}) {
34
37
  const existing = initialNavigationControllers.get(instance);
35
38
  if (existing) {
@@ -142,5 +145,5 @@ function registerInitialNavigationRunner(router, runner, timeoutMs = DEFAULT_INI
142
145
  }
143
146
 
144
147
  //#endregion
145
- export { DEFAULT_INITIAL_NAVIGATION_TIMEOUT, cancelInitialNavigation, ensureInitialNavigation, getInitialNavigationTimeout, registerInitialNavigationRunner };
148
+ export { DEFAULT_INITIAL_NAVIGATION_TIMEOUT, cancelInitialNavigation, ensureInitialNavigation, getInitialNavigationPromise, getInitialNavigationTimeout, registerInitialNavigationRunner };
146
149
  //# sourceMappingURL=initialNavigation.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"initialNavigation.mjs","names":[],"sources":["../../../src/router/initialNavigation.ts"],"sourcesContent":["import type { MiniProgramPageLike } from '../routerInternal/shared'\nimport type { InitialNavigationMode, LocationQueryRaw, NavigationFailure } from './types'\nimport { WEVU_INITIAL_NAVIGATION_TIMEOUT_MARKER } from '@weapp-core/constants'\nimport { queueMicrotaskPolyfill } from '@wevu/web-apis'\nimport { registerInitialNavigationLifecycle } from '../runtime/navigationLifecycle'\nimport { getActiveRouter } from './instance'\n\nexport const DEFAULT_INITIAL_NAVIGATION_TIMEOUT = 10_000\n\nexport type InitialNavigationStatus = 'idle' | 'running' | 'allowed' | 'aborted' | 'timed-out' | 'cancelled'\n\ninterface InitialNavigationRunnerRegistration {\n runner: InitialNavigationRunner\n mode: InitialNavigationMode\n timeoutMs: number\n}\n\nexport type InitialNavigationRunner = (\n page: MiniProgramPageLike,\n query?: LocationQueryRaw,\n isActive?: () => boolean,\n) => Promise<void | NavigationFailure>\n\ninterface InitialNavigationController {\n readonly promise: Promise<boolean>\n readonly blocking: boolean\n readonly status: () => InitialNavigationStatus\n subscribe: (callback: (shouldMount: boolean) => void) => () => void\n start: (query?: LocationQueryRaw) => void\n cancel: () => void\n}\n\nconst initialNavigationRunners = new WeakMap<object, InitialNavigationRunnerRegistration>()\nconst initialNavigationControllers = new WeakMap<object, InitialNavigationController>()\n\nfunction normalizeTimeout(timeoutMs: number) {\n return Number.isFinite(timeoutMs) && timeoutMs > 0\n ? Math.max(1, Math.trunc(timeoutMs))\n : DEFAULT_INITIAL_NAVIGATION_TIMEOUT\n}\n\nfunction snapshotPage(instance: MiniProgramPageLike): MiniProgramPageLike {\n return {\n route: typeof instance.route === 'string' ? instance.route : undefined,\n __route__: typeof instance.__route__ === 'string' ? instance.__route__ : undefined,\n options: instance.options && typeof instance.options === 'object'\n ? { ...instance.options }\n : undefined,\n }\n}\n\nfunction snapshotQuery(query: LocationQueryRaw | undefined): LocationQueryRaw | undefined {\n if (!query || typeof query !== 'object') {\n return undefined\n }\n const snapshot: LocationQueryRaw = {}\n for (const [key, value] of Object.entries(query)) {\n snapshot[key] = Array.isArray(value) ? [...value] : value\n }\n return snapshot\n}\n\nexport function getInitialNavigationRunner(): InitialNavigationRunner | undefined {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.runner : undefined\n}\n\nexport function getInitialNavigationTimeout() {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.timeoutMs ?? DEFAULT_INITIAL_NAVIGATION_TIMEOUT : DEFAULT_INITIAL_NAVIGATION_TIMEOUT\n}\n\nexport function getInitialNavigationMode(): InitialNavigationMode {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.mode ?? 'eager' : 'eager'\n}\n\nfunction isNavigationFailureResult(result: void | NavigationFailure): result is NavigationFailure {\n return Boolean(result && typeof result === 'object' && (result as NavigationFailure).__wevuNavigationFailure)\n}\n\nexport function getInitialNavigationPromise(instance: MiniProgramPageLike) {\n return initialNavigationControllers.get(instance as object)?.promise\n}\n\nexport function getInitialNavigationStatus(instance: MiniProgramPageLike) {\n return initialNavigationControllers.get(instance as object)?.status()\n}\n\nexport function ensureInitialNavigation(\n instance: MiniProgramPageLike,\n query?: LocationQueryRaw,\n options: { start?: boolean, onComplete?: (shouldMount: boolean) => void } = {},\n) {\n const existing = initialNavigationControllers.get(instance as object)\n if (existing) {\n if (options.onComplete && existing.blocking) {\n existing.subscribe(options.onComplete)\n }\n if (options.start !== false) {\n existing.start(query)\n }\n return existing.blocking ? existing.promise : undefined\n }\n\n const router = getActiveRouter()\n const registration = router ? initialNavigationRunners.get(router) : undefined\n let pendingRunner = registration?.runner\n if (!pendingRunner) {\n return undefined\n }\n const blocking = registration?.mode === 'blocking'\n\n let page: MiniProgramPageLike | undefined = snapshotPage(instance)\n let initialQuery = snapshotQuery(query)\n let currentStatus: InitialNavigationStatus = 'idle'\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n const timeoutMs = getInitialNavigationTimeout()\n let resolveNavigation!: (shouldMount: boolean) => void\n const completionCallbacks = new Set<{ callback: (shouldMount: boolean) => void, active: boolean }>()\n const promise = new Promise<boolean>((resolve) => {\n resolveNavigation = resolve\n })\n\n const settle = (status: Exclude<InitialNavigationStatus, 'idle' | 'running'>, shouldMount: boolean) => {\n if (currentStatus !== 'idle' && currentStatus !== 'running') {\n return\n }\n currentStatus = status\n if (timeoutHandle !== undefined) {\n clearTimeout(timeoutHandle)\n timeoutHandle = undefined\n }\n const callbacks = [...completionCallbacks]\n completionCallbacks.clear()\n pendingRunner = undefined\n page = undefined\n initialQuery = undefined\n resolveNavigation(shouldMount)\n queueMicrotaskPolyfill(() => {\n if (currentStatus !== 'allowed' && currentStatus !== 'timed-out') {\n return\n }\n for (const entry of callbacks) {\n if (entry.active) {\n entry.callback(shouldMount)\n entry.active = false\n }\n }\n })\n }\n\n const controller: InitialNavigationController = {\n promise,\n blocking,\n status: () => currentStatus,\n subscribe(callback) {\n const entry = { callback, active: true }\n if (currentStatus !== 'idle' && currentStatus !== 'running') {\n queueMicrotaskPolyfill(() => {\n if (entry.active && (currentStatus === 'allowed' || currentStatus === 'timed-out')) {\n callback(currentStatus === 'allowed' || currentStatus === 'timed-out')\n entry.active = false\n }\n })\n }\n else {\n completionCallbacks.add(entry)\n }\n return () => {\n entry.active = false\n completionCallbacks.delete(entry)\n }\n },\n start(startQuery?: LocationQueryRaw) {\n if (currentStatus !== 'idle') {\n return\n }\n currentStatus = 'running'\n if (blocking) {\n timeoutHandle = setTimeout(() => {\n if (currentStatus !== 'running') {\n return\n }\n // 超时是可恢复的运行时诊断,保留稳定 marker 供宿主和 E2E 观测。\n // eslint-disable-next-line no-console\n console.warn(`${WEVU_INITIAL_NAVIGATION_TIMEOUT_MARKER} initial navigation exceeded ${timeoutMs}ms; mounting page`, page?.route ?? page?.__route__ ?? '')\n settle('timed-out', true)\n }, timeoutMs)\n }\n const runnerPage = page\n const runnerQuery = snapshotQuery(startQuery ?? initialQuery)\n const runner = pendingRunner\n pendingRunner = undefined\n if (!runner || !runnerPage) {\n settle('aborted', false)\n return\n }\n void runner(runnerPage, runnerQuery, () => currentStatus === 'running').then((result) => {\n const failed = isNavigationFailureResult(result)\n settle(failed ? 'aborted' : 'allowed', !failed)\n }, () => {\n settle('aborted', false)\n })\n },\n cancel() {\n settle('cancelled', false)\n },\n }\n\n initialNavigationControllers.set(instance as object, controller)\n if (options.onComplete && blocking) {\n controller.subscribe(options.onComplete)\n }\n if (options.start !== false) {\n controller.start(query)\n }\n return blocking ? promise : undefined\n}\n\nexport function cancelInitialNavigation(instance: MiniProgramPageLike) {\n initialNavigationControllers.get(instance as object)?.cancel()\n}\n\nexport function registerInitialNavigationRunner(\n router: object,\n runner: InitialNavigationRunner,\n timeoutMs = DEFAULT_INITIAL_NAVIGATION_TIMEOUT,\n mode: InitialNavigationMode = 'blocking',\n) {\n registerInitialNavigationLifecycle({ ensure: ensureInitialNavigation, cancel: cancelInitialNavigation })\n initialNavigationRunners.set(router, {\n runner,\n mode,\n timeoutMs: normalizeTimeout(timeoutMs),\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"initialNavigation.mjs","names":[],"sources":["../../../src/router/initialNavigation.ts"],"sourcesContent":["import type { MiniProgramPageLike } from '../routerInternal/shared'\nimport type { InitialNavigationMode, LocationQueryRaw, NavigationFailure } from './types'\nimport { WEVU_INITIAL_NAVIGATION_TIMEOUT_MARKER } from '@weapp-core/constants'\nimport { queueMicrotaskPolyfill } from '@wevu/web-apis'\nimport { registerInitialNavigationLifecycle } from '../runtime/navigationLifecycle'\nimport { getActiveRouter } from './instance'\n\nexport const DEFAULT_INITIAL_NAVIGATION_TIMEOUT = 10_000\n\nexport type InitialNavigationStatus = 'idle' | 'running' | 'allowed' | 'aborted' | 'timed-out' | 'cancelled'\n\ninterface InitialNavigationRunnerRegistration {\n runner: InitialNavigationRunner\n mode: InitialNavigationMode\n timeoutMs: number\n}\n\nexport type InitialNavigationRunner = (\n page: MiniProgramPageLike,\n query?: LocationQueryRaw,\n isActive?: () => boolean,\n) => Promise<void | NavigationFailure>\n\ninterface InitialNavigationController {\n readonly promise: Promise<boolean>\n readonly blocking: boolean\n readonly status: () => InitialNavigationStatus\n subscribe: (callback: (shouldMount: boolean) => void) => () => void\n start: (query?: LocationQueryRaw) => void\n cancel: () => void\n}\n\nconst initialNavigationRunners = new WeakMap<object, InitialNavigationRunnerRegistration>()\nconst initialNavigationControllers = new WeakMap<object, InitialNavigationController>()\n\nfunction normalizeTimeout(timeoutMs: number) {\n return Number.isFinite(timeoutMs) && timeoutMs > 0\n ? Math.max(1, Math.trunc(timeoutMs))\n : DEFAULT_INITIAL_NAVIGATION_TIMEOUT\n}\n\nfunction snapshotPage(instance: MiniProgramPageLike): MiniProgramPageLike {\n return {\n route: typeof instance.route === 'string' ? instance.route : undefined,\n __route__: typeof instance.__route__ === 'string' ? instance.__route__ : undefined,\n options: instance.options && typeof instance.options === 'object'\n ? { ...instance.options }\n : undefined,\n }\n}\n\nfunction snapshotQuery(query: LocationQueryRaw | undefined): LocationQueryRaw | undefined {\n if (!query || typeof query !== 'object') {\n return undefined\n }\n const snapshot: LocationQueryRaw = {}\n for (const [key, value] of Object.entries(query)) {\n snapshot[key] = Array.isArray(value) ? [...value] : value\n }\n return snapshot\n}\n\nexport function getInitialNavigationRunner(): InitialNavigationRunner | undefined {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.runner : undefined\n}\n\nexport function getInitialNavigationTimeout() {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.timeoutMs ?? DEFAULT_INITIAL_NAVIGATION_TIMEOUT : DEFAULT_INITIAL_NAVIGATION_TIMEOUT\n}\n\nexport function getInitialNavigationMode(): InitialNavigationMode {\n const router = getActiveRouter()\n return router ? initialNavigationRunners.get(router)?.mode ?? 'eager' : 'eager'\n}\n\nfunction isNavigationFailureResult(result: void | NavigationFailure): result is NavigationFailure {\n return Boolean(result && typeof result === 'object' && (result as NavigationFailure).__wevuNavigationFailure)\n}\n\nexport function getInitialNavigationPromise(instance: MiniProgramPageLike) {\n return initialNavigationControllers.get(instance as object)?.promise\n}\n\nexport function getInitialNavigationStatus(instance: MiniProgramPageLike) {\n return initialNavigationControllers.get(instance as object)?.status()\n}\n\nexport function ensureInitialNavigation(\n instance: MiniProgramPageLike,\n query?: LocationQueryRaw,\n options: { start?: boolean, onComplete?: (shouldMount: boolean) => void } = {},\n) {\n const existing = initialNavigationControllers.get(instance as object)\n if (existing) {\n if (options.onComplete && existing.blocking) {\n existing.subscribe(options.onComplete)\n }\n if (options.start !== false) {\n existing.start(query)\n }\n return existing.blocking ? existing.promise : undefined\n }\n\n const router = getActiveRouter()\n const registration = router ? initialNavigationRunners.get(router) : undefined\n let pendingRunner = registration?.runner\n if (!pendingRunner) {\n return undefined\n }\n const blocking = registration?.mode === 'blocking'\n\n let page: MiniProgramPageLike | undefined = snapshotPage(instance)\n let initialQuery = snapshotQuery(query)\n let currentStatus: InitialNavigationStatus = 'idle'\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n const timeoutMs = getInitialNavigationTimeout()\n let resolveNavigation!: (shouldMount: boolean) => void\n const completionCallbacks = new Set<{ callback: (shouldMount: boolean) => void, active: boolean }>()\n const promise = new Promise<boolean>((resolve) => {\n resolveNavigation = resolve\n })\n\n const settle = (status: Exclude<InitialNavigationStatus, 'idle' | 'running'>, shouldMount: boolean) => {\n if (currentStatus !== 'idle' && currentStatus !== 'running') {\n return\n }\n currentStatus = status\n if (timeoutHandle !== undefined) {\n clearTimeout(timeoutHandle)\n timeoutHandle = undefined\n }\n const callbacks = [...completionCallbacks]\n completionCallbacks.clear()\n pendingRunner = undefined\n page = undefined\n initialQuery = undefined\n resolveNavigation(shouldMount)\n queueMicrotaskPolyfill(() => {\n if (currentStatus !== 'allowed' && currentStatus !== 'timed-out') {\n return\n }\n for (const entry of callbacks) {\n if (entry.active) {\n entry.callback(shouldMount)\n entry.active = false\n }\n }\n })\n }\n\n const controller: InitialNavigationController = {\n promise,\n blocking,\n status: () => currentStatus,\n subscribe(callback) {\n const entry = { callback, active: true }\n if (currentStatus !== 'idle' && currentStatus !== 'running') {\n queueMicrotaskPolyfill(() => {\n if (entry.active && (currentStatus === 'allowed' || currentStatus === 'timed-out')) {\n callback(currentStatus === 'allowed' || currentStatus === 'timed-out')\n entry.active = false\n }\n })\n }\n else {\n completionCallbacks.add(entry)\n }\n return () => {\n entry.active = false\n completionCallbacks.delete(entry)\n }\n },\n start(startQuery?: LocationQueryRaw) {\n if (currentStatus !== 'idle') {\n return\n }\n currentStatus = 'running'\n if (blocking) {\n timeoutHandle = setTimeout(() => {\n if (currentStatus !== 'running') {\n return\n }\n // 超时是可恢复的运行时诊断,保留稳定 marker 供宿主和 E2E 观测。\n // eslint-disable-next-line no-console\n console.warn(`${WEVU_INITIAL_NAVIGATION_TIMEOUT_MARKER} initial navigation exceeded ${timeoutMs}ms; mounting page`, page?.route ?? page?.__route__ ?? '')\n settle('timed-out', true)\n }, timeoutMs)\n }\n const runnerPage = page\n const runnerQuery = snapshotQuery(startQuery ?? initialQuery)\n const runner = pendingRunner\n pendingRunner = undefined\n if (!runner || !runnerPage) {\n settle('aborted', false)\n return\n }\n void runner(runnerPage, runnerQuery, () => currentStatus === 'running').then((result) => {\n const failed = isNavigationFailureResult(result)\n settle(failed ? 'aborted' : 'allowed', !failed)\n }, () => {\n settle('aborted', false)\n })\n },\n cancel() {\n settle('cancelled', false)\n },\n }\n\n initialNavigationControllers.set(instance as object, controller)\n if (options.onComplete && blocking) {\n controller.subscribe(options.onComplete)\n }\n if (options.start !== false) {\n controller.start(query)\n }\n return blocking ? promise : undefined\n}\n\nexport function cancelInitialNavigation(instance: MiniProgramPageLike) {\n initialNavigationControllers.get(instance as object)?.cancel()\n}\n\nexport function registerInitialNavigationRunner(\n router: object,\n runner: InitialNavigationRunner,\n timeoutMs = DEFAULT_INITIAL_NAVIGATION_TIMEOUT,\n mode: InitialNavigationMode = 'blocking',\n) {\n registerInitialNavigationLifecycle({ ensure: ensureInitialNavigation, cancel: cancelInitialNavigation })\n initialNavigationRunners.set(router, {\n runner,\n mode,\n timeoutMs: normalizeTimeout(timeoutMs),\n })\n}\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { ScrollRestorationHandle, ScrollRestorationRegistrationOptions, ScrollViewRestorationHandle } from "./types.mjs";
2
+ //#region src/router/scrollRestoration/adapters.d.ts
3
+ /** WebView 页面适配器;Skyline 没有隐式页面滚动容器。 */
4
+ export declare function usePageScrollRestoration(options?: ScrollRestorationRegistrationOptions): ScrollRestorationHandle;
5
+ /** 用属性控制 WebView/Skyline scroll-view,scroll 事件不产生 setData。 */
6
+ export declare function useScrollViewRestoration(options?: ScrollRestorationRegistrationOptions): ScrollViewRestorationHandle;
7
+ //#endregion
8
+ //# sourceMappingURL=adapters.d.mts.map
@@ -0,0 +1,86 @@
1
+ import { ref } from "../../reactivity/ref.mjs";
2
+ import { getCurrentMiniProgramGlobalObject } from "../../runtime/platform/current.mjs";
3
+ import { ensurePageHookOnInstance } from "../../runtime/hooks/pageScroll.mjs";
4
+ import { addScrollHook } from "./host.mjs";
5
+ import { resolveScrollController } from "./controller.mjs";
6
+ import { getScrollSetupScope, registerScrollRestoration } from "./index.mjs";
7
+ import { WEVU_PAGE_SCROLL_OBSERVERS_KEY, WEVU_PAGE_SCROLL_RESTORATION_OWNER_KEY } from "@weapp-core/constants";
8
+
9
+ //#region src/router/scrollRestoration/adapters.ts
10
+ /** WebView 页面适配器;Skyline 没有隐式页面滚动容器。 */
11
+ function usePageScrollRestoration(options = {}) {
12
+ const state = resolveScrollController(options.controller);
13
+ const host = getCurrentMiniProgramGlobalObject();
14
+ if (typeof host?.pageScrollTo !== "function") throw new TypeError("当前宿主不支持 pageScrollTo,请使用显式 scroll-view 适配器。");
15
+ let top = 0;
16
+ let page;
17
+ const cleanups = [];
18
+ return registerScrollRestoration({
19
+ ...options,
20
+ capture: () => ({ top }),
21
+ async restore(snapshot, context) {
22
+ const transition = state.transition;
23
+ if (transition && transition.page === page && transition.event.renderer === "skyline") throw new Error("Skyline 不支持页面级滚动,请使用 useScrollViewRestoration()。");
24
+ if (!context.isActive()) return;
25
+ await new Promise((resolve, reject) => {
26
+ host.pageScrollTo({
27
+ scrollTop: snapshot?.top ?? 0,
28
+ duration: 0,
29
+ success: () => resolve(),
30
+ fail: reject
31
+ });
32
+ });
33
+ }
34
+ }, {
35
+ onBindPage(owner) {
36
+ page = owner;
37
+ const releaseOwner = host[WEVU_PAGE_SCROLL_RESTORATION_OWNER_KEY]?.(page);
38
+ if (typeof releaseOwner === "function") cleanups.push(releaseOwner);
39
+ cleanups.push(addScrollHook(page, WEVU_PAGE_SCROLL_OBSERVERS_KEY, (event) => {
40
+ if (event && typeof event === "object" && "scrollTop" in event && typeof event.scrollTop === "number") top = event.scrollTop;
41
+ }));
42
+ ensurePageHookOnInstance(page, "onPageScroll");
43
+ },
44
+ onStop() {
45
+ for (const cleanup of cleanups) cleanup();
46
+ cleanups.length = 0;
47
+ }
48
+ });
49
+ }
50
+ /** 用属性控制 WebView/Skyline scroll-view,scroll 事件不产生 setData。 */
51
+ function useScrollViewRestoration(options = {}) {
52
+ const { commit } = getScrollSetupScope();
53
+ const scrollTop = ref(0);
54
+ const scrollLeft = ref(0);
55
+ let top = 0;
56
+ let left = 0;
57
+ return {
58
+ ...registerScrollRestoration({
59
+ ...options,
60
+ capture: () => ({
61
+ top,
62
+ left
63
+ }),
64
+ async restore(snapshot, context) {
65
+ if (!context.isActive()) return;
66
+ scrollTop.value = top;
67
+ scrollLeft.value = left;
68
+ await commit();
69
+ if (!context.isActive()) return;
70
+ scrollTop.value = snapshot?.top ?? 0;
71
+ scrollLeft.value = snapshot?.left ?? 0;
72
+ await commit();
73
+ }
74
+ }),
75
+ scrollTop,
76
+ scrollLeft,
77
+ onScroll(event) {
78
+ top = event.detail.scrollTop;
79
+ left = event.detail.scrollLeft;
80
+ }
81
+ };
82
+ }
83
+
84
+ //#endregion
85
+ export { usePageScrollRestoration, useScrollViewRestoration };
86
+ //# sourceMappingURL=adapters.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.mjs","names":[],"sources":["../../../../src/router/scrollRestoration/adapters.ts"],"sourcesContent":["import type { InternalRuntimeState } from '../../runtime/types'\nimport type { ScrollPage } from './host'\nimport type { ScrollRestorationHandle, ScrollRestorationRegistrationOptions, ScrollViewRestorationHandle } from './types'\nimport { WEVU_PAGE_SCROLL_OBSERVERS_KEY, WEVU_PAGE_SCROLL_RESTORATION_OWNER_KEY } from '@weapp-core/constants'\nimport { ref } from '../../reactivity'\nimport { ensurePageHookOnInstance } from '../../runtime/hooks/pageScroll'\nimport { getCurrentMiniProgramGlobalObject } from '../../runtime/platform'\nimport { resolveScrollController } from './controller'\nimport { addScrollHook } from './host'\nimport { getScrollSetupScope, registerScrollRestoration } from './index'\n\n/** WebView 页面适配器;Skyline 没有隐式页面滚动容器。 */\nexport function usePageScrollRestoration(options: ScrollRestorationRegistrationOptions = {}): ScrollRestorationHandle {\n const state = resolveScrollController(options.controller)\n const host = getCurrentMiniProgramGlobalObject()\n if (typeof host?.pageScrollTo !== 'function') {\n throw new TypeError('当前宿主不支持 pageScrollTo,请使用显式 scroll-view 适配器。')\n }\n let top = 0\n let page: ScrollPage | undefined\n const cleanups: Array<() => void> = []\n\n return registerScrollRestoration({\n ...options,\n capture: () => ({ top }),\n async restore(snapshot, context) {\n const transition = state.transition\n if (transition && transition.page === page && transition.event.renderer === 'skyline') {\n throw new Error('Skyline 不支持页面级滚动,请使用 useScrollViewRestoration()。')\n }\n if (!context.isActive()) {\n return\n }\n await new Promise<void>((resolve, reject) => {\n host.pageScrollTo({ scrollTop: snapshot?.top ?? 0, duration: 0, success: () => resolve(), fail: reject })\n })\n },\n }, {\n onBindPage(owner) {\n page = owner\n const releaseOwner = host[WEVU_PAGE_SCROLL_RESTORATION_OWNER_KEY]?.(page)\n if (typeof releaseOwner === 'function') {\n cleanups.push(releaseOwner)\n }\n cleanups.push(addScrollHook(page, WEVU_PAGE_SCROLL_OBSERVERS_KEY, (event) => {\n if (event && typeof event === 'object' && 'scrollTop' in event && typeof event.scrollTop === 'number') {\n top = event.scrollTop\n }\n }))\n ensurePageHookOnInstance(page as InternalRuntimeState, 'onPageScroll')\n },\n onStop() {\n for (const cleanup of cleanups) {\n cleanup()\n }\n cleanups.length = 0\n },\n })\n}\n\n/** 用属性控制 WebView/Skyline scroll-view,scroll 事件不产生 setData。 */\nexport function useScrollViewRestoration(options: ScrollRestorationRegistrationOptions = {}): ScrollViewRestorationHandle {\n const { commit } = getScrollSetupScope()\n const scrollTop = ref(0)\n const scrollLeft = ref(0)\n let top = 0\n let left = 0\n const handle = registerScrollRestoration({\n ...options,\n capture: () => ({ top, left }),\n async restore(snapshot, context) {\n if (!context.isActive()) {\n return\n }\n // 先同步实际位置,确保重复恢复相同目标也会产生有效的属性变化。\n // 内容和目标滚动位置分次提交,避免原生 scroll-view 使用更新前的尺寸。\n scrollTop.value = top\n scrollLeft.value = left\n await commit()\n if (!context.isActive()) {\n return\n }\n scrollTop.value = snapshot?.top ?? 0\n scrollLeft.value = snapshot?.left ?? 0\n await commit()\n },\n })\n return {\n ...handle,\n scrollTop,\n scrollLeft,\n onScroll(event) {\n top = event.detail.scrollTop\n left = event.detail.scrollLeft\n },\n }\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,yBAAyB,UAAgD,CAAC,GAA4B;CACpH,MAAM,QAAQ,wBAAwB,QAAQ,UAAU;CACxD,MAAM,OAAO,kCAAkC;CAC/C,IAAI,OAAO,MAAM,iBAAiB,YAChC,MAAM,IAAI,UAAU,6CAA6C;CAEnE,IAAI,MAAM;CACV,IAAI;CACJ,MAAM,WAA8B,CAAC;CAErC,OAAO,0BAA0B;EAC/B,GAAG;EACH,gBAAgB,EAAE,IAAI;EACtB,MAAM,QAAQ,UAAU,SAAS;GAC/B,MAAM,aAAa,MAAM;GACzB,IAAI,cAAc,WAAW,SAAS,QAAQ,WAAW,MAAM,aAAa,WAC1E,MAAM,IAAI,MAAM,kDAAkD;GAEpE,IAAI,CAAC,QAAQ,SAAS,GACpB;GAEF,MAAM,IAAI,SAAe,SAAS,WAAW;IAC3C,KAAK,aAAa;KAAE,WAAW,UAAU,OAAO;KAAG,UAAU;KAAG,eAAe,QAAQ;KAAG,MAAM;IAAO,CAAC;GAC1G,CAAC;EACH;CACF,GAAG;EACD,WAAW,OAAO;GAChB,OAAO;GACP,MAAM,eAAe,KAAK,uCAAuC,GAAG,IAAI;GACxE,IAAI,OAAO,iBAAiB,YAC1B,SAAS,KAAK,YAAY;GAE5B,SAAS,KAAK,cAAc,MAAM,iCAAiC,UAAU;IAC3E,IAAI,SAAS,OAAO,UAAU,YAAY,eAAe,SAAS,OAAO,MAAM,cAAc,UAC3F,MAAM,MAAM;GAEhB,CAAC,CAAC;GACF,yBAAyB,MAA8B,cAAc;EACvE;EACA,SAAS;GACP,KAAK,MAAM,WAAW,UACpB,QAAQ;GAEV,SAAS,SAAS;EACpB;CACF,CAAC;AACH;;AAGA,SAAgB,yBAAyB,UAAgD,CAAC,GAAgC;CACxH,MAAM,EAAE,WAAW,oBAAoB;CACvC,MAAM,YAAY,IAAI,CAAC;CACvB,MAAM,aAAa,IAAI,CAAC;CACxB,IAAI,MAAM;CACV,IAAI,OAAO;CAqBX,OAAO;EACL,GArBa,0BAA0B;GACvC,GAAG;GACH,gBAAgB;IAAE;IAAK;GAAK;GAC5B,MAAM,QAAQ,UAAU,SAAS;IAC/B,IAAI,CAAC,QAAQ,SAAS,GACpB;IAIF,UAAU,QAAQ;IAClB,WAAW,QAAQ;IACnB,MAAM,OAAO;IACb,IAAI,CAAC,QAAQ,SAAS,GACpB;IAEF,UAAU,QAAQ,UAAU,OAAO;IACnC,WAAW,QAAQ,UAAU,QAAQ;IACrC,MAAM,OAAO;GACf;EACF,CAEU;EACR;EACA;EACA,SAAS,OAAO;GACd,MAAM,MAAM,OAAO;GACnB,OAAO,MAAM,OAAO;EACtB;CACF;AACF"}
@@ -0,0 +1,8 @@
1
+ import "../types.mjs";
2
+ import { ScrollRestorationController, ScrollRestorationOptions } from "./types.mjs";
3
+ import "./host.mjs";
4
+ //#region src/router/scrollRestoration/controller.d.ts
5
+ /** 创建显式启用的路由会话;不会安装全局默认滚动行为。 */
6
+ export declare function createScrollRestoration<TRouteMap extends object>(options: ScrollRestorationOptions<TRouteMap>): ScrollRestorationController;
7
+ //#endregion
8
+ //# sourceMappingURL=controller.d.mts.map
@@ -0,0 +1,148 @@
1
+ import { getCurrentMiniProgramPages } from "../../runtime/platform.mjs";
2
+ import { resolvePageRoute } from "../../routerInternal/location.mjs";
3
+ import { getActiveRouter } from "../instance.mjs";
4
+ import { currentScrollPage, subscribeScrollRouteEvents, supportsScrollRouteEvents } from "./host.mjs";
5
+
6
+ //#region src/router/scrollRestoration/controller.ts
7
+ const controllers = /* @__PURE__ */ new WeakMap();
8
+ const routerControllers = /* @__PURE__ */ new WeakMap();
9
+ /** 创建显式启用的路由会话;不会安装全局默认滚动行为。 */
10
+ function createScrollRestoration(options) {
11
+ if (routerControllers.has(options.router)) throw new Error("该 router 已创建 scroll restoration 会话,请复用或先 dispose()。");
12
+ const router = options.router;
13
+ const snapshots = /* @__PURE__ */ new Map();
14
+ const registrations = /* @__PURE__ */ new Map();
15
+ const seenPages = new WeakSet(getCurrentMiniProgramPages());
16
+ const report = options.onError ?? ((error) => console.error("[wevu/router] scroll restoration:", error));
17
+ let disposed = false;
18
+ let generation = 0;
19
+ let transition;
20
+ let unsubscribe;
21
+ const controller = {
22
+ automatic: supportsScrollRouteEvents(),
23
+ clear(key) {
24
+ if (key === void 0) snapshots.clear();
25
+ else snapshots.delete(key);
26
+ for (const records of registrations.values()) for (const record of records) if (key === void 0 || record.key === key) record.invalidate();
27
+ },
28
+ dispose() {
29
+ if (disposed) return;
30
+ disposed = true;
31
+ generation++;
32
+ unsubscribe?.();
33
+ for (const records of registrations.values()) for (const record of records) record.stop();
34
+ registrations.clear();
35
+ snapshots.clear();
36
+ transition = void 0;
37
+ routerControllers.delete(options.router);
38
+ controllers.delete(controller);
39
+ }
40
+ };
41
+ const state = {
42
+ controller,
43
+ get disposed() {
44
+ return disposed;
45
+ },
46
+ get transition() {
47
+ return transition;
48
+ },
49
+ get generation() {
50
+ return generation;
51
+ },
52
+ resolveRoute: (page) => router.resolve(resolvePageRoute(page)),
53
+ register(page, registration) {
54
+ if (disposed) throw new Error("scroll restoration 会话已 dispose()。");
55
+ let records = registrations.get(page);
56
+ if (!records) {
57
+ records = /* @__PURE__ */ new Set();
58
+ registrations.set(page, records);
59
+ }
60
+ for (const record of records) if (record.key === registration.key && record.id === registration.id) throw new Error(`同一页面重复注册 scroll restoration key/id: ${registration.key} / ${registration.id}`);
61
+ records.add(registration);
62
+ return () => {
63
+ records.delete(registration);
64
+ if (records.size === 0) registrations.delete(page);
65
+ };
66
+ },
67
+ read: (key, id) => snapshots.get(key)?.get(id),
68
+ save(key, id, value) {
69
+ if (disposed) return;
70
+ if (value === null) {
71
+ state.clear(key, id);
72
+ return;
73
+ }
74
+ let entries = snapshots.get(key);
75
+ if (!entries) {
76
+ entries = /* @__PURE__ */ new Map();
77
+ snapshots.set(key, entries);
78
+ }
79
+ entries.set(id, value);
80
+ },
81
+ clear(key, id) {
82
+ const entries = snapshots.get(key);
83
+ entries?.delete(id);
84
+ if (entries?.size === 0) snapshots.delete(key);
85
+ for (const records of registrations.values()) for (const record of records) if (record.key === key && record.id === id) record.invalidate();
86
+ },
87
+ report
88
+ };
89
+ if (controller.automatic) unsubscribe = subscribeScrollRouteEvents({
90
+ BeforeAppRoute(event) {
91
+ if (disposed || transition?.event.routeEventId === event.routeEventId) return;
92
+ const page = currentScrollPage();
93
+ if (page) {
94
+ seenPages.add(page);
95
+ for (const record of registrations.get(page) ?? []) record.capture();
96
+ }
97
+ generation++;
98
+ for (const records of registrations.values()) for (const record of records) record.invalidate();
99
+ transition = {
100
+ event,
101
+ fresh: false,
102
+ done: false
103
+ };
104
+ },
105
+ BeforePageUnload(event) {
106
+ const page = event.page;
107
+ if (!page) return;
108
+ if (transition?.event.routeEventId !== event.routeEventId && page === currentScrollPage()) for (const record of registrations.get(page) ?? []) record.capture();
109
+ for (const record of registrations.get(page) ?? []) record.stop();
110
+ },
111
+ AppRoute(event) {
112
+ if (disposed || transition && transition.event.routeEventId !== event.routeEventId) return;
113
+ const page = currentScrollPage();
114
+ if (!page || resolvePageRoute(page).path !== event.path.replace(/^\/+/, "")) return;
115
+ transition ?? (transition = {
116
+ event,
117
+ fresh: false,
118
+ done: false
119
+ });
120
+ if (transition.page) return;
121
+ transition.page = page;
122
+ transition.fresh = !seenPages.has(page);
123
+ seenPages.add(page);
124
+ },
125
+ AppRouteDone(event) {
126
+ if (disposed || !transition) return;
127
+ const completesFreshPage = event.routeEventId === "" && transition.fresh && Number.isInteger(transition.event.webviewId) && event.webviewId === transition.event.webviewId && event.path === transition.event.path && typeof event.openType === "string" && event.openType === transition.event.openType;
128
+ if (transition.event.routeEventId !== event.routeEventId && !completesFreshPage) return;
129
+ if (!transition.page || transition.page !== currentScrollPage() || transition.done) return;
130
+ transition.done = true;
131
+ for (const record of registrations.get(transition.page) ?? []) record.restoreAutomatic();
132
+ }
133
+ });
134
+ controllers.set(controller, state);
135
+ routerControllers.set(options.router, controller);
136
+ return controller;
137
+ }
138
+ function resolveScrollController(controller) {
139
+ const router = getActiveRouter();
140
+ const resolved = controller ?? (router && routerControllers.get(router));
141
+ const state = resolved && controllers.get(resolved);
142
+ if (!state || state.disposed) throw new Error("请先在应用启动时调用 createScrollRestoration({ router }),或传入有效的 controller。");
143
+ return state;
144
+ }
145
+
146
+ //#endregion
147
+ export { createScrollRestoration, resolveScrollController };
148
+ //# sourceMappingURL=controller.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"controller.mjs","names":[],"sources":["../../../../src/router/scrollRestoration/controller.ts"],"sourcesContent":["import type { RouteLocationNormalizedLoaded, RouterNavigation } from '../types'\nimport type { ScrollPage, ScrollRouteEvent } from './host'\nimport type { ScrollRestorationController, ScrollRestorationOptions } from './types'\nimport { resolvePageRoute } from '../../routerInternal/shared'\nimport { getCurrentMiniProgramPages } from '../../runtime/platform'\nimport { getActiveRouter } from '../instance'\nimport { currentScrollPage, subscribeScrollRouteEvents, supportsScrollRouteEvents } from './host'\n\nexport interface ScrollRegistration {\n key: string\n id: string\n capture: () => void\n invalidate: () => void\n restoreAutomatic: () => void\n stop: () => void\n}\n\ninterface ScrollTransition {\n event: ScrollRouteEvent\n page?: ScrollPage\n fresh: boolean\n done: boolean\n}\n\nexport interface ScrollControllerState {\n readonly controller: ScrollRestorationController\n readonly disposed: boolean\n readonly transition: ScrollTransition | undefined\n readonly generation: number\n resolveRoute: (page: ScrollPage) => RouteLocationNormalizedLoaded\n register: (page: ScrollPage, registration: ScrollRegistration) => () => void\n read: (key: string, id: string) => object | undefined\n save: (key: string, id: string, value: object | null) => void\n clear: (key: string, id: string) => void\n report: (error: unknown) => void\n}\n\nconst controllers = new WeakMap<ScrollRestorationController, ScrollControllerState>()\nconst routerControllers = new WeakMap<object, ScrollRestorationController>()\n\n/** 创建显式启用的路由会话;不会安装全局默认滚动行为。 */\nexport function createScrollRestoration<TRouteMap extends object>(options: ScrollRestorationOptions<TRouteMap>): ScrollRestorationController {\n if (routerControllers.has(options.router)) {\n throw new Error('该 router 已创建 scroll restoration 会话,请复用或先 dispose()。')\n }\n // 泛型仅约束公开路由名,内部处理来自原生页面的标准化路径。\n const router = options.router as unknown as RouterNavigation\n const snapshots = new Map<string, Map<string, object>>()\n const registrations = new Map<ScrollPage, Set<ScrollRegistration>>()\n const seenPages = new WeakSet<object>(getCurrentMiniProgramPages())\n // eslint-disable-next-line no-console -- 自动恢复没有调用方 Promise;默认处理必须报告错误。\n const report = options.onError ?? ((error: unknown) => console.error('[wevu/router] scroll restoration:', error))\n let disposed = false\n let generation = 0\n let transition: ScrollTransition | undefined\n let unsubscribe: (() => void) | undefined\n\n const controller: ScrollRestorationController = {\n automatic: supportsScrollRouteEvents(),\n clear(key) {\n if (key === undefined) {\n snapshots.clear()\n }\n else {\n snapshots.delete(key)\n }\n for (const records of registrations.values()) {\n for (const record of records) {\n if (key === undefined || record.key === key) {\n record.invalidate()\n }\n }\n }\n },\n dispose() {\n if (disposed) {\n return\n }\n disposed = true\n generation++\n unsubscribe?.()\n for (const records of registrations.values()) {\n for (const record of records) {\n record.stop()\n }\n }\n registrations.clear()\n snapshots.clear()\n transition = undefined\n routerControllers.delete(options.router)\n controllers.delete(controller)\n },\n }\n\n const state: ScrollControllerState = {\n controller,\n get disposed() {\n return disposed\n },\n get transition() {\n return transition\n },\n get generation() {\n return generation\n },\n resolveRoute: page => router.resolve(resolvePageRoute(page)),\n register(page, registration) {\n if (disposed) {\n throw new Error('scroll restoration 会话已 dispose()。')\n }\n let records = registrations.get(page)\n if (!records) {\n records = new Set()\n registrations.set(page, records)\n }\n for (const record of records) {\n if (record.key === registration.key && record.id === registration.id) {\n throw new Error(`同一页面重复注册 scroll restoration key/id: ${registration.key} / ${registration.id}`)\n }\n }\n records.add(registration)\n return () => {\n records.delete(registration)\n if (records.size === 0) {\n registrations.delete(page)\n }\n }\n },\n read: (key, id) => snapshots.get(key)?.get(id),\n save(key, id, value) {\n if (disposed) {\n return\n }\n if (value === null) {\n state.clear(key, id)\n return\n }\n let entries = snapshots.get(key)\n if (!entries) {\n entries = new Map()\n snapshots.set(key, entries)\n }\n entries.set(id, value)\n },\n clear(key, id) {\n const entries = snapshots.get(key)\n entries?.delete(id)\n if (entries?.size === 0) {\n snapshots.delete(key)\n }\n for (const records of registrations.values()) {\n for (const record of records) {\n if (record.key === key && record.id === id) {\n record.invalidate()\n }\n }\n }\n },\n report,\n }\n\n if (controller.automatic) {\n unsubscribe = subscribeScrollRouteEvents({\n BeforeAppRoute(event) {\n if (disposed || transition?.event.routeEventId === event.routeEventId) {\n return\n }\n const page = currentScrollPage()\n if (page) {\n seenPages.add(page)\n for (const record of registrations.get(page) ?? []) {\n record.capture()\n }\n }\n generation++\n for (const records of registrations.values()) {\n for (const record of records) {\n record.invalidate()\n }\n }\n transition = { event, fresh: false, done: false }\n },\n BeforePageUnload(event) {\n const page = event.page\n if (!page) {\n return\n }\n // 低层卸载可能没有前置路由通知;仍在当前页时最后同步读取内存缓存。\n if (transition?.event.routeEventId !== event.routeEventId && page === currentScrollPage()) {\n for (const record of registrations.get(page) ?? []) {\n record.capture()\n }\n }\n for (const record of registrations.get(page) ?? []) {\n record.stop()\n }\n },\n AppRoute(event) {\n if (disposed || (transition && transition.event.routeEventId !== event.routeEventId)) {\n return\n }\n const page = currentScrollPage()\n if (!page || resolvePageRoute(page).path !== event.path.replace(/^\\/+/, '')) {\n return\n }\n transition ??= { event, fresh: false, done: false }\n if (transition.page) {\n return\n }\n transition.page = page\n transition.fresh = !seenPages.has(page)\n seenPages.add(page)\n },\n AppRouteDone(event) {\n if (disposed || !transition) {\n return\n }\n // 原生同路径 reLaunch 可能返回空 id;只接受已确认的新页面对应的宿主 webview,\n // 不把旧页面、保留页面或仅路径相同的完成事件认作当前导航。\n const completesFreshPage = event.routeEventId === ''\n && transition.fresh\n && Number.isInteger(transition.event.webviewId)\n && event.webviewId === transition.event.webviewId\n && event.path === transition.event.path\n && typeof event.openType === 'string'\n && event.openType === transition.event.openType\n if (transition.event.routeEventId !== event.routeEventId && !completesFreshPage) {\n return\n }\n if (!transition.page || transition.page !== currentScrollPage() || transition.done) {\n return\n }\n transition.done = true\n for (const record of registrations.get(transition.page) ?? []) {\n record.restoreAutomatic()\n }\n },\n })\n }\n controllers.set(controller, state)\n routerControllers.set(options.router, controller)\n return controller\n}\n\nexport function resolveScrollController(controller?: ScrollRestorationController): ScrollControllerState {\n const router = getActiveRouter()\n const resolved = controller ?? (router && routerControllers.get(router))\n const state = resolved && controllers.get(resolved)\n if (!state || state.disposed) {\n throw new Error('请先在应用启动时调用 createScrollRestoration({ router }),或传入有效的 controller。')\n }\n return state\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import "../../routerInternal/types.mjs";
2
+ import "../types.mjs";
3
+ import { WEVU_HOOKS_KEY, WEVU_NATIVE_INSTANCE_KEY, WEVU_READY_CALLED_KEY } from "@weapp-core/constants";
@@ -0,0 +1,76 @@
1
+ import { getCurrentMiniProgramGlobalObject } from "../../runtime/platform/current.mjs";
2
+ import { getCurrentMiniProgramPages } from "../../runtime/platform.mjs";
3
+ import { pushHook } from "../../runtime/hooks/base.mjs";
4
+ import { WEVU_HOOKS_KEY, WEVU_NATIVE_INSTANCE_KEY, WEVU_ROUTE_EVENT_CONTRACT_KEY } from "@weapp-core/constants";
5
+
6
+ //#region src/router/scrollRestoration/host.ts
7
+ const routeEvents = [
8
+ "BeforeAppRoute",
9
+ "BeforePageUnload",
10
+ "AppRoute",
11
+ "AppRouteDone"
12
+ ];
13
+ function supportsScrollRouteEvents(host = getCurrentMiniProgramGlobalObject()) {
14
+ if (!host || !routeEvents.every((name) => typeof host[`on${name}`] === "function" && typeof host[`off${name}`] === "function")) return false;
15
+ if (host[WEVU_ROUTE_EVENT_CONTRACT_KEY] === 1) return true;
16
+ const version = host.getAppBaseInfo?.().SDKVersion ?? host.getSystemInfoSync?.().SDKVersion;
17
+ if (typeof version !== "string") return false;
18
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:\D|$)/.exec(version);
19
+ if (!match) return false;
20
+ const major = Number(match[1]);
21
+ const minor = Number(match[2]);
22
+ const patch = Number(match[3]);
23
+ return major > 3 || major === 3 && (minor > 5 || minor === 5 && patch >= 5);
24
+ }
25
+ function subscribeScrollRouteEvents(handlers) {
26
+ const host = getCurrentMiniProgramGlobalObject();
27
+ const cleanups = [];
28
+ try {
29
+ for (const name of routeEvents) {
30
+ const listener = (event) => {
31
+ if (typeof event?.routeEventId === "string" && typeof event.path === "string") handlers[name](event);
32
+ };
33
+ host[`on${name}`](listener);
34
+ cleanups.push(() => host[`off${name}`](listener));
35
+ }
36
+ } catch (error) {
37
+ for (const cleanup of cleanups) cleanup();
38
+ throw error;
39
+ }
40
+ return () => {
41
+ for (const cleanup of cleanups) cleanup();
42
+ };
43
+ }
44
+ function currentScrollPage() {
45
+ const pages = getCurrentMiniProgramPages();
46
+ return pages[pages.length - 1];
47
+ }
48
+ /** 按实例或宿主页面 id 查找归属,不用路径猜测同路由的多个页面。 */
49
+ function resolveScrollPage(instance) {
50
+ const pages = getCurrentMiniProgramPages();
51
+ const visited = /* @__PURE__ */ new Set();
52
+ let owner = instance;
53
+ while (owner && !visited.has(owner)) {
54
+ visited.add(owner);
55
+ if (pages.includes(owner)) return owner;
56
+ const pageId = owner.__wxWebviewId__;
57
+ if (pageId !== void 0) {
58
+ const page = pages.find((candidate) => candidate.__wxWebviewId__ === pageId);
59
+ if (page) return page;
60
+ }
61
+ const native = owner[WEVU_NATIVE_INSTANCE_KEY] ?? owner.$;
62
+ owner = native && native !== owner ? native : owner.selectOwnerComponent?.();
63
+ }
64
+ }
65
+ function addScrollHook(instance, name, handler) {
66
+ pushHook(instance, name, handler);
67
+ return () => {
68
+ const bucket = instance[WEVU_HOOKS_KEY];
69
+ const hooks = bucket?.[name];
70
+ if (bucket && Array.isArray(hooks)) bucket[name] = hooks.filter((hook) => hook !== handler);
71
+ };
72
+ }
73
+
74
+ //#endregion
75
+ export { addScrollHook, currentScrollPage, resolveScrollPage, subscribeScrollRouteEvents, supportsScrollRouteEvents };
76
+ //# sourceMappingURL=host.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.mjs","names":[],"sources":["../../../../src/router/scrollRestoration/host.ts"],"sourcesContent":["import type { WEVU_READY_CALLED_KEY } from '@weapp-core/constants'\nimport type { MiniProgramPageLike } from '../../routerInternal/types'\nimport type { InternalRuntimeState } from '../../runtime/types'\nimport type { LocationQueryRaw } from '../types'\nimport { WEVU_HOOKS_KEY, WEVU_NATIVE_INSTANCE_KEY, WEVU_ROUTE_EVENT_CONTRACT_KEY } from '@weapp-core/constants'\nimport { pushHook } from '../../runtime/hooks/base'\nimport { getCurrentMiniProgramGlobalObject, getCurrentMiniProgramPages } from '../../runtime/platform'\n\ntype ScrollHook = (...args: unknown[]) => void\n\nexport interface ScrollPage extends MiniProgramPageLike {\n __wxWebviewId__?: number | string\n [WEVU_NATIVE_INSTANCE_KEY]?: ScrollPage\n [WEVU_READY_CALLED_KEY]?: boolean\n [WEVU_HOOKS_KEY]?: Record<string, ScrollHook | ScrollHook[]>\n $?: ScrollPage\n selectOwnerComponent?: () => ScrollPage | undefined\n onPageScroll?: (event: { scrollTop: number }) => unknown\n}\n\nexport interface ScrollRouteEvent {\n routeEventId: string\n path: string\n query?: LocationQueryRaw\n openType?: string\n webviewId?: number\n renderer?: string\n page?: ScrollPage\n}\n\nconst routeEvents = ['BeforeAppRoute', 'BeforePageUnload', 'AppRoute', 'AppRouteDone'] as const\nexport type ScrollRouteEventName = typeof routeEvents[number]\n\nexport function supportsScrollRouteEvents(host = getCurrentMiniProgramGlobalObject()): boolean {\n if (!host || !routeEvents.every(name => typeof host[`on${name}`] === 'function' && typeof host[`off${name}`] === 'function')) {\n return false\n }\n if (host[WEVU_ROUTE_EVENT_CONTRACT_KEY] === 1) {\n return true\n }\n const version = host.getAppBaseInfo?.().SDKVersion ?? host.getSystemInfoSync?.().SDKVersion\n if (typeof version !== 'string') {\n return false\n }\n const match = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:\\D|$)/.exec(version)\n if (!match) {\n return false\n }\n const major = Number(match[1])\n const minor = Number(match[2])\n const patch = Number(match[3])\n return major > 3 || (major === 3 && (minor > 5 || (minor === 5 && patch >= 5)))\n}\n\nexport function subscribeScrollRouteEvents(handlers: Record<ScrollRouteEventName, (event: ScrollRouteEvent) => void>): () => void {\n const host = getCurrentMiniProgramGlobalObject()!\n const cleanups: Array<() => void> = []\n try {\n for (const name of routeEvents) {\n const listener = (event: ScrollRouteEvent) => {\n if (typeof event?.routeEventId === 'string' && typeof event.path === 'string') {\n handlers[name](event)\n }\n }\n host[`on${name}`](listener)\n cleanups.push(() => host[`off${name}`](listener))\n }\n }\n catch (error) {\n for (const cleanup of cleanups) {\n cleanup()\n }\n throw error\n }\n return () => {\n for (const cleanup of cleanups) {\n cleanup()\n }\n }\n}\n\nexport function currentScrollPage(): ScrollPage | undefined {\n const pages = getCurrentMiniProgramPages()\n return pages[pages.length - 1]\n}\n\n/** 按实例或宿主页面 id 查找归属,不用路径猜测同路由的多个页面。 */\nexport function resolveScrollPage(instance: ScrollPage): ScrollPage | undefined {\n const pages = getCurrentMiniProgramPages()\n const visited = new Set<object>()\n let owner: ScrollPage | undefined = instance\n while (owner && !visited.has(owner)) {\n visited.add(owner)\n if (pages.includes(owner)) {\n return owner\n }\n const pageId = owner.__wxWebviewId__\n if (pageId !== undefined) {\n const page = pages.find(candidate => candidate.__wxWebviewId__ === pageId)\n if (page) {\n return page\n }\n }\n const native: ScrollPage | undefined = owner[WEVU_NATIVE_INSTANCE_KEY] ?? owner.$\n owner = native && native !== owner\n ? native\n : owner.selectOwnerComponent?.()\n }\n return undefined\n}\n\nexport function addScrollHook(instance: ScrollPage, name: string, handler: ScrollHook): () => void {\n pushHook(instance as InternalRuntimeState, name, handler)\n return () => {\n const bucket = instance[WEVU_HOOKS_KEY]\n const hooks = bucket?.[name]\n if (bucket && Array.isArray(hooks)) {\n // 不原地修改正在被生命周期分发器遍历的数组,避免跳过后续用户 hook。\n bucket[name] = hooks.filter(hook => hook !== handler)\n }\n }\n}\n"],"mappings":";;;;;;AA8BA,MAAM,cAAc;CAAC;CAAkB;CAAoB;CAAY;AAAc;AAGrF,SAAgB,0BAA0B,OAAO,kCAAkC,GAAY;CAC7F,IAAI,CAAC,QAAQ,CAAC,YAAY,OAAM,SAAQ,OAAO,KAAK,KAAK,YAAY,cAAc,OAAO,KAAK,MAAM,YAAY,UAAU,GACzH,OAAO;CAET,IAAI,KAAK,mCAAmC,GAC1C,OAAO;CAET,MAAM,UAAU,KAAK,iBAAiB,CAAC,CAAC,cAAc,KAAK,oBAAoB,CAAC,CAAC;CACjF,IAAI,OAAO,YAAY,UACrB,OAAO;CAET,MAAM,QAAQ,+BAA+B,KAAK,OAAO;CACzD,IAAI,CAAC,OACH,OAAO;CAET,MAAM,QAAQ,OAAO,MAAM,EAAE;CAC7B,MAAM,QAAQ,OAAO,MAAM,EAAE;CAC7B,MAAM,QAAQ,OAAO,MAAM,EAAE;CAC7B,OAAO,QAAQ,KAAM,UAAU,MAAM,QAAQ,KAAM,UAAU,KAAK,SAAS;AAC7E;AAEA,SAAgB,2BAA2B,UAAuF;CAChI,MAAM,OAAO,kCAAkC;CAC/C,MAAM,WAA8B,CAAC;CACrC,IAAI;EACF,KAAK,MAAM,QAAQ,aAAa;GAC9B,MAAM,YAAY,UAA4B;IAC5C,IAAI,OAAO,OAAO,iBAAiB,YAAY,OAAO,MAAM,SAAS,UACnE,SAAS,KAAK,CAAC,KAAK;GAExB;GACA,KAAK,KAAK,OAAO,CAAC,QAAQ;GAC1B,SAAS,WAAW,KAAK,MAAM,OAAO,CAAC,QAAQ,CAAC;EAClD;CACF,SACO,OAAO;EACZ,KAAK,MAAM,WAAW,UACpB,QAAQ;EAEV,MAAM;CACR;CACA,aAAa;EACX,KAAK,MAAM,WAAW,UACpB,QAAQ;CAEZ;AACF;AAEA,SAAgB,oBAA4C;CAC1D,MAAM,QAAQ,2BAA2B;CACzC,OAAO,MAAM,MAAM,SAAS;AAC9B;;AAGA,SAAgB,kBAAkB,UAA8C;CAC9E,MAAM,QAAQ,2BAA2B;CACzC,MAAM,0BAAU,IAAI,IAAY;CAChC,IAAI,QAAgC;CACpC,OAAO,SAAS,CAAC,QAAQ,IAAI,KAAK,GAAG;EACnC,QAAQ,IAAI,KAAK;EACjB,IAAI,MAAM,SAAS,KAAK,GACtB,OAAO;EAET,MAAM,SAAS,MAAM;EACrB,IAAI,WAAW,QAAW;GACxB,MAAM,OAAO,MAAM,MAAK,cAAa,UAAU,oBAAoB,MAAM;GACzE,IAAI,MACF,OAAO;EAEX;EACA,MAAM,SAAiC,MAAM,6BAA6B,MAAM;EAChF,QAAQ,UAAU,WAAW,QACzB,SACA,MAAM,uBAAuB;CACnC;AAEF;AAEA,SAAgB,cAAc,UAAsB,MAAc,SAAiC;CACjG,SAAS,UAAkC,MAAM,OAAO;CACxD,aAAa;EACX,MAAM,SAAS,SAAS;EACxB,MAAM,QAAQ,SAAS;EACvB,IAAI,UAAU,MAAM,QAAQ,KAAK,GAE/B,OAAO,QAAQ,MAAM,QAAO,SAAQ,SAAS,OAAO;CAExD;AACF"}
@@ -0,0 +1,9 @@
1
+ import { ScrollRestorationContext, ScrollRestorationController, ScrollRestorationHandle, ScrollRestorationOptions, ScrollRestorationRegistrationOptions, ScrollViewRestorationHandle, UseScrollRestorationOptions } from "./types.mjs";
2
+ import "./host.mjs";
3
+ import { createScrollRestoration } from "./controller.mjs";
4
+ //#region src/router/scrollRestoration/index.d.ts
5
+ /** 在同步 setup 中注册快照;只恢复新实例,不覆盖原生保留页的位置。 */
6
+ export declare function useScrollRestoration<T extends object>(options: UseScrollRestorationOptions<T>): ScrollRestorationHandle;
7
+ //#endregion
8
+ export { type ScrollRestorationContext, type ScrollRestorationController, type ScrollRestorationHandle, type ScrollRestorationOptions, type ScrollRestorationRegistrationOptions, type ScrollViewRestorationHandle, type UseScrollRestorationOptions, createScrollRestoration };
9
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1,156 @@
1
+ import { onScopeDispose } from "../../reactivity/core.mjs";
2
+ import { assertInSetup, getCurrentSetupContext } from "../../runtime/hooks/base.mjs";
3
+ import { getInitialNavigationPromise } from "../initialNavigation.mjs";
4
+ import { addScrollHook, currentScrollPage, resolveScrollPage } from "./host.mjs";
5
+ import { createScrollRestoration, resolveScrollController } from "./controller.mjs";
6
+ import { WEVU_READY_CALLED_KEY } from "@weapp-core/constants";
7
+
8
+ //#region src/router/scrollRestoration/index.ts
9
+ function getScrollSetupScope() {
10
+ const instance = assertInSetup("useScrollRestoration");
11
+ const proxy = getCurrentSetupContext()?.proxy;
12
+ if (typeof proxy?.$nextTick !== "function") throw new TypeError("scroll restoration 需要具有宿主提交屏障的 wevu setup 实例。");
13
+ return {
14
+ instance,
15
+ commit: () => proxy.$nextTick()
16
+ };
17
+ }
18
+ function registerScrollRestoration(options, lifecycle) {
19
+ const { instance, commit } = getScrollSetupScope();
20
+ const state = resolveScrollController(options.controller);
21
+ const cleanups = [];
22
+ let page;
23
+ let route;
24
+ let stopped = false;
25
+ let visible = true;
26
+ let captured = false;
27
+ let attempted = false;
28
+ let request = 0;
29
+ let instanceReady = Boolean(instance[WEVU_READY_CALLED_KEY]);
30
+ let pageReady = false;
31
+ let readyPromise;
32
+ let resolveReady;
33
+ let registration;
34
+ function releaseReady() {
35
+ if (stopped || instanceReady && pageReady) resolveReady?.();
36
+ }
37
+ function bindPage() {
38
+ if (page || stopped) return;
39
+ const owner = resolveScrollPage(instance);
40
+ if (!owner) return;
41
+ const nextRoute = state.resolveRoute(owner);
42
+ registration.key = typeof options.key === "function" ? options.key(nextRoute) : options.key ?? nextRoute.fullPath;
43
+ const unregister = state.register(owner, registration);
44
+ page = owner;
45
+ route = nextRoute;
46
+ cleanups.push(unregister);
47
+ pageReady = Boolean(owner[WEVU_READY_CALLED_KEY]);
48
+ cleanups.push(addScrollHook(owner, "onReady", () => {
49
+ pageReady = true;
50
+ releaseReady();
51
+ registration.restoreAutomatic();
52
+ }));
53
+ lifecycle?.onBindPage?.(owner);
54
+ }
55
+ async function scroll() {
56
+ if (stopped || state.disposed) return false;
57
+ bindPage();
58
+ if (!page || !route) throw new Error("无法确定 scroll restoration 的原生归属页面,请在页面 attached 后恢复。");
59
+ if (!visible || currentScrollPage() !== page) return false;
60
+ const ticket = ++request;
61
+ const generation = state.generation;
62
+ const context = {
63
+ route,
64
+ routeEventId: state.transition?.page === page ? state.transition.event.routeEventId : void 0,
65
+ isActive: () => !stopped && !state.disposed && visible && ticket === request && generation === state.generation && currentScrollPage() === page
66
+ };
67
+ if (!instanceReady || !pageReady) {
68
+ readyPromise ?? (readyPromise = new Promise((resolve) => {
69
+ resolveReady = resolve;
70
+ }));
71
+ await readyPromise;
72
+ }
73
+ if (!context.isActive()) return false;
74
+ if (await getInitialNavigationPromise(page) === false || !context.isActive()) return false;
75
+ await commit();
76
+ if (!context.isActive()) return false;
77
+ const snapshot = state.read(registration.key, registration.id);
78
+ await options.restore(snapshot, context);
79
+ return context.isActive();
80
+ }
81
+ registration = {
82
+ key: "",
83
+ id: options.id ?? "default",
84
+ capture() {
85
+ if (stopped || state.disposed || !page || !visible || captured) return;
86
+ captured = true;
87
+ try {
88
+ const snapshot = options.capture();
89
+ if (snapshot !== null && "then" in snapshot && typeof snapshot.then === "function") throw new TypeError("scroll restoration capture 必须同步返回快照,不能返回 Promise。");
90
+ state.save(registration.key, registration.id, snapshot);
91
+ } catch (error) {
92
+ state.clear(registration.key, registration.id);
93
+ state.report(error);
94
+ }
95
+ },
96
+ invalidate() {
97
+ request++;
98
+ resolveReady?.();
99
+ readyPromise = void 0;
100
+ resolveReady = void 0;
101
+ },
102
+ restoreAutomatic() {
103
+ const transition = state.transition;
104
+ if (stopped || options.manual || attempted || !instanceReady || !pageReady || !transition?.fresh || !transition.done || transition.page !== page) return;
105
+ attempted = true;
106
+ scroll().catch(state.report);
107
+ },
108
+ stop() {
109
+ if (stopped) return;
110
+ stopped = true;
111
+ request++;
112
+ releaseReady();
113
+ for (const cleanup of cleanups) cleanup();
114
+ cleanups.length = 0;
115
+ lifecycle?.onStop?.();
116
+ }
117
+ };
118
+ function captureBeforeStop() {
119
+ registration.capture();
120
+ registration.stop();
121
+ }
122
+ cleanups.push(addScrollHook(instance, "onAttached", bindPage), addScrollHook(instance, "onReady", () => {
123
+ bindPage();
124
+ instanceReady = true;
125
+ releaseReady();
126
+ registration.restoreAutomatic();
127
+ }), addScrollHook(instance, "onShow", () => {
128
+ bindPage();
129
+ visible = true;
130
+ captured = false;
131
+ }), addScrollHook(instance, "onHide", () => {
132
+ registration.capture();
133
+ visible = false;
134
+ registration.invalidate();
135
+ }), addScrollHook(instance, "onDetached", captureBeforeStop), addScrollHook(instance, "onUnload", captureBeforeStop));
136
+ onScopeDispose(captureBeforeStop);
137
+ bindPage();
138
+ registration.restoreAutomatic();
139
+ return {
140
+ automatic: state.controller.automatic && !options.manual,
141
+ scroll,
142
+ clear: () => {
143
+ bindPage();
144
+ if (page) state.clear(registration.key, registration.id);
145
+ },
146
+ stop: registration.stop
147
+ };
148
+ }
149
+ /** 在同步 setup 中注册快照;只恢复新实例,不覆盖原生保留页的位置。 */
150
+ function useScrollRestoration(options) {
151
+ return registerScrollRestoration(options);
152
+ }
153
+
154
+ //#endregion
155
+ export { createScrollRestoration, getScrollSetupScope, registerScrollRestoration, useScrollRestoration };
156
+ //# sourceMappingURL=index.mjs.map