@pebble-software/bonsai 0.1.1 → 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.
- package/README.md +29 -2
- package/dist/ast.js +1 -1
- package/dist/chunks/{builder.meta-WpXc5VBB.js → builder.meta-B7Y9M7Ab.js} +128 -122
- package/dist/chunks/builder.meta-B7Y9M7Ab.js.map +1 -0
- package/dist/chunks/create-renderer-C4nV9RV6.js +282 -0
- package/dist/chunks/create-renderer-C4nV9RV6.js.map +1 -0
- package/dist/chunks/index-CD5pykpk.js +367 -0
- package/dist/chunks/index-CD5pykpk.js.map +1 -0
- package/dist/chunks/{render-shared-Ci28BpkI.js → render-shared-Cwb527d4.js} +487 -445
- package/dist/chunks/render-shared-Cwb527d4.js.map +1 -0
- package/dist/components/core/render-shared.d.ts +25 -15
- package/dist/components/core/render-tree-client.d.ts +1 -1
- package/dist/components/ui/accordion.d.ts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +3 -3
- package/dist/core.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +14 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/ast/evaluator.d.ts +1 -1
- package/dist/lib/ast/helpers.d.ts +3 -1
- package/dist/lib/ast/index.d.ts +1 -1
- package/dist/lib/ast/types.d.ts +4 -1
- package/dist/lib/ast/utils.d.ts +1 -1
- package/dist/lib/renderer/compile-template.d.ts +10 -2
- package/dist/lib/renderer/safe-props.d.ts +4 -0
- package/dist/presets/calendar.js +82 -74
- package/dist/presets/calendar.js.map +1 -1
- package/dist/presets/signature.js +1 -1
- package/dist/presets/signature.js.map +1 -1
- package/dist/renderer/core-registry.d.ts +1 -1
- package/dist/schemas.js +1 -1
- package/dist/types.d.ts +8 -1
- package/package.json +7 -9
- package/dist/chunks/builder.meta-WpXc5VBB.js.map +0 -1
- package/dist/chunks/create-renderer-DHneTPd-.js +0 -280
- package/dist/chunks/create-renderer-DHneTPd-.js.map +0 -1
- package/dist/chunks/index-DzpRdatr.js +0 -359
- package/dist/chunks/index-DzpRdatr.js.map +0 -1
- package/dist/chunks/render-shared-Ci28BpkI.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@pebble-software/bonsai)
|
|
4
4
|
[](https://github.com/PebbleSoftwareInc/pebble-bonsai)
|
|
5
|
-
[](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`
|
|
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