twynejs 1.0.4 → 1.0.5

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.
@@ -4,6 +4,46 @@ export interface HtmlValue {
4
4
  readonly value: string;
5
5
  toString(): string;
6
6
  }
7
+ /**
8
+ * Registers the CSS-module map `jsx()` should use to resolve plain class
9
+ * names for the *current* render, so you don't have to write
10
+ * `className={styles.title}` on every single element. Call it once,
11
+ * before building the JSX tree that should use it — typically as the
12
+ * first line of a page's `mount()`:
13
+ *
14
+ * ```tsx
15
+ * import styles from "./about.module.css";
16
+ * import { useStyles } from "twynejs/jsx-runtime";
17
+ *
18
+ * export default function mount(el: HTMLElement) {
19
+ * useStyles(styles);
20
+ * el.innerHTML = (
21
+ * <>
22
+ * <h1 className="title">About</h1>
23
+ * <p className="title lead">Still just a plain string.</p>
24
+ * </>
25
+ * );
26
+ * }
27
+ * ```
28
+ *
29
+ * Every whitespace-separated class token that matches a key in `styles`
30
+ * is swapped for its scoped/hashed value (`"title"` → `styles.title`,
31
+ * e.g. `"_title_a1b2c3_1"`); any token that doesn't match (utility
32
+ * classes, classes from a global stylesheet, …) is left exactly as
33
+ * written. Pass `null` (or nothing) to go back to plain, unresolved class
34
+ * names.
35
+ *
36
+ * Because this whole library renders synchronously — there is no `await`
37
+ * between `useStyles()` and the `jsx()` calls it applies to — one
38
+ * module-level value is enough for a single page's render. It does,
39
+ * however, stay set until something changes it again: if your app's
40
+ * router calls multiple pages' `mount()` functions over the page's
41
+ * lifetime (as the `singleFile` hash-router example does), call
42
+ * `useStyles(null)` right before invoking a page's `mount()` (or have
43
+ * every page call `useStyles()` itself, even with `null`) so a previous
44
+ * page's classes can't leak into one that doesn't expect them.
45
+ */
46
+ export declare function useStyles(styles: Record<string, string> | null): void;
7
47
  export declare function jsx(tag: string | ((props: any) => any), props: Record<string, any> | null, ...children: any[]): HtmlValue | string;
8
48
  export declare function jsxs(tag: string | ((props: any) => any), props: Record<string, any> | null): HtmlValue | string;
9
49
  export declare function Fragment(props: {
@@ -1,5 +1,64 @@
1
1
  // Custom JSX Runtime for the SSG Pages Plugin
2
2
  const HTML = Symbol("html");
3
+ /**
4
+ * The CSS-module map (e.g. `import styles from "./about.module.css"`)
5
+ * that `jsx()` should currently consult when resolving `class`/
6
+ * `className` attribute values — see `useStyles()` below.
7
+ */
8
+ let activeStyles = null;
9
+ /**
10
+ * Registers the CSS-module map `jsx()` should use to resolve plain class
11
+ * names for the *current* render, so you don't have to write
12
+ * `className={styles.title}` on every single element. Call it once,
13
+ * before building the JSX tree that should use it — typically as the
14
+ * first line of a page's `mount()`:
15
+ *
16
+ * ```tsx
17
+ * import styles from "./about.module.css";
18
+ * import { useStyles } from "twynejs/jsx-runtime";
19
+ *
20
+ * export default function mount(el: HTMLElement) {
21
+ * useStyles(styles);
22
+ * el.innerHTML = (
23
+ * <>
24
+ * <h1 className="title">About</h1>
25
+ * <p className="title lead">Still just a plain string.</p>
26
+ * </>
27
+ * );
28
+ * }
29
+ * ```
30
+ *
31
+ * Every whitespace-separated class token that matches a key in `styles`
32
+ * is swapped for its scoped/hashed value (`"title"` → `styles.title`,
33
+ * e.g. `"_title_a1b2c3_1"`); any token that doesn't match (utility
34
+ * classes, classes from a global stylesheet, …) is left exactly as
35
+ * written. Pass `null` (or nothing) to go back to plain, unresolved class
36
+ * names.
37
+ *
38
+ * Because this whole library renders synchronously — there is no `await`
39
+ * between `useStyles()` and the `jsx()` calls it applies to — one
40
+ * module-level value is enough for a single page's render. It does,
41
+ * however, stay set until something changes it again: if your app's
42
+ * router calls multiple pages' `mount()` functions over the page's
43
+ * lifetime (as the `singleFile` hash-router example does), call
44
+ * `useStyles(null)` right before invoking a page's `mount()` (or have
45
+ * every page call `useStyles()` itself, even with `null`) so a previous
46
+ * page's classes can't leak into one that doesn't expect them.
47
+ */
48
+ export function useStyles(styles) {
49
+ activeStyles = styles;
50
+ }
51
+ function resolveClassName(value) {
52
+ if (!activeStyles) {
53
+ return value;
54
+ }
55
+ const styles = activeStyles;
56
+ return value
57
+ .split(/\s+/)
58
+ .filter(Boolean)
59
+ .map((token) => styles[token] ?? token)
60
+ .join(" ");
61
+ }
3
62
  function renderChildren(children) {
4
63
  return children
5
64
  .flat(Infinity)
@@ -47,6 +106,13 @@ export function jsx(tag, props, ...children) {
47
106
  if (typeof value === "boolean" && booleanHtmlAttributes.has(attribute)) {
48
107
  return value ? ` ${attribute}` : "";
49
108
  }
109
+ // `class`/`className`: plain class-name tokens get resolved against
110
+ // whatever `useStyles()` last registered, so authors can keep
111
+ // writing ordinary string class names instead of `styles.xxx` on
112
+ // every element.
113
+ if (attribute === "class" && typeof value === "string") {
114
+ value = resolveClassName(value);
115
+ }
50
116
  // Alles andere (inkl. data-*/aria-*) bekommt immer einen expliziten
51
117
  // String-Wert, damit z.B. dataset.nested === "false" verlässlich geht.
52
118
  if (typeof value === "boolean") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twynejs",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "twine A Vite SSG plugin that turns .ts/.tsx pages, Markdown docs and Liquid/EJS templates into a multi-page static build with a virtual:pages module.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",