@gtkx/css 1.2.2 → 1.3.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 +2 -1
- package/dist/create-css.d.ts.map +1 -1
- package/dist/create-css.js +7 -30
- package/dist/create-css.js.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +3 -0
- package/dist/internal.js.map +1 -0
- package/dist/provider.d.ts +6 -2
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +31 -2
- package/dist/provider.js.map +1 -1
- package/dist/rule-text.d.ts +5 -0
- package/dist/rule-text.d.ts.map +1 -0
- package/dist/rule-text.js +6 -0
- package/dist/rule-text.js.map +1 -0
- package/dist/scoped-rule.d.ts +3 -0
- package/dist/scoped-rule.d.ts.map +1 -0
- package/dist/scoped-rule.js +42 -0
- package/dist/scoped-rule.js.map +1 -0
- package/dist/serialize-rule.d.ts +4 -0
- package/dist/serialize-rule.d.ts.map +1 -0
- package/dist/serialize-rule.js +31 -0
- package/dist/serialize-rule.js.map +1 -0
- package/dist/stylesheet.d.ts.map +1 -1
- package/dist/stylesheet.js +2 -4
- package/dist/stylesheet.js.map +1 -1
- package/package.json +7 -3
- package/src/create-css.ts +7 -40
- package/src/internal.ts +6 -0
- package/src/provider.ts +50 -5
- package/src/rule-text.ts +7 -0
- package/src/scoped-rule.ts +61 -0
- package/src/serialize-rule.ts +42 -0
- package/src/stylesheet.ts +2 -6
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ GTK4 is mature, and GtkBuilder XML can lay out a static interface, but nothing r
|
|
|
98
98
|
- a React reconciler that exposes every GObject as a JSX element,
|
|
99
99
|
- a CLI for scaffolding, development, and production builds,
|
|
100
100
|
- a dev server with Fast Refresh that patches your running UI in place,
|
|
101
|
-
- CSS-in-JS styling and high-level list, grid, and dialog components,
|
|
101
|
+
- CSS-in-JS styling, React Spring animations, and high-level list, grid, and dialog components,
|
|
102
102
|
- a Testing Library-style API for querying and driving your widgets in tests,
|
|
103
103
|
- and a Model Context Protocol (MCP) server that exposes your live app to AI agents.
|
|
104
104
|
|
|
@@ -157,6 +157,7 @@ Explore the [example apps](https://github.com/gtkx-org/gtkx/tree/main/examples):
|
|
|
157
157
|
- [`hello-world`](https://github.com/gtkx-org/gtkx/tree/main/examples/hello-world): the counter above.
|
|
158
158
|
- [`gtk-demo`](https://github.com/gtkx-org/gtkx/tree/main/examples/gtk-demo): a React port of the official GTK4 widget showcase, covering lists, dialogs, gestures, CSS, and OpenGL.
|
|
159
159
|
- [`browser`](https://github.com/gtkx-org/gtkx/tree/main/examples/browser): a WebKitWebView-based web browser.
|
|
160
|
+
- [`animations`](https://github.com/gtkx-org/gtkx/tree/main/examples/animations): a tour of `@gtkx/animated`, React Spring animations driven by the GTK frame clock.
|
|
160
161
|
- [`tutorial`](https://github.com/gtkx-org/gtkx/tree/main/examples/tutorial): the Tasks app the documentation builds.
|
|
161
162
|
|
|
162
163
|
## Status
|
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;AAK9F,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;AAgFF,QAAA,MAAM,SAAS,QAAO,GAUrB,CAAC;AAEF,OAAO,EAAE,SAAS,EAAE,KAAK,GAAG,EAAE,CAAC"}
|
package/dist/create-css.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
1
|
import { serializeStyles } from "@emotion/serialize";
|
|
2
|
-
import {
|
|
3
|
-
import { escapeNamedColors, restoreNamedColors } from "./named-colors.js";
|
|
2
|
+
import { eachRule, terminateDeclarations } from "./serialize-rule.js";
|
|
4
3
|
import { StyleSheet } from "./stylesheet.js";
|
|
5
4
|
const KEY = "gtkx";
|
|
6
|
-
const LABEL_DECL_FIRST_CHAR = 108;
|
|
7
|
-
const LABEL_DECL_THIRD_CHAR = 98;
|
|
8
|
-
const removeLabel = (element) => {
|
|
9
|
-
if (!(element.type === "decl" &&
|
|
10
|
-
element.value.codePointAt(0) === LABEL_DECL_FIRST_CHAR &&
|
|
11
|
-
element.value.codePointAt(2) === LABEL_DECL_THIRD_CHAR)) {
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
element.return = "";
|
|
15
|
-
element.value = "";
|
|
16
|
-
};
|
|
17
5
|
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
|
-
};
|
|
25
6
|
const isNonEmptyString = (token) => typeof token === "string" && token.length > 0;
|
|
26
7
|
const partitionTokens = (tokens, registered) => {
|
|
27
8
|
const rawClasses = [];
|
|
@@ -37,14 +18,10 @@ const partitionTokens = (tokens, registered) => {
|
|
|
37
18
|
}
|
|
38
19
|
return { rawClasses, registeredStyles };
|
|
39
20
|
};
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
rulesheet((rule) => {
|
|
45
|
-
sheet.insert(restoreNamedColors(rule));
|
|
46
|
-
}),
|
|
47
|
-
]));
|
|
21
|
+
const insertRules = (state, input) => {
|
|
22
|
+
eachRule(input, (rule) => {
|
|
23
|
+
state.sheet.insert(rule);
|
|
24
|
+
});
|
|
48
25
|
};
|
|
49
26
|
const didMarkNewStyle = (state, serialized) => {
|
|
50
27
|
if (state.inserted.has(serialized.name)) {
|
|
@@ -59,14 +36,14 @@ const insertStyles = (state, serialized) => {
|
|
|
59
36
|
}
|
|
60
37
|
const className = getClassName(serialized);
|
|
61
38
|
const styles = terminateDeclarations(serialized.styles);
|
|
62
|
-
|
|
39
|
+
insertRules(state, `.${className}{${styles}}`);
|
|
63
40
|
state.registered[className] = styles;
|
|
64
41
|
};
|
|
65
42
|
const insertWithoutScoping = (state, serialized) => {
|
|
66
43
|
if (!didMarkNewStyle(state, serialized)) {
|
|
67
44
|
return;
|
|
68
45
|
}
|
|
69
|
-
|
|
46
|
+
insertRules(state, serialized.styles);
|
|
70
47
|
};
|
|
71
48
|
const cssClassName = (state, args) => {
|
|
72
49
|
const serialized = serializeStyles(args, state.registered);
|
package/dist/create-css.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-css.js","sourceRoot":"","sources":["../src/create-css.ts"],"names":[],"mappings":"
|
|
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 { serializeStyles } from \"@emotion/serialize\";\nimport { eachRule, terminateDeclarations } from \"./serialize-rule.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\";\n\nconst getClassName = (serialized: SerializedStyles): string => `${KEY}-${serialized.name}`;\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 insertRules = (state: CssState, input: string): void => {\n eachRule(input, (rule) => {\n state.sheet.insert(rule);\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 insertRules(state, `.${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 insertRules(state, 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"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,wBAAwB,EACxB,KAAK,eAAe,EACpB,iCAAiC,GACpC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.js","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,wBAAwB,EAExB,iCAAiC,GACpC,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export {\n attachParsingErrorLogger,\n type ProviderOptions,\n registerProviderForDefaultDisplay,\n} from \"./provider.js\";\nexport { scopedRule } from \"./scoped-rule.js\";\n"]}
|
package/dist/provider.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Logger } from "@gtkx/utils";
|
|
2
2
|
import { CssProvider } from "@gtkx/gi/gtk";
|
|
3
|
-
|
|
3
|
+
type ProviderOptions = {
|
|
4
|
+
priority: number;
|
|
5
|
+
followsPreferences: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const registerProviderForDefaultDisplay: (options?: Partial<ProviderOptions>) => CssProvider;
|
|
4
8
|
declare const attachParsingErrorLogger: (provider: CssProvider, log: Logger, subject: string) => void;
|
|
5
|
-
export { registerProviderForDefaultDisplay, attachParsingErrorLogger };
|
|
9
|
+
export { type ProviderOptions, registerProviderForDefaultDisplay, attachParsingErrorLogger };
|
|
6
10
|
//# sourceMappingURL=provider.d.ts.map
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAEH,WAAW,EAId,MAAM,cAAc,CAAC;AAEtB,KAAK,eAAe,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,OAAO,CAAA;CAAE,CAAC;AAoCzE,QAAA,MAAM,iCAAiC,GAAI,UAAU,OAAO,CAAC,eAAe,CAAC,KAAG,WAyB/E,CAAC;AAEF,QAAA,MAAM,wBAAwB,GAAI,UAAU,WAAW,EAAE,KAAK,MAAM,EAAE,SAAS,MAAM,KAAG,IAMvF,CAAC;AAEF,OAAO,EAAE,KAAK,eAAe,EAAE,iCAAiC,EAAE,wBAAwB,EAAE,CAAC"}
|
package/dist/provider.js
CHANGED
|
@@ -1,11 +1,40 @@
|
|
|
1
1
|
import { DisplayManager } from "@gtkx/gi/gdk";
|
|
2
|
-
import { CssProvider, STYLE_PROVIDER_PRIORITY_APPLICATION, StyleContext } from "@gtkx/gi/gtk";
|
|
3
|
-
const
|
|
2
|
+
import { checkVersion, CssProvider, Settings, STYLE_PROVIDER_PRIORITY_APPLICATION, StyleContext, } from "@gtkx/gi/gtk";
|
|
3
|
+
const REDUCED_MOTION_MINOR = 22;
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
priority: STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
6
|
+
followsPreferences: true,
|
|
7
|
+
};
|
|
8
|
+
const hasReducedMotion = () => checkVersion(4, REDUCED_MOTION_MINOR, 0) === null;
|
|
9
|
+
const applyPreferences = (provider, settings) => {
|
|
10
|
+
provider.prefersColorScheme = settings.gtkInterfaceColorScheme;
|
|
11
|
+
provider.prefersContrast = settings.gtkInterfaceContrast;
|
|
12
|
+
if (hasReducedMotion()) {
|
|
13
|
+
provider.prefersReducedMotion = settings.gtkInterfaceReducedMotion;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
const followSystemPreferences = (provider, display) => {
|
|
17
|
+
const settings = Settings.getForDisplay(display);
|
|
18
|
+
const sync = () => {
|
|
19
|
+
applyPreferences(provider, settings);
|
|
20
|
+
};
|
|
21
|
+
sync();
|
|
22
|
+
settings.on("notify::gtk-interface-color-scheme", sync);
|
|
23
|
+
settings.on("notify::gtk-interface-contrast", sync);
|
|
24
|
+
if (hasReducedMotion()) {
|
|
25
|
+
settings.on("notify::gtk-interface-reduced-motion", sync);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const registerProviderForDefaultDisplay = (options) => {
|
|
29
|
+
const { priority, followsPreferences } = { ...DEFAULT_OPTIONS, ...options };
|
|
4
30
|
const provider = new CssProvider();
|
|
5
31
|
const manager = DisplayManager.get();
|
|
6
32
|
const attach = (display) => {
|
|
7
33
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
8
34
|
StyleContext.addProviderForDisplay(display, provider, priority);
|
|
35
|
+
if (followsPreferences) {
|
|
36
|
+
followSystemPreferences(provider, display);
|
|
37
|
+
}
|
|
9
38
|
};
|
|
10
39
|
const initialDisplay = manager.getDefaultDisplay();
|
|
11
40
|
if (initialDisplay) {
|
package/dist/provider.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Logger } from \"@gtkx/utils\";\nimport { type Display, DisplayManager } from \"@gtkx/gi/gdk\";\nimport {\n checkVersion,\n CssProvider,\n Settings,\n STYLE_PROVIDER_PRIORITY_APPLICATION,\n StyleContext,\n} from \"@gtkx/gi/gtk\";\n\ntype ProviderOptions = { priority: number; followsPreferences: boolean };\n\nconst REDUCED_MOTION_MINOR = 22;\n\nconst DEFAULT_OPTIONS: ProviderOptions = {\n priority: STYLE_PROVIDER_PRIORITY_APPLICATION,\n followsPreferences: true,\n};\n\nconst hasReducedMotion = (): boolean => checkVersion(4, REDUCED_MOTION_MINOR, 0) === null;\n\nconst applyPreferences = (provider: CssProvider, settings: Settings): void => {\n provider.prefersColorScheme = settings.gtkInterfaceColorScheme;\n provider.prefersContrast = settings.gtkInterfaceContrast;\n\n if (hasReducedMotion()) {\n provider.prefersReducedMotion = settings.gtkInterfaceReducedMotion;\n }\n};\n\nconst followSystemPreferences = (provider: CssProvider, display: Display): void => {\n const settings = Settings.getForDisplay(display);\n\n const sync = (): void => {\n applyPreferences(provider, settings);\n };\n\n sync();\n settings.on(\"notify::gtk-interface-color-scheme\", sync);\n settings.on(\"notify::gtk-interface-contrast\", sync);\n\n if (hasReducedMotion()) {\n settings.on(\"notify::gtk-interface-reduced-motion\", sync);\n }\n};\n\nconst registerProviderForDefaultDisplay = (options?: Partial<ProviderOptions>): CssProvider => {\n const { priority, followsPreferences } = { ...DEFAULT_OPTIONS, ...options };\n const provider = new CssProvider();\n const manager = DisplayManager.get();\n\n const attach = (display: Display): void => {\n // eslint-disable-next-line @typescript-eslint/no-deprecated\n StyleContext.addProviderForDisplay(display, provider, priority);\n\n if (followsPreferences) {\n followSystemPreferences(provider, display);\n }\n };\n\n const initialDisplay = manager.getDefaultDisplay();\n\n if (initialDisplay) {\n attach(initialDisplay);\n } else {\n manager.once(\"display-opened\", (openedDisplay: Display): void => {\n attach(openedDisplay);\n });\n }\n\n return provider;\n};\n\nconst attachParsingErrorLogger = (provider: CssProvider, log: Logger, subject: string): void => {\n if (process.env.NODE_ENV !== \"production\") {\n provider.on(\"parsing-error\", (section, error) => {\n log.warn(`GTK4 rejected ${subject} at ${section.toString()}: ${error.message}`);\n });\n }\n};\n\nexport { type ProviderOptions, registerProviderForDefaultDisplay, attachParsingErrorLogger };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-text.d.ts","sourceRoot":"","sources":["../src/rule-text.ts"],"names":[],"mappings":"AACA,QAAA,MAAM,UAAU,+CAA+C,CAAC;AAEhE,QAAA,MAAM,MAAM,GAAI,MAAM,MAAM,KAAG,OAA6B,CAAC;AAC7D,QAAA,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,MAA8C,CAAC;AAErF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const NUL = "\u{0}";
|
|
2
|
+
const NUL_REASON = "carries a NUL byte, which GTK4 cannot load";
|
|
3
|
+
const hasNul = (rule) => rule.includes(NUL);
|
|
4
|
+
const printableRule = (rule) => rule.replaceAll(NUL, String.raw `\0`);
|
|
5
|
+
export { hasNul, NUL_REASON, printableRule };
|
|
6
|
+
//# sourceMappingURL=rule-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-text.js","sourceRoot":"","sources":["../src/rule-text.ts"],"names":[],"mappings":"AAAA,MAAM,GAAG,GAAG,OAAO,CAAC;AACpB,MAAM,UAAU,GAAG,4CAA4C,CAAC;AAEhE,MAAM,MAAM,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC7D,MAAM,aAAa,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAA,IAAI,CAAC,CAAC;AAErF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC","sourcesContent":["const NUL = \"\\u{0}\";\nconst NUL_REASON = \"carries a NUL byte, which GTK4 cannot load\";\n\nconst hasNul = (rule: string): boolean => rule.includes(NUL);\nconst printableRule = (rule: string): string => rule.replaceAll(NUL, String.raw`\\0`);\n\nexport { hasNul, NUL_REASON, printableRule };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoped-rule.d.ts","sourceRoot":"","sources":["../src/scoped-rule.ts"],"names":[],"mappings":"AA4CA,QAAA,MAAM,UAAU,GAAI,WAAW,MAAM,EAAE,OAAO,MAAM,KAAG,MActD,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { serializeStyles } from "@emotion/serialize";
|
|
2
|
+
import { createLogger } from "@gtkx/utils";
|
|
3
|
+
import { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
4
|
+
import { eachRule, terminateDeclarations } from "./serialize-rule.js";
|
|
5
|
+
const log = createLogger("css");
|
|
6
|
+
const ESCAPE_REASON = "does not start from the widget it belongs to";
|
|
7
|
+
const NAME_CHAR = /[\w-]/;
|
|
8
|
+
const isScoped = (rule, className) => {
|
|
9
|
+
const prefix = `.${className}`;
|
|
10
|
+
if (!rule.startsWith(prefix)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const next = rule.charAt(prefix.length);
|
|
14
|
+
return next !== "" && !NAME_CHAR.test(next);
|
|
15
|
+
};
|
|
16
|
+
const unusableReason = (rule, className) => {
|
|
17
|
+
if (hasNul(rule)) {
|
|
18
|
+
return NUL_REASON;
|
|
19
|
+
}
|
|
20
|
+
return isScoped(rule, className) ? null : ESCAPE_REASON;
|
|
21
|
+
};
|
|
22
|
+
const collect = (scope, rule) => {
|
|
23
|
+
const reason = unusableReason(rule, scope.className);
|
|
24
|
+
if (reason !== null) {
|
|
25
|
+
log.warn(`Dropped a style rule that ${reason}: ${printableRule(rule)}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
scope.rules.push(rule);
|
|
29
|
+
};
|
|
30
|
+
const scopedRule = (className, style) => {
|
|
31
|
+
const declarations = terminateDeclarations(serializeStyles([style], {}).styles);
|
|
32
|
+
if (declarations.length === 0) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
const scope = { className, rules: [] };
|
|
36
|
+
eachRule(`.${className}{${declarations}}`, (rule) => {
|
|
37
|
+
collect(scope, rule);
|
|
38
|
+
});
|
|
39
|
+
return scope.rules.join("");
|
|
40
|
+
};
|
|
41
|
+
export { scopedRule };
|
|
42
|
+
//# sourceMappingURL=scoped-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoped-rule.js","sourceRoot":"","sources":["../src/scoped-rule.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAItE,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;AAChC,MAAM,aAAa,GAAG,8CAA8C,CAAC;AACrE,MAAM,SAAS,GAAG,OAAO,CAAC;AAE1B,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,SAAiB,EAAW,EAAE;IAC1D,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;IAE/B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACjB,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAExC,OAAO,IAAI,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,IAAY,EAAE,SAAiB,EAAiB,EAAE;IACtE,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC;AAC5D,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,KAAY,EAAE,IAAY,EAAQ,EAAE;IACjD,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAErD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAClB,GAAG,CAAC,IAAI,CAAC,6BAA6B,MAAM,KAAK,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAExE,OAAO;IACX,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,SAAiB,EAAE,KAAa,EAAU,EAAE;IAC5D,MAAM,YAAY,GAAG,qBAAqB,CAAC,eAAe,CAAC,CAAC,KAAkB,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAE7F,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,CAAC;IACd,CAAC;IAED,MAAM,KAAK,GAAU,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;IAE9C,QAAQ,CAAC,IAAI,SAAS,IAAI,YAAY,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE;QAChD,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChC,CAAC,CAAC;AAEF,OAAO,EAAE,UAAU,EAAE,CAAC","sourcesContent":["import type { CSSObject } from \"@emotion/serialize\";\nimport { serializeStyles } from \"@emotion/serialize\";\nimport { createLogger } from \"@gtkx/utils\";\nimport { hasNul, NUL_REASON, printableRule } from \"./rule-text.js\";\nimport { eachRule, terminateDeclarations } from \"./serialize-rule.js\";\n\ntype Scope = { className: string; rules: string[] };\n\nconst log = createLogger(\"css\");\nconst ESCAPE_REASON = \"does not start from the widget it belongs to\";\nconst NAME_CHAR = /[\\w-]/;\n\nconst isScoped = (rule: string, className: string): boolean => {\n const prefix = `.${className}`;\n\n if (!rule.startsWith(prefix)) {\n return false;\n }\n\n const next = rule.charAt(prefix.length);\n\n return next !== \"\" && !NAME_CHAR.test(next);\n};\n\nconst unusableReason = (rule: string, className: string): string | null => {\n if (hasNul(rule)) {\n return NUL_REASON;\n }\n\n return isScoped(rule, className) ? null : ESCAPE_REASON;\n};\n\nconst collect = (scope: Scope, rule: string): void => {\n const reason = unusableReason(rule, scope.className);\n\n if (reason !== null) {\n log.warn(`Dropped a style rule that ${reason}: ${printableRule(rule)}`);\n\n return;\n }\n\n scope.rules.push(rule);\n};\n\nconst scopedRule = (className: string, style: object): string => {\n const declarations = terminateDeclarations(serializeStyles([style as CSSObject], {}).styles);\n\n if (declarations.length === 0) {\n return \"\";\n }\n\n const scope: Scope = { className, rules: [] };\n\n eachRule(`.${className}{${declarations}}`, (rule) => {\n collect(scope, rule);\n });\n\n return scope.rules.join(\"\");\n};\n\nexport { scopedRule };\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize-rule.d.ts","sourceRoot":"","sources":["../src/serialize-rule.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,qBAAqB,GAAI,QAAQ,MAAM,KAAG,MAQ/C,CAAC;AAEF,QAAA,MAAM,QAAQ,GAAI,OAAO,MAAM,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAAG,IAWhE,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { compile, middleware, rulesheet, stringify, serialize as stylisSerialize } from "stylis";
|
|
2
|
+
import { escapeNamedColors, restoreNamedColors } from "./named-colors.js";
|
|
3
|
+
const LABEL_DECL_FIRST_CHAR = 108;
|
|
4
|
+
const LABEL_DECL_THIRD_CHAR = 98;
|
|
5
|
+
const removeLabel = (element) => {
|
|
6
|
+
if (!(element.type === "decl" &&
|
|
7
|
+
element.value.codePointAt(0) === LABEL_DECL_FIRST_CHAR &&
|
|
8
|
+
element.value.codePointAt(2) === LABEL_DECL_THIRD_CHAR)) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
element.return = "";
|
|
12
|
+
element.value = "";
|
|
13
|
+
};
|
|
14
|
+
const terminateDeclarations = (styles) => {
|
|
15
|
+
const trimmed = styles.trimEnd();
|
|
16
|
+
if (trimmed.length === 0 || trimmed.endsWith(";") || trimmed.endsWith("}")) {
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
return `${trimmed};`;
|
|
20
|
+
};
|
|
21
|
+
const eachRule = (input, visit) => {
|
|
22
|
+
stylisSerialize(compile(escapeNamedColors(input)), middleware([
|
|
23
|
+
removeLabel,
|
|
24
|
+
stringify,
|
|
25
|
+
rulesheet((rule) => {
|
|
26
|
+
visit(restoreNamedColors(rule));
|
|
27
|
+
}),
|
|
28
|
+
]));
|
|
29
|
+
};
|
|
30
|
+
export { eachRule, terminateDeclarations };
|
|
31
|
+
//# sourceMappingURL=serialize-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialize-rule.js","sourceRoot":"","sources":["../src/serialize-rule.ts"],"names":[],"mappings":"AACA,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;AAE1E,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,qBAAqB,GAAG,CAAC,MAAc,EAAU,EAAE;IACrD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;IAEjC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACzE,OAAO,OAAO,CAAC;IACnB,CAAC;IAED,OAAO,GAAG,OAAO,GAAG,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,KAA6B,EAAQ,EAAE;IACpE,eAAe,CACX,OAAO,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,EACjC,UAAU,CAAC;QACP,WAAW;QACX,SAAS;QACT,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE;YACf,KAAK,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC,CAAC;KACL,CAAC,CACL,CAAC;AACN,CAAC,CAAC;AAEF,OAAO,EAAE,QAAQ,EAAE,qBAAqB,EAAE,CAAC","sourcesContent":["import type { Element } from \"stylis\";\nimport { compile, middleware, rulesheet, stringify, serialize as stylisSerialize } from \"stylis\";\nimport { escapeNamedColors, restoreNamedColors } from \"./named-colors.js\";\n\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 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 eachRule = (input: string, visit: (rule: string) => void): void => {\n stylisSerialize(\n compile(escapeNamedColors(input)),\n middleware([\n removeLabel,\n stringify,\n rulesheet((rule) => {\n visit(restoreNamedColors(rule));\n }),\n ]),\n );\n};\n\nexport { eachRule, terminateDeclarations };\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":"AAUA,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,11 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "@gtkx/utils";
|
|
2
2
|
import { attachParsingErrorLogger, registerProviderForDefaultDisplay } from "./provider.js";
|
|
3
|
+
import { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
3
4
|
import { containmentFailure } from "./self-contained.js";
|
|
4
5
|
const log = createLogger("css");
|
|
5
|
-
const
|
|
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`);
|
|
6
|
+
const unusableReason = (rule) => (hasNul(rule) ? NUL_REASON : containmentFailure(rule));
|
|
9
7
|
class StyleSheet {
|
|
10
8
|
css = "";
|
|
11
9
|
provider = null;
|
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;AAC5F,OAAO,EAAE,
|
|
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,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEzD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;AAEhC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;AAE/G,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 { hasNul, NUL_REASON, printableRule } from \"./rule-text.js\";\nimport { containmentFailure } from \"./self-contained.js\";\n\nconst log = createLogger(\"css\");\n\nconst unusableReason = (rule: string): string | null => (hasNul(rule) ? NUL_REASON : containmentFailure(rule));\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/css",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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",
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
".": {
|
|
32
32
|
"types": "./dist/index.d.ts",
|
|
33
33
|
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./internal": {
|
|
36
|
+
"types": "./dist/internal.d.ts",
|
|
37
|
+
"default": "./dist/internal.js"
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
"sideEffects": false,
|
|
@@ -54,11 +58,11 @@
|
|
|
54
58
|
"dependencies": {
|
|
55
59
|
"@emotion/serialize": "^1.3.3",
|
|
56
60
|
"stylis": "^4.4.0",
|
|
57
|
-
"@gtkx/utils": "1.
|
|
61
|
+
"@gtkx/utils": "1.3.0"
|
|
58
62
|
},
|
|
59
63
|
"devDependencies": {
|
|
60
64
|
"@types/stylis": "^4.2.7",
|
|
61
|
-
"@gtkx/vitest": "1.
|
|
65
|
+
"@gtkx/vitest": "1.3.0"
|
|
62
66
|
},
|
|
63
67
|
"scripts": {
|
|
64
68
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/create-css.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { CSSInterpolation, RegisteredCache, SerializedStyles } from "@emotion/serialize";
|
|
2
|
-
import type { Element } from "stylis";
|
|
3
2
|
import { serializeStyles } from "@emotion/serialize";
|
|
4
|
-
import {
|
|
5
|
-
import { escapeNamedColors, restoreNamedColors } from "./named-colors.js";
|
|
3
|
+
import { eachRule, terminateDeclarations } from "./serialize-rule.js";
|
|
6
4
|
import { StyleSheet } from "./stylesheet.js";
|
|
7
5
|
|
|
8
6
|
/** A `cx` argument: a non-empty class name, or a boolean or nullish value that is dropped. */
|
|
@@ -28,32 +26,8 @@ type TokenPartition = { rawClasses: string[]; registeredStyles: string[] };
|
|
|
28
26
|
type CssState = { sheet: StyleSheet; inserted: Set<string>; registered: RegisteredCache };
|
|
29
27
|
|
|
30
28
|
const KEY = "gtkx";
|
|
31
|
-
const LABEL_DECL_FIRST_CHAR = 108;
|
|
32
|
-
const LABEL_DECL_THIRD_CHAR = 98;
|
|
33
|
-
|
|
34
|
-
const removeLabel = (element: Element): void => {
|
|
35
|
-
if (!(element.type === "decl" &&
|
|
36
|
-
element.value.codePointAt(0) === LABEL_DECL_FIRST_CHAR &&
|
|
37
|
-
element.value.codePointAt(2) === LABEL_DECL_THIRD_CHAR)) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
element.return = "";
|
|
42
|
-
element.value = "";
|
|
43
|
-
};
|
|
44
29
|
|
|
45
30
|
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
|
-
|
|
57
31
|
const isNonEmptyString = (token: CxToken): token is string => typeof token === "string" && token.length > 0;
|
|
58
32
|
|
|
59
33
|
const partitionTokens = (tokens: string[], registered: RegisteredCache): TokenPartition => {
|
|
@@ -73,17 +47,10 @@ const partitionTokens = (tokens: string[], registered: RegisteredCache): TokenPa
|
|
|
73
47
|
return { rawClasses, registeredStyles };
|
|
74
48
|
};
|
|
75
49
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
removeLabel,
|
|
81
|
-
stringify,
|
|
82
|
-
rulesheet((rule) => {
|
|
83
|
-
sheet.insert(restoreNamedColors(rule));
|
|
84
|
-
}),
|
|
85
|
-
]),
|
|
86
|
-
);
|
|
50
|
+
const insertRules = (state: CssState, input: string): void => {
|
|
51
|
+
eachRule(input, (rule) => {
|
|
52
|
+
state.sheet.insert(rule);
|
|
53
|
+
});
|
|
87
54
|
};
|
|
88
55
|
|
|
89
56
|
const didMarkNewStyle = (state: CssState, serialized: SerializedStyles): boolean => {
|
|
@@ -103,7 +70,7 @@ const insertStyles = (state: CssState, serialized: SerializedStyles): void => {
|
|
|
103
70
|
|
|
104
71
|
const className = getClassName(serialized);
|
|
105
72
|
const styles = terminateDeclarations(serialized.styles);
|
|
106
|
-
|
|
73
|
+
insertRules(state, `.${className}{${styles}}`);
|
|
107
74
|
state.registered[className] = styles;
|
|
108
75
|
};
|
|
109
76
|
|
|
@@ -112,7 +79,7 @@ const insertWithoutScoping = (state: CssState, serialized: SerializedStyles): vo
|
|
|
112
79
|
return;
|
|
113
80
|
}
|
|
114
81
|
|
|
115
|
-
|
|
82
|
+
insertRules(state, serialized.styles);
|
|
116
83
|
};
|
|
117
84
|
|
|
118
85
|
const cssClassName = (state: CssState, args: CSSInterpolation[]): string => {
|
package/src/internal.ts
ADDED
package/src/provider.ts
CHANGED
|
@@ -1,16 +1,61 @@
|
|
|
1
1
|
import type { Logger } from "@gtkx/utils";
|
|
2
2
|
import { type Display, DisplayManager } from "@gtkx/gi/gdk";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
checkVersion,
|
|
5
|
+
CssProvider,
|
|
6
|
+
Settings,
|
|
7
|
+
STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
8
|
+
StyleContext,
|
|
9
|
+
} from "@gtkx/gi/gtk";
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
11
|
+
type ProviderOptions = { priority: number; followsPreferences: boolean };
|
|
12
|
+
|
|
13
|
+
const REDUCED_MOTION_MINOR = 22;
|
|
14
|
+
|
|
15
|
+
const DEFAULT_OPTIONS: ProviderOptions = {
|
|
16
|
+
priority: STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
17
|
+
followsPreferences: true,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const hasReducedMotion = (): boolean => checkVersion(4, REDUCED_MOTION_MINOR, 0) === null;
|
|
21
|
+
|
|
22
|
+
const applyPreferences = (provider: CssProvider, settings: Settings): void => {
|
|
23
|
+
provider.prefersColorScheme = settings.gtkInterfaceColorScheme;
|
|
24
|
+
provider.prefersContrast = settings.gtkInterfaceContrast;
|
|
25
|
+
|
|
26
|
+
if (hasReducedMotion()) {
|
|
27
|
+
provider.prefersReducedMotion = settings.gtkInterfaceReducedMotion;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const followSystemPreferences = (provider: CssProvider, display: Display): void => {
|
|
32
|
+
const settings = Settings.getForDisplay(display);
|
|
33
|
+
|
|
34
|
+
const sync = (): void => {
|
|
35
|
+
applyPreferences(provider, settings);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
sync();
|
|
39
|
+
settings.on("notify::gtk-interface-color-scheme", sync);
|
|
40
|
+
settings.on("notify::gtk-interface-contrast", sync);
|
|
41
|
+
|
|
42
|
+
if (hasReducedMotion()) {
|
|
43
|
+
settings.on("notify::gtk-interface-reduced-motion", sync);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const registerProviderForDefaultDisplay = (options?: Partial<ProviderOptions>): CssProvider => {
|
|
48
|
+
const { priority, followsPreferences } = { ...DEFAULT_OPTIONS, ...options };
|
|
8
49
|
const provider = new CssProvider();
|
|
9
50
|
const manager = DisplayManager.get();
|
|
10
51
|
|
|
11
52
|
const attach = (display: Display): void => {
|
|
12
53
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
13
54
|
StyleContext.addProviderForDisplay(display, provider, priority);
|
|
55
|
+
|
|
56
|
+
if (followsPreferences) {
|
|
57
|
+
followSystemPreferences(provider, display);
|
|
58
|
+
}
|
|
14
59
|
};
|
|
15
60
|
|
|
16
61
|
const initialDisplay = manager.getDefaultDisplay();
|
|
@@ -34,4 +79,4 @@ const attachParsingErrorLogger = (provider: CssProvider, log: Logger, subject: s
|
|
|
34
79
|
}
|
|
35
80
|
};
|
|
36
81
|
|
|
37
|
-
export { registerProviderForDefaultDisplay, attachParsingErrorLogger };
|
|
82
|
+
export { type ProviderOptions, registerProviderForDefaultDisplay, attachParsingErrorLogger };
|
package/src/rule-text.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const NUL = "\u{0}";
|
|
2
|
+
const NUL_REASON = "carries a NUL byte, which GTK4 cannot load";
|
|
3
|
+
|
|
4
|
+
const hasNul = (rule: string): boolean => rule.includes(NUL);
|
|
5
|
+
const printableRule = (rule: string): string => rule.replaceAll(NUL, String.raw`\0`);
|
|
6
|
+
|
|
7
|
+
export { hasNul, NUL_REASON, printableRule };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { CSSObject } from "@emotion/serialize";
|
|
2
|
+
import { serializeStyles } from "@emotion/serialize";
|
|
3
|
+
import { createLogger } from "@gtkx/utils";
|
|
4
|
+
import { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
5
|
+
import { eachRule, terminateDeclarations } from "./serialize-rule.js";
|
|
6
|
+
|
|
7
|
+
type Scope = { className: string; rules: string[] };
|
|
8
|
+
|
|
9
|
+
const log = createLogger("css");
|
|
10
|
+
const ESCAPE_REASON = "does not start from the widget it belongs to";
|
|
11
|
+
const NAME_CHAR = /[\w-]/;
|
|
12
|
+
|
|
13
|
+
const isScoped = (rule: string, className: string): boolean => {
|
|
14
|
+
const prefix = `.${className}`;
|
|
15
|
+
|
|
16
|
+
if (!rule.startsWith(prefix)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const next = rule.charAt(prefix.length);
|
|
21
|
+
|
|
22
|
+
return next !== "" && !NAME_CHAR.test(next);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const unusableReason = (rule: string, className: string): string | null => {
|
|
26
|
+
if (hasNul(rule)) {
|
|
27
|
+
return NUL_REASON;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return isScoped(rule, className) ? null : ESCAPE_REASON;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const collect = (scope: Scope, rule: string): void => {
|
|
34
|
+
const reason = unusableReason(rule, scope.className);
|
|
35
|
+
|
|
36
|
+
if (reason !== null) {
|
|
37
|
+
log.warn(`Dropped a style rule that ${reason}: ${printableRule(rule)}`);
|
|
38
|
+
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
scope.rules.push(rule);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const scopedRule = (className: string, style: object): string => {
|
|
46
|
+
const declarations = terminateDeclarations(serializeStyles([style as CSSObject], {}).styles);
|
|
47
|
+
|
|
48
|
+
if (declarations.length === 0) {
|
|
49
|
+
return "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const scope: Scope = { className, rules: [] };
|
|
53
|
+
|
|
54
|
+
eachRule(`.${className}{${declarations}}`, (rule) => {
|
|
55
|
+
collect(scope, rule);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return scope.rules.join("");
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export { scopedRule };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Element } from "stylis";
|
|
2
|
+
import { compile, middleware, rulesheet, stringify, serialize as stylisSerialize } from "stylis";
|
|
3
|
+
import { escapeNamedColors, restoreNamedColors } from "./named-colors.js";
|
|
4
|
+
|
|
5
|
+
const LABEL_DECL_FIRST_CHAR = 108;
|
|
6
|
+
const LABEL_DECL_THIRD_CHAR = 98;
|
|
7
|
+
|
|
8
|
+
const removeLabel = (element: Element): void => {
|
|
9
|
+
if (!(element.type === "decl" &&
|
|
10
|
+
element.value.codePointAt(0) === LABEL_DECL_FIRST_CHAR &&
|
|
11
|
+
element.value.codePointAt(2) === LABEL_DECL_THIRD_CHAR)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
element.return = "";
|
|
16
|
+
element.value = "";
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const terminateDeclarations = (styles: string): string => {
|
|
20
|
+
const trimmed = styles.trimEnd();
|
|
21
|
+
|
|
22
|
+
if (trimmed.length === 0 || trimmed.endsWith(";") || trimmed.endsWith("}")) {
|
|
23
|
+
return trimmed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return `${trimmed};`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const eachRule = (input: string, visit: (rule: string) => void): void => {
|
|
30
|
+
stylisSerialize(
|
|
31
|
+
compile(escapeNamedColors(input)),
|
|
32
|
+
middleware([
|
|
33
|
+
removeLabel,
|
|
34
|
+
stringify,
|
|
35
|
+
rulesheet((rule) => {
|
|
36
|
+
visit(restoreNamedColors(rule));
|
|
37
|
+
}),
|
|
38
|
+
]),
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export { eachRule, terminateDeclarations };
|
package/src/stylesheet.ts
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
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 { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
4
5
|
import { containmentFailure } from "./self-contained.js";
|
|
5
6
|
|
|
6
7
|
const log = createLogger("css");
|
|
7
|
-
const NUL = "\u{0}";
|
|
8
|
-
const NUL_REASON = "carries a NUL byte, which GTK4 cannot load";
|
|
9
8
|
|
|
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`);
|
|
9
|
+
const unusableReason = (rule: string): string | null => (hasNul(rule) ? NUL_REASON : containmentFailure(rule));
|
|
14
10
|
|
|
15
11
|
class StyleSheet {
|
|
16
12
|
private css = "";
|