openink 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.
Files changed (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +164 -0
  4. package/bin/openink.js +4 -0
  5. package/docs/ai-assistants.md +35 -0
  6. package/docs/architecture.md +50 -0
  7. package/docs/blocks.md +604 -0
  8. package/docs/extending.md +86 -0
  9. package/docs/getting-started.md +97 -0
  10. package/docs/spec.md +215 -0
  11. package/package.json +38 -0
  12. package/schema/spec.schema.json +2641 -0
  13. package/src/build.js +82 -0
  14. package/src/cli.js +133 -0
  15. package/src/dev.js +53 -0
  16. package/src/export.js +68 -0
  17. package/src/icons.js +47 -0
  18. package/src/index.js +14 -0
  19. package/src/render/blocks/actions.js +68 -0
  20. package/src/render/blocks/content.js +86 -0
  21. package/src/render/blocks/data.js +24 -0
  22. package/src/render/blocks/feedback.js +29 -0
  23. package/src/render/blocks/forms.js +86 -0
  24. package/src/render/blocks/index.js +42 -0
  25. package/src/render/blocks/layout.js +102 -0
  26. package/src/render/blocks/media.js +65 -0
  27. package/src/render/blocks/navigation.js +67 -0
  28. package/src/render/blocks/overlay.js +18 -0
  29. package/src/render/blocks/shared.js +20 -0
  30. package/src/render/blocks/text.js +49 -0
  31. package/src/render/context.js +63 -0
  32. package/src/render/page.js +76 -0
  33. package/src/runtime/chart.js +85 -0
  34. package/src/runtime/dom.js +21 -0
  35. package/src/runtime/i18n.js +24 -0
  36. package/src/runtime/icon.js +28 -0
  37. package/src/runtime/index.js +64 -0
  38. package/src/runtime/navigation.js +27 -0
  39. package/src/runtime/placeholder.js +88 -0
  40. package/src/spec/docs.js +65 -0
  41. package/src/spec/schema.js +110 -0
  42. package/src/spec/validate.js +219 -0
  43. package/src/styles/openink.css +250 -0
  44. package/src/styles/themes/blueprint.css +31 -0
  45. package/src/styles/themes/color.css +28 -0
  46. package/src/styles/themes/dark.css +31 -0
  47. package/src/styles/themes/pastel.css +26 -0
  48. package/src/themes.js +10 -0
  49. package/templates/starter/AGENTS.md +18 -0
  50. package/templates/starter/README.md +11 -0
  51. package/templates/starter/gitignore +3 -0
  52. package/templates/starter/spec.yaml +55 -0
package/docs/blocks.md ADDED
@@ -0,0 +1,604 @@
1
+ # Block reference
2
+
3
+ <!-- Generated from src/render/blocks by `npm run generate`. Do not edit by hand. -->
4
+
5
+ Every entry in a screen's `blocks:` list is `{ type: <name>, ...props }`. Blocks marked **children** also take a `children:` list of blocks.
6
+ A `text` value is a string, a number, or a translation object such as `{ en: "Hello", de: "Hallo" }` (needs `languages:` in the spec).
7
+
8
+ ## Props every block accepts
9
+
10
+ | Prop | Type | Description |
11
+ |---|---|---|
12
+ | `tone` | `blue` \| `green` \| `yellow` \| `red` \| `purple` \| `pink` \| `orange` \| `teal` \| `gray` | Colour this block and everything inside it: outlines, text and drawn shapes. |
13
+ | `fill` | boolean | Give the block a tinted background (uses `tone`, or a neutral tint). |
14
+
15
+ ## Actions
16
+
17
+ Blocks that list `go`, `toast`, `open` or `close` in their props are clickable (`button`, `card`, `avatar`, `link`, `nextbar`, nav and tab-bar items) and accept:
18
+
19
+ | Prop | Type | Description |
20
+ |---|---|---|
21
+ | `go` | string | Id of the screen to open when clicked. |
22
+ | `toast` | text | Message shown in a toast when clicked. |
23
+ | `open` | string | Id of a `modal` block to open when clicked. |
24
+ | `close` | boolean | Close the modal this block is inside when clicked. |
25
+
26
+ ## Icons
27
+
28
+ Used by `icon`, `button.icon`, nav items and `tabbar`:
29
+
30
+ `home` · `search` · `heart` · `comment` · `share` · `bookmark` · `plus` · `bell` · `user` · `users` · `mail` · `settings` · `camera` · `image` · `video` · `star` · `menu` · `more` · `close` · `check` · `arrow-right` · `arrow-left` · `chevron-down` · `chevron-right` · `play` · `pin` · `trash` · `edit` · `upload` · `download` · `lock` · `cart` · `chart` · `calendar` · `filter` · `info` · `send`
31
+
32
+ ## Layout
33
+
34
+ ### `stack` · children
35
+
36
+ Vertical stack of blocks.
37
+
38
+ | Prop | Type | Description |
39
+ |---|---|---|
40
+ | `center` | boolean | Center the children horizontally. |
41
+
42
+ \* required
43
+
44
+ ### `row` · children
45
+
46
+ Horizontal row that wraps on small screens.
47
+
48
+ | Prop | Type | Description |
49
+ |---|---|---|
50
+ | `between` | boolean | Push the first and last child to opposite ends. |
51
+ | `center` | boolean | Center the children. |
52
+
53
+ \* required
54
+
55
+ ### `grid` · children
56
+
57
+ Equal-width columns. Collapses to one column on phones.
58
+
59
+ | Prop | Type | Description |
60
+ |---|---|---|
61
+ | `cols` | number | Number of columns (default 2). |
62
+
63
+ \* required
64
+
65
+ ### `card` · children
66
+
67
+ Hand-drawn box around related blocks. Clickable when `go` is set.
68
+
69
+ | Prop | Type | Description |
70
+ |---|---|---|
71
+ | `title` | text | Small heading at the top of the card. |
72
+ | `dash` | boolean | No fill (a lighter, secondary card). |
73
+ | `center` | boolean | Center the content horizontally. |
74
+ | `elevation` | number | Shadow layers, 1 to 5 (default 1). |
75
+ | `go` | string | Id of the screen to open when clicked. |
76
+ | `toast` | text | Message shown in a toast when clicked. |
77
+ | `open` | string | Id of a `modal` block to open when clicked. |
78
+ | `close` | boolean | Close the modal this block is inside when clicked. |
79
+
80
+ \* required
81
+
82
+ ### `accordion`
83
+
84
+ Collapsible sections (FAQ, filters, settings groups).
85
+
86
+ | Prop | Type | Description |
87
+ |---|---|---|
88
+ | `items` * | list of `{ label, children }` | List of `{ label, children }`. |
89
+ | `open` | number | Index of the section open at first (default: all closed). |
90
+
91
+ \* required
92
+
93
+ ### `device` · children
94
+
95
+ Wrap screens in a phone, tablet or browser frame. Ideal for mobile-app and responsive sketches.
96
+
97
+ | Prop | Type | Description |
98
+ |---|---|---|
99
+ | `kind` | `phone` \| `tablet` \| `browser` | Frame type (default `phone`). |
100
+ | `title` | text | Address shown in the browser bar. |
101
+
102
+ \* required
103
+
104
+ ### `divider`
105
+
106
+ Hand-drawn horizontal line.
107
+
108
+ ### `spacer`
109
+
110
+ Empty vertical space.
111
+
112
+ | Prop | Type | Description |
113
+ |---|---|---|
114
+ | `h` | number | Height in px (default 16). |
115
+
116
+ \* required
117
+
118
+ ## Text
119
+
120
+ ### `h1`
121
+
122
+ Level 1 heading.
123
+
124
+ | Prop | Type | Description |
125
+ |---|---|---|
126
+ | `text` * | text | Heading text. |
127
+
128
+ \* required
129
+
130
+ ### `h2`
131
+
132
+ Level 2 heading.
133
+
134
+ | Prop | Type | Description |
135
+ |---|---|---|
136
+ | `text` * | text | Heading text. |
137
+
138
+ \* required
139
+
140
+ ### `h3`
141
+
142
+ Level 3 heading.
143
+
144
+ | Prop | Type | Description |
145
+ |---|---|---|
146
+ | `text` * | text | Heading text. |
147
+
148
+ \* required
149
+
150
+ ### `text`
151
+
152
+ Paragraph. A bare string in a `children` list is shorthand for this block.
153
+
154
+ | Prop | Type | Description |
155
+ |---|---|---|
156
+ | `text` * | text | Paragraph text. |
157
+ | `muted` | boolean | Grey, smaller text. |
158
+ | `bold` | boolean | Bold text. |
159
+
160
+ \* required
161
+
162
+ ### `list`
163
+
164
+ Bulleted list.
165
+
166
+ | Prop | Type | Description |
167
+ |---|---|---|
168
+ | `items` * | list of text | List items. |
169
+
170
+ \* required
171
+
172
+ ### `note`
173
+
174
+ Yellow sticky-note annotation. Use it for behaviour a sketch cannot show.
175
+
176
+ | Prop | Type | Description |
177
+ |---|---|---|
178
+ | `text` * | text | Note text. |
179
+
180
+ \* required
181
+
182
+ ### `badge`
183
+
184
+ Small status pill.
185
+
186
+ | Prop | Type | Description |
187
+ |---|---|---|
188
+ | `text` * | text | Badge text. |
189
+ | `status` | `on` \| `wait` \| `off` | Colour: `on` green, `wait` orange, `off` grey. |
190
+
191
+ \* required
192
+
193
+ ## Content
194
+
195
+ ### `icon`
196
+
197
+ Hand-drawn icon.
198
+
199
+ | Prop | Type | Description |
200
+ |---|---|---|
201
+ | `name` * | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon name. |
202
+ | `size` | number | Size in px (default 22). |
203
+ | `filled` | boolean | Solid instead of outline (heart, star, bookmark…). |
204
+
205
+ \* required
206
+
207
+ ### `avatar`
208
+
209
+ Round profile picture with an optional name and sub-line. Clickable.
210
+
211
+ | Prop | Type | Description |
212
+ |---|---|---|
213
+ | `name` | text | Bold name next to the picture. |
214
+ | `sub` | text | Smaller line under the name. |
215
+ | `size` | number | Picture size in px (default 44). |
216
+ | `go` | string | Id of the screen to open when clicked. |
217
+ | `toast` | text | Message shown in a toast when clicked. |
218
+ | `open` | string | Id of a `modal` block to open when clicked. |
219
+ | `close` | boolean | Close the modal this block is inside when clicked. |
220
+
221
+ \* required
222
+
223
+ ### `hero` · children
224
+
225
+ Big headline block for landing pages. Put call-to-action buttons (or an image) in `children`.
226
+
227
+ | Prop | Type | Description |
228
+ |---|---|---|
229
+ | `title` * | text | Headline. |
230
+ | `text` | text | Supporting sentence. |
231
+ | `align` | `left` \| `center` | Text alignment (default `center`). |
232
+
233
+ \* required
234
+
235
+ ### `stat`
236
+
237
+ Number card for dashboards: label, big value and a change indicator.
238
+
239
+ | Prop | Type | Description |
240
+ |---|---|---|
241
+ | `label` * | text | What is measured. |
242
+ | `value` * | text | The number. |
243
+ | `delta` | text | Change, e.g. `+8%`. |
244
+ | `trend` | `up` \| `down` \| `flat` | Colours the change: `up` green, `down` orange, `flat` grey (default `up`). |
245
+
246
+ \* required
247
+
248
+ ### `rating`
249
+
250
+ Star rating.
251
+
252
+ | Prop | Type | Description |
253
+ |---|---|---|
254
+ | `value` | number | Filled stars (default 4). |
255
+ | `max` | number | Total stars (default 5). |
256
+ | `text` | text | Text after the stars, e.g. `(128 reviews)`. |
257
+
258
+ \* required
259
+
260
+ ### `link`
261
+
262
+ Underlined text link.
263
+
264
+ | Prop | Type | Description |
265
+ |---|---|---|
266
+ | `text` * | text | Link text. |
267
+ | `go` | string | Id of the screen to open when clicked. |
268
+ | `toast` | text | Message shown in a toast when clicked. |
269
+ | `open` | string | Id of a `modal` block to open when clicked. |
270
+ | `close` | boolean | Close the modal this block is inside when clicked. |
271
+
272
+ \* required
273
+
274
+ ## Media
275
+
276
+ ### `image`
277
+
278
+ Image placeholder: hand-drawn box with a cross. Use `round` for avatars.
279
+
280
+ | Prop | Type | Description |
281
+ |---|---|---|
282
+ | `h` | number | Height in px. |
283
+ | `label` | text | Caption centered in the box. |
284
+ | `round` | boolean | Draw a circle instead of a box. |
285
+
286
+ \* required
287
+
288
+ ### `map`
289
+
290
+ Map placeholder with a pin.
291
+
292
+ | Prop | Type | Description |
293
+ |---|---|---|
294
+ | `h` | number | Height in px. |
295
+ | `label` | text | Caption centered in the box. |
296
+
297
+ \* required
298
+
299
+ ### `box`
300
+
301
+ Generic hand-drawn box, for ads, embeds, anything else.
302
+
303
+ | Prop | Type | Description |
304
+ |---|---|---|
305
+ | `h` | number | Height in px. |
306
+ | `label` | text | Caption centered in the box. |
307
+
308
+ \* required
309
+
310
+ ### `video`
311
+
312
+ Video placeholder with a play button.
313
+
314
+ | Prop | Type | Description |
315
+ |---|---|---|
316
+ | `h` | number | Height in px. |
317
+ | `label` | text | Caption centered in the box. |
318
+
319
+ \* required
320
+
321
+ ### `carousel`
322
+
323
+ Swipeable gallery: image placeholder with arrows and page dots.
324
+
325
+ | Prop | Type | Description |
326
+ |---|---|---|
327
+ | `h` | number | Height in px. |
328
+ | `label` | text | Caption centered in the box. |
329
+ | `count` | number | Number of slides shown as dots (default 4). |
330
+
331
+ \* required
332
+
333
+ ### `dropzone`
334
+
335
+ Dashed upload area with an arrow.
336
+
337
+ | Prop | Type | Description |
338
+ |---|---|---|
339
+ | `h` | number | Height in px. |
340
+ | `label` | text | Caption centered in the box. |
341
+
342
+ \* required
343
+
344
+ ### `chart`
345
+
346
+ Hand-drawn chart with sample data. It shows where a chart goes and what kind it is.
347
+
348
+ | Prop | Type | Description |
349
+ |---|---|---|
350
+ | `kind` | `line` \| `area` \| `bar` \| `pie` \| `donut` | Chart type (default `line`). |
351
+ | `h` | number | Height in px. |
352
+ | `values` | list of numbers | Your own data points. Leave out for sample data. |
353
+ | `label` | text | Caption in the corner. |
354
+
355
+ \* required
356
+
357
+ ## Forms
358
+
359
+ ### `input`
360
+
361
+ Single-line text field.
362
+
363
+ | Prop | Type | Description |
364
+ |---|---|---|
365
+ | `label` | text | Label shown above the control. |
366
+ | `placeholder` | text | Placeholder text. |
367
+ | `value` | text | Pre-filled value. |
368
+ | `inputType` | string | HTML input type: `text`, `email`, `password`, `number`… |
369
+ | `disabled` | boolean | Grey out the field. |
370
+
371
+ \* required
372
+
373
+ ### `search`
374
+
375
+ Search field with a magnifier.
376
+
377
+ | Prop | Type | Description |
378
+ |---|---|---|
379
+ | `placeholder` | text | Placeholder text. |
380
+
381
+ \* required
382
+
383
+ ### `textarea`
384
+
385
+ Multi-line text field.
386
+
387
+ | Prop | Type | Description |
388
+ |---|---|---|
389
+ | `label` | text | Label shown above the control. |
390
+ | `placeholder` | text | Placeholder text. |
391
+ | `rows` | number | Visible rows (default 3). |
392
+
393
+ \* required
394
+
395
+ ### `select`
396
+
397
+ Dropdown.
398
+
399
+ | Prop | Type | Description |
400
+ |---|---|---|
401
+ | `label` | text | Label shown above the control. |
402
+ | `options` * | list of text | Choices. |
403
+ | `selected` | number | Index of the pre-selected option (default 0). |
404
+
405
+ \* required
406
+
407
+ ### `checkbox`
408
+
409
+ Checkbox with a label.
410
+
411
+ | Prop | Type | Description |
412
+ |---|---|---|
413
+ | `label` * | text | Label shown above the control. |
414
+ | `checked` | boolean | Start ticked. |
415
+
416
+ \* required
417
+
418
+ ### `toggle`
419
+
420
+ On/off switch with a label.
421
+
422
+ | Prop | Type | Description |
423
+ |---|---|---|
424
+ | `label` * | text | Label shown above the control. |
425
+ | `checked` | boolean | Start switched on. |
426
+
427
+ \* required
428
+
429
+ ### `radio`
430
+
431
+ Radio group.
432
+
433
+ | Prop | Type | Description |
434
+ |---|---|---|
435
+ | `label` | text | Label shown above the control. |
436
+ | `options` * | list of text | Choices. |
437
+ | `selected` | number | Index of the pre-selected option (default 0). |
438
+
439
+ \* required
440
+
441
+ ### `slider`
442
+
443
+ Range slider.
444
+
445
+ | Prop | Type | Description |
446
+ |---|---|---|
447
+ | `label` | text | Label shown above the control. |
448
+ | `value` | number | Start position, 0 to 100 (default 30). |
449
+
450
+ \* required
451
+
452
+ ## Actions
453
+
454
+ ### `button`
455
+
456
+ Button, optionally with an icon. Navigates with `go`, opens a `modal` with `open`, shows a message with `toast`.
457
+
458
+ | Prop | Type | Description |
459
+ |---|---|---|
460
+ | `label` | text | Button text. Leave out for an icon-only button. |
461
+ | `icon` | `home` \| `search` \| `heart` \| `comment` \| `share` \| `bookmark` \| `plus` \| `bell` \| `user` \| `users` \| `mail` \| `settings` \| `camera` \| `image` \| `video` \| `star` \| `menu` \| `more` \| `close` \| `check` \| `arrow-right` \| `arrow-left` \| `chevron-down` \| `chevron-right` \| `play` \| `pin` \| `trash` \| `edit` \| `upload` \| `download` \| `lock` \| `cart` \| `chart` \| `calendar` \| `filter` \| `info` \| `send` | Icon shown before the label. |
462
+ | `primary` | boolean | Heavier border: the main action on the screen. |
463
+ | `disabled` | boolean | Grey out the button. |
464
+ | `go` | string | Id of the screen to open when clicked. |
465
+ | `toast` | text | Message shown in a toast when clicked. |
466
+ | `open` | string | Id of a `modal` block to open when clicked. |
467
+ | `close` | boolean | Close the modal this block is inside when clicked. |
468
+
469
+ \* required
470
+
471
+ ### `chips`
472
+
473
+ Filter pills. Clicking one selects it and shows a “Results updated” toast.
474
+
475
+ | Prop | Type | Description |
476
+ |---|---|---|
477
+ | `options` * | list of text | Pill labels. |
478
+ | `active` | number | Index of the selected pill (default 0). |
479
+
480
+ \* required
481
+
482
+ ### `tabs`
483
+
484
+ Tab strip. Each tab has its own list of blocks.
485
+
486
+ | Prop | Type | Description |
487
+ |---|---|---|
488
+ | `tabs` * | list of `{ label, children }` | List of `{ label, children }`. |
489
+
490
+ \* required
491
+
492
+ ### `nextbar`
493
+
494
+ “Next →” strip at the bottom of a screen. Hidden in the PDF.
495
+
496
+ | Prop | Type | Description |
497
+ |---|---|---|
498
+ | `label` | text | Bold lead-in (default “Next →”). |
499
+ | `text` | text | What happens next. |
500
+ | `button` | text | Button text (default “Continue →”). |
501
+ | `go` | string | Id of the screen to open when clicked. |
502
+ | `toast` | text | Message shown in a toast when clicked. |
503
+ | `open` | string | Id of a `modal` block to open when clicked. |
504
+ | `close` | boolean | Close the modal this block is inside when clicked. |
505
+
506
+ \* required
507
+
508
+ ## Navigation
509
+
510
+ ### `breadcrumb`
511
+
512
+ Path to the current page. The last item is the current one.
513
+
514
+ | Prop | Type | Description |
515
+ |---|---|---|
516
+ | `items` * | list of text | Path segments. |
517
+
518
+ \* required
519
+
520
+ ### `pagination`
521
+
522
+ Previous / page numbers / next.
523
+
524
+ | Prop | Type | Description |
525
+ |---|---|---|
526
+ | `pages` | number | Number of pages (default 5). |
527
+ | `active` | number | Current page, starting at 1 (default 1). |
528
+
529
+ \* required
530
+
531
+ ### `steps`
532
+
533
+ Numbered progress through a multi-step flow (checkout, onboarding).
534
+
535
+ | Prop | Type | Description |
536
+ |---|---|---|
537
+ | `items` * | list of text | Step names. |
538
+ | `active` | number | Index of the current step, starting at 0 (default 0). |
539
+
540
+ \* required
541
+
542
+ ### `tabbar`
543
+
544
+ Bottom tab bar of a mobile app: icons with small labels.
545
+
546
+ | Prop | Type | Description |
547
+ |---|---|---|
548
+ | `items` * | list of `{ icon, label, go, toast, open }` | List of `{ icon, label, go, toast, open }`. |
549
+ | `active` | number | Index of the highlighted item (default 0). |
550
+
551
+ \* required
552
+
553
+ ## Feedback
554
+
555
+ ### `alert`
556
+
557
+ Banner for information, success, warnings and errors.
558
+
559
+ | Prop | Type | Description |
560
+ |---|---|---|
561
+ | `text` * | text | Message. |
562
+ | `title` | text | Bold first line. |
563
+ | `kind` | `info` \| `success` \| `warning` \| `error` | Type and colour (default `info`). |
564
+
565
+ \* required
566
+
567
+ ### `progress`
568
+
569
+ Progress bar.
570
+
571
+ | Prop | Type | Description |
572
+ |---|---|---|
573
+ | `value` * | number | Percent complete, 0 to 100. |
574
+ | `label` | text | Caption above the bar. |
575
+
576
+ \* required
577
+
578
+ ## Overlays
579
+
580
+ ### `modal` · children
581
+
582
+ Dialog that opens over the screen when a button, card or link has `open: <id>`. Close with a `close: true` button, the X, or a click outside.
583
+
584
+ | Prop | Type | Description |
585
+ |---|---|---|
586
+ | `id` * | string | Unique id; other blocks use it in `open:`. |
587
+ | `title` | text | Heading of the dialog. |
588
+ | `width` | `narrow` \| `medium` \| `wide` | Dialog width (default `medium`). |
589
+
590
+ \* required
591
+
592
+ ## Data
593
+
594
+ ### `table`
595
+
596
+ Table. A cell is text or a block (e.g. `badge`, `button`). A row can be `{ cells, go }` to make it clickable.
597
+
598
+ | Prop | Type | Description |
599
+ |---|---|---|
600
+ | `columns` * | list of text | Column headings. |
601
+ | `rows` * | list of rows | List of rows: `[cell, cell]` or `{ cells: [...], go: screenId }`. |
602
+
603
+ \* required
604
+
@@ -0,0 +1,86 @@
1
+ # Extending openink
2
+
3
+ ## Add a block
4
+
5
+ A block is one object in `src/render/blocks/*.js`. That single definition is used for rendering, validation, the generated docs and the JSON Schema, so there is nothing else to register.
6
+
7
+ ```js
8
+ // src/render/blocks/text.js
9
+ {
10
+ name: "quote", // the `type:` value in a spec
11
+ group: "Text", // section in docs/blocks.md
12
+ summary: "Pull quote with an author.",
13
+ props: {
14
+ text: { type: "text", doc: "The quote.", required: true },
15
+ author: { type: "text", doc: "Who said it." },
16
+ },
17
+ render: (b, c) =>
18
+ `<blockquote class="quote">${c.tx(b.text)}<cite>${c.tx(b.author)}</cite></blockquote>`,
19
+ }
20
+ ```
21
+
22
+ Then:
23
+
24
+ ```bash
25
+ npm run generate # refreshes docs/blocks.md and schema/spec.schema.json
26
+ npm test
27
+ ```
28
+
29
+ ### Prop types
30
+
31
+ | `type` | Accepts |
32
+ |---|---|
33
+ | `text` | string, number, or `{ lang: text }` (translatable) |
34
+ | `string` | string; add `enum: [...]` to restrict values |
35
+ | `number`, `boolean` | as named |
36
+ | `text[]` | list of `text` |
37
+ | `number[]` | list of numbers |
38
+ | `sections`, `links`, `rows` | structured props: `[{ label, children }]` (tabs, accordion), `[{ icon, label, go, … }]` (tab bar), table rows |
39
+
40
+ Set `required: true` on a prop to make the validator insist on it. Spread `...ACTION` (from `shared.js`) into `props` to make a block clickable (`go`, `toast`, `open`, `close`), and use `c.act(b)` in `render`. Every block also accepts `tone` and `fill` automatically.
41
+
42
+ ### Containers
43
+
44
+ Set `children: true` and render the children with `c.kids(b)`:
45
+
46
+ ```js
47
+ { name: "sidebar-layout", group: "Layout", summary: "...", children: true, props: {},
48
+ render: (b, c) => `<div class="sidebar-layout">${c.kids(b)}</div>` }
49
+ ```
50
+
51
+ If a block hides other blocks somewhere other than `children` (like `tabs` and `table` do), add `nested(b)` returning `[pathSuffix, block]` pairs so the validator can look inside, and `targets(b)` for extra `go` links.
52
+
53
+ ### Render helpers (`c`)
54
+
55
+ | Helper | Use |
56
+ |---|---|
57
+ | `c.tx(v)` | Text content. Escapes HTML and expands translations. |
58
+ | `c.attr(name, v)` | An attribute whose value may be translated. |
59
+ | `c.plain(v)` | Plain string of a text value (first language). |
60
+ | `c.esc(s)` | Escape a raw string. **Always escape anything you interpolate.** |
61
+ | `c.act(b)` | `data-go` / `data-toast` / `data-open` / `data-close` attributes for clickable blocks. |
62
+ | `c.icon(name, size, filled)` | A hand-drawn icon. |
63
+ | `c.inner(b)` | Icon + label markup for buttons. |
64
+ | `c.label(b)` | A form label from `b.label`. |
65
+ | `c.block(x)` / `c.kids(b)` | Render one nested block / all `children`. |
66
+
67
+ ## Change the look
68
+
69
+ Everything visual is in `src/styles/openink.css`, driven by CSS variables at the top. Add new component styles there. Projects can also override tokens with their own `theme:` file, without touching the framework.
70
+
71
+ ## Add runtime behaviour
72
+
73
+ Code that runs in the generated page lives in `src/runtime/` and is bundled into `openink.js` by esbuild. Interactive blocks mark their markup with `data-*` attributes (`data-chips`, `data-tabs`, …) and `src/runtime/index.js` handles the clicks by delegation.
74
+
75
+ Remember that wired-elements only draw when their size changes and draw at 0×0 while hidden. Call `redraw()` from `src/runtime/dom.js` after you show something that was hidden.
76
+
77
+ ## Use it from code
78
+
79
+ ```js
80
+ import { build, validate, exportFiles } from "openink";
81
+
82
+ await build({ dir: "./my-project", out: "dist" });
83
+ await exportFiles({ dir: "./my-project", png: true });
84
+ ```
85
+
86
+ See `src/index.js` for the full API.