@finesoft/front 0.1.36 → 0.1.38
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/dist/browser.cjs +61 -16
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +10 -0
- package/dist/browser.d.ts +10 -0
- package/dist/browser.js +1 -1
- package/dist/{chunk-J43ASC3C.js → chunk-PHDR7PIL.js} +62 -17
- package/dist/chunk-PHDR7PIL.js.map +1 -0
- package/dist/index.cjs +61 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-J43ASC3C.js.map +0 -1
|
@@ -122,6 +122,22 @@ var History = class {
|
|
|
122
122
|
tryScroll(this.log, () => this.getScrollablePageElement(), scrollY);
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
|
+
/** 仅推入 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
126
|
+
pushUrl(url) {
|
|
127
|
+
const id = generateUuid();
|
|
128
|
+
window.history.pushState({ id }, "", url);
|
|
129
|
+
this.currentStateId = id;
|
|
130
|
+
this.scrollTop = 0;
|
|
131
|
+
this.log.info("pushUrl (no state)", url, id);
|
|
132
|
+
}
|
|
133
|
+
/** 仅替换 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
134
|
+
replaceUrl(url) {
|
|
135
|
+
const id = generateUuid();
|
|
136
|
+
window.history.replaceState({ id }, "", url);
|
|
137
|
+
this.currentStateId = id;
|
|
138
|
+
this.scrollTop = 0;
|
|
139
|
+
this.log.info("replaceUrl (no state)", url, id);
|
|
140
|
+
}
|
|
125
141
|
updateState(update) {
|
|
126
142
|
if (!this.currentStateId) {
|
|
127
143
|
this.log.warn(
|
|
@@ -152,8 +168,10 @@ var History = class {
|
|
|
152
168
|
function registerFlowActionHandler(deps) {
|
|
153
169
|
const { framework, log, callbacks, updateApp } = deps;
|
|
154
170
|
let isFirstPage = true;
|
|
171
|
+
let navigationId = 0;
|
|
172
|
+
const defaultGetScrollable = () => document.getElementById("scrollable-page-override") || document.getElementById("scrollable-page") || document.documentElement;
|
|
155
173
|
const history = new History(log, {
|
|
156
|
-
getScrollablePageElement:
|
|
174
|
+
getScrollablePageElement: deps.getScrollablePageElement ?? defaultGetScrollable
|
|
157
175
|
});
|
|
158
176
|
framework.onAction(ACTION_KINDS.FLOW, async (action) => {
|
|
159
177
|
const flowAction = action;
|
|
@@ -169,7 +187,8 @@ function registerFlowActionHandler(deps) {
|
|
|
169
187
|
}
|
|
170
188
|
return;
|
|
171
189
|
}
|
|
172
|
-
const
|
|
190
|
+
const thisNav = ++navigationId;
|
|
191
|
+
const shouldReplace = isFirstPage || url === window.location.pathname + window.location.search;
|
|
173
192
|
const match = framework.routeUrl(url);
|
|
174
193
|
if (!match) {
|
|
175
194
|
log.warn(`FlowAction: no route for ${url}`);
|
|
@@ -183,21 +202,45 @@ function registerFlowActionHandler(deps) {
|
|
|
183
202
|
new Promise((r) => setTimeout(r, 500))
|
|
184
203
|
]).catch(() => {
|
|
185
204
|
});
|
|
205
|
+
if (thisNav !== navigationId) {
|
|
206
|
+
log.info("FlowAction superseded by newer navigation", url);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
186
209
|
history.beforeTransition();
|
|
187
210
|
updateApp({
|
|
188
|
-
page: pagePromise.then(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
page: pagePromise.then(
|
|
212
|
+
(page) => {
|
|
213
|
+
if (thisNav !== navigationId) {
|
|
214
|
+
log.info("FlowAction commit superseded", url);
|
|
215
|
+
return page;
|
|
216
|
+
}
|
|
217
|
+
const canonicalURL = url;
|
|
218
|
+
if (shouldReplace) {
|
|
219
|
+
history.replaceState({ page }, canonicalURL);
|
|
220
|
+
} else {
|
|
221
|
+
history.pushState({ page }, canonicalURL);
|
|
222
|
+
}
|
|
223
|
+
callbacks.onNavigate(
|
|
224
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
225
|
+
);
|
|
226
|
+
didEnterPage(page);
|
|
227
|
+
return page;
|
|
228
|
+
},
|
|
229
|
+
(error) => {
|
|
230
|
+
if (thisNav === navigationId) {
|
|
231
|
+
const canonicalURL = url;
|
|
232
|
+
if (shouldReplace) {
|
|
233
|
+
history.replaceUrl(canonicalURL);
|
|
234
|
+
} else {
|
|
235
|
+
history.pushUrl(canonicalURL);
|
|
236
|
+
}
|
|
237
|
+
callbacks.onNavigate(
|
|
238
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
194
242
|
}
|
|
195
|
-
|
|
196
|
-
new URL(canonicalURL, window.location.origin).pathname
|
|
197
|
-
);
|
|
198
|
-
didEnterPage(page);
|
|
199
|
-
return page;
|
|
200
|
-
}),
|
|
243
|
+
),
|
|
201
244
|
isFirstPage
|
|
202
245
|
});
|
|
203
246
|
isFirstPage = false;
|
|
@@ -261,7 +304,8 @@ function registerActionHandlers(deps) {
|
|
|
261
304
|
framework,
|
|
262
305
|
log,
|
|
263
306
|
callbacks,
|
|
264
|
-
updateApp
|
|
307
|
+
updateApp,
|
|
308
|
+
getScrollablePageElement: deps.getScrollablePageElement
|
|
265
309
|
});
|
|
266
310
|
registerExternalUrlHandler({ framework, log });
|
|
267
311
|
}
|
|
@@ -312,7 +356,8 @@ async function startBrowserApp(config) {
|
|
|
312
356
|
framework,
|
|
313
357
|
log,
|
|
314
358
|
callbacks,
|
|
315
|
-
updateApp
|
|
359
|
+
updateApp,
|
|
360
|
+
getScrollablePageElement: config.getScrollablePageElement
|
|
316
361
|
});
|
|
317
362
|
if (initialAction) {
|
|
318
363
|
await framework.perform(initialAction.action);
|
|
@@ -334,4 +379,4 @@ export {
|
|
|
334
379
|
createPrefetchedIntentsFromDom,
|
|
335
380
|
startBrowserApp
|
|
336
381
|
};
|
|
337
|
-
//# sourceMappingURL=chunk-
|
|
382
|
+
//# sourceMappingURL=chunk-PHDR7PIL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../browser/src/action-handlers/external-url-action.ts","../../browser/src/utils/try-scroll.ts","../../browser/src/utils/history.ts","../../browser/src/action-handlers/flow-action.ts","../../browser/src/action-handlers/register.ts","../../browser/src/server-data.ts","../../browser/src/start-app.ts"],"sourcesContent":["/**\n * ExternalUrl Action Handler — 外部链接处理器\n */\n\nimport type { ExternalUrlAction, Framework, Logger } from \"@finesoft/core\";\nimport { ACTION_KINDS } from \"@finesoft/core\";\n\nexport interface ExternalUrlDependencies {\n\tframework: Framework;\n\tlog: Logger;\n}\n\nexport function registerExternalUrlHandler(\n\tdeps: ExternalUrlDependencies,\n): void {\n\tconst { framework, log } = deps;\n\n\tframework.onAction(\n\t\tACTION_KINDS.EXTERNAL_URL,\n\t\t(action: ExternalUrlAction) => {\n\t\t\tlog.debug(`ExternalUrlAction → ${action.url}`);\n\t\t\twindow.open(action.url, \"_blank\", \"noopener,noreferrer\");\n\t\t},\n\t);\n}\n","/**\n * tryScroll — 渐进式滚动位置恢复\n *\n * 使用 rAF 循环等待页面高度足够后恢复滚动位置。\n */\n\nimport type { Logger } from \"@finesoft/core\";\n\nconst MAX_TRIES = 100;\nconst FUDGE = 16;\n\nlet pendingFrame: number | null = null;\n\nexport function tryScroll(\n\tlog: Logger,\n\tgetScrollableElement: () => HTMLElement | null,\n\tscrollY: number,\n): void {\n\tif (pendingFrame !== null) {\n\t\tcancelAnimationFrame(pendingFrame);\n\t\tpendingFrame = null;\n\t}\n\n\tlet tries = 0;\n\n\tpendingFrame = requestAnimationFrame(function attempt() {\n\t\tif (++tries >= MAX_TRIES) {\n\t\t\tlog.warn(\n\t\t\t\t`tryScroll: gave up after ${MAX_TRIES} frames, target=${scrollY}`,\n\t\t\t);\n\t\t\tpendingFrame = null;\n\t\t\treturn;\n\t\t}\n\n\t\tconst el = getScrollableElement();\n\t\tif (!el) {\n\t\t\tlog.warn(\n\t\t\t\t\"could not restore scroll: the scrollable element is missing\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { scrollHeight, offsetHeight } = el;\n\t\tconst canScroll = scrollY + offsetHeight <= scrollHeight + FUDGE;\n\n\t\tif (!canScroll) {\n\t\t\tlog.info(\"page is not tall enough for scroll yet\", {\n\t\t\t\tscrollHeight,\n\t\t\t\toffsetHeight,\n\t\t\t});\n\t\t\tpendingFrame = requestAnimationFrame(attempt);\n\t\t\treturn;\n\t\t}\n\n\t\tel.scrollTop = scrollY;\n\t\tlog.info(\"scroll restored to\", scrollY);\n\t\tpendingFrame = null;\n\t});\n}\n","/**\n * History 管理器 — 浏览器历史状态 + 滚动位置\n */\n\nimport type { Logger } from \"@finesoft/core\";\nimport { LruMap, generateUuid } from \"@finesoft/core\";\nimport { tryScroll } from \"./try-scroll\";\n\nconst HISTORY_SIZE_LIMIT = 10;\n\ninterface HistoryEntry<State> {\n\tstate: State;\n\tscrollY: number;\n}\n\ninterface HistoryOptions {\n\tgetScrollablePageElement: () => HTMLElement | null;\n}\n\nexport class History<State> {\n\tprivate readonly entries: LruMap<string, HistoryEntry<State>>;\n\tprivate readonly log: Logger;\n\tprivate readonly getScrollablePageElement: () => HTMLElement | null;\n\tprivate currentStateId: string | undefined;\n\n\tconstructor(\n\t\tlog: Logger,\n\t\toptions: HistoryOptions,\n\t\tsizeLimit = HISTORY_SIZE_LIMIT,\n\t) {\n\t\tthis.entries = new LruMap(sizeLimit);\n\t\tthis.log = log;\n\t\tthis.getScrollablePageElement = options.getScrollablePageElement;\n\t}\n\n\treplaceState(state: State, url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.replaceState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.entries.set(id, { state, scrollY: 0 });\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"replaceState\", state, url, id);\n\t}\n\n\tpushState(state: State, url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.pushState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.entries.set(id, { state, scrollY: 0 });\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"pushState\", state, url, id);\n\t}\n\n\tbeforeTransition(): void {\n\t\tconst { state } = window.history;\n\t\tif (!state) return;\n\n\t\tconst oldEntry = this.entries.get(state.id);\n\t\tif (!oldEntry) {\n\t\t\tthis.log.info(\n\t\t\t\t\"current history state evicted from LRU, not saving scroll position\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst { scrollTop } = this;\n\t\tthis.entries.set(state.id, { ...oldEntry, scrollY: scrollTop });\n\t\tthis.log.info(\"saving scroll position\", scrollTop);\n\t}\n\n\tonPopState(\n\t\tlistener: (url: string, state?: State) => void | Promise<void>,\n\t): void {\n\t\twindow.addEventListener(\"popstate\", async (event: PopStateEvent) => {\n\t\t\tthis.currentStateId = event.state?.id;\n\n\t\t\tif (!this.currentStateId) {\n\t\t\t\tthis.log.warn(\n\t\t\t\t\t\"encountered a null event.state.id in onPopState event:\",\n\t\t\t\t\twindow.location.href,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.log.info(\"popstate\", this.entries, this.currentStateId);\n\n\t\t\tconst entry = this.currentStateId\n\t\t\t\t? this.entries.get(this.currentStateId)\n\t\t\t\t: undefined;\n\n\t\t\tawait listener(window.location.href, entry?.state);\n\n\t\t\tif (!entry) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst { scrollY } = entry;\n\t\t\tthis.log.info(\"restoring scroll to\", scrollY);\n\t\t\ttryScroll(this.log, () => this.getScrollablePageElement(), scrollY);\n\t\t});\n\t}\n\n\t/** 仅推入 URL,不缓存页面状态(用于页面加载失败场景) */\n\tpushUrl(url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.pushState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"pushUrl (no state)\", url, id);\n\t}\n\n\t/** 仅替换 URL,不缓存页面状态(用于页面加载失败场景) */\n\treplaceUrl(url: string): void {\n\t\tconst id = generateUuid();\n\t\twindow.history.replaceState({ id }, \"\", url);\n\t\tthis.currentStateId = id;\n\t\tthis.scrollTop = 0;\n\t\tthis.log.info(\"replaceUrl (no state)\", url, id);\n\t}\n\n\tupdateState(update: (current?: State) => State): void {\n\t\tif (!this.currentStateId) {\n\t\t\tthis.log.warn(\n\t\t\t\t\"failed: encountered a null currentStateId inside updateState\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst currentState = this.entries.get(this.currentStateId);\n\t\tconst newState = update(currentState?.state);\n\t\tthis.log.info(\"updateState\", newState, this.currentStateId);\n\t\tthis.entries.set(this.currentStateId, {\n\t\t\t...(currentState as HistoryEntry<State>),\n\t\t\tstate: newState,\n\t\t});\n\t}\n\n\tprivate get scrollTop(): number {\n\t\treturn this.getScrollablePageElement()?.scrollTop || 0;\n\t}\n\n\tprivate set scrollTop(scrollTop: number) {\n\t\tconst element = this.getScrollablePageElement();\n\t\tif (element) {\n\t\t\telement.scrollTop = scrollTop;\n\t\t}\n\t}\n}\n","/**\n * FlowAction Handler — 核心 SPA 导航处理器\n *\n * 通过 FlowActionCallbacks 注入 UI 更新回调,\n * 不直接依赖任何 UI 框架的 store。\n */\n\nimport type { BasePage, FlowAction, Framework, Logger } from \"@finesoft/core\";\nimport { ACTION_KINDS } from \"@finesoft/core\";\nimport { History } from \"../utils/history\";\n\n/** FlowAction handler 的 History state */\ninterface FlowState {\n\tpage: BasePage;\n}\n\n/** UI 框架回调 — 解耦 Svelte store 等依赖 */\nexport interface FlowActionCallbacks {\n\t/** 导航后更新当前路径(替代 currentPath.set()) */\n\tonNavigate(pathname: string): void;\n\t/** 模态页面展示(替代 openModal()) */\n\tonModal(page: BasePage): void;\n}\n\n/** 注册 FlowAction handler 所需的依赖 */\nexport interface FlowActionDependencies {\n\tframework: Framework;\n\tlog: Logger;\n\tcallbacks: FlowActionCallbacks;\n\t/** 更新应用 UI 的回调,page 可以是 Promise */\n\tupdateApp: (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n}\n\nexport function registerFlowActionHandler(deps: FlowActionDependencies): void {\n\tconst { framework, log, callbacks, updateApp } = deps;\n\tlet isFirstPage = true;\n\tlet navigationId = 0;\n\n\tconst defaultGetScrollable = (): HTMLElement | null =>\n\t\tdocument.getElementById(\"scrollable-page-override\") ||\n\t\tdocument.getElementById(\"scrollable-page\") ||\n\t\tdocument.documentElement;\n\n\tconst history = new History<FlowState>(log, {\n\t\tgetScrollablePageElement:\n\t\t\tdeps.getScrollablePageElement ?? defaultGetScrollable,\n\t});\n\n\t// ===== FlowAction handler =====\n\tframework.onAction(ACTION_KINDS.FLOW, async (action) => {\n\t\tconst flowAction = action as FlowAction;\n\t\tconst url = flowAction.url;\n\t\tlog.debug(`FlowAction → ${url}`);\n\n\t\t// 模态展示\n\t\tif (flowAction.presentationContext === \"modal\") {\n\t\t\tconst match = framework.routeUrl(url);\n\t\t\tif (match) {\n\t\t\t\tconst page = (await framework.dispatch(\n\t\t\t\t\tmatch.intent,\n\t\t\t\t)) as BasePage;\n\t\t\t\tcallbacks.onModal(page);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst thisNav = ++navigationId;\n\t\t// 首页初始化 或 重试同一 URL 时用 replace,避免重复历史条目\n\t\tconst shouldReplace =\n\t\t\tisFirstPage ||\n\t\t\turl === window.location.pathname + window.location.search;\n\n\t\tconst match = framework.routeUrl(url);\n\t\tif (!match) {\n\t\t\tlog.warn(`FlowAction: no route for ${url}`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst pagePromise = framework.dispatch(\n\t\t\tmatch.intent,\n\t\t) as Promise<BasePage>;\n\n\t\tawait Promise.race([\n\t\t\tpagePromise,\n\t\t\tnew Promise((r) => setTimeout(r, 500)),\n\t\t]).catch(() => {});\n\n\t\tif (thisNav !== navigationId) {\n\t\t\tlog.info(\"FlowAction superseded by newer navigation\", url);\n\t\t\treturn;\n\t\t}\n\n\t\thistory.beforeTransition();\n\n\t\tupdateApp({\n\t\t\tpage: pagePromise.then(\n\t\t\t\t(page: BasePage): BasePage => {\n\t\t\t\t\tif (thisNav !== navigationId) {\n\t\t\t\t\t\tlog.info(\"FlowAction commit superseded\", url);\n\t\t\t\t\t\treturn page;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst canonicalURL = url;\n\n\t\t\t\t\tif (shouldReplace) {\n\t\t\t\t\t\thistory.replaceState({ page }, canonicalURL);\n\t\t\t\t\t} else {\n\t\t\t\t\t\thistory.pushState({ page }, canonicalURL);\n\t\t\t\t\t}\n\n\t\t\t\t\tcallbacks.onNavigate(\n\t\t\t\t\t\tnew URL(canonicalURL, window.location.origin).pathname,\n\t\t\t\t\t);\n\n\t\t\t\t\tdidEnterPage(page);\n\t\t\t\t\treturn page;\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\t// 页面加载失败仍需推入历史记录,确保 back/forward 正常\n\t\t\t\t\tif (thisNav === navigationId) {\n\t\t\t\t\t\tconst canonicalURL = url;\n\t\t\t\t\t\tif (shouldReplace) {\n\t\t\t\t\t\t\thistory.replaceUrl(canonicalURL);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thistory.pushUrl(canonicalURL);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcallbacks.onNavigate(\n\t\t\t\t\t\t\tnew URL(canonicalURL, window.location.origin)\n\t\t\t\t\t\t\t\t.pathname,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\tthrow error;\n\t\t\t\t},\n\t\t\t),\n\t\t\tisFirstPage,\n\t\t});\n\n\t\tisFirstPage = false;\n\t});\n\n\t// ===== popstate handler =====\n\thistory.onPopState(async (url, cachedState) => {\n\t\tlog.debug(`popstate → ${url}, cached=${!!cachedState}`);\n\n\t\tcallbacks.onNavigate(new URL(url).pathname);\n\n\t\tif (cachedState) {\n\t\t\tconst { page } = cachedState;\n\t\t\tdidEnterPage(page);\n\t\t\tupdateApp({ page, isFirstPage });\n\t\t\treturn;\n\t\t}\n\n\t\tconst parsed = new URL(url);\n\t\tconst routeMatch = framework.routeUrl(parsed.pathname + parsed.search);\n\t\tif (!routeMatch) {\n\t\t\tlog.error(\n\t\t\t\t\"received popstate without data, but URL was unroutable:\",\n\t\t\t\turl,\n\t\t\t);\n\t\t\tdidEnterPage(null);\n\t\t\tupdateApp({\n\t\t\t\tpage: Promise.reject(new Error(\"404\")),\n\t\t\t\tisFirstPage,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst pagePromise = framework.dispatch(\n\t\t\trouteMatch.intent,\n\t\t) as Promise<BasePage>;\n\n\t\tawait Promise.race([\n\t\t\tpagePromise,\n\t\t\tnew Promise((r) => setTimeout(r, 500)),\n\t\t]).catch(() => {});\n\n\t\tupdateApp({\n\t\t\tpage: pagePromise.then((page: BasePage): BasePage => {\n\t\t\t\tdidEnterPage(page);\n\t\t\t\treturn page;\n\t\t\t}),\n\t\t\tisFirstPage,\n\t\t});\n\t});\n\n\tfunction didEnterPage(page: BasePage | null): void {\n\t\t(async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tif (page) {\n\t\t\t\t\tawait framework.didEnterPage(page);\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tlog.error(\"didEnterPage error:\", e);\n\t\t\t}\n\t\t})();\n\t}\n}\n","/**\n * Action Handler 统一注册入口\n */\n\nimport type { BasePage, Framework, Logger } from \"@finesoft/core\";\nimport { registerExternalUrlHandler } from \"./external-url-action\";\nimport {\n\tregisterFlowActionHandler,\n\ttype FlowActionCallbacks,\n} from \"./flow-action\";\n\nexport type { FlowActionCallbacks };\n\nexport interface ActionHandlerDependencies {\n\tframework: Framework;\n\tlog: Logger;\n\tcallbacks: FlowActionCallbacks;\n\tupdateApp: (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n}\n\nexport function registerActionHandlers(deps: ActionHandlerDependencies): void {\n\tconst { framework, log, callbacks, updateApp } = deps;\n\n\tregisterFlowActionHandler({\n\t\tframework,\n\t\tlog,\n\t\tcallbacks,\n\t\tupdateApp,\n\t\tgetScrollablePageElement: deps.getScrollablePageElement,\n\t});\n\n\tregisterExternalUrlHandler({ framework, log });\n}\n","/**\n * Server Data (browser side) — 从 DOM 反序列化服务端嵌入数据\n */\n\nimport { PrefetchedIntents, type PrefetchedIntent } from \"@finesoft/core\";\n\n/** DOM 中嵌入数据的 script 标签 ID */\nexport const SERVER_DATA_ID = \"serialized-server-data\";\n\n/**\n * 从 DOM 反序列化服务端嵌入的数据。\n * 读取 `<script id=\"serialized-server-data\">` 的内容并移除标签。\n */\nexport function deserializeServerData(): PrefetchedIntent[] | undefined {\n\tconst script = document.getElementById(SERVER_DATA_ID);\n\tif (!script?.textContent) return undefined;\n\n\tscript.parentNode?.removeChild(script);\n\n\ttry {\n\t\treturn JSON.parse(script.textContent);\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * 从 DOM 提取 SSR 数据并构建 PrefetchedIntents 实例。\n * 替代原来的 PrefetchedIntents.fromDom()。\n */\nexport function createPrefetchedIntentsFromDom(): PrefetchedIntents {\n\tconst data = deserializeServerData();\n\tif (!data || !Array.isArray(data)) {\n\t\treturn PrefetchedIntents.empty();\n\t}\n\treturn PrefetchedIntents.fromArray(data);\n}\n","/**\n * startBrowserApp — 客户端 hydration 一站式启动\n *\n * 封装:\n * 1. PrefetchedIntents 从 DOM 提取\n * 2. Framework 创建 + 引导\n * 3. 应用层挂载(通过 mount 回调,框架无关)\n * 4. Action handlers 注册\n * 5. 初始页面触发\n */\n\nimport type { BasePage, Logger } from \"@finesoft/core\";\nimport { DEP_KEYS, Framework, type LoggerFactory } from \"@finesoft/core\";\nimport {\n\tregisterActionHandlers,\n\ttype FlowActionCallbacks,\n} from \"./action-handlers/register\";\nimport { createPrefetchedIntentsFromDom } from \"./server-data\";\n\nexport interface BrowserAppConfig {\n\t/** 注册 controllers 和路由的引导函数 */\n\tbootstrap: (framework: Framework) => void;\n\n\t/** 默认语言(回退值) */\n\tdefaultLocale?: string;\n\n\t/** DOM 挂载点 ID(默认 \"app\") */\n\tmountId?: string;\n\n\t/** 获取可滚动页面元素,用于滚动位置保存/恢复 */\n\tgetScrollablePageElement?: () => HTMLElement | null;\n\n\t/**\n\t * 挂载应用到 DOM\n\t *\n\t * 框架无关 — Svelte / React / Vue 均可通过此回调实现。\n\t *\n\t * @param target - DOM 挂载点\n\t * @param context - Framework 实例 + 语言\n\t * @returns 更新函数,用于后续页面切换\n\t */\n\tmount: (\n\t\ttarget: HTMLElement,\n\t\tcontext: { framework: Framework; locale: string },\n\t) => (props: {\n\t\tpage: Promise<BasePage> | BasePage;\n\t\tisFirstPage?: boolean;\n\t}) => void;\n\n\t/** FlowAction / ExternalUrl 回调 */\n\tcallbacks: FlowActionCallbacks;\n}\n\n/**\n * 启动客户端应用\n *\n * 自动执行 hydration 全流程。\n */\nexport async function startBrowserApp(config: BrowserAppConfig): Promise<void> {\n\tconst {\n\t\tbootstrap,\n\t\tdefaultLocale = \"en\",\n\t\tmountId = \"app\",\n\t\tmount,\n\t\tcallbacks,\n\t} = config;\n\n\t// 1. 从 DOM 提取 PrefetchedIntents 缓存\n\tconst prefetchedIntents = createPrefetchedIntentsFromDom();\n\n\t// 2. 初始化 Framework + 注册 Controllers\n\tconst framework = Framework.create({ prefetchedIntents });\n\tbootstrap(framework);\n\n\tconst loggerFactory = framework.container.resolve<LoggerFactory>(\n\t\tDEP_KEYS.LOGGER_FACTORY,\n\t);\n\tconst log: Logger = loggerFactory.loggerFor(\"browser\");\n\n\t// 3. 路由初始 URL\n\tconst initialAction = framework.routeUrl(\n\t\twindow.location.pathname + window.location.search,\n\t);\n\n\t// 4. 挂载应用(框架无关)\n\tconst locale = document.documentElement.lang || defaultLocale;\n\tconst target = document.getElementById(mountId)!;\n\tconst updateApp = mount(target, { framework, locale });\n\n\t// 5. 注册 Action Handlers\n\tregisterActionHandlers({\n\t\tframework,\n\t\tlog,\n\t\tcallbacks,\n\t\tupdateApp,\n\t\tgetScrollablePageElement: config.getScrollablePageElement,\n\t});\n\n\t// 6. 触发初始页面\n\tif (initialAction) {\n\t\tawait framework.perform(initialAction.action);\n\t} else {\n\t\tupdateApp({\n\t\t\tpage: Promise.reject(new Error(\"404\")),\n\t\t\tisFirstPage: true,\n\t\t});\n\t}\n}\n"],"mappings":"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","names":["match"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1540,6 +1540,22 @@ var History = class {
|
|
|
1540
1540
|
tryScroll(this.log, () => this.getScrollablePageElement(), scrollY);
|
|
1541
1541
|
});
|
|
1542
1542
|
}
|
|
1543
|
+
/** 仅推入 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
1544
|
+
pushUrl(url) {
|
|
1545
|
+
const id = generateUuid();
|
|
1546
|
+
window.history.pushState({ id }, "", url);
|
|
1547
|
+
this.currentStateId = id;
|
|
1548
|
+
this.scrollTop = 0;
|
|
1549
|
+
this.log.info("pushUrl (no state)", url, id);
|
|
1550
|
+
}
|
|
1551
|
+
/** 仅替换 URL,不缓存页面状态(用于页面加载失败场景) */
|
|
1552
|
+
replaceUrl(url) {
|
|
1553
|
+
const id = generateUuid();
|
|
1554
|
+
window.history.replaceState({ id }, "", url);
|
|
1555
|
+
this.currentStateId = id;
|
|
1556
|
+
this.scrollTop = 0;
|
|
1557
|
+
this.log.info("replaceUrl (no state)", url, id);
|
|
1558
|
+
}
|
|
1543
1559
|
updateState(update) {
|
|
1544
1560
|
if (!this.currentStateId) {
|
|
1545
1561
|
this.log.warn(
|
|
@@ -1570,8 +1586,10 @@ var History = class {
|
|
|
1570
1586
|
function registerFlowActionHandler(deps) {
|
|
1571
1587
|
const { framework, log, callbacks, updateApp } = deps;
|
|
1572
1588
|
let isFirstPage = true;
|
|
1589
|
+
let navigationId = 0;
|
|
1590
|
+
const defaultGetScrollable = () => document.getElementById("scrollable-page-override") || document.getElementById("scrollable-page") || document.documentElement;
|
|
1573
1591
|
const history = new History(log, {
|
|
1574
|
-
getScrollablePageElement:
|
|
1592
|
+
getScrollablePageElement: deps.getScrollablePageElement ?? defaultGetScrollable
|
|
1575
1593
|
});
|
|
1576
1594
|
framework.onAction(ACTION_KINDS.FLOW, async (action) => {
|
|
1577
1595
|
const flowAction = action;
|
|
@@ -1587,7 +1605,8 @@ function registerFlowActionHandler(deps) {
|
|
|
1587
1605
|
}
|
|
1588
1606
|
return;
|
|
1589
1607
|
}
|
|
1590
|
-
const
|
|
1608
|
+
const thisNav = ++navigationId;
|
|
1609
|
+
const shouldReplace = isFirstPage || url === window.location.pathname + window.location.search;
|
|
1591
1610
|
const match = framework.routeUrl(url);
|
|
1592
1611
|
if (!match) {
|
|
1593
1612
|
log.warn(`FlowAction: no route for ${url}`);
|
|
@@ -1601,21 +1620,45 @@ function registerFlowActionHandler(deps) {
|
|
|
1601
1620
|
new Promise((r) => setTimeout(r, 500))
|
|
1602
1621
|
]).catch(() => {
|
|
1603
1622
|
});
|
|
1623
|
+
if (thisNav !== navigationId) {
|
|
1624
|
+
log.info("FlowAction superseded by newer navigation", url);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1604
1627
|
history.beforeTransition();
|
|
1605
1628
|
updateApp({
|
|
1606
|
-
page: pagePromise.then(
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1629
|
+
page: pagePromise.then(
|
|
1630
|
+
(page) => {
|
|
1631
|
+
if (thisNav !== navigationId) {
|
|
1632
|
+
log.info("FlowAction commit superseded", url);
|
|
1633
|
+
return page;
|
|
1634
|
+
}
|
|
1635
|
+
const canonicalURL = url;
|
|
1636
|
+
if (shouldReplace) {
|
|
1637
|
+
history.replaceState({ page }, canonicalURL);
|
|
1638
|
+
} else {
|
|
1639
|
+
history.pushState({ page }, canonicalURL);
|
|
1640
|
+
}
|
|
1641
|
+
callbacks.onNavigate(
|
|
1642
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
1643
|
+
);
|
|
1644
|
+
didEnterPage(page);
|
|
1645
|
+
return page;
|
|
1646
|
+
},
|
|
1647
|
+
(error) => {
|
|
1648
|
+
if (thisNav === navigationId) {
|
|
1649
|
+
const canonicalURL = url;
|
|
1650
|
+
if (shouldReplace) {
|
|
1651
|
+
history.replaceUrl(canonicalURL);
|
|
1652
|
+
} else {
|
|
1653
|
+
history.pushUrl(canonicalURL);
|
|
1654
|
+
}
|
|
1655
|
+
callbacks.onNavigate(
|
|
1656
|
+
new URL(canonicalURL, window.location.origin).pathname
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
throw error;
|
|
1612
1660
|
}
|
|
1613
|
-
|
|
1614
|
-
new URL(canonicalURL, window.location.origin).pathname
|
|
1615
|
-
);
|
|
1616
|
-
didEnterPage(page);
|
|
1617
|
-
return page;
|
|
1618
|
-
}),
|
|
1661
|
+
),
|
|
1619
1662
|
isFirstPage
|
|
1620
1663
|
});
|
|
1621
1664
|
isFirstPage = false;
|
|
@@ -1679,7 +1722,8 @@ function registerActionHandlers(deps) {
|
|
|
1679
1722
|
framework,
|
|
1680
1723
|
log,
|
|
1681
1724
|
callbacks,
|
|
1682
|
-
updateApp
|
|
1725
|
+
updateApp,
|
|
1726
|
+
getScrollablePageElement: deps.getScrollablePageElement
|
|
1683
1727
|
});
|
|
1684
1728
|
registerExternalUrlHandler({ framework, log });
|
|
1685
1729
|
}
|
|
@@ -1734,7 +1778,8 @@ async function startBrowserApp(config) {
|
|
|
1734
1778
|
framework,
|
|
1735
1779
|
log,
|
|
1736
1780
|
callbacks,
|
|
1737
|
-
updateApp
|
|
1781
|
+
updateApp,
|
|
1782
|
+
getScrollablePageElement: config.getScrollablePageElement
|
|
1738
1783
|
});
|
|
1739
1784
|
if (initialAction) {
|
|
1740
1785
|
await framework.perform(initialAction.action);
|