tutuca 0.9.111 → 0.9.113

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tutuca",
3
- "version": "0.9.111",
3
+ "version": "0.9.113",
4
4
  "type": "module",
5
5
  "description": "SPA framework with immutable state and virtual DOM; dependency-free browser bundle",
6
6
  "main": "./dist/tutuca.js",
@@ -34,7 +34,7 @@
34
34
  "sync-docs-storybook": "bun scripts/sync-docs-storybook.js",
35
35
  "build-skill": "bun scripts/build-skill.js",
36
36
  "clean-skill": "rm -rf skill",
37
- "test": "bun test test/*.test.js",
37
+ "test": "bun run dist && bun run dist-ext && bun test test/*.test.js",
38
38
  "test-watch": "bun test --watch test/*.test.js",
39
39
  "format": "bunx @biomejs/biome format --write",
40
40
  "lint": "bunx @biomejs/biome lint",
@@ -68,6 +68,45 @@ that reason):
68
68
  Trade-off: no runtime network dependency and a frozen version, at the
69
69
  cost of updating the vendored files by hand.
70
70
 
71
+ ## Dark mode and the other palettes
72
+
73
+ A margaui theme is a block of CSS custom properties (`--color-*`, `--radius-*`,
74
+ …) under a `[data-theme="<name>"]` selector, and every class it compiles reads
75
+ those through `var(--color-*)`. So switching theme is **one attribute flip on
76
+ `<html>`** — the compiled stylesheet never changes:
77
+
78
+ ```js
79
+ document.documentElement.dataset.theme = "dark";
80
+ ```
81
+
82
+ Two things to know, both of which bite if you assume otherwise:
83
+
84
+ - **Dark mode never turns itself on.** `dark.css` is keyed on
85
+ `[data-theme="dark"]` alone — there is no `prefers-color-scheme` fallback and
86
+ no `.dark` class. Link `theme.css` and do nothing else and the page is light
87
+ forever, on every machine. Following the OS is your job:
88
+
89
+ ```js
90
+ const dark = matchMedia("(prefers-color-scheme: dark)");
91
+ document.documentElement.dataset.theme = dark.matches ? "dark" : "light";
92
+ ```
93
+
94
+ - **`theme.css` is only light + dark.** It is literally
95
+ `@import"./light.css";@import"./dark.css";`. margaui ships ~33 more palettes
96
+ (dracula, nord, cyberpunk, …) as sibling files, each linked separately and
97
+ each cheap:
98
+
99
+ ```html
100
+ <link rel="stylesheet" href="https://marianoguerra.github.io/margaui/themes/dracula.css" />
101
+ ```
102
+
103
+ Link it **after** `theme.css`: `light.css` claims plain `:root` as well as
104
+ `[data-theme=light]`, which ties on specificity with `[data-theme=dracula]`,
105
+ so a palette only wins by coming later in the cascade.
106
+
107
+ `tutuca storybook` does all of this for you — see the Themes section of
108
+ [storybook.md](./storybook.md).
109
+
71
110
  ## Wire it into tutuca
72
111
 
73
112
  However you obtained `compile`, the integration is the same: register
@@ -215,6 +215,32 @@ version-pinned CDN. `--out` always pins the CDN so the artifact is portable —
215
215
  host it from the project root so `/*.dev.js` paths resolve. See [cli.md](./cli.md)
216
216
  for the exhaustive flag list and exit codes.
217
217
 
218
+ ## Themes
219
+
220
+ The page starts in the theme the URL asks for (`?theme=dracula`), else the one
221
+ the OS prefers, and the sidebar has a switcher for margaui's palettes. Picking
222
+ one sets `?theme=`, so a themed storybook is shareable and survives a reload.
223
+ That OS step is on us, not margaui: its `dark.css` is keyed on
224
+ `[data-theme="dark"]` alone, with **no** `prefers-color-scheme` fallback, so a
225
+ page that only links `theme.css` renders light forever. See
226
+ [margaui.md](./margaui.md).
227
+
228
+ Embedding the storybook yourself? The switcher is opt-in through the `themes`
229
+ option, which — like `compileCss` — is injected, so the library still never
230
+ imports margaui:
231
+
232
+ ```js
233
+ mountStorybook("#app", modules, {
234
+ compileCss: (app) => compileClassesToStyleText(app, compile),
235
+ // The directory your theme.css lives in. Every other palette is a sibling
236
+ // file there; only the one selected is fetched, and only the first time.
237
+ themes: { baseUrl: "https://marianoguerra.github.io/margaui/themes/" },
238
+ });
239
+ ```
240
+
241
+ Omit `themes` (what `--no-margaui` does) and no switcher renders — there would
242
+ be no theme CSS to switch to.
243
+
218
244
  ## Footguns
