flux-md 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ Notable changes to flux-md. Format based on
4
4
  [Keep a Changelog](https://keepachangelog.com/); this project aims to follow
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## 0.11.0 — 2026-05-30
8
+
9
+ ### Added
10
+
11
+ - **Opt-in live region + root attributes** on `<FluxMarkdown>` and
12
+ `mountFluxMarkdown`. The root accepts `className` (appended to `flux-md`),
13
+ `id`, `role`, and `aria-live` / `aria-atomic`. Set `aria-live="polite"` to
14
+ announce streamed content to screen readers — `polite` coalesces rapid updates
15
+ and does **not** read every token. Off by default; covers React and the DOM
16
+ mount (so the Web Component and the Vue/Svelte/Solid adapters too).
17
+
18
+ ### Docs
19
+
20
+ - A repository root README, a "Structured block data" guide in the package
21
+ README, and a runnable **Data Studio** demo in the playground — a
22
+ sort/filter/CSV table and a live table of contents built entirely from
23
+ `block.data`, mid-stream.
24
+
7
25
  ## 0.10.0 — 2026-05-30
8
26
 
9
27
  Server-side rendering safety, plus an opt-in structured-data channel so consumers
package/README.md CHANGED
@@ -412,6 +412,13 @@ Subscribes to a `FluxClient`, renders each block keyed by its stable parser-assi
412
412
  <FluxMarkdown client={client} />
413
413
  ```
414
414
 
415
+ The root element accepts opt-in `className` (appended to `flux-md`), `id`,
416
+ `role`, and `aria-live` / `aria-atomic`. Set `aria-live="polite"` to make the
417
+ output a live region so screen readers announce streamed content as it settles —
418
+ `polite` coalesces rapid updates and does **not** read every token. The same
419
+ options exist on the DOM mount (`mountFluxMarkdown(client, el, { ariaLive: "polite" })`),
420
+ covering the Web Component and the Vue/Svelte/Solid adapters.
421
+
415
422
  #### Custom components / overrides
416
423
 
417
424
  Pass a `components` map to replace how elements render. Keys come in **two
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flux-md",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Zero-dep streaming markdown for the browser. Rust→WASM core, Web Worker per stream, incremental parse with speculative closure.",
5
5
  "type": "module",
6
6
  "sideEffects": ["./src/worker.ts"],
@@ -30,12 +30,21 @@
30
30
  "solid-js": "^1.8.0"
31
31
  },
32
32
  "peerDependenciesMeta": {
33
- "react": { "optional": true },
34
- "vue": { "optional": true },
35
- "svelte": { "optional": true },
36
- "solid-js": { "optional": true }
33
+ "react": {
34
+ "optional": true
35
+ },
36
+ "vue": {
37
+ "optional": true
38
+ },
39
+ "svelte": {
40
+ "optional": true
41
+ },
42
+ "solid-js": {
43
+ "optional": true
44
+ }
37
45
  },
38
46
  "devDependencies": {
47
+ "@types/bun": "^1.3.14",
39
48
  "@types/react": "^18.3.12",
40
49
  "@types/react-dom": "^18.3.1",
41
50
  "happy-dom": "^15.11.6",
@@ -49,6 +58,7 @@
49
58
  "scripts": {
50
59
  "test": "bun test",
51
60
  "test:ssr-cold": "bun test/ssr-cold.mjs",
61
+ "typecheck:test": "tsc --noEmit -p tsconfig.test.json",
52
62
  "prepublishOnly": "cd ../.. && bun run build:wasm"
53
63
  },
54
64
  "keywords": ["markdown", "streaming", "wasm", "rust", "react", "vue", "svelte", "solid", "web-component", "custom-element", "incremental", "llm", "ai", "math", "katex", "latex", "bidi", "rtl"],
package/src/dom.ts CHANGED
@@ -61,6 +61,19 @@ export interface MountOptions {
61
61
  highlightCode?: boolean;
62
62
  /** Coalesce patches into one DOM write per animation frame. Default true. */
63
63
  batch?: boolean;
64
+ /** Appended to the root's `className` (the `flux-md` class is always present). */
65
+ className?: string;
66
+ /** Set on the root element. */
67
+ id?: string;
68
+ /** Set on the root element (e.g. `"article"`, `"log"`). */
69
+ role?: string;
70
+ /**
71
+ * Make the root a live region so screen readers announce streamed content.
72
+ * `"polite"` coalesces rapid updates (does not read every token). Off by default.
73
+ */
74
+ ariaLive?: "off" | "polite" | "assertive";
75
+ /** Live-region atomicity; pair with `ariaLive`. Off by default. */
76
+ ariaAtomic?: boolean;
64
77
  }
65
78
 
66
79
  // Per-kind off-screen size estimate for `contain-intrinsic-size`. Duplicated
@@ -99,7 +112,11 @@ export function mountFluxMarkdown(
99
112
  const batch = options.batch !== false && typeof requestAnimationFrame === "function";
100
113
 
101
114
  const root = document.createElement("div");
102
- root.className = "flux-md";
115
+ root.className = options.className ? `flux-md ${options.className}` : "flux-md";
116
+ if (options.id) root.id = options.id;
117
+ if (options.role) root.setAttribute("role", options.role);
118
+ if (options.ariaLive) root.setAttribute("aria-live", options.ariaLive);
119
+ if (options.ariaAtomic !== undefined) root.setAttribute("aria-atomic", String(options.ariaAtomic));
103
120
  container.appendChild(root);
104
121
 
105
122
  // CSS-only stick-to-bottom: a permanent sentinel pinned as the last child.
package/src/react.tsx CHANGED
@@ -101,6 +101,20 @@ interface FluxMarkdownProps {
101
101
  * run through it. When omitted, rendering is byte-identical and zero-cost.
102
102
  */
103
103
  sanitize?: (html: string) => string;
104
+ /** Appended to the root's `className` (the `flux-md` class is always present). */
105
+ className?: string;
106
+ /** Set on the root element. */
107
+ id?: string;
108
+ /** Set on the root element (e.g. `"article"`, `"log"`). */
109
+ role?: string;
110
+ /**
111
+ * Make the root a live region so screen readers announce streamed content.
112
+ * `"polite"` (recommended) coalesces rapid updates and announces when the
113
+ * reader is idle — it does **not** read every token. Off by default.
114
+ */
115
+ "aria-live"?: "off" | "polite" | "assertive";
116
+ /** Live-region atomicity; pair with `aria-live`. Off by default. */
117
+ "aria-atomic"?: boolean;
104
118
  }
105
119
 
106
120
  // The original render path: subscribe to a (required, caller- or hook-owned)
@@ -111,13 +125,24 @@ function FluxMarkdownFromClient({
111
125
  virtualize,
112
126
  stickToBottom,
113
127
  sanitize,
128
+ className,
129
+ id,
130
+ role,
131
+ "aria-live": ariaLive,
132
+ "aria-atomic": ariaAtomic,
114
133
  }: FluxMarkdownProps & { client: FluxClient }) {
115
134
  const blocks = useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot);
116
135
  // Normalize "no overrides" to a stable `undefined` so memo comparisons and
117
136
  // the fast path don't churn on an empty object identity.
118
137
  const comps = components && Object.keys(components).length > 0 ? components : undefined;
119
138
  return (
120
- <div className="flux-md">
139
+ <div
140
+ className={className ? `flux-md ${className}` : "flux-md"}
141
+ id={id}
142
+ role={role}
143
+ aria-live={ariaLive}
144
+ aria-atomic={ariaAtomic}
145
+ >
121
146
  {blocks.map((b) => (
122
147
  <BlockView key={b.id} block={b} components={comps} virtualize={virtualize} sanitize={sanitize} />
123
148
  ))}
@@ -1,5 +1,6 @@
1
1
  import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
2
  import { highlight } from "../hi";
3
+ import { extractLang } from "../block-props";
3
4
 
4
5
  /**
5
6
  * Deferred-highlighting code block. Open (streaming) blocks render plain;
@@ -19,10 +20,6 @@ function decodeText(html: string): string {
19
20
  .replace(/&amp;/g, "&");
20
21
  }
21
22
 
22
- function extractLang(html: string): string {
23
- const m = html.match(/data-lang="([^"]+)"/);
24
- return m ? m[1] : "";
25
- }
26
23
 
27
24
  interface Props {
28
25
  html: string;
Binary file
@@ -2,7 +2,7 @@
2
2
  "name": "flux-md-core",
3
3
  "type": "module",
4
4
  "description": "Incremental, streaming-aware markdown parser with speculative closure",
5
- "version": "0.10.0",
5
+ "version": "0.11.0",
6
6
  "license": "MIT",
7
7
  "files": [
8
8
  "flux_md_core_bg.wasm",