Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Layers #354

Open
1 of 24 tasks
1aron opened this issue May 31, 2024 · 0 comments
Open
1 of 24 tasks

✨ Layers #354

1aron opened this issue May 31, 2024 · 0 comments
Assignees
Labels
feature New feature

Comments

@1aron
Copy link
Member

1aron commented May 31, 2024

Description

🚧 Under internal discussion

開發 Master UI 第一個 Button 元件後我意識到沒有透過 CSS Layers 管理優先級會導致未預期的樣式覆蓋並使 config.styles 無法支援選取器及媒體查詢。

假設 master.css.js:

export default {
    styles: {
        btn: 'inline-flex p:4x bg:white:focus bg:black:hover h:32@sm'
    }
}

預期重構後的 style.sheet

@layer base, preset, theme, style, utility;
@layer theme {
    :root { --text-strong: 0 0 0 }
    .light { --text-strong: 24 32 48 }
    .light { --text-light: 95 115 149 }
    .dark { --text-strong: 239 238 240 }
    .dark { --text-light: 137 136 138 }
}
@layer style {
    .btn { display: inline-flex }               /* inline-flex */
    .btn { padding: 1rem }                      /* p:4x */
    .btn:hover { background-color: #000000 }    /* bg:black:hover */
    .btn:focus { background-color: #ffffff }    /* bg:white:focus */
    @media (min-width: 834px) {                 /* h:32@sm */
        .btn { height: 2rem; }
    }
}
@layer utility {
    .p\:4x { padding: 1rem }
    .pt\:8x { padding-top: 2rem }
}
/* anonymous layer */
@keyframes fade-in {}

產生的 style.sheet.cssRules

[
    CSSLayerStatementRule,
    CSSLayerBlockRule { name: 'theme'  },
    CSSLayerBlockRule { name: 'style'  },
    CSSLayerBlockRule { name: 'utility'  },
    CSSKeyframesRule { name: 'fade'  },
    CSSKeyframesRule { name: 'flash'  },
]
  • @layer base: 無需操作,通常給 @master/normal.css 使用
  • @layer theme: variables
  • @layer style: styles
  • @layer utility: rules
  • CSSLayerBlockRule.cssRules 可以直接插入

實作

  • @keyframes 隨意插入至匿名層
  • css.hasKeyframesRule 相關邏輯不再需要,包含 variable
  • enum Layer 應改為 enum SyntaxType,它壓根就不是 layer,相關變數也要跟著改
  • config.rules 重新命名為 config.syntaxes
  • 固定插入第一個陳述 @layer base, preset, theme, style, utility;
  • css.Rules 可以重命名為 css.syntaxes

全新 class LayerRule

現在引入 Layer 概念就必須創建 class Layer 形成多個封閉區來排序各自的 rules

// core.ts
class MasterCSS {
    layerStatementRule = new Rule('layer-statement', [
        { text: '@layer base,preset,theme,style,utility' }
    ], this);
    themeLayer = new Layer('theme', this);
    styleLayer = new SyntaxLayer('style', this);
    utilityLayer = new SyntaxLayer('utility', this);
    keyframeLayer = new AnonymousLayer('keyframe', this);
    sheet = new AnonymousLayer('', this);
    constructor() {
        this.sheet.rules = [
            this.layerStatementRule,
            this.themeLayer,
            this.styleLayer,
            this.utilityLayer,
            this.keyframeLayer
        ]
    }
    get text(): string {
        return this.sheet.text
    }
}
// layer.ts
class Layer {
    native?: CSSLayerBlockRule | CSSStyleSheet
    rules: Rule[] = []
    usages: Record<string, number>
    constructor(
        public name: string,
        public css: MasterCSS
    ) {}
    insert(rule: Rule, index?: number) {}
    delete(rule: Rule) {}
    get text(): string {
        return '@layer ' + this.name + '{' + this.rules.map((eachRule) => eachRule.text).join('') + '}'
    }
}
  • layer.insert() 實作簡易的 this.rules.push(rule)
    • 透過 index 可以插入指定的位置,實際會用到的像是在 anonymousLayer 的頂部插入 CSSLayerStatementRule
  • layer.delete() 實作簡易的 this.rules.splice(this.rules.indexOf(rule), 1)
  • 執行插入及刪除時必須記錄 usages
    • 舉例 themeLayer.usages 的 keys 可能是 --text-purple
    • 舉例 styleLayer.usages 的 keys 可能是 btn
    • 舉例 utilityLayer.usages 的 keys 可能是 fg:white
    • 舉例 keyframeLayer.usages 的 keys 可能是 fade
// anonymous-layer.ts
class AnonymousLayer extends Layer {
    native?: CSSStyleSheet
    rules: (Rule | Layer)[] = []
    constructor(
        public name: string,
        public css: MasterCSS
    ) {}
    get text(): string {
        return this.rules.map((eachRule) => eachRule.text).join('')
    }
}
  • 匿名層主要用來連接 CSSStyleSheet 並方便統一操縱
// syntax-layer.ts
class SyntaxLayer extends Layer {
    native?: CSSLayerBlockRule
    constructor(
        public name: string,
        public css: MasterCSS
    ) {
        super()
    }
    insert(rule: Rule) {}
    delete(rule: Rule) {}
}
  • css.insert() 應改寫為 syntaxLayer.insert()
  • css.delete() 內的 deleteRule 函數應改寫為 syntaxLayer.delete()
  • css.delete() 重新命名為 css.remove() 以對應 css.add()
  • css.add() 時判斷該 className 屬於哪個 style 還是 utility layer

重構 Rule

// rule.ts
class Rule {
    constructor(
        public readonly name: string,
        public natives: NativeRule[] = [],
        public css: MasterCSS
    ) {}
    get text(): string {
        return this.natives.map((eachNative) => eachNative.text).join('')
    }
}
// syntax-rule.ts
class SyntaxRule extends Rule {
    constructor(
        public readonly name: string,
        public natives: NativeRule[] = [],
        public css: MasterCSS,
        public readonly RegisteredRule: RegisteredRule,
    ) {
        super(name, layer, natives)
    }
}
  • Rule 重新命名為 SyntaxRule 並調整 constructor 傳入參數
  • Rule 改為一個共通類
    • SyntaxRule 擴展
    • 創建 VariableRule extends Rule 並將相關的 methods 整併
    • 創建 KeyframeRule extends Rule 並將相關的 methods 整併

@layer theme

  • 主題變數統一放在 @layer theme {},這樣可以更容易地與其他規則分開。
  • 特別注意的是 :root 相關變數必須從 0 插入,避免出現在 .light .dark 之後
  • 目前 --variable 以單個 .light 包含所有變數,一個 class/host/media 只能包含一個 --variable,這才能對單一變數進行新增/刪除,而不是 PUT 整個規則導致額外的開銷。

@layer style

  • 一個 style class 會對應多個 layers[].rules
  • 建立 rule.fixedClass 用來固定為目標 class 名稱。像原本是 .bg\:black\:hover:hover {} 要固定為 .btn:hover {}

解決問題

  • 使用 utility 修飾 style 不需要再加 !btn font:semibold! -> btn font:semibold
  • config.styles 支援 at-rules。btn-sm@<sm
@1aron 1aron added the feature New feature label May 31, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature
Projects
None yet
Development

No branches or pull requests

2 participants