@gtkx/css 1.4.0 → 1.6.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 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":"AA6CA,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAQnD,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
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"}
@@ -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;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"]}
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"]}
@@ -1 +1 @@
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"}
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"}
@@ -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) => (hasNul(rule) ? NUL_REASON : containmentFailure(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;
@@ -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;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"]}
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 brokenToken: (text: string) => string | null;
2
- export { brokenToken };
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
@@ -1 +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"}
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 NEWLINE = "\n";
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 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);
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 took(index + 2);
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 (text.startsWith(COMMENT_OPEN, index)) {
61
- return readComment(text, index + COMMENT_OPEN.length);
14
+ if (scan.quote === null) {
15
+ return { index: scan.index + 1, quote: QUOTES.has(char) ? char : null, broken: false };
62
16
  }
63
- return isUrlStart(text, index) ? readUrl(text, index + URL_OPEN.length) : took(index + 1);
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 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;
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 null;
31
+ return false;
75
32
  };
76
- export { brokenToken };
33
+ export { hasNewlineInString, NEWLINE_IN_STRING };
77
34
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA,MAAM,YAAY,GAAG,8CAA8C,CAAC;AACpE,MAAM,iBAAiB,GAAG,mCAAmC,CAAC;AAC9D,MAAM,MAAM,GAAG,IAAI,CAAC;AACpB,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,MAAM,WAAW,GAAG,GAAG,CAAC;AACxB,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B,MAAM,aAAa,GAAG,IAAI,CAAC;AAC3B,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AACnC,MAAM,SAAS,GAAG,SAAS,CAAC;AAC5B,MAAM,UAAU,GAAG,yBAAyB,CAAC;AAE7C,MAAM,UAAU,GAAG,CAAC,OAAe,EAAE,IAAY,EAAE,KAAa,EAAW,EAAE;IACzE,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC;IAE1B,OAAO,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAW,EAAE,CACxD,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAE/E,MAAM,IAAI,GAAG,CAAC,KAAa,EAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;AAChE,MAAM,KAAK,GAAG,CAAC,KAAa,EAAE,MAAc,EAAQ,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;AAC3E,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,KAAa,EAAU,EAAE,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAEzG,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa,EAAe,EAAE;IAC5E,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACjB,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,KAAa,EAAQ,EAAE;IACpE,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAE5C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;YACf,OAAO,GAAG,CAAC;QACf,CAAC;QAED,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,KAAa,EAAQ,EAAE;IACtD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;IAE/C,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAC5F,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,KAAa,EAAQ,EAAE;IAClD,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,WAAW,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC3B,CAAC;QAED,KAAK,GAAG,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,KAAK,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,KAAa,EAAQ,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAClB,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IAC3B,CAAC;IAED,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,OAAO,UAAU,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,IAAI,CAAC,UAAU,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,WAAW,CAAC,IAAI,EAAE,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AAC9F,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,IAAY,EAAiB,EAAE;IAChD,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAEnC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,IAAI,CAAC,MAAM,CAAC;QACvB,CAAC;QAED,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACvB,CAAC;IAED,OAAO,IAAI,CAAC;AAChB,CAAC,CAAC;AAEF,OAAO,EAAE,WAAW,EAAE,CAAC","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"]}
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.4.0",
3
+ "version": "1.6.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.4.0"
61
+ "@gtkx/utils": "1.6.0"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/stylis": "^4.2.7",
65
- "@gtkx/vitest": "1.4.0"
65
+ "@gtkx/vitest": "1.6.0"
66
66
  },
67
67
  "scripts": {
68
68
  "release": "tsx ../../scripts/release-package.ts"
@@ -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
- 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
- };
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 => (hasNul(rule) ? NUL_REASON : containmentFailure(rule));
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 Step = { index: number; reason: string | null };
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 NEWLINE = "\n";
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 URL_TOKEN = /url\(/iy;
13
- const QUOTED_URL = /url\([\t\n\f\r ]*["']/iy;
7
+ const NEWLINES = new Set(["\n", "\r", "\f"]);
8
+ const AFTER_ESCAPE = 2;
9
+ const AFTER_ESCAPED_CRLF = 3;
14
10
 
15
- const hasMatchAt = (pattern: RegExp, text: string, index: number): boolean => {
16
- pattern.lastIndex = index;
11
+ const skipEscape = (rule: string, index: number): number =>
12
+ index + (rule.startsWith(CRLF, index + 1) ? AFTER_ESCAPED_CRLF : AFTER_ESCAPE);
17
13
 
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);
14
+ const stepped = (rule: string, scan: Scan): Scan => {
15
+ const char = rule.charAt(scan.index);
76
16
 
77
17
  if (char === ESCAPE) {
78
- return took(index + 2);
18
+ return { index: skipEscape(rule, scan.index), quote: scan.quote, broken: false };
79
19
  }
80
20
 
81
- if (QUOTES.has(char)) {
82
- return readString(text, index + 1, char);
21
+ if (scan.quote === null) {
22
+ return { index: scan.index + 1, quote: QUOTES.has(char) ? char : null, broken: false };
83
23
  }
84
24
 
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);
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 brokenToken = (text: string): string | null => {
93
- let index = 0;
32
+ const hasNewlineInString = (rule: string): boolean => {
33
+ let scan: Scan = { index: 0, quote: null, broken: false };
94
34
 
95
- while (index < text.length) {
96
- const step = readNext(text, index);
35
+ while (scan.index < rule.length) {
36
+ scan = stepped(rule, scan);
97
37
 
98
- if (step.reason !== null) {
99
- return step.reason;
38
+ if (scan.broken) {
39
+ return true;
100
40
  }
101
-
102
- index = step.index;
103
41
  }
104
42
 
105
- return null;
43
+ return false;
106
44
  };
107
45
 
108
- export { brokenToken };
46
+ export { hasNewlineInString, NEWLINE_IN_STRING };