✨ Layers
1aron opened this issue · comments
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
: rulesCSSLayerBlockRule
有.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