@gtkx/css 1.4.0 → 1.5.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/self-contained.d.ts.map +1 -1
- package/dist/self-contained.js +1 -8
- package/dist/self-contained.js.map +1 -1
- package/dist/stylesheet.d.ts.map +1 -1
- package/dist/stylesheet.js +7 -1
- package/dist/stylesheet.js.map +1 -1
- package/dist/tokens.d.ts +3 -2
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +23 -66
- package/dist/tokens.js.map +1 -1
- package/package.json +3 -3
- package/src/self-contained.ts +2 -10
- package/src/stylesheet.ts +8 -1
- package/src/tokens.ts +25 -87
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, React Spring animations, React Navigation stack, tab, drawer, and split view navigators, and high-level list, grid, and dialog components,
|
|
101
|
+
- CSS-in-JS styling, React Hook Form-powered Adwaita controls, gettext-backed react-i18next localization, React Spring animations, React Navigation stack, tab, drawer, and split view navigators, 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
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"self-contained.d.ts","sourceRoot":"","sources":["../src/self-contained.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"self-contained.d.ts","sourceRoot":"","sources":["../src/self-contained.ts"],"names":[],"mappings":"AA4CA,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IACyB,CAAC;AAE9E,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
|
package/dist/self-contained.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CssProvider } from "@gtkx/gi/gtk";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { brokenToken } from "./tokens.js";
|
|
4
3
|
const PROBE_BYTES = 16;
|
|
5
4
|
const PROBE_RULE = `.gtkx-probe-${randomBytes(PROBE_BYTES).toString("hex")}{color:rgb(0, 0, 0);}`;
|
|
6
5
|
const SWALLOWS_WHAT_FOLLOWS = "would disable every rule after it";
|
|
@@ -28,12 +27,6 @@ const hasProbe = (serialized) => {
|
|
|
28
27
|
const ruleset = probe();
|
|
29
28
|
return ruleset.length > 0 && `\n${serialized}`.includes(`\n${ruleset}`);
|
|
30
29
|
};
|
|
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
|
-
};
|
|
30
|
+
const containmentFailure = (rule) => hasProbe(parsed(`${rule}\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;
|
|
38
31
|
export { containmentFailure };
|
|
39
32
|
//# sourceMappingURL=self-contained.js.map
|
|
@@ -1 +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;
|
|
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;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,CACvD,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,qBAAqB,CAAC;AAE9E,OAAO,EAAE,kBAAkB,EAAE,CAAC","sourcesContent":["import { CssProvider } from \"@gtkx/gi/gtk\";\nimport { randomBytes } from \"node:crypto\";\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 hasProbe(parsed(`${rule}\\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;\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":"AAiBA,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
|
@@ -2,8 +2,14 @@ import { createLogger } from "@gtkx/utils";
|
|
|
2
2
|
import { attachParsingErrorLogger, registerProviderForDefaultDisplay } from "./provider.js";
|
|
3
3
|
import { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
4
4
|
import { containmentFailure } from "./self-contained.js";
|
|
5
|
+
import { hasNewlineInString, NEWLINE_IN_STRING } from "./tokens.js";
|
|
5
6
|
const log = createLogger("css");
|
|
6
|
-
const unusableReason = (rule) =>
|
|
7
|
+
const unusableReason = (rule) => {
|
|
8
|
+
if (hasNul(rule)) {
|
|
9
|
+
return NUL_REASON;
|
|
10
|
+
}
|
|
11
|
+
return hasNewlineInString(rule) ? NEWLINE_IN_STRING : containmentFailure(rule);
|
|
12
|
+
};
|
|
7
13
|
class StyleSheet {
|
|
8
14
|
css = "";
|
|
9
15
|
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,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,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,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpE,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;AAEhC,MAAM,cAAc,GAAG,CAAC,IAAY,EAAiB,EAAE;IACnD,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,UAAU,CAAC;IACtB,CAAC;IAED,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;AACnF,CAAC,CAAC;AAEF,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\";\nimport { hasNewlineInString, NEWLINE_IN_STRING } from \"./tokens.js\";\n\nconst log = createLogger(\"css\");\n\nconst unusableReason = (rule: string): string | null => {\n if (hasNul(rule)) {\n return NUL_REASON;\n }\n\n return hasNewlineInString(rule) ? NEWLINE_IN_STRING : containmentFailure(rule);\n};\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
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
declare const
|
|
2
|
-
|
|
1
|
+
declare const NEWLINE_IN_STRING = "carries a newline inside a string";
|
|
2
|
+
declare const hasNewlineInString: (rule: string) => boolean;
|
|
3
|
+
export { hasNewlineInString, NEWLINE_IN_STRING };
|
|
3
4
|
//# sourceMappingURL=tokens.d.ts.map
|
package/dist/tokens.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA,QAAA,MAAM,iBAAiB,sCAAsC,CAAC;AA6B9D,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAY1C,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAC"}
|
package/dist/tokens.js
CHANGED
|
@@ -1,77 +1,34 @@
|
|
|
1
|
-
const UNTERMINATED = "leaves a string, comment or url unterminated";
|
|
2
1
|
const NEWLINE_IN_STRING = "carries a newline inside a string";
|
|
3
2
|
const ESCAPE = "\\";
|
|
4
|
-
const
|
|
5
|
-
const CLOSE_PAREN = ")";
|
|
6
|
-
const COMMENT_OPEN = "/*";
|
|
7
|
-
const COMMENT_CLOSE = "*/";
|
|
8
|
-
const URL_OPEN = "url(";
|
|
3
|
+
const CRLF = "\r\n";
|
|
9
4
|
const QUOTES = new Set(['"', "'"]);
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
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);
|
|
5
|
+
const NEWLINES = new Set(["\n", "\r", "\f"]);
|
|
6
|
+
const AFTER_ESCAPE = 2;
|
|
7
|
+
const AFTER_ESCAPED_CRLF = 3;
|
|
8
|
+
const skipEscape = (rule, index) => index + (rule.startsWith(CRLF, index + 1) ? AFTER_ESCAPED_CRLF : AFTER_ESCAPE);
|
|
9
|
+
const stepped = (rule, scan) => {
|
|
10
|
+
const char = rule.charAt(scan.index);
|
|
54
11
|
if (char === ESCAPE) {
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
if (QUOTES.has(char)) {
|
|
58
|
-
return readString(text, index + 1, char);
|
|
12
|
+
return { index: skipEscape(rule, scan.index), quote: scan.quote, broken: false };
|
|
59
13
|
}
|
|
60
|
-
if (
|
|
61
|
-
return
|
|
14
|
+
if (scan.quote === null) {
|
|
15
|
+
return { index: scan.index + 1, quote: QUOTES.has(char) ? char : null, broken: false };
|
|
62
16
|
}
|
|
63
|
-
return
|
|
17
|
+
return {
|
|
18
|
+
index: scan.index + 1,
|
|
19
|
+
quote: char === scan.quote ? null : scan.quote,
|
|
20
|
+
broken: NEWLINES.has(char),
|
|
21
|
+
};
|
|
64
22
|
};
|
|
65
|
-
const
|
|
66
|
-
let
|
|
67
|
-
while (index <
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
return
|
|
23
|
+
const hasNewlineInString = (rule) => {
|
|
24
|
+
let scan = { index: 0, quote: null, broken: false };
|
|
25
|
+
while (scan.index < rule.length) {
|
|
26
|
+
scan = stepped(rule, scan);
|
|
27
|
+
if (scan.broken) {
|
|
28
|
+
return true;
|
|
71
29
|
}
|
|
72
|
-
index = step.index;
|
|
73
30
|
}
|
|
74
|
-
return
|
|
31
|
+
return false;
|
|
75
32
|
};
|
|
76
|
-
export {
|
|
33
|
+
export { hasNewlineInString, NEWLINE_IN_STRING };
|
|
77
34
|
//# sourceMappingURL=tokens.js.map
|
package/dist/tokens.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA,MAAM,
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,mCAAmC,CAAC;AAC9D,MAAM,MAAM,GAAG,IAAI,CAAC;AACpB,MAAM,IAAI,GAAG,MAAM,CAAC;AACpB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAC7C,MAAM,YAAY,GAAG,CAAC,CAAC;AACvB,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAE7B,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CACvD,KAAK,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;AAEnF,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,IAAU,EAAQ,EAAE;IAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAErC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IACrF,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;QACtB,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC3F,CAAC;IAED,OAAO;QACH,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,CAAC;QACrB,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;QAC9C,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;KAC7B,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAW,EAAE;IACjD,IAAI,IAAI,GAAS,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAE1D,OAAO,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAE3B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,OAAO,IAAI,CAAC;QAChB,CAAC;IACL,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,CAAC","sourcesContent":["type Scan = { index: number; quote: string | null; broken: boolean };\n\nconst NEWLINE_IN_STRING = \"carries a newline inside a string\";\nconst ESCAPE = \"\\\\\";\nconst CRLF = \"\\r\\n\";\nconst QUOTES = new Set(['\"', \"'\"]);\nconst NEWLINES = new Set([\"\\n\", \"\\r\", \"\\f\"]);\nconst AFTER_ESCAPE = 2;\nconst AFTER_ESCAPED_CRLF = 3;\n\nconst skipEscape = (rule: string, index: number): number =>\n index + (rule.startsWith(CRLF, index + 1) ? AFTER_ESCAPED_CRLF : AFTER_ESCAPE);\n\nconst stepped = (rule: string, scan: Scan): Scan => {\n const char = rule.charAt(scan.index);\n\n if (char === ESCAPE) {\n return { index: skipEscape(rule, scan.index), quote: scan.quote, broken: false };\n }\n\n if (scan.quote === null) {\n return { index: scan.index + 1, quote: QUOTES.has(char) ? char : null, broken: false };\n }\n\n return {\n index: scan.index + 1,\n quote: char === scan.quote ? null : scan.quote,\n broken: NEWLINES.has(char),\n };\n};\n\nconst hasNewlineInString = (rule: string): boolean => {\n let scan: Scan = { index: 0, quote: null, broken: false };\n\n while (scan.index < rule.length) {\n scan = stepped(rule, scan);\n\n if (scan.broken) {\n return true;\n }\n }\n\n return false;\n};\n\nexport { hasNewlineInString, NEWLINE_IN_STRING };\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/css",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CSS-in-JS for GTK that compiles to real GTK4 CSS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@emotion/serialize": "^1.3.3",
|
|
60
60
|
"stylis": "^4.4.0",
|
|
61
|
-
"@gtkx/utils": "1.
|
|
61
|
+
"@gtkx/utils": "1.5.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/stylis": "^4.2.7",
|
|
65
|
-
"@gtkx/vitest": "1.
|
|
65
|
+
"@gtkx/vitest": "1.5.0"
|
|
66
66
|
},
|
|
67
67
|
"scripts": {
|
|
68
68
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/self-contained.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { CssProvider } from "@gtkx/gi/gtk";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import { brokenToken } from "./tokens.js";
|
|
4
3
|
|
|
5
4
|
type Gate = { provider: CssProvider | null; ruleset: string | null };
|
|
6
5
|
|
|
@@ -43,14 +42,7 @@ const hasProbe = (serialized: string): boolean => {
|
|
|
43
42
|
return ruleset.length > 0 && `\n${serialized}`.includes(`\n${ruleset}`);
|
|
44
43
|
};
|
|
45
44
|
|
|
46
|
-
const containmentFailure = (rule: string): string | null =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
if (broken !== null) {
|
|
50
|
-
return broken;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return hasProbe(parsed(`${rule}\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;
|
|
54
|
-
};
|
|
45
|
+
const containmentFailure = (rule: string): string | null =>
|
|
46
|
+
hasProbe(parsed(`${rule}\n${PROBE_RULE}`)) ? null : SWALLOWS_WHAT_FOLLOWS;
|
|
55
47
|
|
|
56
48
|
export { containmentFailure };
|
package/src/stylesheet.ts
CHANGED
|
@@ -3,10 +3,17 @@ import { createLogger } from "@gtkx/utils";
|
|
|
3
3
|
import { attachParsingErrorLogger, registerProviderForDefaultDisplay } from "./provider.js";
|
|
4
4
|
import { hasNul, NUL_REASON, printableRule } from "./rule-text.js";
|
|
5
5
|
import { containmentFailure } from "./self-contained.js";
|
|
6
|
+
import { hasNewlineInString, NEWLINE_IN_STRING } from "./tokens.js";
|
|
6
7
|
|
|
7
8
|
const log = createLogger("css");
|
|
8
9
|
|
|
9
|
-
const unusableReason = (rule: string): string | null =>
|
|
10
|
+
const unusableReason = (rule: string): string | null => {
|
|
11
|
+
if (hasNul(rule)) {
|
|
12
|
+
return NUL_REASON;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return hasNewlineInString(rule) ? NEWLINE_IN_STRING : containmentFailure(rule);
|
|
16
|
+
};
|
|
10
17
|
|
|
11
18
|
class StyleSheet {
|
|
12
19
|
private css = "";
|
package/src/tokens.ts
CHANGED
|
@@ -1,108 +1,46 @@
|
|
|
1
|
-
type
|
|
1
|
+
type Scan = { index: number; quote: string | null; broken: boolean };
|
|
2
2
|
|
|
3
|
-
const UNTERMINATED = "leaves a string, comment or url unterminated";
|
|
4
3
|
const NEWLINE_IN_STRING = "carries a newline inside a string";
|
|
5
4
|
const ESCAPE = "\\";
|
|
6
|
-
const
|
|
7
|
-
const CLOSE_PAREN = ")";
|
|
8
|
-
const COMMENT_OPEN = "/*";
|
|
9
|
-
const COMMENT_CLOSE = "*/";
|
|
10
|
-
const URL_OPEN = "url(";
|
|
5
|
+
const CRLF = "\r\n";
|
|
11
6
|
const QUOTES = new Set(['"', "'"]);
|
|
12
|
-
const
|
|
13
|
-
const
|
|
7
|
+
const NEWLINES = new Set(["\n", "\r", "\f"]);
|
|
8
|
+
const AFTER_ESCAPE = 2;
|
|
9
|
+
const AFTER_ESCAPED_CRLF = 3;
|
|
14
10
|
|
|
15
|
-
const
|
|
16
|
-
|
|
11
|
+
const skipEscape = (rule: string, index: number): number =>
|
|
12
|
+
index + (rule.startsWith(CRLF, index + 1) ? AFTER_ESCAPED_CRLF : AFTER_ESCAPE);
|
|
17
13
|
|
|
18
|
-
|
|
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);
|
|
14
|
+
const stepped = (rule: string, scan: Scan): Scan => {
|
|
15
|
+
const char = rule.charAt(scan.index);
|
|
76
16
|
|
|
77
17
|
if (char === ESCAPE) {
|
|
78
|
-
return
|
|
18
|
+
return { index: skipEscape(rule, scan.index), quote: scan.quote, broken: false };
|
|
79
19
|
}
|
|
80
20
|
|
|
81
|
-
if (
|
|
82
|
-
return
|
|
21
|
+
if (scan.quote === null) {
|
|
22
|
+
return { index: scan.index + 1, quote: QUOTES.has(char) ? char : null, broken: false };
|
|
83
23
|
}
|
|
84
24
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
25
|
+
return {
|
|
26
|
+
index: scan.index + 1,
|
|
27
|
+
quote: char === scan.quote ? null : scan.quote,
|
|
28
|
+
broken: NEWLINES.has(char),
|
|
29
|
+
};
|
|
90
30
|
};
|
|
91
31
|
|
|
92
|
-
const
|
|
93
|
-
let
|
|
32
|
+
const hasNewlineInString = (rule: string): boolean => {
|
|
33
|
+
let scan: Scan = { index: 0, quote: null, broken: false };
|
|
94
34
|
|
|
95
|
-
while (index <
|
|
96
|
-
|
|
35
|
+
while (scan.index < rule.length) {
|
|
36
|
+
scan = stepped(rule, scan);
|
|
97
37
|
|
|
98
|
-
if (
|
|
99
|
-
return
|
|
38
|
+
if (scan.broken) {
|
|
39
|
+
return true;
|
|
100
40
|
}
|
|
101
|
-
|
|
102
|
-
index = step.index;
|
|
103
41
|
}
|
|
104
42
|
|
|
105
|
-
return
|
|
43
|
+
return false;
|
|
106
44
|
};
|
|
107
45
|
|
|
108
|
-
export {
|
|
46
|
+
export { hasNewlineInString, NEWLINE_IN_STRING };
|