@pebble-software/bonsai 0.1.2 → 0.2.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.
Files changed (39) hide show
  1. package/README.md +29 -2
  2. package/dist/ast.js +1 -1
  3. package/dist/chunks/{builder.meta-WpXc5VBB.js → builder.meta-B7Y9M7Ab.js} +128 -122
  4. package/dist/chunks/builder.meta-B7Y9M7Ab.js.map +1 -0
  5. package/dist/chunks/create-renderer-C4nV9RV6.js +282 -0
  6. package/dist/chunks/create-renderer-C4nV9RV6.js.map +1 -0
  7. package/dist/chunks/index-CD5pykpk.js +367 -0
  8. package/dist/chunks/index-CD5pykpk.js.map +1 -0
  9. package/dist/chunks/{render-shared-Ci28BpkI.js → render-shared-Cwb527d4.js} +487 -445
  10. package/dist/chunks/render-shared-Cwb527d4.js.map +1 -0
  11. package/dist/components/core/render-shared.d.ts +25 -15
  12. package/dist/components/core/render-tree-client.d.ts +1 -1
  13. package/dist/components/ui/accordion.d.ts +1 -1
  14. package/dist/core.d.ts +1 -1
  15. package/dist/core.js +3 -3
  16. package/dist/core.js.map +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +14 -10
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/ast/evaluator.d.ts +1 -1
  21. package/dist/lib/ast/helpers.d.ts +3 -1
  22. package/dist/lib/ast/index.d.ts +1 -1
  23. package/dist/lib/ast/types.d.ts +4 -1
  24. package/dist/lib/ast/utils.d.ts +1 -1
  25. package/dist/lib/renderer/compile-template.d.ts +10 -2
  26. package/dist/lib/renderer/safe-props.d.ts +4 -0
  27. package/dist/presets/calendar.js.map +1 -1
  28. package/dist/presets/signature.js +1 -1
  29. package/dist/presets/signature.js.map +1 -1
  30. package/dist/renderer/core-registry.d.ts +1 -1
  31. package/dist/schemas.js +1 -1
  32. package/dist/types.d.ts +8 -1
  33. package/package.json +7 -9
  34. package/dist/chunks/builder.meta-WpXc5VBB.js.map +0 -1
  35. package/dist/chunks/create-renderer-DHneTPd-.js +0 -280
  36. package/dist/chunks/create-renderer-DHneTPd-.js.map +0 -1
  37. package/dist/chunks/index-DzpRdatr.js +0 -359
  38. package/dist/chunks/index-DzpRdatr.js.map +0 -1
  39. package/dist/chunks/render-shared-Ci28BpkI.js.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/%40pebble-software%2Fbonsai)](https://www.npmjs.com/package/@pebble-software/bonsai)
4
4
  [![status: preview](https://img.shields.io/badge/status-preview-orange)](https://github.com/PebbleSoftwareInc/pebble-bonsai)
5
- [![react](https://img.shields.io/badge/react-18%20%7C%2019-61dafb)](https://react.dev)
5
+ [![react](https://img.shields.io/badge/react-19.2%2B-61dafb)](https://react.dev)
6
6
 
7
7
  > ⚠️ **Active development / preview** — this package is pre-1.0 and evolving quickly. APIs, the configuration schema, and the component set may change between releases without a deprecation cycle. Pin an exact version and read release notes before upgrading. Feedback and issues are very welcome!
8
8
 
@@ -22,6 +22,7 @@ Bonsai is the rendering engine behind [Pebble Bonsai](https://github.com/PebbleS
22
22
  - [Expression language](#expression-language)
23
23
  - [Structural nodes: each & if](#structural-nodes-each--if)
24
24
  - [Controlled inputs & state](#controlled-inputs--state)
25
+ - [Click actions](#click-actions)
25
26
  - [Component reference](#component-reference)
26
27
  - [Styling](#styling)
27
28
  - [Extending Bonsai](#extending-bonsai)
@@ -67,7 +68,7 @@ npm and pnpm don't auto-install optional peers — if you import the root entry
67
68
 
68
69
  Requirements:
69
70
 
70
- - `react` and `react-dom` 18 or 19
71
+ - `react` and `react-dom` 19.2 or newer
71
72
  - A bundler that understands ES modules (Vite, Next.js, Rspack, etc.) — the package is ESM-only.
72
73
  - Tailwind CSS v4 in your build, for the styles — `index.css` ships as Tailwind source (see [Styling](#styling)).
73
74
 
@@ -261,6 +262,7 @@ The template format is defined by the Zod schema in [`library/schemas/src/bonsai
261
262
  **The renderer does not validate templates; it renders whatever it is given.** The schema is a discriminated union on `type` with a per-component `args` contract, and the Pebble Bonsai platform validates every template against it before rendering. At render time:
262
263
 
263
264
  - An unknown `type` (one not in the active registry) follows the registry's `fallback`: a plain `<div>` by default (all built-in registries), a component of your choosing, or — with `fallback: "error"` — an error, which the node's error guard logs and renders as nothing.
265
+ - A few `args` never reach the component, whatever the template says: raw-HTML props (`dangerouslySetInnerHTML`, `srcDoc`) and overrides of where a form submits (`formAction`, `formMethod`, `formTarget`, `formEncType`). URL props (`href`, `src`, `action`, `xlinkHref`) keep only `http(s):`, `mailto:`, `tel:`, `data:image/` and relative URLs, bound values included; anything else is dropped, and an `image` without a usable `src` renders nothing. This is a floor, not a replacement for validation: pages that render untrusted templates should also send a Content Security Policy without `'unsafe-inline'` scripts.
264
266
  - A node that throws while rendering is logged with `console.error` and rendered as nothing; the rest of the tree still renders.
265
267
  - The exported `Config` type is `any`, so TypeScript won't catch template mistakes either.
266
268
 
@@ -419,6 +421,29 @@ Allowlisted methods: `map`, `filter`, `some`, `every`, `find`. Anything else —
419
421
  - The store's `data` is deep-copied from the `data` prop *for plain objects and arrays* — an instance never mutates the object you passed in, and two instances seeded from the same plain-object `data` still get fully independent state. **Non-plain values nested inside `data` (class instances, `Map`/`Set`, functions, `Date`, `File`, etc.) are passed through by reference, not cloned.** If `data` nests something like `{ profile: new SomeClass() }`, that nested value is shared between every instance seeded from it, and writes through it are visible to the caller. Keep non-plain values out of `data` if you need per-instance isolation.
420
422
  - **Reactivity model**: every leaf reads the store itself (via `valtio`'s `useSnapshot`) and evaluates its own bindings against that snapshot — the read *is* the subscription. A node with no bindings at all (a plain layout `div`) is detected and skipped from this entirely, so it never re-renders due to an unrelated store write. This is what keeps a keystroke in one field from re-rendering the rest of the form.
421
423
 
424
+ ## Click actions
425
+
426
+ Any component can take an `onClick` arg: a list of store writes applied in order when it's clicked. Templates carry data, never code: each action names a store path (`set`) and the value to write there (`to`).
427
+
428
+ ```json
429
+ {
430
+ "type": "button",
431
+ "args": {
432
+ "type": "button",
433
+ "className": "{{pizzaChoice == 'pizza1' ? 'bg-accent-orange' : 'bg-background'}}",
434
+ "onClick": [{ "set": "pizzaChoice", "to": "pizza1" }]
435
+ },
436
+ "children": ["Classic Margherita"]
437
+ }
438
+ ```
439
+
440
+ - `set` is a plain path using the same grammar as bindings (`"order.size"`, `"items[0].done"`), not a `{{binding}}`. Missing objects along the path are created.
441
+ - `to` is required. It's either a literal (`"pizza1"`, `true`, `null` to clear, an object) or a `{{...}}` expression such as `"{{!open}}"` or `"{{step + 1}}"`. Every `to` in one click is evaluated against the state from before the click, so `[{ "set": "step", "to": "{{step + 1}}" }, { "set": "previous", "to": "{{step}}" }]` records the old step. Object values are copied into the store, so later edits never change the template.
442
+ - Inside an `each`, a path that starts with the row alias (`"row.selected"`) writes to that row's real store path, just like a two-way binding. Rows of a computed `items` expression have no store path, so clicks there write nothing.
443
+ - Use a `button` with `"type": "button"` for anything that looks clickable. It gets keyboard and focus support for free, and inside the form wrapper a button without a type submits the form.
444
+ - Handlers only exist after client hydration. The server render emits the same markup with or without `onClick`.
445
+ - In a registry, a component that lists `capabilities` must include `"events"` to receive click actions (see [Bring your own registry](#bring-your-own-registry)). All built-in components include it.
446
+
422
447
  ## Component reference
423
448
 
424
449
  The full list of built-in component types is the `COMPONENT` enum in [`library/constants/src/components.ts`](https://github.com/PebbleSoftwareInc/pebble-bonsai/blob/main/library/constants/src/components.ts); allowed `args` for each are in the [schema](#configuration-schema).
@@ -543,6 +568,8 @@ const acmeRegistry = defineBonsaiRegistry({
543
568
  export const { Bonsai } = createBonsaiRenderer({ registry: acmeRegistry });
544
569
  ```
545
570
 
571
+ `capabilities` is optional; leaving it out means unrestricted. When present, `validateTemplate` rejects anything the list doesn't allow: children without `"children"`, a render mode without `"ssr"`/`"pdf"`, and [click actions](#click-actions) without `"events"`. The renderer also skips `onClick` on a component without `"events"`. In the example above, `rating` can't take `onClick`; add `"events"` to allow it.
572
+
546
573
  A `registry` prop on an individual `<Bonsai>` replaces the renderer's registry for that instance only. `extendRegistry(base, preset)` layers a preset-shaped `{ components, controlledInputs }` over any registry — `createBonsaiRenderer` uses it for its `components`/`controlledInputs` options.
547
574
 
548
575
  ### Contributing a built-in component
package/dist/ast.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as r, c as i, e as p, i as o, p as x } from "./chunks/index-DzpRdatr.js";
1
+ import { a as r, c as i, e as p, i as o, p as x } from "./chunks/index-CD5pykpk.js";
2
2
  import "jsep";
3
3
  export {
4
4
  r as clearExpressionCache,