219
245
 
220
246
  - ⚠️ `value` must be a real instance (`Comp.make(...)`), not a plain object or
@@ -157,8 +157,10 @@ you want to test them as a pipeline: filter + loop-data + enrichment
157
157
  together produce a list of bindings the view sees. Use
158
158
  `collectIterBindings` for that — a functional implementation only ships
159
159
  in the dev build (`tutuca/dev`); the core `tutuca` build exports a no-op
160
- stub. `tutuca test` redirects the bare `tutuca` import to the dev build
161
- automatically, so test modules can import it as below:
160
+ stub that returns `[]`. Both commands that run `getTests()` in the
161
+ terminal `tutuca test` and `tutuca storybook` (including `--dry-run`)
162
+ redirect the bare `tutuca` import to the dev build automatically, as does
163
+ the browser storybook's import map. So test modules can import it as below:
162
164
 
163
165
  ```js
164
166
  import { collectIterBindings } from "tutuca";
@@ -180,6 +182,10 @@ const r = collectIterBindings(MyComp, c, c.items, {
180
182
  - The `compInstance` is `this` for every handler. Pass
181
183
  `MyComp.make({ field: ... })` so handlers that read `this.field` see
182
184
  the value you want.
185
+ - The redirect uses Node's `module.register`, which is how the `tutuca`
186
+ bin runs. Under a Bun-driven CLI it degrades to the no-op stub — if you
187
+ see `collectIterBindings` return `[]`, import it from `tutuca/dev`
188
+ explicitly.
183
189
 
184
190
  Example:
185
191
 
@@ -540,7 +540,6 @@ var PREDICATES = {
540
540
 
541
541
  class ValParser {
542
542
  constructor() {
543
- this.bindValIt = new BindVal("it");
544
543
  this.nullConstVal = new ConstVal(null);
545
544
  }
546
545
  const(v) {
@@ -2086,7 +2085,7 @@ function parseXOp(attrs, childs, opIdx, px) {
2086
2085
  node = px.addNodeIf(RenderNode, parseXOpVal(name, value, px, vp.parseComponent), as);
2087
2086
  break;
2088
2087
  case "render-it":
2089
- node = px.addNodeIf(RenderItNode, vp.bindValIt, as);
2088
+ node = px.addNode(RenderItNode, as);
2090
2089
  break;
2091
2090
  case "render-each":
2092
2091
  node = parseRenderEach(px, value, as, attrs);
@@ -2269,7 +2268,7 @@ function parseRenderEach(px, value, as, attrs) {
2269
2268
  const seqVal = parseXOpVal("render-each", value, px, vp.parseSequence);
2270
2269
  if (seqVal === null)
2271
2270
  return null;
2272
- const renderIt = px.addNodeIf(RenderItNode, vp.bindValIt, as);
2271
+ const renderIt = px.addNode(RenderItNode, as);
2273
2272
  const attrParser = getAttrParser(px);
2274
2273
  const eachAttr = attrParser.eachAttr = attrParser.pushWrapper("each", value, seqVal);
2275
2274
  const when = attrs.getNamedItem("@when") ?? attrs.getNamedItem("when");
@@ -2469,6 +2468,12 @@ class ParseContext {
2469
2468
  }
2470
2469
  return null;
2471
2470
  }
2471
+ addNode(Class, extra) {
2472
+ const nodeId = this.nodes.length;
2473
+ const node = new Class(nodeId, null, extra);
2474
+ this.nodes.push(node);
2475
+ return node;
2476
+ }
2472
2477
  registerEvents() {
2473
2478
  const id = this.events.length;
2474
2479
  const events = new NodeEvents(id);
@@ -2900,7 +2905,7 @@ class Stack {
2900
2905
  return new Stack(comps, it, binds, newDynBinds, views, viewsId, ctx);
2901
2906
  }
2902
2907
  static root(comps, it, ctx) {
2903
- const binds = [new BindFrame(it, { it }, true), null];
2908
+ const binds = [new BindFrame(it, {}, true), null];
2904
2909
  const dynBinds = [new ObjectFrame({}), null];
2905
2910
  const views = ["main", null];
2906
2911
  return new Stack(comps, it, binds, dynBinds, views, "", ctx)._pushProvides();