css
css copied to clipboard
✨ Layers
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,相關變數也要跟著改 - [x]
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還是utilitylayer
重構 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