diff --git a/packages/gem-examples/src/life-cycle/children.ts b/packages/gem-examples/src/life-cycle/children.ts index eef4a995..ae31569a 100644 --- a/packages/gem-examples/src/life-cycle/children.ts +++ b/packages/gem-examples/src/life-cycle/children.ts @@ -17,6 +17,27 @@ import { export type Message = number[]; +@customElement('app-descendant') +@shadow() +export class Descendant extends GemElement { + key = 0; + + constructor() { + super(); + console.log(`descendant${this.key} cons`); + } + + @mounted() + #init = () => { + console.log(`descendant${this.key} mounted`); + }; + + render() { + console.log(`descendant${this.key} render`); + return html``; + } +} + /** * @attr first-name * @attr last-name @@ -70,6 +91,8 @@ export class Children extends GemElement {

properties: ${JSON.stringify(this.message)}

+ + `; } } diff --git a/packages/gem-examples/src/multi-page/index.ts b/packages/gem-examples/src/multi-page/index.ts index 4abf84bf..6fb2d1af 100644 --- a/packages/gem-examples/src/multi-page/index.ts +++ b/packages/gem-examples/src/multi-page/index.ts @@ -8,6 +8,8 @@ import '../elements/layout'; import './page-b'; import './page-c'; +// 没写 `basePath` + const routes = [ { pattern: '/', @@ -32,8 +34,21 @@ const routes = [ content: html`C: `, }, { - pattern: '/', - content: html`
hello
`, + pattern: '/empty', + content: html``, + }, + { + pattern: '/*', + content: html`
+ +
`, }, ]; @@ -62,6 +77,8 @@ class _App extends GemElement { PageA PageB PageC + Empty + Test
`; diff --git a/packages/gem/docs/en/003-api/008-utils.md b/packages/gem/docs/en/003-api/008-utils.md index 7385d0fd..89e4a763 100644 --- a/packages/gem/docs/en/003-api/008-utils.md +++ b/packages/gem/docs/en/003-api/008-utils.md @@ -2,13 +2,12 @@ Some frequently used functions -| name | description | -| --------------------- | -------------------------------------------------------------------------------- | -| `css` | Template string label function, only use for CSS text syntax highlighting | -| `raw` | Template string tag function, only use for HTML text syntax highlighting | -| `styled` | A collection of template string label functions, provide CSS highlighting | -| `styleMap` | Convert objects into `style` attribute | -| `classMap` | Convert objects into `class` attribute | -| `partMap` | Convert objects into `part` attribute | -| `addMicrotask` | Add microtask, avoid repetitive | -| `addMicrotaskToStack` | Similar to `addMicrotask`, but the microtasks added later will be executed first | +| name | description | +| -------------- | ------------------------------------------------------------------------- | +| `css` | Template string label function, only use for CSS text syntax highlighting | +| `raw` | Template string tag function, only use for HTML text syntax highlighting | +| `styled` | A collection of template string label functions, provide CSS highlighting | +| `styleMap` | Convert objects into `style` attribute | +| `classMap` | Convert objects into `class` attribute | +| `partMap` | Convert objects into `part` attribute | +| `addMicrotask` | Add microtask, avoid repetitive | diff --git a/packages/gem/docs/zh/003-api/008-utils.md b/packages/gem/docs/zh/003-api/008-utils.md index 8c6727cf..194baa7b 100644 --- a/packages/gem/docs/zh/003-api/008-utils.md +++ b/packages/gem/docs/zh/003-api/008-utils.md @@ -2,13 +2,12 @@ 一些经常被用到的函数 -| 名称 | 描述 | -| --------------------- | --------------------------------------------------------- | -| `css` | 模版字符串标签函数,仅用于 CSS 文本语法高亮 | -| `raw` | 模版字符串标签函数,仅用于 HTML 文本语法高亮 | -| `styled` | 模版字符串标签函数的集合,用来提供 CSS 规则高亮和 JS 引用 | -| `styleMap` | 将对象构建成 `style` 属性 | -| `classMap` | 将对象构建成 `class` 属性 | -| `partMap` | 将对象构建成 `part` 属性 | -| `addMicrotask` | 添加微任务,会避免重复 | -| `addMicrotaskToStack` | 类似 `addMicrotask`, 但先添加的微任务后执行 | +| 名称 | 描述 | +| -------------- | --------------------------------------------------------- | +| `css` | 模版字符串标签函数,仅用于 CSS 文本语法高亮 | +| `raw` | 模版字符串标签函数,仅用于 HTML 文本语法高亮 | +| `styled` | 模版字符串标签函数的集合,用来提供 CSS 规则高亮和 JS 引用 | +| `styleMap` | 将对象构建成 `style` 属性 | +| `classMap` | 将对象构建成 `class` 属性 | +| `partMap` | 将对象构建成 `part` 属性 | +| `addMicrotask` | 添加微任务,会避免重复 | diff --git a/packages/gem/src/elements/base/link.ts b/packages/gem/src/elements/base/link.ts index 98530632..1c04e63a 100644 --- a/packages/gem/src/elements/base/link.ts +++ b/packages/gem/src/elements/base/link.ts @@ -10,6 +10,7 @@ import { adoptedStyle, mounted, effect, + boolattribute, } from '../../lib/decorators'; import { history, basePathStore } from '../../lib/history'; import { absoluteLocation, css } from '../../lib/utils'; @@ -61,8 +62,9 @@ export class GemLinkElement extends GemElement { @attribute path: string; @attribute query: string; @attribute hash: string; - @attribute docTitle: string; @attribute hint: 'on' | 'off'; + /** Preload route content when point over */ + @boolattribute preload: boolean; // 动态路由,根据 route.pattern 和 options.params 计算出 path @property route?: RouteItem; @@ -113,24 +115,19 @@ export class GemLinkElement extends GemElement { await this.prepare?.(); if (this.route) { - history.pushIgnoreCloseHandle({ - ...createHistoryParams(this.route, this.#routeOptions), - title: this.route.title || this.docTitle, - }); + history.pushIgnoreCloseHandle(createHistoryParams(this.route, this.#routeOptions)); } else if (this.href) { const url = new URL(locationString, location.origin); history.pushIgnoreCloseHandle({ path: url.pathname, query: url.search, hash: url.hash, - title: this.docTitle, }); } else { history.pushIgnoreCloseHandle({ path: this.path, query: this.query, hash: this.hash, - title: this.docTitle, }); } }; @@ -146,9 +143,17 @@ export class GemLinkElement extends GemElement { : new URL(history.basePath + locationString, location.origin).toString(); }; + #preload = () => { + // 也许是 `getter` + this.route?.content; + // 只触发 `getContent` 调用,参数不使用 + this.route?.getContent?.({}, this.shadowRoot!); + }; + @mounted() #init() { this.addEventListener('click', this.#onClick); + this.addEventListener('pointerenter', this.#preload); } render() { diff --git a/packages/gem/src/elements/base/route.ts b/packages/gem/src/elements/base/route.ts index df462acc..86fd9804 100644 --- a/packages/gem/src/elements/base/route.ts +++ b/packages/gem/src/elements/base/route.ts @@ -314,7 +314,10 @@ export class GemLightRouteElement extends GemElement { return; } if (this.trigger === history) { - updateStore(titleStore, { title: route?.title }); + // 嵌套路由执行顺序也是从父到子 + if (location.href !== titleStore.url || route?.title) { + updateStore(titleStore, { url: location.href, title: route?.title }); + } } const contentOrLoader = content || getContent?.(params, this.shadowRoot || this); this.loading(route); diff --git a/packages/gem/src/elements/base/title.ts b/packages/gem/src/elements/base/title.ts index a36d4f5c..72938b47 100644 --- a/packages/gem/src/elements/base/title.ts +++ b/packages/gem/src/elements/base/title.ts @@ -7,41 +7,34 @@ * 修改标题:titleStore 的 title 优先级高,比如 history 添加的 dialog Title * * - `` 匹配路由时自动设置 `route.title` - * - `` 的 `doc-title` 属性和 `route.title` - * - 修改路径时,`history` 默认设置 `title` 为空 + * - 修改路径(查询参数)时,`history` 默认设置 `title` 为空,运行 Modal 设置临时标题 * - 数据获取后,手动调用 `Title.setTitle` * - `` 作为默认值设置 */ import { GemElement, html } from '../../lib/element'; -import { attribute, boolattribute, effect, shadow } from '../../lib/decorators'; +import { attribute, boolattribute, effect, mounted, shadow } from '../../lib/decorators'; import { updateStore, connect } from '../../lib/store'; import { titleStore } from '../../lib/history'; -import { addMicrotask } from '../../lib/utils'; -const defaultTitle = document.title; +// 避免重定向时的中间状态标题 +let timer = 0; +const setTitle = (documentTitle: string) => { + clearTimeout(timer); + timer = window.setTimeout(() => (document.title = documentTitle)); +}; -let documentTitle = ''; -const setTitle = () => (document.title = documentTitle); - -function setDocumentTitle(str?: string | null, prefix = '', suffix = '') { - const title = titleStore.title || str; - if (title && title !== defaultTitle) { +function setDocumentTitle(defaultTitle?: string | null, prefix = '', suffix = '') { + const title = titleStore.title || defaultTitle; + if (title && title !== titleStore.defaultTitle) { GemTitleElement.title = title; - documentTitle = prefix + GemTitleElement.title + suffix; + setTitle(prefix + GemTitleElement.title + suffix); } else { - GemTitleElement.title = defaultTitle; - documentTitle = GemTitleElement.title; + GemTitleElement.title = titleStore.defaultTitle; + setTitle(GemTitleElement.title); } - // 避免重定向时的中间状态 - addMicrotask(setTitle); } -connect(titleStore, setDocumentTitle); - -export const PREFIX = `${defaultTitle} | `; -export const SUFFIX = ` - ${defaultTitle}`; - /** * @customElement gem-title * @attr prefix @@ -60,21 +53,19 @@ export class GemTitleElement extends GemElement { updateStore(titleStore, { title }); } - @effect(() => []) - #mounted = () => { - new MutationObserver(() => this.update()).observe(this, { - characterData: true, - childList: true, - subtree: true, - }); - }; - @effect((i) => [i.inert]) #connectStore = () => !this.inert && connect(titleStore, this.update); - render() { - // 多个 时,最终 document.title 按执行顺序决定 + #ob = new MutationObserver(() => this.update()); + + @mounted() + #obTextContent = () => { + this.#ob.observe(this, { characterData: true, childList: true, subtree: true }); + }; + + render = () => { + // 多个 时,最终 document.title 按渲染顺序决定 setDocumentTitle(this.textContent?.trim(), this.prefix, this.suffix); return html`${GemTitleElement.title}`; - } + }; } diff --git a/packages/gem/src/lib/history.ts b/packages/gem/src/lib/history.ts index 6383ea5a..d00a4eb8 100644 --- a/packages/gem/src/lib/history.ts +++ b/packages/gem/src/lib/history.ts @@ -183,7 +183,7 @@ class GemHistory extends EventTarget { // `` 只读写父应用 `basePath` const gemHistory = new GemHistory(); -const [gemTitleStore, updateTitleStore] = useStore({ title: '' }); +const [gemTitleStore, updateTitleStore] = useStore({ defaultTitle: document.title, url: '', title: '' }); const _GEMHISTORY = { history: gemHistory, titleStore: gemTitleStore, basePathStore: gemBasePathStore }; diff --git a/packages/gem/src/lib/utils.ts b/packages/gem/src/lib/utils.ts index 83e4bc13..d7632a1e 100644 --- a/packages/gem/src/lib/utils.ts +++ b/packages/gem/src/lib/utils.ts @@ -12,26 +12,6 @@ export function addMicrotask(func: () => void) { microtaskSet.add(func); } -const microtaskStack: (() => void)[] = []; - -function execMicrotaskStack() { - for (let i = microtaskStack.length - 1; i >= 0; i--) { - microtaskStack[i](); - } - microtaskStack.length = 0; -} - -/** - * 添加回调函数到微任务队列栈; - * 先进后执行,使用这个函数可以改变嵌套的元素 `mounted` 的顺序; - */ -export function addMicrotaskToStack(func: () => void) { - if (!microtaskStack.length) { - addMicrotask(execMicrotaskStack); - } - microtaskStack.push(func); -} - // 不编码 hash 用于比较 export function absoluteLocation(currentPath = '', relativePath = '') { const { pathname, search, hash } = new URL(relativePath, location.origin + currentPath);