Skip to content

Commit

Permalink
Closed #184
Browse files Browse the repository at this point in the history
  • Loading branch information
mantou132 committed Jul 26, 2024
1 parent ad97b51 commit ba772b3
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 39 deletions.
2 changes: 1 addition & 1 deletion packages/duoyun-ui/docs/en/02-elements/003-scroll-base.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DuoyunScrollBaseElement

`DuoyunScrollBaseElement` fades content at scrollable border, e.g: [`<dy-more>`](./more.md),
and it has `--top-overflow` `--right-overflow` `--bottom-overflow` `--left-overflow` CSS state.
and it has `top-overflow` `right-overflow` `bottom-overflow` `left-overflow` CSS state.
2 changes: 1 addition & 1 deletion packages/duoyun-ui/docs/zh/02-elements/003-scroll-base.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DuoyunScrollBaseElement

`DuoyunScrollBaseElement` 在可滚动边界渐隐内容,例如 [`<dy-more>`](./more.md),
它有 `--top-overflow` `--right-overflow` `--bottom-overflow` `--left-overflow` CSS 状态。
它有 `top-overflow` `right-overflow` `bottom-overflow` `left-overflow` CSS 状态。
8 changes: 6 additions & 2 deletions packages/duoyun-ui/src/lib/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ export const contentsContainer = createCSSSheet(css`

export const noneTemplate = html`
<style>
:host,
:scope {
:host {
display: none;
}
@scope {
:scope {
display: none;
}
}
</style>
`;
1 change: 1 addition & 0 deletions packages/duoyun-ui/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type Maybe<T> = T | undefined | null;
export type StringList<T> = T | (string & Record<string, unknown>);

export type ElementOf<T> = T extends (infer E)[] ? E : never;
Expand Down
2 changes: 1 addition & 1 deletion packages/gem-devtools/src/elements/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const maybeBuildInPrefix = '[[Gem?]] ';
const buildInPrefix = '[[Gem]] ';

export const style = createCSSSheet(css`
:host {
:host(:not([hidden])) {
display: block;
line-height: 1.5;
cursor: default;
Expand Down
2 changes: 1 addition & 1 deletion packages/gem-devtools/src/scripts/get-gem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ export const getSelectedGem = function (data: PanelStore): PanelStore | string {
value: objectToString($0[key]),
type: typeof $0[key],
path: [key],
buildIn: buildInProperty.has(key) ? 1 : 0,
buildIn: buildInProperty.has(key) ? 2 : 0,
});
});

Expand Down
70 changes: 42 additions & 28 deletions packages/gem/src/lib/element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,20 @@ declare global {
interface HTMLElement {
ref: string;
}
interface DOMStringMap {
// 手动设置 'false' 让自定义元素不作为样式边界
styleScope?: 'false' | '';
gemReflect?: '';
[name: string]: string | undefined;
}
// https://github.com/tc39/proposal-decorator-metadata
interface SymbolConstructor {
metadata: symbol;
readonly metadata: symbol;
}
}
const { assign, defineProperty } = Object;

// https://github.com/tc39/proposal-decorator-metadata
Symbol.metadata = Symbol.for('Symbol.metadata');
defineProperty(Symbol, 'metadata', { value: Symbol.for('Symbol.metadata') });

function execCallback(fun: any) {
typeof fun === 'function' && fun();
Expand Down Expand Up @@ -65,7 +72,8 @@ export function appleCSSStyleSheet(ele: HTMLElement, sheets: CSSStyleSheet[]) {
// 先找到外部样式表
const newSheets = root.adoptedStyleSheets.filter((e) => !map.has(e));
map.forEach((count, sheet) => count && newSheets.push(sheet));
root.adoptedStyleSheets = newSheets;
// 外层元素的样式要放到最后,以提升优先级,但是只考虑第一次出现样式表的位置
root.adoptedStyleSheets = newSheets.reverse();
});
}

Expand Down Expand Up @@ -119,7 +127,7 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
readonly state?: State;

