Files
gitea-github-theme/src/core/css.ts
T
2026-06-27 11:49:59 +08:00

94 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*!
* Copyright (c) https://github.com/lutinglt
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { StyleRule } from "@vanilla-extract/css";
import { transform } from "lightningcss";
import { getCallerInfo } from "../utils";
import { captureStyle } from "../vanilla-extract";
declare const CSSBrand: unique symbol;
type CSSObject = { [CSSBrand]: true };
type CSSPrimitive = string | number;
type CSSInterpolation = CSSPrimitive | CSSObject | undefined;
type SelectorStyleRule =
| StyleRule
| {
[key: string]: StyleRule | SelectorStyleRule;
};
export type CSSString = string & CSSObject;
/** 将类型标记为品牌类型 */
function brandCSS<T>(input: T): T & CSSObject {
return input as T & CSSObject;
}
/**
* css 模板字符串 —— 类式 linaria 的 css tag,用于 vscode-styled-components 高亮。
* 构建时校验 CSS 语法并返回 CSS 字符串。
*/
export function css(strings: TemplateStringsArray, ...values: CSSInterpolation[]): CSSString {
const result = strings.reduce((acc, s, i) => acc + s + String(values[i] ?? ""), "");
// 构建时校验 CSS 语法
transform({ filename: getCallerInfo(1), code: Buffer.from(result) });
return brandCSS(result);
}
/**
* 将多个 CSS 字符串合并为一个 CSS 字符串
*
* 合并按参数顺序排列
*/
export function cssCombine(...styles: (CSSString | undefined)[]): CSSString {
return brandCSS(styles.filter(Boolean).join("\n"));
}
/** cssStyle 用于 css 模板字符串的插值
* 提取可复用的样式, 并保留 vanilla-extract 的类型检查
* - 属性访问:保留原值(如 style.padding → "0px 6px")
* - 字符串插值:自动输出 CSS 片段(如 `${style}` → "padding:0px 6px;")
*/
export function cssStyle<T extends SelectorStyleRule>(input: T): T & CSSObject {
const flat: Record<string, CSSPrimitive> = {};
const nested: Record<string, SelectorStyleRule> = {};
// Object.entries 对无索引签名的 StyleRule 只能返回 any/unknown,
// 这里做最小断言:entry 的值是 CSS 叶子值(string|number)或嵌套对象
for (const [key, val] of Object.entries(input) as [string, CSSPrimitive | SelectorStyleRule][]) {
if (val === null || val === undefined) continue;
if (typeof val === "object") {
nested[key] = cssStyle(val);
} else {
flat[key] = val;
}
}
// 构建 CSS 字符串
const cssParts: string[] = [];
if (Object.keys(flat).length > 0) {
cssParts.push(captureStyle(flat));
}
for (const [key, val] of Object.entries(nested)) {
cssParts.push(`${key}{${String(val)}}`);
}
const cssText = cssParts.join("");
input.toString = () => cssText;
return brandCSS(input);
}