@luckydraw/blex 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.
- package/README.md +298 -0
- package/dist/blex-chart.min.global.js +23 -0
- package/dist/blex-chart.min.global.js.map +1 -0
- package/dist/blex.min.global.js +105 -0
- package/dist/blex.min.global.js.map +1 -0
- package/dist/esm/base-renderer.d.ts +28 -0
- package/dist/esm/base-renderer.d.ts.map +1 -0
- package/dist/esm/calendar-HUZDQQN2.js +314 -0
- package/dist/esm/calendar-HUZDQQN2.js.map +1 -0
- package/dist/esm/chart/index.d.ts +14 -0
- package/dist/esm/chart/index.d.ts.map +1 -0
- package/dist/esm/chart/index.js +3 -0
- package/dist/esm/chart/index.js.map +1 -0
- package/dist/esm/chart-2QT47W6E.js +38 -0
- package/dist/esm/chart-2QT47W6E.js.map +1 -0
- package/dist/esm/chunk-ODHF7VXV.js +74 -0
- package/dist/esm/chunk-ODHF7VXV.js.map +1 -0
- package/dist/esm/chunk-PBRPD4A5.js +91 -0
- package/dist/esm/chunk-PBRPD4A5.js.map +1 -0
- package/dist/esm/chunk-ZKSJGHJI.js +1573 -0
- package/dist/esm/chunk-ZKSJGHJI.js.map +1 -0
- package/dist/esm/defaults.d.ts +6 -0
- package/dist/esm/defaults.d.ts.map +1 -0
- package/dist/esm/fallback.d.ts +11 -0
- package/dist/esm/fallback.d.ts.map +1 -0
- package/dist/esm/gallery-ISM7FZA3.js +130 -0
- package/dist/esm/gallery-ISM7FZA3.js.map +1 -0
- package/dist/esm/index.d.ts +8 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +91 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/kanban-XUXVTRX2.js +126 -0
- package/dist/esm/kanban-XUXVTRX2.js.map +1 -0
- package/dist/esm/mermaid-W3HX2CK2.js +130 -0
- package/dist/esm/mermaid-W3HX2CK2.js.map +1 -0
- package/dist/esm/placeholder.d.ts +8 -0
- package/dist/esm/placeholder.d.ts.map +1 -0
- package/dist/esm/plugin.d.ts +34 -0
- package/dist/esm/plugin.d.ts.map +1 -0
- package/dist/esm/react/chart.d.ts +12 -0
- package/dist/esm/react/chart.d.ts.map +1 -0
- package/dist/esm/react/chart.js +50 -0
- package/dist/esm/react/chart.js.map +1 -0
- package/dist/esm/react/index.d.ts +13 -0
- package/dist/esm/react/index.d.ts.map +1 -0
- package/dist/esm/react/index.js +66 -0
- package/dist/esm/react/index.js.map +1 -0
- package/dist/esm/registry.d.ts +46 -0
- package/dist/esm/registry.d.ts.map +1 -0
- package/dist/esm/render.d.ts +13 -0
- package/dist/esm/render.d.ts.map +1 -0
- package/dist/esm/renderers/calendar.d.ts +34 -0
- package/dist/esm/renderers/calendar.d.ts.map +1 -0
- package/dist/esm/renderers/chart.d.ts +12 -0
- package/dist/esm/renderers/chart.d.ts.map +1 -0
- package/dist/esm/renderers/code.d.ts +19 -0
- package/dist/esm/renderers/code.d.ts.map +1 -0
- package/dist/esm/renderers/confirm.d.ts +13 -0
- package/dist/esm/renderers/confirm.d.ts.map +1 -0
- package/dist/esm/renderers/diff.d.ts +24 -0
- package/dist/esm/renderers/diff.d.ts.map +1 -0
- package/dist/esm/renderers/file-tree.d.ts +24 -0
- package/dist/esm/renderers/file-tree.d.ts.map +1 -0
- package/dist/esm/renderers/form.d.ts +27 -0
- package/dist/esm/renderers/form.d.ts.map +1 -0
- package/dist/esm/renderers/gallery.d.ts +24 -0
- package/dist/esm/renderers/gallery.d.ts.map +1 -0
- package/dist/esm/renderers/image.d.ts +17 -0
- package/dist/esm/renderers/image.d.ts.map +1 -0
- package/dist/esm/renderers/kanban.d.ts +29 -0
- package/dist/esm/renderers/kanban.d.ts.map +1 -0
- package/dist/esm/renderers/layout.d.ts +17 -0
- package/dist/esm/renderers/layout.d.ts.map +1 -0
- package/dist/esm/renderers/mermaid.d.ts +19 -0
- package/dist/esm/renderers/mermaid.d.ts.map +1 -0
- package/dist/esm/renderers/metric.d.ts +16 -0
- package/dist/esm/renderers/metric.d.ts.map +1 -0
- package/dist/esm/renderers/poll.d.ts +17 -0
- package/dist/esm/renderers/poll.d.ts.map +1 -0
- package/dist/esm/renderers/progress.d.ts +18 -0
- package/dist/esm/renderers/progress.d.ts.map +1 -0
- package/dist/esm/renderers/status.d.ts +17 -0
- package/dist/esm/renderers/status.d.ts.map +1 -0
- package/dist/esm/renderers/svg.d.ts +13 -0
- package/dist/esm/renderers/svg.d.ts.map +1 -0
- package/dist/esm/renderers/table.d.ts +20 -0
- package/dist/esm/renderers/table.d.ts.map +1 -0
- package/dist/esm/renderers/terminal.d.ts +16 -0
- package/dist/esm/renderers/terminal.d.ts.map +1 -0
- package/dist/esm/renderers/timeline.d.ts +20 -0
- package/dist/esm/renderers/timeline.d.ts.map +1 -0
- package/dist/esm/theme.d.ts +10 -0
- package/dist/esm/theme.d.ts.map +1 -0
- package/dist/esm/types-C42V92x6.d.ts +75 -0
- package/dist/esm/types.d.ts +81 -0
- package/dist/esm/types.d.ts.map +1 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# @luckydraw/blex
|
|
2
|
+
|
|
3
|
+
A TypeScript library for rendering interactive content blocks from JSON. Framework-agnostic, streaming-aware, with lazy-loaded heavy renderers.
|
|
4
|
+
|
|
5
|
+
Used by **cumulus** (AI chat) to display rich content inline in conversations, and by **plastic** (app platform) for charts, dashboards, and application view components.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @luckydraw/blex
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Distribution
|
|
14
|
+
|
|
15
|
+
Two build outputs from the same source:
|
|
16
|
+
|
|
17
|
+
- **ESM** — `import { renderBlock } from '@luckydraw/blex'` — for bundled consumers (plastic, janus)
|
|
18
|
+
- **IIFE** — `<script src="blex.min.js">` → `window.Blex` — for cumulus widget (no build step)
|
|
19
|
+
- **Chart standalone** — `blex-chart.min.js` / `import { renderChart } from '@luckydraw/blex/chart'`
|
|
20
|
+
|
|
21
|
+
Target: ~11KB core (gzipped), heavy renderers lazy-loaded on first use.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { renderBlock, registerBlockType } from '@luckydraw/blex';
|
|
27
|
+
|
|
28
|
+
// Render a block into a container
|
|
29
|
+
const handle = await renderBlock(
|
|
30
|
+
{ type: 'confirm', id: 'c1', data: { message: 'Deploy to production?' } },
|
|
31
|
+
document.getElementById('container')
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Listen for interactions
|
|
35
|
+
handle.onInteraction((interaction) => {
|
|
36
|
+
console.log(interaction.type); // 'click'
|
|
37
|
+
console.log(interaction.serialized); // 'User confirmed: Deploy to production?'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Clean up
|
|
41
|
+
handle.destroy();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Chart Subpath Export
|
|
45
|
+
|
|
46
|
+
A standalone chart API shared between cumulus and plastic. Fully independent — no side effects, no DOMPurify, no registry setup:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { renderChart } from '@luckydraw/blex/chart';
|
|
50
|
+
|
|
51
|
+
const chart = renderChart(container, {
|
|
52
|
+
type: 'bar',
|
|
53
|
+
data: {
|
|
54
|
+
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
55
|
+
datasets: [{ label: 'Revenue', values: [10, 25, 15, 30] }]
|
|
56
|
+
},
|
|
57
|
+
options: { title: 'Quarterly Revenue', legend: true }
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
chart.update({ data: { labels: ['Q1', 'Q2'], datasets: [{ label: 'Revenue', values: [10, 25] }] } });
|
|
61
|
+
chart.destroy();
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API
|
|
65
|
+
|
|
66
|
+
### `renderBlock(block, container, options?): Promise<BlockHandle>`
|
|
67
|
+
|
|
68
|
+
Resolves the block type from the registry, loads the renderer (lazy if needed), renders into the container, and wires up interaction callbacks.
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
- `onReady?: () => void` — called when the block finishes initial render
|
|
72
|
+
|
|
73
|
+
### `renderPlaceholder(type, container): HTMLElement`
|
|
74
|
+
|
|
75
|
+
Renders a typed skeleton/spinner for a block that hasn't finished streaming yet. Returns the placeholder element for later replacement. Used by cumulus while buffering `~~~blex:TYPE` fence content.
|
|
76
|
+
|
|
77
|
+
### `registerBlockType(type, renderer): void`
|
|
78
|
+
|
|
79
|
+
Register a custom block type renderer. Overrides built-in renderers if the type matches.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
registerBlockType('my-widget', {
|
|
83
|
+
render(block, container) { /* ... */ },
|
|
84
|
+
update(block) { /* streaming updates */ },
|
|
85
|
+
onInteraction(callback) { /* wire up events */ },
|
|
86
|
+
destroy() { /* cleanup */ }
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Types
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface ContentBlock<T = unknown> {
|
|
94
|
+
type: string;
|
|
95
|
+
data: T;
|
|
96
|
+
id: string; // unique per block instance, stable across re-renders
|
|
97
|
+
streaming?: boolean; // true while data is still arriving
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface BlockRenderer<T = unknown> {
|
|
101
|
+
render(block: ContentBlock<T>, container: HTMLElement): void;
|
|
102
|
+
update?(block: ContentBlock<T>): void;
|
|
103
|
+
onInteraction?(callback: (interaction: BlockInteraction) => void): void;
|
|
104
|
+
destroy(): void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface BlockHandle {
|
|
108
|
+
update(block: ContentBlock): void;
|
|
109
|
+
onInteraction(callback: (interaction: BlockInteraction) => void): void;
|
|
110
|
+
destroy(): void;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface BlockInteraction {
|
|
114
|
+
blockId: string;
|
|
115
|
+
type: string; // 'select', 'click', 'submit', 'apply', 'reject', 'move'
|
|
116
|
+
payload: unknown;
|
|
117
|
+
serialized: string; // pre-formatted text for chat input injection
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Chart Types
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
interface ChartConfig {
|
|
125
|
+
type: 'bar' | 'line' | 'pie' | 'doughnut';
|
|
126
|
+
data: { labels: string[]; datasets: DataSet[] };
|
|
127
|
+
options?: { title?: string; legend?: boolean; responsive?: boolean };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
interface DataSet {
|
|
131
|
+
label: string;
|
|
132
|
+
values: number[];
|
|
133
|
+
color?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface ChartInstance {
|
|
137
|
+
update(config: Partial<ChartConfig>): void;
|
|
138
|
+
destroy(): void;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Built-in Block Types
|
|
143
|
+
|
|
144
|
+
### Core Blocks (bundled, <5KB each)
|
|
145
|
+
|
|
146
|
+
| Type | Description | Interaction Types |
|
|
147
|
+
|------|-------------|-------------------|
|
|
148
|
+
| `confirm` | Yes/No/Cancel buttons | `click` |
|
|
149
|
+
| `poll` | Single/multi choice with optional write-in | `submit` |
|
|
150
|
+
| `status` | Key/value pairs with status indicators (ok/warning/error) | — |
|
|
151
|
+
| `metric` | Single big number with label and trend indicator | — |
|
|
152
|
+
| `image` | URL/base64 image with caption, zoom/download | `click` |
|
|
153
|
+
| `svg` | Sanitized inline SVG with download/copy | `click` |
|
|
154
|
+
| `table` | Sortable columns, row/cell/column selection | `select` |
|
|
155
|
+
| `code` | Syntax-highlighted code with line selection | `select` |
|
|
156
|
+
| `diff` | Unified or side-by-side diff | `apply`, `reject` |
|
|
157
|
+
| `file-tree` | Expandable directory tree | `select` |
|
|
158
|
+
| `form` | JSON schema → dynamic form | `submit` |
|
|
159
|
+
| `progress` | Live-updating step tracker | — |
|
|
160
|
+
| `terminal` | Collapsible command output with exit code | `click` |
|
|
161
|
+
| `timeline` | Vertical event timeline with timestamps | — |
|
|
162
|
+
|
|
163
|
+
### Lazy-loaded Blocks
|
|
164
|
+
|
|
165
|
+
| Type | Description | Loaded Size |
|
|
166
|
+
|------|-------------|-------------|
|
|
167
|
+
| `mermaid` | Diagram rendering via mermaid.js | ~200KB |
|
|
168
|
+
| `chart` | Bar/line/pie/doughnut via chart library | ~60KB |
|
|
169
|
+
| `kanban` | Drag-and-drop columns via HTML5 DnD | ~8KB |
|
|
170
|
+
| `calendar` | Month/week/day event views | ~12KB |
|
|
171
|
+
| `gallery` | Image grid/carousel with lazy loading | ~5KB |
|
|
172
|
+
|
|
173
|
+
Heavy renderers are loaded on first use. Subsequent renders of the same type reuse the loaded module.
|
|
174
|
+
|
|
175
|
+
### Unknown Types
|
|
176
|
+
|
|
177
|
+
Any unrecognized block type renders as raw JSON inside a collapsible `<details>` element, so content is never lost.
|
|
178
|
+
|
|
179
|
+
## Theming
|
|
180
|
+
|
|
181
|
+
blex uses CSS custom properties with sensible defaults. Consumers override them to match their theme:
|
|
182
|
+
|
|
183
|
+
```css
|
|
184
|
+
/* blex defaults — override in your app */
|
|
185
|
+
--blex-bg: #ffffff;
|
|
186
|
+
--blex-text: #1a1a1a;
|
|
187
|
+
--blex-border: #e5e5e5;
|
|
188
|
+
--blex-accent: #3b82f6;
|
|
189
|
+
--blex-success: #22c55e;
|
|
190
|
+
--blex-warning: #f59e0b;
|
|
191
|
+
--blex-error: #ef4444;
|
|
192
|
+
--blex-chart-bg: var(--blex-bg);
|
|
193
|
+
--blex-chart-text: var(--blex-text);
|
|
194
|
+
--blex-animation-duration: 200ms;
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Dark mode:** Set `data-theme="dark"` on a parent element. blex adjusts defaults automatically.
|
|
198
|
+
|
|
199
|
+
**Test mode:** Set `data-test-mode="true"` or `--blex-animation-duration: 0ms` to disable all animations for puppet/browser testing.
|
|
200
|
+
|
|
201
|
+
## Streaming
|
|
202
|
+
|
|
203
|
+
Renderers that implement `update()` receive incremental data without a full destroy/re-render cycle. The `streaming` flag on `ContentBlock` indicates data is still arriving.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const handle = await renderBlock(
|
|
207
|
+
{ type: 'table', id: 't1', data: { rows: [] }, streaming: true },
|
|
208
|
+
container
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// As data arrives:
|
|
212
|
+
handle.update({ type: 'table', id: 't1', data: { rows: newRows }, streaming: true });
|
|
213
|
+
|
|
214
|
+
// When complete:
|
|
215
|
+
handle.update({ type: 'table', id: 't1', data: { rows: allRows }, streaming: false });
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
When `streaming: false` (e.g., historical message replay), blocks render immediately with no transition/animation.
|
|
219
|
+
|
|
220
|
+
## Interaction Serialization
|
|
221
|
+
|
|
222
|
+
Every `BlockInteraction` includes a `serialized` field — a pre-formatted text string suitable for injecting into a chat input. Each block type controls its own serialization format.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Table selection might serialize as:
|
|
226
|
+
{
|
|
227
|
+
blockId: 't1',
|
|
228
|
+
type: 'select',
|
|
229
|
+
payload: { rows: [0, 2], columns: ['name', 'status'] },
|
|
230
|
+
serialized: 'Selected from table:\n| name | status |\n| Alice | active |\n| Charlie | pending |'
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Consumers receive interactions via the `BlockHandle.onInteraction()` callback and can forward `serialized` text directly to the chat input.
|
|
235
|
+
|
|
236
|
+
## Cleanup Contract
|
|
237
|
+
|
|
238
|
+
`destroy()` is a hard guarantee: after calling it, **zero references remain**. This includes:
|
|
239
|
+
|
|
240
|
+
- Event listeners (click, resize, keyboard)
|
|
241
|
+
- Animation frames (RAF callbacks)
|
|
242
|
+
- Canvas contexts
|
|
243
|
+
- Resize/intersection/mutation observers
|
|
244
|
+
- Timers (setTimeout, setInterval)
|
|
245
|
+
- Pending lazy-load promises
|
|
246
|
+
- DOM references
|
|
247
|
+
|
|
248
|
+
Enforced via `BaseRenderer` abstract class with cleanup tracking. All built-in renderers extend it.
|
|
249
|
+
|
|
250
|
+
## Design Decisions
|
|
251
|
+
|
|
252
|
+
- **Vanilla JS/TS** — no React dependency. Consumers wrap as needed.
|
|
253
|
+
- **Dual output** — ESM for bundled consumers, IIFE (`window.Blex`) for cumulus widget.
|
|
254
|
+
- **DOMPurify** — the one required runtime dependency, used for SVG/HTML sanitization.
|
|
255
|
+
- **Lazy loading** — heavy renderers (mermaid, chart.js, kanban, calendar, gallery) loaded on first use.
|
|
256
|
+
- **Subpath export** — `@luckydraw/blex/chart` is fully independent: no side effects, no registry, no DOMPurify. Just `renderChart()` + Chart.js lazy load.
|
|
257
|
+
- **CSS custom properties** — `--blex-*` variables for theming. No hardcoded colors.
|
|
258
|
+
- **`data-testid`** — on all interactive elements for puppet/browser automation.
|
|
259
|
+
- **BaseRenderer** — abstract class enforcing `destroy()` cleanup contract.
|
|
260
|
+
|
|
261
|
+
## Package Structure
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
src/
|
|
265
|
+
├── index.ts # main entry: renderBlock, registerBlockType, renderPlaceholder
|
|
266
|
+
├── types.ts # ContentBlock, BlockRenderer, BlockInteraction, BlockHandle
|
|
267
|
+
├── registry.ts # block type registry + lazy loader
|
|
268
|
+
├── base-renderer.ts # BaseRenderer abstract class with cleanup tracking
|
|
269
|
+
├── fallback.ts # unknown type fallback renderer
|
|
270
|
+
├── placeholder.ts # streaming skeleton/placeholder utility
|
|
271
|
+
├── theme.ts # CSS custom property defaults + dark mode
|
|
272
|
+
├── chart/
|
|
273
|
+
│ └── index.ts # subpath export: renderChart (fully independent)
|
|
274
|
+
└── renderers/
|
|
275
|
+
├── confirm.ts
|
|
276
|
+
├── poll.ts
|
|
277
|
+
├── status.ts
|
|
278
|
+
├── metric.ts
|
|
279
|
+
├── image.ts
|
|
280
|
+
├── mermaid.ts # lazy-loaded
|
|
281
|
+
├── svg.ts
|
|
282
|
+
├── table.ts
|
|
283
|
+
├── chart.ts # lazy-loaded, wraps chart/index.ts
|
|
284
|
+
├── code.ts
|
|
285
|
+
├── diff.ts
|
|
286
|
+
├── file-tree.ts
|
|
287
|
+
├── form.ts
|
|
288
|
+
├── progress.ts
|
|
289
|
+
├── terminal.ts
|
|
290
|
+
├── timeline.ts
|
|
291
|
+
├── kanban.ts # lazy-loaded
|
|
292
|
+
├── calendar.ts # lazy-loaded
|
|
293
|
+
└── gallery.ts # lazy-loaded
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
MIT
|