#renderRoot: HTMLElement | ShadowRoot;
#internals?: ElementInternals;
#internals: ElementInternals;
#isAppendReason?: boolean;
// 和 isConnected 有区别
#isMounted?: boolean;
Expand All @@ -128,6 +136,7 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
#rendering?: boolean;
#memoList?: EffectItem<any>[];
#unmountCallback?: any;
#clearStyle?: any;

[updateTokenAlias]() {
if (this.#isMounted) {
Expand All @@ -138,9 +147,10 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
constructor() {
super();

const { mode, serializable, delegatesFocus, slotAssignment, focusable, adoptedStyleSheets, aria } = this.#metadata;
const { mode, serializable, delegatesFocus, slotAssignment, focusable, aria } = this.#metadata;

this.#renderRoot = !mode ? this : this.attachShadow({ mode, serializable, delegatesFocus, slotAssignment });
this.#internals = this.attachInternals();

// https://stackoverflow.com/questions/43836886/failed-to-construct-customelement-error-when-javascript-file-is-placed-in-head
// focusable 元素一般同时具备 disabled 属性
Expand All @@ -150,7 +160,7 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
([disabled = false]) => {
if (hasInitTabIndex === undefined) hasInitTabIndex = this.hasAttribute('tabindex');

this.internals.ariaDisabled = String(disabled);
this.#internals.ariaDisabled = String(disabled);

if (focusable && !hasInitTabIndex) {
this.tabIndex = -Number(disabled);
Expand All @@ -165,31 +175,14 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
() => [(this as any).disabled],
);

Object.assign(this.internals, aria);

const sheets = adoptedStyleSheets?.map((item) => item[SheetToken].getStyle(this)) || [];
if (this.#renderRoot instanceof ShadowRoot) {
this.#renderRoot.adoptedStyleSheets = sheets;
} else {
this.effect(
() => {
// 阻止其他元素应用样式到当前元素
this.dataset.styleScope = '';
return appleCSSStyleSheet(this, sheets);
},
() => [],
);
}
assign(this.#internals, aria);
}

get #metadata(): Metadata {
return (this.constructor as any)[Symbol.metadata];
}

get internals() {
if (!this.#internals) {
this.#internals = this.attachInternals();
}
return this.#internals;
}

Expand All @@ -207,7 +200,7 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
* */
setState = (payload: Partial<State>) => {
if (!this.state) throw new GemError('`state` not initialized');
Object.assign(this.state, payload);
assign(this.state, payload);
// 避免无限刷新
if (!this.#rendering) addMicrotask(this.#update);
};
Expand Down Expand Up @@ -313,7 +306,7 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
* @lifecycle
*
* - 不提供 `render` 时显示子内容
* - 返回 `null` 时渲染空内容
* - 返回 `null` 时渲染空的子内容
* - 返回 `undefined` 时不会调用 `render()`, 也就是不会更新以前的内容
* */
render?(): TemplateResult | null | undefined;
Expand Down Expand Up @@ -380,8 +373,23 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
*/
unmounted?(): void | Promise<void>;

#prepareStyle = () => {
const { adoptedStyleSheets } = this.#metadata;

const { shadowRoot } = this.#internals;
// 阻止其他元素应用样式到当前元素
if (!shadowRoot && !this.dataset.styleScope) this.dataset.styleScope = '';
// 依赖 `dataset.styleScope`
const sheets = adoptedStyleSheets?.map((item) => item[SheetToken].getStyle(this)) || [];
if (shadowRoot) {
shadowRoot.adoptedStyleSheets = sheets;
} else {
return appleCSSStyleSheet(this, sheets);
}
};

#disconnectStore?: (() => void)[];
#connectedCallback = () => {
#connectedCallback = async () => {
if (this.#isAppendReason) {
this.#isAppendReason = false;
return;
Expand All @@ -394,6 +402,10 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
this.#disconnectStore = observedStores?.map((store) => connect(store, this.#update));
this.#render();
this.#isMounted = true;
this.#clearStyle = this.#prepareStyle();
// 等待所有元素的样式被应用,再执行回调
// 这让 mounted 和 effect 回调和其他回调一样保持一样的异步行为
await Promise.resolve();
this.#unmountCallback = this.mounted?.();
this.#initEffect();
if (rootElement && (this.getRootNode() as ShadowRoot).host?.tagName !== rootElement.toUpperCase()) {
Expand Down Expand Up @@ -437,10 +449,12 @@ export abstract class GemElement<State = Record<string, unknown>> extends HTMLEl
noBlockingTaskList.delete(this.#updateCallback);
this.#isMounted = false;
this.#disconnectStore?.forEach((disconnect) => disconnect());
// 是否要异步执行回调?
execCallback(this.#unmountCallback);
this.unmounted?.();
this.#effectList = this.#clearEffect(this.#effectList);
this.#memoList = this.#clearEffect(this.#memoList);
execCallback(this.#clearStyle);
return GemElement.#final;
}

Expand Down
14 changes: 9 additions & 5 deletions packages/gem/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,8 @@ export class GemCSSSheet {
#record = new Map<any, CSSStyleSheet>();
#applyd = new Map<CSSStyleSheet, string>();
getStyle(host?: HTMLElement) {
// GemElement.internals
const isLight = host && !(host as any).internals?.shadowRoot;
// GemElement Metadata, 支持 closed 模式
const isLight = host && !(host as any).constructor[Symbol.metadata]?.mode;

// 对同一类 dom 只使用同一个样式表
const key = isLight ? host.constructor : this;
Expand All @@ -270,9 +270,13 @@ export class GemCSSSheet {
if (!this.#applyd.has(sheet)) {
let style = this.#content;
let scope = '';
if (isLight) {
scope = `@scope (${host.tagName}) to ([data-style-scope])`;
style = `${scope}{${style}}`;
if (isLight && host.dataset.styleScope !== 'false') {
// light dom 嵌套时需要选择子元素
// `> *` 实际上是多范围?是否存在性能问题
scope = `@scope (${host.tagName}) to ([data-style-scope=''] > *)`;
// 不能使用 @layer,两个 @layer 不能覆盖,只有顺序起作用
// 所以外部不能通过元素名称选择器来覆盖样式,除非样式在之前插入(会自动反转应用到尾部, see `appleCSSStyleSheet`)
style = `${scope}{ ${style} }`;
}
sheet.replaceSync(style);
this.#applyd.set(sheet, scope);
Expand Down
4 changes: 4 additions & 0 deletions packages/gem/src/test/gem-element/advance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ describe('gem element 生命周期', () => {
const el: LifecycleGemElement = await fixture(html`<lifecycle-gem-demo></lifecycle-gem-demo>`);
// test #isMounted
customElements.define('lifecycle-gem-demo', LifecycleGemElement);
await Promise.resolve();
expect(!!el.refInConstructor).to.equal(true);
expect(el.renderCount).to.equal(1);
el.remove();
Expand All @@ -153,12 +154,14 @@ describe('gem element 生命周期', () => {

// disconnectedCallback, connectedCallback
container.append(el2);
await Promise.resolve();
expect(el2.mountedCount).to.equal(1);
expect(el2.renderCount).to.equal(1);
el2.remove();
expect(el2.mountedCount).to.equal(1);
expect(el2.renderCount).to.equal(0);
container.append(el2);
await Promise.resolve();
expect(el2.mountedCount).to.equal(2);
expect(el2.renderCount).to.equal(1);
expect(el2.updatedCount).to.equal(0);
Expand Down Expand Up @@ -241,6 +244,7 @@ describe('gem element 副作用', () => {
expect(el.effectCallCount).to.equal(0);
expect(el.hasConstructorEffect).to.equal(false);
document.body.append(el);
await Promise.resolve();
expect(el.hasConstructorEffect).to.equal(true);
});
});
Expand Down

0 comments on commit ba772b3

Please sign in to comment.