@pantoken/css 0.1.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 ADDED
@@ -0,0 +1,69 @@
1
+ # @pantoken/css
2
+
3
+ Emit Instructure design tokens as `@property`-typed CSS, with `light-dark()` theming and icon
4
+ data-URIs, from the pantoken IR. Concrete tokens register as typed `@property` rules; themed and
5
+ reference tokens stay as scoped declarations.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm i @pantoken/css
11
+ ```
12
+
13
+ Also available as `pantoken/css`.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { toCss, css } from "@pantoken/css";
19
+ import { byTheme } from "@pantoken/tokens";
20
+
21
+ css; // the ready-made rebrand stylesheet string
22
+ toCss(byTheme("canvas"), { scope: ":root" }); // build CSS for another theme
23
+ ```
24
+
25
+ Side-effect injection (browser):
26
+
27
+ ```ts
28
+ import "@pantoken/css/inject"; // appends <style data-pantoken> to document.head
29
+ ```
30
+
31
+ Or a plain stylesheet: `@pantoken/css/style.css`.
32
+
33
+ A plugin's `css` hook can contribute or post-process CSS — for example, inject a focus ring that
34
+ isn't in the source tokens:
35
+
36
+ ```ts
37
+ toCss(tokens, {
38
+ plugins: [
39
+ {
40
+ name: "focus",
41
+ css: () => ({ append: ":focus-visible { outline: 2px solid var(--instui-focus-color); }" }),
42
+ },
43
+ ],
44
+ });
45
+ ```
46
+
47
+ ## API
48
+
49
+ - **`css: string`** — the ready-made `rebrand` stylesheet (also the default export).
50
+ - **`toCss(tokens, options?): string`** — build CSS for any token IR. `options.scope` sets the
51
+ declaration selector (default `":root"`); `options.plugins` runs `css` hooks after the base build.
52
+ - **`ToCssOptions`** — options for `toCss`.
53
+ - **`buildCssFile(args): string`** — the low-level builder: typed `@property` registrations followed
54
+ by scoped declaration sections.
55
+ - **`CssSection`** — one scoped block of custom properties, optionally wrapped in an `@layer`.
56
+ - **`inject(doc?): HTMLStyleElement | undefined`** (from `@pantoken/css/inject`) — inject the
57
+ stylesheet into `document.head` once; no-ops off the DOM.
58
+ - **`./inject`** — a DOM side-effect entry that injects the stylesheet on import.
59
+ - **`./style.css`** — the generated plain stylesheet, for a direct `<link>` or `@import`.
60
+
61
+ ## Related
62
+
63
+ - Pairs with `@pantoken/tokens` for the underlying token IR.
64
+ - `@pantoken/scss`, `@pantoken/less`, and `@pantoken/stylus` emit the same tokens as preprocessor
65
+ variables.
66
+
67
+ ## License
68
+
69
+ MIT
@@ -0,0 +1,112 @@
1
+ import { PantokenPlugin, PropertyRule, Token } from "@pantoken/model";
2
+
3
+ //#region src/to-css.d.ts
4
+ /** Options for {@link toCss}. */
5
+ interface ToCssOptions {
6
+ /** The selector scoped declarations are emitted under (default `":root"`). */
7
+ scope?: string;
8
+ /** Plugins whose `css` hooks run after the base CSS is built (default: none). */
9
+ plugins?: readonly PantokenPlugin[];
10
+ }
11
+ /**
12
+ * Emit CSS for a token IR.
13
+ *
14
+ * @param tokens - The IR (e.g. from `@pantoken/tokens`).
15
+ * @param options - {@link ToCssOptions}.
16
+ * @returns The CSS string.
17
+ *
18
+ * @example Build the default stylesheet
19
+ * ```ts
20
+ * import { toCss } from "@pantoken/css";
21
+ * import { tokens } from "@pantoken/tokens";
22
+ *
23
+ * const stylesheet = toCss(tokens); // declarations under :root
24
+ * ```
25
+ *
26
+ * @example Scope declarations to a class and build another theme
27
+ * ```ts
28
+ * import { toCss } from "@pantoken/css";
29
+ * import { byTheme } from "@pantoken/tokens";
30
+ *
31
+ * toCss(byTheme("canvas"), { scope: '[class*="instui"]' });
32
+ * ```
33
+ *
34
+ * @example Post-process with a plugin css hook
35
+ * ```ts
36
+ * import { toCss } from "@pantoken/css";
37
+ * import { tokens } from "@pantoken/tokens";
38
+ *
39
+ * toCss(tokens, {
40
+ * plugins: [
41
+ * {
42
+ * name: "focus",
43
+ * css: () => ({ append: ":focus-visible { outline: 2px solid var(--instui-focus-color); }" }),
44
+ * },
45
+ * ],
46
+ * });
47
+ * ```
48
+ */
49
+ declare function toCss(tokens: readonly Token[], options?: ToCssOptions): string;
50
+ //#endregion
51
+ //#region src/emit.d.ts
52
+ /** One scoped block of custom properties, optionally wrapped in an `@layer`. */
53
+ interface CssSection {
54
+ /** Plain layer name (e.g. `"pantoken"`), no `@` prefix. Omit to skip the wrapper. */
55
+ layer?: string;
56
+ pairs: [string, string][];
57
+ }
58
+ /**
59
+ * Build a CSS file string: typed `@property` registrations (top-level, theme-agnostic) followed by
60
+ * one or more scoped declaration sections (whose selector the consuming build may rewrite).
61
+ *
62
+ * @example Register one typed property and emit a scoped declaration
63
+ * ```ts
64
+ * import { buildCssFile } from "@pantoken/css";
65
+ *
66
+ * buildCssFile({
67
+ * comments: ["/* my tokens *\/"],
68
+ * scope: ":root",
69
+ * properties: [{ name: "--instui-color-brand", syntax: "<color>", value: "#0374b5" }],
70
+ * sections: [{ pairs: [["--instui-color-text-base", "var(--instui-color-brand)"]] }],
71
+ * });
72
+ * ```
73
+ *
74
+ * @example Wrap a section in a CSS cascade layer
75
+ * ```ts
76
+ * import { buildCssFile } from "@pantoken/css";
77
+ *
78
+ * buildCssFile({
79
+ * comments: [],
80
+ * scope: ":root",
81
+ * sections: [{ layer: "pantoken", pairs: [["--instui-spacing-space-md", "1rem"]] }],
82
+ * });
83
+ * ```
84
+ */
85
+ declare function buildCssFile({
86
+ comments,
87
+ scope,
88
+ properties,
89
+ sections,
90
+ inherits
91
+ }: {
92
+ comments: string[];
93
+ scope: string;
94
+ properties?: PropertyRule[];
95
+ sections: CssSection[];
96
+ inherits?: boolean;
97
+ }): string;
98
+ //#endregion
99
+ //#region src/index.d.ts
100
+ /**
101
+ * The ready-made `rebrand` stylesheet string.
102
+ *
103
+ * @example
104
+ * ```ts
105
+ * import { css } from "@pantoken/css";
106
+ *
107
+ * document.head.insertAdjacentHTML("beforeend", `<style>${css}</style>`);
108
+ * ```
109
+ */
110
+ declare const css: string;
111
+ //#endregion
112
+ export { type CssSection, type ToCssOptions, buildCssFile, css, css as default, toCss };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as toCss, r as buildCssFile, t as css } from "./src-DnTeei9H.mjs";
2
+ export { buildCssFile, css, css as default, toCss };
@@ -0,0 +1,22 @@
1
+ //#region src/inject.d.ts
2
+ /**
3
+ * Inject the pantoken stylesheet into `document.head` once. Returns the created element, if any.
4
+ *
5
+ * @example Inject on the default document
6
+ * ```ts
7
+ * import { inject } from "@pantoken/css/inject";
8
+ *
9
+ * const style = inject(); // <style data-pantoken> appended to document.head
10
+ * ```
11
+ *
12
+ * @example Inject into a specific document (e.g. an iframe)
13
+ * ```ts
14
+ * import { inject } from "@pantoken/css/inject";
15
+ *
16
+ * const frame = document.querySelector("iframe")!;
17
+ * inject(frame.contentDocument ?? undefined);
18
+ * ```
19
+ */
20
+ declare function inject(doc?: Document | undefined): HTMLStyleElement | undefined;
21
+ //#endregion
22
+ export { inject };
@@ -0,0 +1,39 @@
1
+ import { t as css } from "./src-DnTeei9H.mjs";
2
+ //#region src/inject.ts
3
+ /**
4
+ * `@pantoken/css/inject` — a DOM side-effect entry. Importing it injects the pantoken stylesheet
5
+ * into `document.head`. Safe to import in non-DOM environments (it no-ops).
6
+ *
7
+ * @module
8
+ */
9
+ /**
10
+ * Inject the pantoken stylesheet into `document.head` once. Returns the created element, if any.
11
+ *
12
+ * @example Inject on the default document
13
+ * ```ts
14
+ * import { inject } from "@pantoken/css/inject";
15
+ *
16
+ * const style = inject(); // <style data-pantoken> appended to document.head
17
+ * ```
18
+ *
19
+ * @example Inject into a specific document (e.g. an iframe)
20
+ * ```ts
21
+ * import { inject } from "@pantoken/css/inject";
22
+ *
23
+ * const frame = document.querySelector("iframe")!;
24
+ * inject(frame.contentDocument ?? undefined);
25
+ * ```
26
+ */
27
+ function inject(doc = globalThis.document) {
28
+ if (!doc) return void 0;
29
+ const existing = doc.querySelector("style[data-pantoken]");
30
+ if (existing) return existing;
31
+ const style = doc.createElement("style");
32
+ style.setAttribute("data-pantoken", "css");
33
+ style.textContent = css;
34
+ doc.head.append(style);
35
+ return style;
36
+ }
37
+ inject();
38
+ //#endregion
39
+ export { inject };
@@ -0,0 +1,181 @@
1
+ import { tokens } from "@pantoken/tokens";
2
+ import { checkPlugins } from "@pantoken/plugin-kit";
3
+ //#region src/emit.ts
4
+ function renderSection(scope, { layer, pairs }) {
5
+ const declarations = [
6
+ ` ${scope} {`,
7
+ ...pairs.map(([name, val]) => ` ${name}: ${val.trim()};`),
8
+ " }"
9
+ ];
10
+ return layer ? [
11
+ `@layer ${layer} {`,
12
+ ...declarations,
13
+ "}"
14
+ ].join("\n") : declarations.join("\n");
15
+ }
16
+ function renderProperty({ name, syntax, value }, inherits) {
17
+ return [
18
+ `@property ${name} {`,
19
+ ` syntax: "${syntax}";`,
20
+ ` inherits: ${inherits};`,
21
+ ` initial-value: ${value.trim()};`,
22
+ `}`
23
+ ].join("\n");
24
+ }
25
+ /**
26
+ * Build a CSS file string: typed `@property` registrations (top-level, theme-agnostic) followed by
27
+ * one or more scoped declaration sections (whose selector the consuming build may rewrite).
28
+ *
29
+ * @example Register one typed property and emit a scoped declaration
30
+ * ```ts
31
+ * import { buildCssFile } from "@pantoken/css";
32
+ *
33
+ * buildCssFile({
34
+ * comments: ["/* my tokens *\/"],
35
+ * scope: ":root",
36
+ * properties: [{ name: "--instui-color-brand", syntax: "<color>", value: "#0374b5" }],
37
+ * sections: [{ pairs: [["--instui-color-text-base", "var(--instui-color-brand)"]] }],
38
+ * });
39
+ * ```
40
+ *
41
+ * @example Wrap a section in a CSS cascade layer
42
+ * ```ts
43
+ * import { buildCssFile } from "@pantoken/css";
44
+ *
45
+ * buildCssFile({
46
+ * comments: [],
47
+ * scope: ":root",
48
+ * sections: [{ layer: "pantoken", pairs: [["--instui-spacing-space-md", "1rem"]] }],
49
+ * });
50
+ * ```
51
+ */
52
+ function buildCssFile({ comments, scope, properties = [], sections, inherits = true }) {
53
+ const blocks = [properties.map((p) => renderProperty(p, inherits)).join("\n\n"), ...sections.filter((s) => s.pairs.length > 0).map((s) => renderSection(scope, s))].filter(Boolean);
54
+ return [
55
+ `/* DO NOT EDIT — generated by @pantoken/css */`,
56
+ "",
57
+ ...comments,
58
+ "",
59
+ ...blocks,
60
+ ""
61
+ ].join("\n");
62
+ }
63
+ //#endregion
64
+ //#region src/to-css.ts
65
+ /**
66
+ * `toCss` — turn the token IR into CSS: concrete tokens become typed `@property` registrations,
67
+ * themed/reference tokens become scoped declarations. Plugins can contribute or post-process CSS.
68
+ *
69
+ * @module
70
+ */
71
+ /** True when a value cannot be a typed `@property` initial-value (`var()` / `light-dark()`). */
72
+ function isContextual(value) {
73
+ return /var\(|light-dark\(/.test(value);
74
+ }
75
+ function marked(marker, css) {
76
+ return marker ? `/* ${marker} */\n${css}` : css;
77
+ }
78
+ /**
79
+ * Emit CSS for a token IR.
80
+ *
81
+ * @param tokens - The IR (e.g. from `@pantoken/tokens`).
82
+ * @param options - {@link ToCssOptions}.
83
+ * @returns The CSS string.
84
+ *
85
+ * @example Build the default stylesheet
86
+ * ```ts
87
+ * import { toCss } from "@pantoken/css";
88
+ * import { tokens } from "@pantoken/tokens";
89
+ *
90
+ * const stylesheet = toCss(tokens); // declarations under :root
91
+ * ```
92
+ *
93
+ * @example Scope declarations to a class and build another theme
94
+ * ```ts
95
+ * import { toCss } from "@pantoken/css";
96
+ * import { byTheme } from "@pantoken/tokens";
97
+ *
98
+ * toCss(byTheme("canvas"), { scope: '[class*="instui"]' });
99
+ * ```
100
+ *
101
+ * @example Post-process with a plugin css hook
102
+ * ```ts
103
+ * import { toCss } from "@pantoken/css";
104
+ * import { tokens } from "@pantoken/tokens";
105
+ *
106
+ * toCss(tokens, {
107
+ * plugins: [
108
+ * {
109
+ * name: "focus",
110
+ * css: () => ({ append: ":focus-visible { outline: 2px solid var(--instui-focus-color); }" }),
111
+ * },
112
+ * ],
113
+ * });
114
+ * ```
115
+ */
116
+ function toCss(tokens, options = {}) {
117
+ const { scope = ":root", plugins = [] } = options;
118
+ const properties = [];
119
+ const declarations = [];
120
+ for (const token of tokens) if (isContextual(token.value)) declarations.push([token.name, token.value]);
121
+ else properties.push({
122
+ name: token.name,
123
+ syntax: token.syntax,
124
+ value: token.value
125
+ });
126
+ let css = buildCssFile({
127
+ comments: ["/* Instructure design tokens + icon set (pantoken) */", "/* Concrete tokens are registered with @property; themed/reference tokens are declarations. */"],
128
+ scope,
129
+ properties,
130
+ sections: [{ pairs: declarations }]
131
+ });
132
+ const prepends = [];
133
+ const appends = [];
134
+ for (const plugin of checkPlugins(plugins, "css")) {
135
+ const c = plugin.css?.({
136
+ tokens,
137
+ css
138
+ });
139
+ if (!c) continue;
140
+ appendContribution(c, prepends, appends);
141
+ }
142
+ return [
143
+ ...prepends,
144
+ css,
145
+ ...appends
146
+ ].filter(Boolean).join("\n\n");
147
+ }
148
+ function appendContribution(c, prepends, appends) {
149
+ if (c.prepend) prepends.push(marked(c.marker, c.prepend));
150
+ const block = [
151
+ (c.properties ?? []).map((p) => `@property ${p.name} {\n syntax: "${p.syntax}";\n inherits: true;\n initial-value: ${p.value};\n}`).join("\n\n"),
152
+ c.declarations?.length ? `:root {\n${c.declarations.map(([n, v]) => ` ${n}: ${v};`).join("\n")}\n}` : "",
153
+ c.append
154
+ ].filter(Boolean).join("\n\n");
155
+ if (block) appends.push(marked(c.marker, block));
156
+ }
157
+ //#endregion
158
+ //#region src/index.ts
159
+ /**
160
+ * `@pantoken/css` — emit Instructure design tokens as `@property`-typed CSS.
161
+ *
162
+ * {@link toCss} turns any token IR into CSS; {@link css} is the ready-made `rebrand` stylesheet.
163
+ * A DOM side-effect entry lives at `@pantoken/css/inject`, and a static file at
164
+ * `@pantoken/css/style.css`.
165
+ *
166
+ * @module
167
+ * @beta
168
+ */
169
+ /**
170
+ * The ready-made `rebrand` stylesheet string.
171
+ *
172
+ * @example
173
+ * ```ts
174
+ * import { css } from "@pantoken/css";
175
+ *
176
+ * document.head.insertAdjacentHTML("beforeend", `<style>${css}</style>`);
177
+ * ```
178
+ */
179
+ const css = toCss(tokens);
180
+ //#endregion
181
+ export { toCss as n, buildCssFile as r, css as t };