@gtkx/css 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/create-css.d.ts +2 -4
- package/dist/create-css.d.ts.map +1 -1
- package/dist/create-css.js +11 -3
- package/dist/create-css.js.map +1 -1
- package/dist/named-colors.d.ts +1 -3
- package/dist/named-colors.d.ts.map +1 -1
- package/dist/named-colors.js +1 -1
- package/dist/named-colors.js.map +1 -1
- package/dist/self-contained.d.ts +3 -0
- package/dist/self-contained.d.ts.map +1 -0
- package/dist/self-contained.js +39 -0
- package/dist/self-contained.js.map +1 -0
- package/dist/stylesheet.d.ts.map +1 -1
- package/dist/stylesheet.js +10 -0
- package/dist/stylesheet.js.map +1 -1
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +77 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +13 -3
- package/src/create-css.ts +16 -4
- package/src/named-colors.ts +1 -1
- package/src/self-contained.ts +56 -0
- package/src/stylesheet.ts +16 -0
- package/src/tokens.ts +108 -0
package/README.md
CHANGED
package/dist/create-css.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { CSSInterpolation } from "@emotion/serialize";
|
|
2
|
-
import type { Element } from "stylis";
|
|
3
2
|
/** A `cx` argument: a non-empty class name, or a boolean or nullish value that is dropped. */
|
|
4
3
|
type CxToken = string | boolean | undefined | null;
|
|
5
|
-
/** The `css`, `cx` and `injectGlobal`
|
|
4
|
+
/** The `css`, `cx` and `injectGlobal` helpers bound to one GTK4 stylesheet and its cache of inserted styles. */
|
|
6
5
|
type Css = {
|
|
7
6
|
/**
|
|
8
7
|
* Serializes the given style interpolations, inserts the resulting rules into the stylesheet, and
|
|
@@ -17,7 +16,6 @@ type Css = {
|
|
|
17
16
|
/** Serializes and inserts the given styles into the stylesheet globally, unscoped by any class. */
|
|
18
17
|
injectGlobal: (...args: CSSInterpolation[]) => void;
|
|
19
18
|
};
|
|
20
|
-
declare const removeLabel: (element: Element) => void;
|
|
21
19
|
declare const createCss: () => Css;
|
|
22
|
-
export {
|
|
20
|
+
export { createCss, type Css };
|
|
23
21
|
//# sourceMappingURL=create-css.d.ts.map
|
package/dist/create-css.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-css.d.ts","sourceRoot":"","sources":["../src/create-css.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAqC,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"create-css.d.ts","sourceRoot":"","sources":["../src/create-css.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAqC,MAAM,oBAAoB,CAAC;AAO9F,8FAA8F;AAC9F,KAAK,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,IAAI,CAAC;AAEnD,gHAAgH;AAChH,KAAK,GAAG,GAAG;IACP;;;OAGG;IACH,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,gBAAgB,EAAE,KAAK,MAAM,CAAC;IAC7C;;;OAGG;IACH,EAAE,EAAE,CAAC,GAAG,UAAU,EAAE,OAAO,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3C,mGAAmG;IACnG,YAAY,EAAE,CAAC,GAAG,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;CACvD,CAAC;AA+GF,QAAA,MAAM,SAAS,QAAO,GAUrB,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,CAAC"}
|
package/dist/create-css.js
CHANGED
|
@@ -15,6 +15,13 @@ const removeLabel = (element) => {
|
|
|
15
15
|
element.value = "";
|
|
16
16
|
};
|
|
17
17
|
const getClassName = (serialized) => `${KEY}-${serialized.name}`;
|
|
18
|
+
const terminateDeclarations = (styles) => {
|
|
19
|
+
const trimmed = styles.trimEnd();
|
|
20
|
+
if (trimmed.length === 0 || trimmed.endsWith(";") || trimmed.endsWith("}")) {
|
|
21
|
+
return trimmed;
|
|
22
|
+
}
|
|
23
|
+
return `${trimmed};`;
|
|
24
|
+
};
|
|
18
25
|
const isNonEmptyString = (token) => typeof token === "string" && token.length > 0;
|
|
19
26
|
const partitionTokens = (tokens, registered) => {
|
|
20
27
|
const rawClasses = [];
|
|
@@ -51,8 +58,9 @@ const insertStyles = (state, serialized) => {
|
|
|
51
58
|
return;
|
|
52
59
|
}
|
|
53
60
|
const className = getClassName(serialized);
|
|
54
|
-
|
|
55
|
-
state.
|
|
61
|
+
const styles = terminateDeclarations(serialized.styles);
|
|
62
|
+
runStylis(state.sheet, `.${className}{${styles}}`);
|
|
63
|
+
state.registered[className] = styles;
|
|
56
64
|
};
|
|
57
65
|
const insertWithoutScoping = (state, serialized) => {
|
|
58
66
|
if (!didMarkNewStyle(state, serialized)) {
|
|
@@ -83,5 +91,5 @@ const createCss = () => {
|
|
|
83
91
|
},
|
|
84
92
|
};
|
|
85
93
|
};
|
|
86
|
-
export {
|
|
94
|
+
export { createCss };
|
|
87
95
|
//# sourceMappingURL=create-css.js.map
|
package/dist/create-css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-css.js","sourceRoot":"","sources":["../src/create-css.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,IAAI,eAAe,EAAE,MAAM,QAAQ,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAwB7C,MAAM,GAAG,GAAG,MAAM,CAAC;AACnB,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAEjC,MAAM,WAAW,GAAG,CAAC,OAAgB,EAAQ,EAAE;IAC3C,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM;QACzB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,qBAAqB;QACtD,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,KAAK,qBAAqB,CAAC,EAAE,CAAC;QAC1D,OAAO;IACX,CAAC;IAED,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;AACvB,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,UAA4B,EAAU,EAAE,CAAC,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"create-css.js","sourceRoot":"","sources":["../src/create-css.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CSSInterpolation, RegisteredCache, SerializedStyles } from \"@emotion/serialize\";\nimport type { Element } from \"stylis\";\nimport { serializeStyles } from \"@emotion/serialize\";\nimport { compile, middleware, rulesheet, stringify, serialize as stylisSerialize } from \"stylis\";\nimport { escapeNamedColors, restoreNamedColors } from \"./named-colors.js\";\nimport { StyleSheet } from \"./stylesheet.js\";\n\n/** A `cx` argument: a non-empty class name, or a boolean or nullish value that is dropped. */\ntype CxToken = string | boolean | undefined | null;\n\n/** The `css`, `cx` and `injectGlobal` helpers bound to one GTK4 stylesheet and its cache of inserted styles. */\ntype Css = {\n /**\n * Serializes the given style interpolations, inserts the resulting rules into the stylesheet, and\n * returns the generated GTK4 CSS class name.\n */\n css: (...args: CSSInterpolation[]) => string;\n /**\n * Filters out falsy tokens and returns the remaining class names, collapsing two or more\n * GTKX-generated classes into one merged class where later styles win.\n */\n cx: (...classNames: CxToken[]) => string[];\n /** Serializes and inserts the given styles into the stylesheet globally, unscoped by any class. */\n injectGlobal: (...args: CSSInterpolation[]) => void;\n};\n\ntype TokenPartition = { rawClasses: string[]; registeredStyles: string[] };\ntype CssState = { sheet: StyleSheet; inserted: Set<string>; registered: RegisteredCache };\n\nconst KEY = \"gtkx\";\nconst LABEL_DECL_FIRST_CHAR = 108;\nconst LABEL_DECL_THIRD_CHAR = 98;\n\nconst removeLabel = (element: Element): void => {\n if (!(element.type === \"decl\" &&\n element.value.codePointAt(0) === LABEL_DECL_FIRST_CHAR &&\n element.value.codePointAt(2) === LABEL_DECL_THIRD_CHAR)) {\n return;\n }\n\n element.return = \"\";\n element.value = \"\";\n};\n\nconst getClassName = (serialized: SerializedStyles): string => `${KEY}-${serialized.name}`;\n\nconst terminateDeclarations = (styles: string): string => {\n const trimmed = styles.trimEnd();\n\n if (trimmed.length === 0 || trimmed.endsWith(\";\") || trimmed.endsWith(\"}\")) {\n return trimmed;\n }\n\n return `${trimmed};`;\n};\n\nconst isNonEmptyString = (token: CxToken): token is string => typeof token === \"string\" && token.length > 0;\n\nconst partitionTokens = (tokens: string[], registered: RegisteredCache): TokenPartition => {\n const rawClasses: string[] = [];\n const registeredStyles: string[] = [];\n\n for (const token of tokens) {\n const styles = registered[token];\n\n if (styles === undefined) {\n rawClasses.push(token);\n } else {\n registeredStyles.push(styles);\n }\n }\n\n return { rawClasses, registeredStyles };\n};\n\nconst runStylis = (sheet: StyleSheet, input: string): void => {\n stylisSerialize(\n compile(escapeNamedColors(input)),\n middleware([\n removeLabel,\n stringify,\n rulesheet((rule) => {\n sheet.insert(restoreNamedColors(rule));\n }),\n ]),\n );\n};\n\nconst didMarkNewStyle = (state: CssState, serialized: SerializedStyles): boolean => {\n if (state.inserted.has(serialized.name)) {\n return false;\n }\n\n state.inserted.add(serialized.name);\n\n return true;\n};\n\nconst insertStyles = (state: CssState, serialized: SerializedStyles): void => {\n if (!didMarkNewStyle(state, serialized)) {\n return;\n }\n\n const className = getClassName(serialized);\n const styles = terminateDeclarations(serialized.styles);\n runStylis(state.sheet, `.${className}{${styles}}`);\n state.registered[className] = styles;\n};\n\nconst insertWithoutScoping = (state: CssState, serialized: SerializedStyles): void => {\n if (!didMarkNewStyle(state, serialized)) {\n return;\n }\n\n runStylis(state.sheet, serialized.styles);\n};\n\nconst cssClassName = (state: CssState, args: CSSInterpolation[]): string => {\n const serialized = serializeStyles(args, state.registered);\n insertStyles(state, serialized);\n\n return getClassName(serialized);\n};\n\nconst cxClassNames = (state: CssState, classNames: CxToken[]): string[] => {\n const tokens = classNames.filter(isNonEmptyString);\n const { rawClasses, registeredStyles } = partitionTokens(tokens, state.registered);\n\n if (registeredStyles.length < 2) {\n return tokens;\n }\n\n return [...rawClasses, cssClassName(state, [registeredStyles.join(\"\")])];\n};\n\nconst createCss = (): Css => {\n const state: CssState = { sheet: new StyleSheet(), inserted: new Set(), registered: {} };\n\n return {\n css: (...args) => cssClassName(state, args),\n cx: (...classNames) => cxClassNames(state, classNames),\n injectGlobal: (...args) => {\n insertWithoutScoping(state, serializeStyles(args, state.registered));\n },\n };\n};\n\nexport { createCss, type Css };\n"]}
|
package/dist/named-colors.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
declare const AT_RULE_KEYWORDS: Set<string>;
|
|
2
|
-
declare const NAMED_COLOR_TOKEN = "gtkx-named-color__";
|
|
3
1
|
declare const escapeNamedColors: (input: string) => string;
|
|
4
2
|
declare const restoreNamedColors: (rule: string) => string;
|
|
5
|
-
export {
|
|
3
|
+
export { escapeNamedColors, restoreNamedColors };
|
|
6
4
|
//# sourceMappingURL=named-colors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"named-colors.d.ts","sourceRoot":"","sources":["../src/named-colors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"named-colors.d.ts","sourceRoot":"","sources":["../src/named-colors.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MAGtC,CAAC;AAEN,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAA2D,CAAC;AAEvG,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/named-colors.js
CHANGED
|
@@ -4,5 +4,5 @@ const NAMED_COLOR_TOKEN = "gtkx-named-color__";
|
|
|
4
4
|
const NAMED_COLOR_TOKEN_PATTERN = new RegExp(String.raw `${NAMED_COLOR_TOKEN}([\w-]+)`, "g");
|
|
5
5
|
const escapeNamedColors = (input) => input.replaceAll(AT_IDENTIFIER_PATTERN, (match, name) => AT_RULE_KEYWORDS.has(name) ? match : `${NAMED_COLOR_TOKEN}${name}`);
|
|
6
6
|
const restoreNamedColors = (rule) => rule.replaceAll(NAMED_COLOR_TOKEN_PATTERN, "@$1");
|
|
7
|
-
export {
|
|
7
|
+
export { escapeNamedColors, restoreNamedColors };
|
|
8
8
|
//# sourceMappingURL=named-colors.js.map
|
package/dist/named-colors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"named-colors.js","sourceRoot":"","sources":["../src/named-colors.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAgB,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAChG,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AACpD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAC/C,MAAM,yBAAyB,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,GAAG,iBAAiB,UAAU,EAAE,GAAG,CAAC,CAAC;AAE5F,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAU,EAAE,CAChD,KAAK,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAC5D,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,iBAAiB,GAAG,IAAI,EAAE,CACrE,CAAC;AAEN,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;AAEvG,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"named-colors.js","sourceRoot":"","sources":["../src/named-colors.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAgB,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAChG,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AACpD,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAC/C,MAAM,yBAAyB,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,GAAG,iBAAiB,UAAU,EAAE,GAAG,CAAC,CAAC;AAE5F,MAAM,iBAAiB,GAAG,CAAC,KAAa,EAAU,EAAE,CAChD,KAAK,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAC5D,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,iBAAiB,GAAG,IAAI,EAAE,CACrE,CAAC;AAEN,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;AAEvG,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["const AT_RULE_KEYWORDS: Set<string> = new Set([\"define-color\", \"import\", \"keyframes\", \"media\"]);\nconst AT_IDENTIFIER_PATTERN = /@([A-Za-z_][\\w-]*)/g;\nconst NAMED_COLOR_TOKEN = \"gtkx-named-color__\";\nconst NAMED_COLOR_TOKEN_PATTERN = new RegExp(String.raw`${NAMED_COLOR_TOKEN}([\\w-]+)`, \"g\");\n\nconst escapeNamedColors = (input: string): string =>\n input.replaceAll(AT_IDENTIFIER_PATTERN, (match, name: string) =>\n AT_RULE_KEYWORDS.has(name) ? match : `${NAMED_COLOR_TOKEN}${name}`,\n );\n\nconst restoreNamedColors = (rule: string): string => rule.replaceAll(NAMED_COLOR_TOKEN_PATTERN, \"@$1\");\n\nexport { escapeNamedColors, restoreNamedColors };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-contained.d.ts","sourceRoot":"","sources":["../src/self-contained.ts"],"names":[],"mappings":"AA6CA,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAQnD,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CssProvider } from "@gtkx/gi/gtk";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { brokenToken } from "./tokens.js";
|
|
4
|
+
const PROBE_BYTES = 16;
|
|
5
|
+
const PROBE_RULE = `.gtkx-probe-${randomBytes(PROBE_BYTES).toString("hex")}{color:rgb(0, 0, 0);}`;
|
|
6
|
+
const SWALLOWS_WHAT_FOLLOWS = "would disable every rule after it";
|
|
7
|
+
const gate = { provider: null, ruleset: null };
|
|
8
|
+
const ignoreParsingError = () => undefined;
|
|
9
|
+
const createQuietProvider = () => {
|
|
10
|
+
const provider = new CssProvider();
|
|
11
|
+
provider.on("parsing-error", ignoreParsingError);
|
|
12
|
+
return provider;
|
|
13
|
+
};
|
|
14
|
+
const quiet = () => {
|
|
15
|
+
gate.provider ??= createQuietProvider();
|
|
16
|
+
return gate.provider;
|
|
17
|
+
};
|
|
18
|
+
const parsed = (document) => {
|
|
19
|
+
const provider = quiet();
|
|
20
|
+
provider.loadFromString(document);
|
|
21
|
+
return provider.toString();
|
|
22
|
+
};
|
|
23
|
+
const probe = () => {
|
|
24
|
+
gate.ruleset ??= parsed(PROBE_RULE);
|
|
25
|
+
return gate.ruleset;
|
|
26
|
+
};
|
|
27
|
+
const hasProbe = (serialized) => {
|
|
28
|
+
const ruleset = probe();
|
|
29
|
+
return ruleset.length > 0 && `\n${serialized}`.includes(`\n${ruleset}`);
|
|
30
|
+
};
|
|
31
|
+
const containmentFailure = (rule) => {
|
|
32
|
+
const broken = brokenToken(rule);
|
|
33
|
+
if (broken !== null) {
|
|
34
|
+
return broken;
|
|
35
|
+
}
|
|
36
|
+
return hasProbe(parsed(`${rule}\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;
|
|
37
|
+
};
|
|
38
|
+
export { containmentFailure };
|
|
39
|
+
//# sourceMappingURL=self-contained.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-contained.js","sourceRoot":"","sources":["../src/self-contained.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,UAAU,GAAG,eAAe,WAAW,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,uBAAuB,CAAC;AAClG,MAAM,qBAAqB,GAAG,mCAAmC,CAAC;AAClE,MAAM,IAAI,GAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAErD,MAAM,kBAAkB,GAAG,GAAc,EAAE,CAAC,SAAS,CAAC;AAEtD,MAAM,mBAAmB,GAAG,GAAgB,EAAE;IAC1C,MAAM,QAAQ,GAAG,IAAI,WAAW,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,eAAe,EAAE,kBAAkB,CAAC,CAAC;IAEjD,OAAO,QAAQ,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,GAAgB,EAAE;IAC5B,IAAI,CAAC,QAAQ,KAAK,mBAAmB,EAAE,CAAC;IAExC,OAAO,IAAI,CAAC,QAAQ,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,MAAM,GAAG,CAAC,QAAgB,EAAU,EAAE;IACxC,MAAM,QAAQ,GAAG,KAAK,EAAE,CAAC;IACzB,QAAQ,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;IAElC,OAAO,QAAQ,CAAC,QAAQ,EAAE,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,GAAW,EAAE;IACvB,IAAI,CAAC,OAAO,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC;IAEpC,OAAO,IAAI,CAAC,OAAO,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,UAAkB,EAAW,EAAE;IAC7C,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IAExB,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC,QAAQ,CAAC,KAAK,OAAO,EAAE,CAAC,CAAC;AAC5E,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAiB,EAAE;IACvD,MAAM,MAAM,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAEjC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC;AACrF,CAAC,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import { CssProvider } from \"@gtkx/gi/gtk\";\nimport { randomBytes } from \"node:crypto\";\nimport { brokenToken } from \"./tokens.js\";\n\ntype Gate = { provider: CssProvider | null; ruleset: string | null };\n\nconst PROBE_BYTES = 16;\nconst PROBE_RULE = `.gtkx-probe-${randomBytes(PROBE_BYTES).toString(\"hex\")}{color:rgb(0, 0, 0);}`;\nconst SWALLOWS_WHAT_FOLLOWS = \"would disable every rule after it\";\nconst gate: Gate = { provider: null, ruleset: null };\n\nconst ignoreParsingError = (): undefined => undefined;\n\nconst createQuietProvider = (): CssProvider => {\n const provider = new CssProvider();\n provider.on(\"parsing-error\", ignoreParsingError);\n\n return provider;\n};\n\nconst quiet = (): CssProvider => {\n gate.provider ??= createQuietProvider();\n\n return gate.provider;\n};\n\nconst parsed = (document: string): string => {\n const provider = quiet();\n provider.loadFromString(document);\n\n return provider.toString();\n};\n\nconst probe = (): string => {\n gate.ruleset ??= parsed(PROBE_RULE);\n\n return gate.ruleset;\n};\n\nconst hasProbe = (serialized: string): boolean => {\n const ruleset = probe();\n\n return ruleset.length > 0 && `\\n${serialized}`.includes(`\\n${ruleset}`);\n};\n\nconst containmentFailure = (rule: string): string | null => {\n const broken = brokenToken(rule);\n\n if (broken !== null) {\n return broken;\n }\n\n return hasProbe(parsed(`${rule}\\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;\n};\n\nexport { containmentFailure };\n"]}
|
package/dist/stylesheet.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stylesheet.d.ts","sourceRoot":"","sources":["../src/stylesheet.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stylesheet.d.ts","sourceRoot":"","sources":["../src/stylesheet.ts"],"names":[],"mappings":"AAcA,cAAM,UAAU;IACZ,OAAO,CAAC,GAAG,CAAM;IACjB,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,eAAe,CAAS;IAEhC,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,cAAc;IAatB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;CAY7B;AAED,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/stylesheet.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { createLogger } from "@gtkx/utils";
|
|
2
2
|
import { attachParsingErrorLogger, registerProviderForDefaultDisplay } from "./provider.js";
|
|
3
|
+
import { containmentFailure } from "./self-contained.js";
|
|
3
4
|
const log = createLogger("css");
|
|
5
|
+
const NUL = "\u{0}";
|
|
6
|
+
const NUL_REASON = "carries a NUL byte, which GTK4 cannot load";
|
|
7
|
+
const unusableReason = (rule) => rule.includes(NUL) ? NUL_REASON : containmentFailure(rule);
|
|
8
|
+
const printableRule = (rule) => rule.replaceAll(NUL, String.raw `\0`);
|
|
4
9
|
class StyleSheet {
|
|
5
10
|
css = "";
|
|
6
11
|
provider = null;
|
|
@@ -25,6 +30,11 @@ class StyleSheet {
|
|
|
25
30
|
});
|
|
26
31
|
}
|
|
27
32
|
insert(rule) {
|
|
33
|
+
const reason = unusableReason(rule);
|
|
34
|
+
if (reason !== null) {
|
|
35
|
+
log.warn(`Dropped a malformed CSS rule that ${reason}: ${printableRule(rule)}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
28
38
|
this.css += this.css.length > 0 ? `\n${rule}` : rule;
|
|
29
39
|
this.scheduleUpdate();
|
|
30
40
|
}
|
package/dist/stylesheet.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stylesheet.js","sourceRoot":"","sources":["../src/stylesheet.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"stylesheet.js","sourceRoot":"","sources":["../src/stylesheet.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,wBAAwB,EAAE,iCAAiC,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;AAChC,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,UAAU,GAAG,4CAA4C,CAAC;AAEhE,MAAM,cAAc,GAAG,CAAC,IAAY,EAAiB,EAAE,CACnD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAE/D,MAAM,aAAa,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,IAAI,CAAC,CAAC;AAErF,MAAM,UAAU;IACJ,GAAG,GAAG,EAAE,CAAC;IACT,QAAQ,GAA2B,IAAI,CAAC;IACxC,eAAe,GAAG,KAAK,CAAC;IAExB,cAAc;QAClB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC,QAAQ,CAAC;QACzB,CAAC;QAED,MAAM,QAAQ,GAAG,iCAAiC,EAAE,CAAC;QACrD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,wBAAwB,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QAE/C,OAAO,QAAQ,CAAC;IACpB,CAAC;IAEO,cAAc;QAClB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,OAAO;QACX,CAAC;QAED,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAE5B,cAAc,CAAC,GAAG,EAAE;YAChB,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;YAC7B,IAAI,CAAC,cAAc,EAAE,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACP,CAAC;IAED,MAAM,CAAC,IAAY;QACf,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAEpC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClB,GAAG,CAAC,IAAI,CAAC,qCAAqC,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEhF,OAAO;QACX,CAAC;QAED,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACrD,IAAI,CAAC,cAAc,EAAE,CAAC;IAC1B,CAAC;CACJ;AAED,OAAO,EAAE,UAAU,EAAE,CAAC","sourcesContent":["import type * as Gtk from \"@gtkx/gi/gtk\";\nimport { createLogger } from \"@gtkx/utils\";\nimport { attachParsingErrorLogger, registerProviderForDefaultDisplay } from \"./provider.js\";\nimport { containmentFailure } from \"./self-contained.js\";\n\nconst log = createLogger(\"css\");\nconst NUL = \"\\u{0}\";\nconst NUL_REASON = \"carries a NUL byte, which GTK4 cannot load\";\n\nconst unusableReason = (rule: string): string | null =>\n rule.includes(NUL) ? NUL_REASON : containmentFailure(rule);\n\nconst printableRule = (rule: string): string => rule.replaceAll(NUL, String.raw`\\0`);\n\nclass StyleSheet {\n private css = \"\";\n private provider: Gtk.CssProvider | null = null;\n private updateScheduled = false;\n\n private ensureProvider(): Gtk.CssProvider {\n if (this.provider) {\n return this.provider;\n }\n\n const provider = registerProviderForDefaultDisplay();\n this.provider = provider;\n attachParsingErrorLogger(provider, log, \"CSS\");\n\n return provider;\n }\n\n private scheduleUpdate(): void {\n if (this.updateScheduled) {\n return;\n }\n\n this.updateScheduled = true;\n\n queueMicrotask(() => {\n this.updateScheduled = false;\n this.ensureProvider().loadFromString(this.css);\n });\n }\n\n insert(rule: string): void {\n const reason = unusableReason(rule);\n\n if (reason !== null) {\n log.warn(`Dropped a malformed CSS rule that ${reason}: ${printableRule(rule)}`);\n\n return;\n }\n\n this.css += this.css.length > 0 ? `\\n${rule}` : rule;\n this.scheduleUpdate();\n }\n}\n\nexport { StyleSheet };\n"]}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AA2FA,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAc5C,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const UNTERMINATED = "leaves a string, comment or url unterminated";
|
|
2
|
+
const NEWLINE_IN_STRING = "carries a newline inside a string";
|
|
3
|
+
const ESCAPE = "\\";
|
|
4
|
+
const NEWLINE = "\n";
|
|
5
|
+
const CLOSE_PAREN = ")";
|
|
6
|
+
const COMMENT_OPEN = "/*";
|
|
7
|
+
const COMMENT_CLOSE = "*/";
|
|
8
|
+
const URL_OPEN = "url(";
|
|
9
|
+
const QUOTES = new Set(['"', "'"]);
|
|
10
|
+
const URL_TOKEN = /url\(/iy;
|
|
11
|
+
const QUOTED_URL = /url\([\t\n\f\r ]*["']/iy;
|
|
12
|
+
const hasMatchAt = (pattern, text, index) => {
|
|
13
|
+
pattern.lastIndex = index;
|
|
14
|
+
return pattern.test(text);
|
|
15
|
+
};
|
|
16
|
+
const isUrlStart = (text, index) => hasMatchAt(URL_TOKEN, text, index) && !hasMatchAt(QUOTED_URL, text, index);
|
|
17
|
+
const took = (index) => ({ index, reason: null });
|
|
18
|
+
const broke = (index, reason) => ({ index, reason });
|
|
19
|
+
const stepped = (text, index) => index + (text.charAt(index) === ESCAPE ? 2 : 1);
|
|
20
|
+
const endOfString = (text, index, quote) => {
|
|
21
|
+
const char = text.charAt(index);
|
|
22
|
+
if (char === quote) {
|
|
23
|
+
return took(index + 1);
|
|
24
|
+
}
|
|
25
|
+
return char === NEWLINE ? broke(index + 1, NEWLINE_IN_STRING) : null;
|
|
26
|
+
};
|
|
27
|
+
const readString = (text, start, quote) => {
|
|
28
|
+
let index = start;
|
|
29
|
+
while (index < text.length) {
|
|
30
|
+
const end = endOfString(text, index, quote);
|
|
31
|
+
if (end !== null) {
|
|
32
|
+
return end;
|
|
33
|
+
}
|
|
34
|
+
index = stepped(text, index);
|
|
35
|
+
}
|
|
36
|
+
return broke(index, UNTERMINATED);
|
|
37
|
+
};
|
|
38
|
+
const readComment = (text, start) => {
|
|
39
|
+
const end = text.indexOf(COMMENT_CLOSE, start);
|
|
40
|
+
return end === -1 ? broke(text.length, UNTERMINATED) : took(end + COMMENT_CLOSE.length);
|
|
41
|
+
};
|
|
42
|
+
const readUrl = (text, start) => {
|
|
43
|
+
let index = start;
|
|
44
|
+
while (index < text.length) {
|
|
45
|
+
if (text.charAt(index) === CLOSE_PAREN) {
|
|
46
|
+
return took(index + 1);
|
|
47
|
+
}
|
|
48
|
+
index = stepped(text, index);
|
|
49
|
+
}
|
|
50
|
+
return broke(index, UNTERMINATED);
|
|
51
|
+
};
|
|
52
|
+
const readNext = (text, index) => {
|
|
53
|
+
const char = text.charAt(index);
|
|
54
|
+
if (char === ESCAPE) {
|
|
55
|
+
return took(index + 2);
|
|
56
|
+
}
|
|
57
|
+
if (QUOTES.has(char)) {
|
|
58
|
+
return readString(text, index + 1, char);
|
|
59
|
+
}
|
|
60
|
+
if (text.startsWith(COMMENT_OPEN, index)) {
|
|
61
|
+
return readComment(text, index + COMMENT_OPEN.length);
|
|
62
|
+
}
|
|
63
|
+
return isUrlStart(text, index) ? readUrl(text, index + URL_OPEN.length) : took(index + 1);
|
|
64
|
+
};
|
|
65
|
+
const brokenToken = (text) => {
|
|
66
|
+
let index = 0;
|
|
67
|
+
while (index < text.length) {
|
|
68
|
+
const step = readNext(text, index);
|
|
69
|
+
if (step.reason !== null) {
|
|
70
|
+
return step.reason;
|
|
71
|
+
}
|
|
72
|
+
index = step.index;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
export { brokenToken };
|
|
77
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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","sourcesContent":["type Step = { index: number; reason: string | null };\n\nconst UNTERMINATED = \"leaves a string, comment or url unterminated\";\nconst NEWLINE_IN_STRING = \"carries a newline inside a string\";\nconst ESCAPE = \"\\\\\";\nconst NEWLINE = \"\\n\";\nconst CLOSE_PAREN = \")\";\nconst COMMENT_OPEN = \"/*\";\nconst COMMENT_CLOSE = \"*/\";\nconst URL_OPEN = \"url(\";\nconst QUOTES = new Set(['\"', \"'\"]);\nconst URL_TOKEN = /url\\(/iy;\nconst QUOTED_URL = /url\\([\\t\\n\\f\\r ]*[\"']/iy;\n\nconst hasMatchAt = (pattern: RegExp, text: string, index: number): boolean => {\n pattern.lastIndex = index;\n\n return pattern.test(text);\n};\n\nconst isUrlStart = (text: string, index: number): boolean =>\n hasMatchAt(URL_TOKEN, text, index) && !hasMatchAt(QUOTED_URL, text, index);\n\nconst took = (index: number): Step => ({ index, reason: null });\nconst broke = (index: number, reason: string): Step => ({ index, reason });\nconst stepped = (text: string, index: number): number => index + (text.charAt(index) === ESCAPE ? 2 : 1);\n\nconst endOfString = (text: string, index: number, quote: string): Step | null => {\n const char = text.charAt(index);\n\n if (char === quote) {\n return took(index + 1);\n }\n\n return char === NEWLINE ? broke(index + 1, NEWLINE_IN_STRING) : null;\n};\n\nconst readString = (text: string, start: number, quote: string): Step => {\n let index = start;\n\n while (index < text.length) {\n const end = endOfString(text, index, quote);\n\n if (end !== null) {\n return end;\n }\n\n index = stepped(text, index);\n }\n\n return broke(index, UNTERMINATED);\n};\n\nconst readComment = (text: string, start: number): Step => {\n const end = text.indexOf(COMMENT_CLOSE, start);\n\n return end === -1 ? broke(text.length, UNTERMINATED) : took(end + COMMENT_CLOSE.length);\n};\n\nconst readUrl = (text: string, start: number): Step => {\n let index = start;\n\n while (index < text.length) {\n if (text.charAt(index) === CLOSE_PAREN) {\n return took(index + 1);\n }\n\n index = stepped(text, index);\n }\n\n return broke(index, UNTERMINATED);\n};\n\nconst readNext = (text: string, index: number): Step => {\n const char = text.charAt(index);\n\n if (char === ESCAPE) {\n return took(index + 2);\n }\n\n if (QUOTES.has(char)) {\n return readString(text, index + 1, char);\n }\n\n if (text.startsWith(COMMENT_OPEN, index)) {\n return readComment(text, index + COMMENT_OPEN.length);\n }\n\n return isUrlStart(text, index) ? readUrl(text, index + URL_OPEN.length) : took(index + 1);\n};\n\nconst brokenToken = (text: string): string | null => {\n let index = 0;\n\n while (index < text.length) {\n const step = readNext(text, index);\n\n if (step.reason !== null) {\n return step.reason;\n }\n\n index = step.index;\n }\n\n return null;\n};\n\nexport { brokenToken };\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/css",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Emotion-flavored CSS-in-JS that compiles to real GTK4 CSS and installs it on the default display",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -38,17 +38,27 @@
|
|
|
38
38
|
"dist",
|
|
39
39
|
"src"
|
|
40
40
|
],
|
|
41
|
+
"nx": {
|
|
42
|
+
"targets": {
|
|
43
|
+
"lint": {
|
|
44
|
+
"options": {
|
|
45
|
+
"cwd": ".",
|
|
46
|
+
"command": "eslint {projectRoot}"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
41
51
|
"engines": {
|
|
42
52
|
"node": ">=24"
|
|
43
53
|
},
|
|
44
54
|
"dependencies": {
|
|
45
55
|
"@emotion/serialize": "^1.3.3",
|
|
46
56
|
"stylis": "^4.4.0",
|
|
47
|
-
"@gtkx/utils": "1.
|
|
57
|
+
"@gtkx/utils": "1.2.0"
|
|
48
58
|
},
|
|
49
59
|
"devDependencies": {
|
|
50
60
|
"@types/stylis": "^4.2.7",
|
|
51
|
-
"@gtkx/vitest": "1.
|
|
61
|
+
"@gtkx/vitest": "1.2.0"
|
|
52
62
|
},
|
|
53
63
|
"scripts": {
|
|
54
64
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/create-css.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { StyleSheet } from "./stylesheet.js";
|
|
|
8
8
|
/** A `cx` argument: a non-empty class name, or a boolean or nullish value that is dropped. */
|
|
9
9
|
type CxToken = string | boolean | undefined | null;
|
|
10
10
|
|
|
11
|
-
/** The `css`, `cx` and `injectGlobal`
|
|
11
|
+
/** The `css`, `cx` and `injectGlobal` helpers bound to one GTK4 stylesheet and its cache of inserted styles. */
|
|
12
12
|
type Css = {
|
|
13
13
|
/**
|
|
14
14
|
* Serializes the given style interpolations, inserts the resulting rules into the stylesheet, and
|
|
@@ -43,6 +43,17 @@ const removeLabel = (element: Element): void => {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const getClassName = (serialized: SerializedStyles): string => `${KEY}-${serialized.name}`;
|
|
46
|
+
|
|
47
|
+
const terminateDeclarations = (styles: string): string => {
|
|
48
|
+
const trimmed = styles.trimEnd();
|
|
49
|
+
|
|
50
|
+
if (trimmed.length === 0 || trimmed.endsWith(";") || trimmed.endsWith("}")) {
|
|
51
|
+
return trimmed;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `${trimmed};`;
|
|
55
|
+
};
|
|
56
|
+
|
|
46
57
|
const isNonEmptyString = (token: CxToken): token is string => typeof token === "string" && token.length > 0;
|
|
47
58
|
|
|
48
59
|
const partitionTokens = (tokens: string[], registered: RegisteredCache): TokenPartition => {
|
|
@@ -91,8 +102,9 @@ const insertStyles = (state: CssState, serialized: SerializedStyles): void => {
|
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
const className = getClassName(serialized);
|
|
94
|
-
|
|
95
|
-
state.
|
|
105
|
+
const styles = terminateDeclarations(serialized.styles);
|
|
106
|
+
runStylis(state.sheet, `.${className}{${styles}}`);
|
|
107
|
+
state.registered[className] = styles;
|
|
96
108
|
};
|
|
97
109
|
|
|
98
110
|
const insertWithoutScoping = (state: CssState, serialized: SerializedStyles): void => {
|
|
@@ -133,4 +145,4 @@ const createCss = (): Css => {
|
|
|
133
145
|
};
|
|
134
146
|
};
|
|
135
147
|
|
|
136
|
-
export {
|
|
148
|
+
export { createCss, type Css };
|
package/src/named-colors.ts
CHANGED
|
@@ -10,4 +10,4 @@ const escapeNamedColors = (input: string): string =>
|
|
|
10
10
|
|
|
11
11
|
const restoreNamedColors = (rule: string): string => rule.replaceAll(NAMED_COLOR_TOKEN_PATTERN, "@$1");
|
|
12
12
|
|
|
13
|
-
export {
|
|
13
|
+
export { escapeNamedColors, restoreNamedColors };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { CssProvider } from "@gtkx/gi/gtk";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { brokenToken } from "./tokens.js";
|
|
4
|
+
|
|
5
|
+
type Gate = { provider: CssProvider | null; ruleset: string | null };
|
|
6
|
+
|
|
7
|
+
const PROBE_BYTES = 16;
|
|
8
|
+
const PROBE_RULE = `.gtkx-probe-${randomBytes(PROBE_BYTES).toString("hex")}{color:rgb(0, 0, 0);}`;
|
|
9
|
+
const SWALLOWS_WHAT_FOLLOWS = "would disable every rule after it";
|
|
10
|
+
const gate: Gate = { provider: null, ruleset: null };
|
|
11
|
+
|
|
12
|
+
const ignoreParsingError = (): undefined => undefined;
|
|
13
|
+
|
|
14
|
+
const createQuietProvider = (): CssProvider => {
|
|
15
|
+
const provider = new CssProvider();
|
|
16
|
+
provider.on("parsing-error", ignoreParsingError);
|
|
17
|
+
|
|
18
|
+
return provider;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const quiet = (): CssProvider => {
|
|
22
|
+
gate.provider ??= createQuietProvider();
|
|
23
|
+
|
|
24
|
+
return gate.provider;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const parsed = (document: string): string => {
|
|
28
|
+
const provider = quiet();
|
|
29
|
+
provider.loadFromString(document);
|
|
30
|
+
|
|
31
|
+
return provider.toString();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const probe = (): string => {
|
|
35
|
+
gate.ruleset ??= parsed(PROBE_RULE);
|
|
36
|
+
|
|
37
|
+
return gate.ruleset;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const hasProbe = (serialized: string): boolean => {
|
|
41
|
+
const ruleset = probe();
|
|
42
|
+
|
|
43
|
+
return ruleset.length > 0 && `\n${serialized}`.includes(`\n${ruleset}`);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const containmentFailure = (rule: string): string | null => {
|
|
47
|
+
const broken = brokenToken(rule);
|
|
48
|
+
|
|
49
|
+
if (broken !== null) {
|
|
50
|
+
return broken;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return hasProbe(parsed(`${rule}\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export { containmentFailure };
|
package/src/stylesheet.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type * as Gtk from "@gtkx/gi/gtk";
|
|
2
2
|
import { createLogger } from "@gtkx/utils";
|
|
3
3
|
import { attachParsingErrorLogger, registerProviderForDefaultDisplay } from "./provider.js";
|
|
4
|
+
import { containmentFailure } from "./self-contained.js";
|
|
4
5
|
|
|
5
6
|
const log = createLogger("css");
|
|
7
|
+
const NUL = "\u{0}";
|
|
8
|
+
const NUL_REASON = "carries a NUL byte, which GTK4 cannot load";
|
|
9
|
+
|
|
10
|
+
const unusableReason = (rule: string): string | null =>
|
|
11
|
+
rule.includes(NUL) ? NUL_REASON : containmentFailure(rule);
|
|
12
|
+
|
|
13
|
+
const printableRule = (rule: string): string => rule.replaceAll(NUL, String.raw`\0`);
|
|
6
14
|
|
|
7
15
|
class StyleSheet {
|
|
8
16
|
private css = "";
|
|
@@ -35,6 +43,14 @@ class StyleSheet {
|
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
insert(rule: string): void {
|
|
46
|
+
const reason = unusableReason(rule);
|
|
47
|
+
|
|
48
|
+
if (reason !== null) {
|
|
49
|
+
log.warn(`Dropped a malformed CSS rule that ${reason}: ${printableRule(rule)}`);
|
|
50
|
+
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
this.css += this.css.length > 0 ? `\n${rule}` : rule;
|
|
39
55
|
this.scheduleUpdate();
|
|
40
56
|
}
|
package/src/tokens.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
type Step = { index: number; reason: string | null };
|
|
2
|
+
|
|
3
|
+
const UNTERMINATED = "leaves a string, comment or url unterminated";
|
|
4
|
+
const NEWLINE_IN_STRING = "carries a newline inside a string";
|
|
5
|
+
const ESCAPE = "\\";
|
|
6
|
+
const NEWLINE = "\n";
|
|
7
|
+
const CLOSE_PAREN = ")";
|
|
8
|
+
const COMMENT_OPEN = "/*";
|
|
9
|
+
const COMMENT_CLOSE = "*/";
|
|
10
|
+
const URL_OPEN = "url(";
|
|
11
|
+
const QUOTES = new Set(['"', "'"]);
|
|
12
|
+
const URL_TOKEN = /url\(/iy;
|
|
13
|
+
const QUOTED_URL = /url\([\t\n\f\r ]*["']/iy;
|
|
14
|
+
|
|
15
|
+
const hasMatchAt = (pattern: RegExp, text: string, index: number): boolean => {
|
|
16
|
+
pattern.lastIndex = index;
|
|
17
|
+
|
|
18
|
+
return pattern.test(text);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const isUrlStart = (text: string, index: number): boolean =>
|
|
22
|
+
hasMatchAt(URL_TOKEN, text, index) && !hasMatchAt(QUOTED_URL, text, index);
|
|
23
|
+
|
|
24
|
+
const took = (index: number): Step => ({ index, reason: null });
|
|
25
|
+
const broke = (index: number, reason: string): Step => ({ index, reason });
|
|
26
|
+
const stepped = (text: string, index: number): number => index + (text.charAt(index) === ESCAPE ? 2 : 1);
|
|
27
|
+
|
|
28
|
+
const endOfString = (text: string, index: number, quote: string): Step | null => {
|
|
29
|
+
const char = text.charAt(index);
|
|
30
|
+
|
|
31
|
+
if (char === quote) {
|
|
32
|
+
return took(index + 1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return char === NEWLINE ? broke(index + 1, NEWLINE_IN_STRING) : null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const readString = (text: string, start: number, quote: string): Step => {
|
|
39
|
+
let index = start;
|
|
40
|
+
|
|
41
|
+
while (index < text.length) {
|
|
42
|
+
const end = endOfString(text, index, quote);
|
|
43
|
+
|
|
44
|
+
if (end !== null) {
|
|
45
|
+
return end;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
index = stepped(text, index);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return broke(index, UNTERMINATED);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const readComment = (text: string, start: number): Step => {
|
|
55
|
+
const end = text.indexOf(COMMENT_CLOSE, start);
|
|
56
|
+
|
|
57
|
+
return end === -1 ? broke(text.length, UNTERMINATED) : took(end + COMMENT_CLOSE.length);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const readUrl = (text: string, start: number): Step => {
|
|
61
|
+
let index = start;
|
|
62
|
+
|
|
63
|
+
while (index < text.length) {
|
|
64
|
+
if (text.charAt(index) === CLOSE_PAREN) {
|
|
65
|
+
return took(index + 1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
index = stepped(text, index);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return broke(index, UNTERMINATED);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const readNext = (text: string, index: number): Step => {
|
|
75
|
+
const char = text.charAt(index);
|
|
76
|
+
|
|
77
|
+
if (char === ESCAPE) {
|
|
78
|
+
return took(index + 2);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (QUOTES.has(char)) {
|
|
82
|
+
return readString(text, index + 1, char);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (text.startsWith(COMMENT_OPEN, index)) {
|
|
86
|
+
return readComment(text, index + COMMENT_OPEN.length);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return isUrlStart(text, index) ? readUrl(text, index + URL_OPEN.length) : took(index + 1);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const brokenToken = (text: string): string | null => {
|
|
93
|
+
let index = 0;
|
|
94
|
+
|
|
95
|
+
while (index < text.length) {
|
|
96
|
+
const step = readNext(text, index);
|
|
97
|
+
|
|
98
|
+
if (step.reason !== null) {
|
|
99
|
+
return step.reason;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
index = step.index;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export { brokenToken };
|