@localix-ai/flowdown 0.5.0 → 0.5.1

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 (47) hide show
  1. package/README.md +8 -4
  2. package/dist/core/blocks.js +3 -2
  3. package/dist/core/blocks.js.map +1 -1
  4. package/dist/core/continues.js +15 -0
  5. package/dist/core/continues.js.map +1 -0
  6. package/dist/core/fence-scan.js +10 -1
  7. package/dist/core/fence-scan.js.map +1 -1
  8. package/dist/core/file-tree-parse.js +1 -1
  9. package/dist/core/file-tree-parse.js.map +1 -1
  10. package/dist/core/heal.js +1 -1
  11. package/dist/core/heal.js.map +1 -1
  12. package/dist/document/fence.js +3 -2
  13. package/dist/document/fence.js.map +1 -1
  14. package/dist/dom/index.js +25 -7
  15. package/dist/dom/index.js.map +1 -1
  16. package/dist/element/index.d.ts +6 -0
  17. package/dist/element/index.js.map +1 -1
  18. package/dist/engine/blocks.js +11 -0
  19. package/dist/engine/blocks.js.map +1 -1
  20. package/dist/engine/inlines.js +40 -5
  21. package/dist/engine/inlines.js.map +1 -1
  22. package/dist/react/code-block.js +15 -2
  23. package/dist/react/code-block.js.map +1 -1
  24. package/dist/react/draw.js +1 -0
  25. package/dist/react/draw.js.map +1 -1
  26. package/dist/react/markdown.js +4 -2
  27. package/dist/react/markdown.js.map +1 -1
  28. package/dist/react/scrolls.js +62 -0
  29. package/dist/react/scrolls.js.map +1 -0
  30. package/dist/react/table.js +6 -20
  31. package/dist/react/table.js.map +1 -1
  32. package/dist/react/use-smooth-text.js +2 -1
  33. package/dist/react/use-smooth-text.js.map +1 -1
  34. package/dist/render/labels.d.ts +5 -0
  35. package/dist/render/labels.js +3 -0
  36. package/dist/render/labels.js.map +1 -1
  37. package/dist/render/tree.js +2 -0
  38. package/dist/render/tree.js.map +1 -1
  39. package/dist/render/view.d.ts +4 -1
  40. package/dist/render/view.js +6 -2
  41. package/dist/render/view.js.map +1 -1
  42. package/dist/render/views.js +2 -2
  43. package/dist/render/views.js.map +1 -1
  44. package/dist/styles.css +12 -1
  45. package/dist/tui/wrap.js +5 -4
  46. package/dist/tui/wrap.js.map +1 -1
  47. package/package.json +3 -2
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { continues } from "../core/continues.js";
2
3
  import { useEffect, useState } from "react";
3
4
  /** The least time between two steps: about 30 a second, each of which is drawn. */
4
5
  const STEP_MS = 30;
@@ -92,7 +93,7 @@ function createPacer(initial) {
92
93
  /** The length of `next` to show now. */
93
94
  take(next, live) {
94
95
  if (next !== text) {
95
- const shown = !live && !streaming ? next.length : next.startsWith(text.slice(0, pace.shown)) ? pace.shown : commonPrefix(text, next);
96
+ const shown = !live && !streaming ? next.length : continues(next, text, pace.shown) ? pace.shown : commonPrefix(text, next);
96
97
  text = next;
97
98
  pace = {
98
99
  shown,
@@ -1 +1 @@
1
- {"version":3,"file":"use-smooth-text.js","names":[],"sources":["../../src/react/use-smooth-text.ts"],"sourcesContent":["'use client'\n/**\n * Pacing a reply that arrives in bursts.\n *\n * A model's text reaches the page in uneven pieces: a network frame holds one word or forty, a\n * cache hit holds the whole reply. Drawn as it lands, the page jumps by a line at a time and then\n * stands still. This shows the text at an even pace instead, a few frames behind what has arrived:\n *\n * - the speed follows the backlog (what has arrived and is not shown yet is shown over about\n * `catchUp` ms) and changes gradually, so a burst speeds the flow up instead of landing whole;\n * - text is shown a word at a time: a word typed letter by letter at the end of a line grows\n * there and then jumps to the next line when it no longer fits, and a whole word never does;\n * - when the reply ends, what is left is shown at the same pace instead of landing at once;\n * - text that was there before streaming began is shown at once: only growth is paced;\n * - with `prefers-reduced-motion`, nothing is paced.\n *\n * It never shows text that has not arrived, and costs nothing once it has caught up: the frame\n * loop runs only while there is a backlog.\n */\nimport { useEffect, useState } from 'react'\n\n/** How long a backlog takes to show, by default. */\nexport const DEFAULT_CATCH_UP_MS = 240\n/** The least time between two steps: about 30 a second, each of which is drawn. */\nconst STEP_MS = 30\n/** How fast the speed itself follows the backlog. */\nconst EASE_MS = 140\n/** A reply that has not grown for this long is between words, not inside one. */\nconst PAUSE_MS = 160\n/** A word longer than this (a URL, a hash) is shown in pieces rather than waited for. */\nconst LONGEST_WORD = 24\n\nexport interface Pace {\n /** Characters shown. */\n shown: number\n /** Characters a millisecond, as of the last step. */\n speed: number\n}\n\nconst isSpace = (code: number) => code === 32 || code === 10 || code === 9 || code === 13\n/** CJK and kana are written without spaces: every character there is a place to stop. */\nconst isWide = (code: number) => code >= 0x2e80 && code <= 0xffef\n\n/** Where the last word of `text` begins, or its length when it ends on a break. */\nfunction lastWordStart(text: string): number {\n let at = text.length\n while (at > 0) {\n const code = text.charCodeAt(at - 1)\n if (isSpace(code) || isWide(code)) break\n at--\n }\n return at\n}\n\n/**\n * How much of `text` may be shown: all of it once it is `settled`, and otherwise all but a last\n * word that is still being typed. A long token (a URL, a hash) is not waited for.\n */\nexport function showable(text: string, settled: boolean): number {\n if (settled) return text.length\n const start = lastWordStart(text)\n return text.length - start > LONGEST_WORD ? text.length : start\n}\n\n/** From `at`, the end of the word it is inside (and the spaces after it), no further than `limit`. */\nexport function wordEnd(text: string, at: number, limit: number): number {\n if (at >= limit) return limit\n let end = at\n const reach = Math.min(limit, at + LONGEST_WORD)\n while (end < reach) {\n const code = text.charCodeAt(end)\n if (isSpace(code) || isWide(code)) break\n end++\n }\n // No break in reach: a long token, shown as far as the pace got.\n if (end === reach && reach < limit) return at\n while (end < limit && isSpace(text.charCodeAt(end))) end++\n // A surrogate pair is one character.\n if (end < limit && isLowSurrogate(text.charCodeAt(end))) end++\n return end\n}\n\nconst isLowSurrogate = (code: number) => code >= 0xdc00 && code <= 0xdfff\n\n/**\n * One step: how much of `text` is shown `elapsed` ms after `pace`. `settled` says no more of the\n * last word is coming (the reply ended, or has stood still for a moment).\n */\nexport function advance(\n text: string,\n pace: Pace,\n elapsed: number,\n catchUp: number,\n settled: boolean,\n): Pace {\n const limit = showable(text, settled)\n if (pace.shown >= limit) return { shown: Math.min(pace.shown, text.length), speed: 0 }\n const backlog = limit - pace.shown\n const wanted = backlog / catchUp\n const speed = pace.speed + (wanted - pace.speed) * Math.min(1, elapsed / EASE_MS)\n const reached = Math.min(limit, pace.shown + Math.max(1, Math.round(speed * elapsed)))\n let shown = wordEnd(text, reached, limit)\n if (shown < text.length && isLowSurrogate(text.charCodeAt(shown))) shown++\n return { shown, speed }\n}\n\nconst reducedMotion = () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\nconst now = () => (typeof performance === 'undefined' ? 0 : performance.now())\n\nfunction commonPrefix(a: string, b: string): number {\n const limit = Math.min(a.length, b.length)\n let at = 0\n while (at < limit && a.charCodeAt(at) === b.charCodeAt(at)) at++\n return at\n}\n\n/**\n * The pacer of one text: how much of it is shown, and the frame loop that shows more. It lives\n * outside React's state on purpose: the length belongs to the text it was counted in, and is\n * corrected in the same render that brings a new text, never a frame later.\n */\nfunction createPacer(initial: string) {\n // Text that is there from the start (a settled reply, a remount mid-stream) is not replayed.\n let text = initial\n let pace: Pace = { shown: initial.length, speed: 0 }\n let streaming = false\n let grew = 0\n let stepped = 0\n let frame = 0\n\n return {\n /** The length of `next` to show now. */\n take(next: string, live: boolean): number {\n if (next !== text) {\n // A reply that is not streaming is shown whole, whatever it was before. Otherwise, a\n // different text, not a longer one (a regenerated reply, an edit): keep what they share.\n const shown =\n !live && !streaming\n ? next.length\n : next.startsWith(text.slice(0, pace.shown))\n ? pace.shown\n : commonPrefix(text, next)\n text = next\n pace = { shown, speed: pace.speed }\n grew = now()\n }\n streaming = live\n if (pace.shown < text.length && reducedMotion()) pace = { shown: text.length, speed: 0 }\n return Math.min(pace.shown, text.length)\n },\n /** Show the rest, a step at a time; `redraw` is called after each step that showed more. */\n run(catchUp: number, redraw: () => void) {\n if (frame !== 0 || pace.shown >= text.length) return\n stepped = now()\n const step = (time: number) => {\n const elapsed = time - stepped\n if (elapsed >= STEP_MS) {\n stepped = time\n // A reply that has stood still for a moment is not in the middle of a word.\n const settled = !streaming || time - grew > PAUSE_MS\n const next = advance(text, pace, elapsed, catchUp, settled)\n const moved = next.shown !== pace.shown\n pace = next\n if (moved) redraw()\n }\n frame = pace.shown < text.length ? requestAnimationFrame(step) : 0\n }\n frame = requestAnimationFrame(step)\n },\n stop() {\n cancelAnimationFrame(frame)\n frame = 0\n },\n }\n}\n\n/**\n * `text` as it should be shown now. While `streaming`, growth is paced; when it stops, what is\n * left is shown out. The second value says whether the shown text is still behind: a caller keeps\n * its \"still arriving\" look until it is false.\n */\nexport function useSmoothText(\n text: string,\n streaming: boolean,\n catchUp: number = DEFAULT_CATCH_UP_MS,\n): [shown: string, behind: boolean] {\n const [pacer] = useState(() => createPacer(text))\n const [, setDrawn] = useState(0)\n const end = pacer.take(text, streaming)\n\n // `text` and `streaming` are here to start the loop again when there is more to show.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n pacer.run(catchUp, () => setDrawn((count) => count + 1))\n }, [pacer, text, streaming, catchUp])\n useEffect(() => () => pacer.stop(), [pacer])\n\n return [end >= text.length ? text : text.slice(0, end), end < text.length]\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-smooth-text.js","names":[],"sources":["../../src/react/use-smooth-text.ts"],"sourcesContent":["'use client'\n/**\n * Pacing a reply that arrives in bursts.\n *\n * A model's text reaches the page in uneven pieces: a network frame holds one word or forty, a\n * cache hit holds the whole reply. Drawn as it lands, the page jumps by a line at a time and then\n * stands still. This shows the text at an even pace instead, a few frames behind what has arrived:\n *\n * - the speed follows the backlog (what has arrived and is not shown yet is shown over about\n * `catchUp` ms) and changes gradually, so a burst speeds the flow up instead of landing whole;\n * - text is shown a word at a time: a word typed letter by letter at the end of a line grows\n * there and then jumps to the next line when it no longer fits, and a whole word never does;\n * - when the reply ends, what is left is shown at the same pace instead of landing at once;\n * - text that was there before streaming began is shown at once: only growth is paced;\n * - with `prefers-reduced-motion`, nothing is paced.\n *\n * It never shows text that has not arrived, and costs nothing once it has caught up: the frame\n * loop runs only while there is a backlog.\n */\nimport { useEffect, useState } from 'react'\nimport { continues } from '../core/continues'\n\n/** How long a backlog takes to show, by default. */\nexport const DEFAULT_CATCH_UP_MS = 240\n/** The least time between two steps: about 30 a second, each of which is drawn. */\nconst STEP_MS = 30\n/** How fast the speed itself follows the backlog. */\nconst EASE_MS = 140\n/** A reply that has not grown for this long is between words, not inside one. */\nconst PAUSE_MS = 160\n/** A word longer than this (a URL, a hash) is shown in pieces rather than waited for. */\nconst LONGEST_WORD = 24\n\nexport interface Pace {\n /** Characters shown. */\n shown: number\n /** Characters a millisecond, as of the last step. */\n speed: number\n}\n\nconst isSpace = (code: number) => code === 32 || code === 10 || code === 9 || code === 13\n/** CJK and kana are written without spaces: every character there is a place to stop. */\nconst isWide = (code: number) => code >= 0x2e80 && code <= 0xffef\n\n/** Where the last word of `text` begins, or its length when it ends on a break. */\nfunction lastWordStart(text: string): number {\n let at = text.length\n while (at > 0) {\n const code = text.charCodeAt(at - 1)\n if (isSpace(code) || isWide(code)) break\n at--\n }\n return at\n}\n\n/**\n * How much of `text` may be shown: all of it once it is `settled`, and otherwise all but a last\n * word that is still being typed. A long token (a URL, a hash) is not waited for.\n */\nexport function showable(text: string, settled: boolean): number {\n if (settled) return text.length\n const start = lastWordStart(text)\n return text.length - start > LONGEST_WORD ? text.length : start\n}\n\n/** From `at`, the end of the word it is inside (and the spaces after it), no further than `limit`. */\nexport function wordEnd(text: string, at: number, limit: number): number {\n if (at >= limit) return limit\n let end = at\n const reach = Math.min(limit, at + LONGEST_WORD)\n while (end < reach) {\n const code = text.charCodeAt(end)\n if (isSpace(code) || isWide(code)) break\n end++\n }\n // No break in reach: a long token, shown as far as the pace got.\n if (end === reach && reach < limit) return at\n while (end < limit && isSpace(text.charCodeAt(end))) end++\n // A surrogate pair is one character.\n if (end < limit && isLowSurrogate(text.charCodeAt(end))) end++\n return end\n}\n\nconst isLowSurrogate = (code: number) => code >= 0xdc00 && code <= 0xdfff\n\n/**\n * One step: how much of `text` is shown `elapsed` ms after `pace`. `settled` says no more of the\n * last word is coming (the reply ended, or has stood still for a moment).\n */\nexport function advance(\n text: string,\n pace: Pace,\n elapsed: number,\n catchUp: number,\n settled: boolean,\n): Pace {\n const limit = showable(text, settled)\n if (pace.shown >= limit) return { shown: Math.min(pace.shown, text.length), speed: 0 }\n const backlog = limit - pace.shown\n const wanted = backlog / catchUp\n const speed = pace.speed + (wanted - pace.speed) * Math.min(1, elapsed / EASE_MS)\n const reached = Math.min(limit, pace.shown + Math.max(1, Math.round(speed * elapsed)))\n let shown = wordEnd(text, reached, limit)\n if (shown < text.length && isLowSurrogate(text.charCodeAt(shown))) shown++\n return { shown, speed }\n}\n\nconst reducedMotion = () =>\n typeof window !== 'undefined' &&\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\nconst now = () => (typeof performance === 'undefined' ? 0 : performance.now())\n\nfunction commonPrefix(a: string, b: string): number {\n const limit = Math.min(a.length, b.length)\n let at = 0\n while (at < limit && a.charCodeAt(at) === b.charCodeAt(at)) at++\n return at\n}\n\n/**\n * The pacer of one text: how much of it is shown, and the frame loop that shows more. It lives\n * outside React's state on purpose: the length belongs to the text it was counted in, and is\n * corrected in the same render that brings a new text, never a frame later.\n */\nfunction createPacer(initial: string) {\n // Text that is there from the start (a settled reply, a remount mid-stream) is not replayed.\n let text = initial\n let pace: Pace = { shown: initial.length, speed: 0 }\n let streaming = false\n let grew = 0\n let stepped = 0\n let frame = 0\n\n return {\n /** The length of `next` to show now. */\n take(next: string, live: boolean): number {\n if (next !== text) {\n // A reply that is not streaming is shown whole, whatever it was before. Otherwise, a\n // different text, not a longer one (a regenerated reply, an edit): keep what they share.\n const shown =\n !live && !streaming\n ? next.length\n : continues(next, text, pace.shown)\n ? pace.shown\n : commonPrefix(text, next)\n text = next\n pace = { shown, speed: pace.speed }\n grew = now()\n }\n streaming = live\n if (pace.shown < text.length && reducedMotion()) pace = { shown: text.length, speed: 0 }\n return Math.min(pace.shown, text.length)\n },\n /** Show the rest, a step at a time; `redraw` is called after each step that showed more. */\n run(catchUp: number, redraw: () => void) {\n if (frame !== 0 || pace.shown >= text.length) return\n stepped = now()\n const step = (time: number) => {\n const elapsed = time - stepped\n if (elapsed >= STEP_MS) {\n stepped = time\n // A reply that has stood still for a moment is not in the middle of a word.\n const settled = !streaming || time - grew > PAUSE_MS\n const next = advance(text, pace, elapsed, catchUp, settled)\n const moved = next.shown !== pace.shown\n pace = next\n if (moved) redraw()\n }\n frame = pace.shown < text.length ? requestAnimationFrame(step) : 0\n }\n frame = requestAnimationFrame(step)\n },\n stop() {\n cancelAnimationFrame(frame)\n frame = 0\n },\n }\n}\n\n/**\n * `text` as it should be shown now. While `streaming`, growth is paced; when it stops, what is\n * left is shown out. The second value says whether the shown text is still behind: a caller keeps\n * its \"still arriving\" look until it is false.\n */\nexport function useSmoothText(\n text: string,\n streaming: boolean,\n catchUp: number = DEFAULT_CATCH_UP_MS,\n): [shown: string, behind: boolean] {\n const [pacer] = useState(() => createPacer(text))\n const [, setDrawn] = useState(0)\n const end = pacer.take(text, streaming)\n\n // `text` and `streaming` are here to start the loop again when there is more to show.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n pacer.run(catchUp, () => setDrawn((count) => count + 1))\n }, [pacer, text, streaming, catchUp])\n useEffect(() => () => pacer.stop(), [pacer])\n\n return [end >= text.length ? text : text.slice(0, end), end < text.length]\n}\n"],"mappings":"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"}
@@ -13,6 +13,11 @@ export interface MarkdownLabels {
13
13
  fileTree: string;
14
14
  /** A table wider than the reply, which then scrolls sideways and takes the keyboard's focus. */
15
15
  scrollableTable: string;
16
+ /** A code block wider than the reply: the same, for its `<pre>`. */
17
+ scrollableCode: string;
18
+ /** A task list's box, ticked and not: the item's text follows it. */
19
+ taskDone: string;
20
+ taskOpen: string;
16
21
  /** A diagram, while drawn, while drawing, and when it could not be drawn. */
17
22
  diagram: string;
18
23
  drawingDiagram: string;
@@ -10,6 +10,9 @@ const DEFAULT_LABELS = {
10
10
  variants: "Variants",
11
11
  fileTree: "File tree",
12
12
  scrollableTable: "Scrollable table",
13
+ scrollableCode: "Scrollable code",
14
+ taskDone: "Completed task",
15
+ taskOpen: "Incomplete task",
13
16
  diagram: "Diagram",
14
17
  drawingDiagram: "Drawing diagram…",
15
18
  diagramFailed: (reason) => `Diagram could not be drawn: ${reason}`,
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","names":[],"sources":["../../src/render/labels.ts"],"sourcesContent":["import type { CalloutKind } from '../dialect'\n\n/**\n * Every word the renderer itself puts on the page or speaks to assistive tech. All of it is\n * replaceable (`<Markdown labels={…}>`), because a reply in Russian should not announce\n * \"Warning\" in English. The callout names are spoken, never printed: a visible English label over\n * a translated sentence was what read as foreign.\n */\nexport interface MarkdownLabels {\n callouts: Record<CalloutKind, string>\n /** The tab list of a `[!TABS]` form. */\n variants: string\n /** The tree of a ```tree fence. */\n fileTree: string\n /** A table wider than the reply, which then scrolls sideways and takes the keyboard's focus. */\n scrollableTable: string\n /** A diagram, while drawn, while drawing, and when it could not be drawn. */\n diagram: string\n drawingDiagram: string\n diagramFailed: (reason: string) => string\n /** A drawn diagram's toolbar: enlarge it, swap it for its source and back, copy, download. */\n enlarge: string\n showSource: string\n showDiagram: string\n copySource: string\n downloadSvg: string\n /** The enlarged diagram's controls. */\n zoomIn: string\n zoomOut: string\n fit: string\n actualSize: string\n close: string\n /** The copy button of a code block or a diagram, before and after a copy. */\n copy: string\n copied: string\n /** A code block's download button, which saves the code as a file. */\n download: string\n}\n\nexport const DEFAULT_LABELS: MarkdownLabels = {\n callouts: {\n note: 'Note',\n tip: 'Tip',\n important: 'Important',\n warning: 'Warning',\n caution: 'Caution',\n },\n variants: 'Variants',\n fileTree: 'File tree',\n scrollableTable: 'Scrollable table',\n diagram: 'Diagram',\n drawingDiagram: 'Drawing diagram…',\n diagramFailed: (reason) => `Diagram could not be drawn: ${reason}`,\n enlarge: 'Enlarge diagram',\n showSource: 'Show source',\n showDiagram: 'Show diagram',\n copySource: 'Copy source',\n downloadSvg: 'Download SVG',\n zoomIn: 'Zoom in',\n zoomOut: 'Zoom out',\n fit: 'Fit to window',\n actualSize: 'Actual size',\n close: 'Close',\n copy: 'Copy code',\n copied: 'Copied',\n download: 'Download code',\n}\n\nexport type LabelOverrides = Partial<Omit<MarkdownLabels, 'callouts'>> & {\n callouts?: Partial<MarkdownLabels['callouts']>\n}\n\nexport function mergeLabels(overrides: LabelOverrides | undefined): MarkdownLabels {\n if (!overrides) return DEFAULT_LABELS\n return {\n ...DEFAULT_LABELS,\n ...overrides,\n callouts: { ...DEFAULT_LABELS.callouts, ...overrides.callouts },\n }\n}\n"],"mappings":";AAuCA,MAAa,iBAAiC;CAC5C,UAAU;EACR,MAAM;EACN,KAAK;EACL,WAAW;EACX,SAAS;EACT,SAAS;CACX;CACA,UAAU;CACV,UAAU;CACV,iBAAiB;CACjB,SAAS;CACT,gBAAgB;CAChB,gBAAgB,WAAW,+BAA+B;CAC1D,SAAS;CACT,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,SAAS;CACT,KAAK;CACL,YAAY;CACZ,OAAO;CACP,MAAM;CACN,QAAQ;CACR,UAAU;AACZ;AAMA,SAAgB,YAAY,WAAuD;CACjF,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,GAAG;EACH,GAAG;EACH,UAAU;GAAE,GAAG,eAAe;GAAU,GAAG,UAAU;EAAS;CAChE;AACF"}
1
+ {"version":3,"file":"labels.js","names":[],"sources":["../../src/render/labels.ts"],"sourcesContent":["import type { CalloutKind } from '../dialect'\n\n/**\n * Every word the renderer itself puts on the page or speaks to assistive tech. All of it is\n * replaceable (`<Markdown labels={…}>`), because a reply in Russian should not announce\n * \"Warning\" in English. The callout names are spoken, never printed: a visible English label over\n * a translated sentence was what read as foreign.\n */\nexport interface MarkdownLabels {\n callouts: Record<CalloutKind, string>\n /** The tab list of a `[!TABS]` form. */\n variants: string\n /** The tree of a ```tree fence. */\n fileTree: string\n /** A table wider than the reply, which then scrolls sideways and takes the keyboard's focus. */\n scrollableTable: string\n /** A code block wider than the reply: the same, for its `<pre>`. */\n scrollableCode: string\n /** A task list's box, ticked and not: the item's text follows it. */\n taskDone: string\n taskOpen: string\n /** A diagram, while drawn, while drawing, and when it could not be drawn. */\n diagram: string\n drawingDiagram: string\n diagramFailed: (reason: string) => string\n /** A drawn diagram's toolbar: enlarge it, swap it for its source and back, copy, download. */\n enlarge: string\n showSource: string\n showDiagram: string\n copySource: string\n downloadSvg: string\n /** The enlarged diagram's controls. */\n zoomIn: string\n zoomOut: string\n fit: string\n actualSize: string\n close: string\n /** The copy button of a code block or a diagram, before and after a copy. */\n copy: string\n copied: string\n /** A code block's download button, which saves the code as a file. */\n download: string\n}\n\nexport const DEFAULT_LABELS: MarkdownLabels = {\n callouts: {\n note: 'Note',\n tip: 'Tip',\n important: 'Important',\n warning: 'Warning',\n caution: 'Caution',\n },\n variants: 'Variants',\n fileTree: 'File tree',\n scrollableTable: 'Scrollable table',\n scrollableCode: 'Scrollable code',\n taskDone: 'Completed task',\n taskOpen: 'Incomplete task',\n diagram: 'Diagram',\n drawingDiagram: 'Drawing diagram…',\n diagramFailed: (reason) => `Diagram could not be drawn: ${reason}`,\n enlarge: 'Enlarge diagram',\n showSource: 'Show source',\n showDiagram: 'Show diagram',\n copySource: 'Copy source',\n downloadSvg: 'Download SVG',\n zoomIn: 'Zoom in',\n zoomOut: 'Zoom out',\n fit: 'Fit to window',\n actualSize: 'Actual size',\n close: 'Close',\n copy: 'Copy code',\n copied: 'Copied',\n download: 'Download code',\n}\n\nexport type LabelOverrides = Partial<Omit<MarkdownLabels, 'callouts'>> & {\n callouts?: Partial<MarkdownLabels['callouts']>\n}\n\nexport function mergeLabels(overrides: LabelOverrides | undefined): MarkdownLabels {\n if (!overrides) return DEFAULT_LABELS\n return {\n ...DEFAULT_LABELS,\n ...overrides,\n callouts: { ...DEFAULT_LABELS.callouts, ...overrides.callouts },\n }\n}\n"],"mappings":";AA4CA,MAAa,iBAAiC;CAC5C,UAAU;EACR,MAAM;EACN,KAAK;EACL,WAAW;EACX,SAAS;EACT,SAAS;CACX;CACA,UAAU;CACV,UAAU;CACV,iBAAiB;CACjB,gBAAgB;CAChB,UAAU;CACV,UAAU;CACV,SAAS;CACT,gBAAgB;CAChB,gBAAgB,WAAW,+BAA+B;CAC1D,SAAS;CACT,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,SAAS;CACT,KAAK;CACL,YAAY;CACZ,OAAO;CACP,MAAM;CACN,QAAQ;CACR,UAAU;AACZ;AAMA,SAAgB,YAAY,WAAuD;CACjF,IAAI,CAAC,WAAW,OAAO;CACvB,OAAO;EACL,GAAG;EACH,GAAG;EACH,UAAU;GAAE,GAAG,eAAe;GAAU,GAAG,UAAU;EAAS;CAChE;AACF"}
@@ -1,5 +1,6 @@
1
1
  import "../dialect.js";
2
2
  import { normalizeUri } from "../engine/entities.js";
3
+ import { DEFAULT_LABELS } from "./labels.js";
3
4
  // src/render/tree.ts
4
5
  /**
5
6
  * A Markdown tree as elements, for any target that has elements: the one place that says which
@@ -93,6 +94,7 @@ const createRenderer = ({ element: h, text, fragment }) => (root, options) => {
93
94
  const box = task ? h(pageTag("input"), {
94
95
  type: "checkbox",
95
96
  disabled: true,
97
+ "aria-label": (options.labels ?? DEFAULT_LABELS)[node.checked ? "taskDone" : "taskOpen"],
96
98
  "data-task-key": options.offset + (node.position?.start.offset ?? 0),
97
99
  ...node.checked ? { checked: true } : null
98
100
  }, []) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"tree.js","names":[],"sources":["../../src/render/tree.ts"],"sourcesContent":["/**\n * A Markdown tree as elements, for any target that has elements: the one place that says which\n * tag a node becomes, what a task list or a footnote is made of, and which URL is let through.\n */\nimport { DIAGRAM_FENCE_LANGUAGE, TREE_FENCE_LANGUAGE } from '../dialect'\nimport { normalizeUri } from '../engine/entities'\nimport type { Node } from '../engine/types'\nimport type { UrlTransform } from '../engine/url'\nimport type { Factory, Props } from './factory'\n\n/** A page's own element for a tag: it gets the tag's properties, and the node it draws as `node`. */\nexport type Components<C> = Record<string, C | undefined>\n\nexport interface RenderOptions<E, C> {\n components: Components<C>\n urlTransform: UrlTransform\n /** Where the tree's text starts in the document: task boxes and headings are keyed by it. */\n offset: number\n /** Ids for the headings, by where each starts in the document. */\n headingIds: ReadonlyMap<number, string> | null\n /** A ```mermaid or ```tree fence, drawn by the page that asked for one. */\n special: (language: string, source: string) => E | undefined\n}\n\n/** The prefix on every id content makes, so it cannot stand in for one of the page's own. */\nexport const ID_PREFIX = 'user-content-'\n\nconst URL_PROPS = ['href', 'src', 'cite', 'longDesc'] as const\n\n/** `dataCalloutTitle` as the attribute React writes: `data-callout-title`. */\nconst attribute = (name: string): string =>\n name.length > 4 && name.startsWith('data') && name.charCodeAt(4) >= 65 && name.charCodeAt(4) <= 90\n ? `data-${name.slice(4).replace(/[A-Z]/g, (letter, at: number) => (at > 0 ? '-' : '') + letter.toLowerCase())}`\n : name\n\n/** A node's own element name and properties (`data.hName`, `data.hProperties`) over the default. */\nconst own = (node: Node, tag: string, props: Props | null): [string, Props | null] => {\n const data = node.data\n if (data === undefined) return [tag, props]\n const name = typeof data.hName === 'string' ? data.hName : tag\n const extra = data.hProperties as Props | undefined\n if (extra === undefined) return [name, props]\n const merged: Props = props === null ? {} : { ...props }\n for (const key in extra) {\n const value = extra[key]\n if (value === undefined || value === null || value === false) continue\n if (key === 'className') merged.className = Array.isArray(value) ? value.join(' ') : value\n else merged[attribute(key)] = value\n }\n return [name, merged]\n}\n\n/**\n * The function that draws a tree with `factory`'s elements. Every call of it gives new elements;\n * the caller remembers them by the tree's text.\n */\nexport const createRenderer =\n <E, C>({ element: h, text, fragment }: Factory<E, C>) =>\n (root: Node, options: RenderOptions<E, C>): E => {\n const { components, urlTransform } = options\n const NEWLINE = text('\\n')\n const SPACE = text(' ')\n /**\n * The URL the page's check lets through, or undefined when it refuses one: emptied, or no\n * answer. An empty URL as written (`[a]()`) stays empty, the link CommonMark writes.\n */\n const allowed = (url: string, key: string, node: Node): string | undefined => {\n const checked = urlTransform(url, key, node)\n return checked || (url === '' && checked === '' ? '' : undefined)\n }\n /** The page's component for an element the renderer writes itself, or the element. */\n const pageTag = (name: string): string | C => components[name] ?? name\n\n const element = (node: Node, tag: string, props: Props | null, children: E[]): E => {\n const [name, merged] = own(node, tag, props)\n if (merged !== null && node.data?.hProperties !== undefined) {\n // An element an extension made: its URLs pass the page's check like a link's or an image's,\n // and one refused is dropped, not left as an empty attribute.\n for (const key of URL_PROPS) {\n if (typeof merged[key] !== 'string') continue\n const url = allowed(merged[key] as string, key, node)\n if (url === undefined) delete merged[key]\n else merged[key] = url\n }\n // An element given its HTML has no children of its own.\n if (merged.innerHTML !== undefined) children = []\n }\n const Page = components[name]\n if (Page === undefined) return h(name, merged, children)\n return h(Page, merged === null ? { node } : { ...merged, node }, children)\n }\n\n /** A run of text-level nodes. */\n const inline = (nodes: Node[] | undefined): E[] => {\n const out: E[] = []\n if (nodes !== undefined) for (const node of nodes) one(node, out, false)\n return out\n }\n\n /** A run of blocks, a line break between each: before the first and after the last as well when `around`. */\n const blocks = (nodes: Node[] | undefined, around: boolean): E[] => {\n const out: E[] = []\n if (nodes === undefined) return out\n if (around) out.push(NEWLINE)\n for (let index = 0; index < nodes.length; index++) {\n const node = nodes[index] as Node\n // Text among blocks is what raw HTML left there, with its own line breaks.\n if (index > 0 && node.type !== 'text' && (nodes[index - 1] as Node).type !== 'text')\n out.push(NEWLINE)\n one(node, out, false)\n }\n if (around && nodes.length > 0) out.push(NEWLINE)\n return out\n }\n\n const item = (node: Node, loose: boolean): E => {\n const children = node.children ?? []\n const out: E[] = []\n const task = typeof node.checked === 'boolean'\n const box = task\n ? h(\n pageTag('input'),\n {\n type: 'checkbox',\n disabled: true,\n 'data-task-key': options.offset + (node.position?.start.offset ?? 0),\n ...(node.checked ? { checked: true } : null),\n },\n [],\n )\n : null\n const first = children[0]\n // A task's box opens the item's first paragraph, or stands as one when there is none.\n if (\n box !== null &&\n (first === undefined || first.type !== 'paragraph' || first.data?.hName !== undefined)\n ) {\n if (loose) out.push(NEWLINE, h(pageTag('p'), null, [box]))\n else out.push(box)\n }\n for (let index = 0; index < children.length; index++) {\n const child = children[index] as Node\n const paragraph = child.type === 'paragraph' && child.data?.hName === undefined\n if (loose || index !== 0 || !paragraph) out.push(NEWLINE)\n if (paragraph) {\n const words = inline(child.children)\n if (box !== null && index === 0) {\n if (words.length > 0) words.unshift(SPACE)\n words.unshift(box)\n }\n if (loose) out.push(element(child, 'p', null, words))\n else out.push(...words)\n } else one(child, out, false)\n }\n const last = children[children.length - 1]\n if (\n last !== undefined &&\n (loose || last.type !== 'paragraph' || last.data?.hName !== undefined)\n )\n out.push(NEWLINE)\n return element(node, 'li', task ? { className: 'task-list-item' } : null, out)\n }\n\n const footnotes = (node: Node): E => {\n const items: E[] = [NEWLINE]\n const definitions = node.children ?? []\n for (let at = 0; at < definitions.length; at++) {\n const definition = definitions[at] as Node\n const id = normalizeUri(definition.identifier ?? '')\n const back: E[] = []\n for (let nth = 1; nth <= (definition.count ?? 1); nth++) {\n const suffix = nth > 1 ? `-${nth}` : ''\n if (nth > 1) back.push(SPACE)\n back.push(\n h(\n pageTag('a'),\n {\n href: `#${ID_PREFIX}fnref-${id}${suffix}`,\n 'data-footnote-backref': '',\n 'aria-label': `Back to reference ${definition.index}${suffix}`,\n className: 'data-footnote-backref',\n },\n nth > 1 ? [text('↩'), h(pageTag('sup'), null, [text(String(nth))])] : [text('↩')],\n ),\n )\n }\n const content = definition.children ?? []\n const body: E[] = [NEWLINE]\n const tail = content[content.length - 1]\n for (let index = 0; index < content.length; index++) {\n const child = content[index] as Node\n if (index > 0) body.push(NEWLINE)\n if (child === tail && child.type === 'paragraph' && child.data?.hName === undefined)\n body.push(h(pageTag('p'), null, [...inline(child.children), SPACE, ...back]))\n else one(child, body, false)\n }\n if (tail === undefined || tail.type !== 'paragraph' || tail.data?.hName !== undefined)\n body.push(NEWLINE, ...back)\n body.push(NEWLINE)\n if (at > 0) items.push(NEWLINE)\n items.push(h(pageTag('li'), { id: `${ID_PREFIX}fn-${id}` }, body))\n }\n items.push(NEWLINE)\n return h(pageTag('section'), { 'data-footnotes': '', className: 'footnotes' }, [\n h(pageTag('h2'), { className: 'sr-only', id: `${ID_PREFIX}footnote-label` }, [\n text('Footnotes'),\n ]),\n NEWLINE,\n h(pageTag('ol'), null, items),\n NEWLINE,\n ])\n }\n\n const one = (node: Node, out: E[], _tight: boolean): void => {\n switch (node.type) {\n case 'text':\n out.push(text(node.value ?? ''))\n return\n case 'paragraph':\n out.push(element(node, 'p', null, inline(node.children)))\n return\n case 'heading': {\n const id = options.headingIds?.get(options.offset + (node.position?.start.offset ?? 0))\n out.push(\n element(\n node,\n `h${node.depth}`,\n id === undefined ? null : { id },\n inline(node.children),\n ),\n )\n return\n }\n case 'emphasis':\n out.push(element(node, 'em', null, inline(node.children)))\n return\n case 'strong':\n out.push(element(node, 'strong', null, inline(node.children)))\n return\n case 'delete':\n out.push(element(node, 'del', null, inline(node.children)))\n return\n case 'inlineCode':\n out.push(\n element(node, 'code', null, [text((node.value ?? '').replace(/\\r?\\n|\\r/g, ' '))]),\n )\n return\n case 'break':\n out.push(element(node, 'br', null, []), NEWLINE)\n return\n case 'link': {\n const url = node.url ?? ''\n const href = allowed(url, 'href', node)\n // A bare address the page refuses to link (`ftp://…`) stays the text it was written as.\n if (\n href === undefined &&\n node.children?.length === 1 &&\n node.children[0]?.value === url\n ) {\n out.push(text(url))\n return\n }\n // A refused URL leaves the link without an `href`: an empty one would link to the page.\n const props: Props = href === undefined ? {} : { href }\n if (node.title) props.title = node.title\n out.push(element(node, 'a', props, inline(node.children)))\n return\n }\n case 'image': {\n // A refused or empty source is left off: `src=\"\"` makes a browser fetch the page again.\n const src = allowed(node.url ?? '', 'src', node)\n const props: Props = src ? { src, alt: node.alt ?? '' } : { alt: node.alt ?? '' }\n if (node.title) props.title = node.title\n out.push(element(node, 'img', props, []))\n return\n }\n case 'thematicBreak':\n out.push(element(node, 'hr', null, []))\n return\n case 'blockquote':\n out.push(element(node, 'blockquote', null, blocks(node.children, true)))\n return\n case 'code': {\n const language = node.lang\n if (language === DIAGRAM_FENCE_LANGUAGE || language === TREE_FENCE_LANGUAGE) {\n const drawn = options.special(language, (node.value ?? '').replace(/\\n$/, ''))\n if (drawn !== undefined) {\n out.push(drawn)\n return\n }\n }\n const code = element(\n node,\n 'code',\n language ? { className: `language-${language}` } : null,\n [text(node.value ?? '')],\n )\n const Pre = components.pre\n out.push(Pre === undefined ? h('pre', null, [code]) : h(Pre, { node }, [code]))\n return\n }\n case 'list': {\n const children = node.children ?? []\n let loose = node.spread === true\n let tasks = false\n for (const child of children) {\n if (child.spread === true) loose = true\n if (typeof child.checked === 'boolean') tasks = true\n }\n const props: Props = {}\n if (node.ordered && typeof node.start === 'number' && node.start !== 1)\n props.start = node.start\n if (tasks) props.className = 'contains-task-list'\n const rows: E[] = [NEWLINE]\n for (let index = 0; index < children.length; index++) {\n if (index > 0) rows.push(NEWLINE)\n rows.push(item(children[index] as Node, loose))\n }\n if (children.length > 0) rows.push(NEWLINE)\n out.push(element(node, node.ordered ? 'ol' : 'ul', props, rows))\n return\n }\n case 'listItem':\n out.push(item(node, node.spread === true))\n return\n case 'table': {\n const rows = node.children ?? []\n const align = node.align ?? []\n const row = (from: Node, header: boolean): E =>\n element(\n from,\n 'tr',\n null,\n (from.children ?? []).map((cell, index) =>\n element(\n cell,\n header ? 'th' : 'td',\n align[index] ? { style: { textAlign: align[index] } } : null,\n inline(cell.children),\n ),\n ),\n )\n const parts: E[] = []\n const head = rows[0]\n if (head !== undefined) parts.push(h(pageTag('thead'), null, [row(head, true)]))\n if (rows.length > 1)\n parts.push(\n h(\n pageTag('tbody'),\n null,\n rows.slice(1).map((from) => row(from, false)),\n ),\n )\n out.push(element(node, 'table', null, parts))\n return\n }\n case 'footnoteReference': {\n const id = normalizeUri(node.identifier ?? '')\n const nth = (node.occurrence ?? 1) > 1 ? `-${node.occurrence}` : ''\n out.push(\n h(pageTag('sup'), null, [\n h(\n pageTag('a'),\n {\n href: `#${ID_PREFIX}fn-${id}`,\n id: `${ID_PREFIX}fnref-${id}${nth}`,\n 'data-footnote-ref': '',\n 'aria-describedby': `${ID_PREFIX}footnote-label`,\n },\n [text(String(node.index))],\n ),\n ]),\n )\n return\n }\n case 'footnotes':\n out.push(footnotes(node))\n return\n case 'html':\n // Raw HTML is drawn only once the HTML stage has made elements of it.\n return\n case 'root':\n out.push(...blocks(node.children, false))\n return\n default: {\n // An extension's node: drawn by the element it names for itself, or as its children.\n const children =\n node.children === undefined\n ? node.value === undefined\n ? []\n : [text(node.value)]\n : mixed(node.children)\n if (typeof node.data?.hName === 'string') out.push(element(node, 'div', null, children))\n else out.push(...children)\n }\n }\n }\n\n /** The children of an element the tree does not know: blocks or text, as they come. */\n const mixed = (nodes: Node[]): E[] => {\n const out: E[] = []\n for (const node of nodes) one(node, out, false)\n return out\n }\n\n const out: E[] = []\n one(root, out, false)\n return fragment(out)\n }\n"],"mappings":"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"}
1
+ {"version":3,"file":"tree.js","names":[],"sources":["../../src/render/tree.ts"],"sourcesContent":["/**\n * A Markdown tree as elements, for any target that has elements: the one place that says which\n * tag a node becomes, what a task list or a footnote is made of, and which URL is let through.\n */\nimport { DIAGRAM_FENCE_LANGUAGE, TREE_FENCE_LANGUAGE } from '../dialect'\nimport { normalizeUri } from '../engine/entities'\nimport type { Node } from '../engine/types'\nimport type { UrlTransform } from '../engine/url'\nimport type { Factory, Props } from './factory'\nimport { DEFAULT_LABELS, type MarkdownLabels } from './labels'\n\n/** A page's own element for a tag: it gets the tag's properties, and the node it draws as `node`. */\nexport type Components<C> = Record<string, C | undefined>\n\nexport interface RenderOptions<E, C> {\n components: Components<C>\n urlTransform: UrlTransform\n /** Where the tree's text starts in the document: task boxes and headings are keyed by it. */\n offset: number\n /** Ids for the headings, by where each starts in the document. */\n headingIds: ReadonlyMap<number, string> | null\n /** A ```mermaid or ```tree fence, drawn by the page that asked for one. */\n special: (language: string, source: string) => E | undefined\n /** The names of a task list's boxes; the English ones when left out. */\n labels?: Pick<MarkdownLabels, 'taskDone' | 'taskOpen'> | undefined\n}\n\n/** The prefix on every id content makes, so it cannot stand in for one of the page's own. */\nexport const ID_PREFIX = 'user-content-'\n\nconst URL_PROPS = ['href', 'src', 'cite', 'longDesc'] as const\n\n/** `dataCalloutTitle` as the attribute React writes: `data-callout-title`. */\nconst attribute = (name: string): string =>\n name.length > 4 && name.startsWith('data') && name.charCodeAt(4) >= 65 && name.charCodeAt(4) <= 90\n ? `data-${name.slice(4).replace(/[A-Z]/g, (letter, at: number) => (at > 0 ? '-' : '') + letter.toLowerCase())}`\n : name\n\n/** A node's own element name and properties (`data.hName`, `data.hProperties`) over the default. */\nconst own = (node: Node, tag: string, props: Props | null): [string, Props | null] => {\n const data = node.data\n if (data === undefined) return [tag, props]\n const name = typeof data.hName === 'string' ? data.hName : tag\n const extra = data.hProperties as Props | undefined\n if (extra === undefined) return [name, props]\n const merged: Props = props === null ? {} : { ...props }\n for (const key in extra) {\n const value = extra[key]\n if (value === undefined || value === null || value === false) continue\n if (key === 'className') merged.className = Array.isArray(value) ? value.join(' ') : value\n else merged[attribute(key)] = value\n }\n return [name, merged]\n}\n\n/**\n * The function that draws a tree with `factory`'s elements. Every call of it gives new elements;\n * the caller remembers them by the tree's text.\n */\nexport const createRenderer =\n <E, C>({ element: h, text, fragment }: Factory<E, C>) =>\n (root: Node, options: RenderOptions<E, C>): E => {\n const { components, urlTransform } = options\n const NEWLINE = text('\\n')\n const SPACE = text(' ')\n /**\n * The URL the page's check lets through, or undefined when it refuses one: emptied, or no\n * answer. An empty URL as written (`[a]()`) stays empty, the link CommonMark writes.\n */\n const allowed = (url: string, key: string, node: Node): string | undefined => {\n const checked = urlTransform(url, key, node)\n return checked || (url === '' && checked === '' ? '' : undefined)\n }\n /** The page's component for an element the renderer writes itself, or the element. */\n const pageTag = (name: string): string | C => components[name] ?? name\n\n const element = (node: Node, tag: string, props: Props | null, children: E[]): E => {\n const [name, merged] = own(node, tag, props)\n if (merged !== null && node.data?.hProperties !== undefined) {\n // An element an extension made: its URLs pass the page's check like a link's or an image's,\n // and one refused is dropped, not left as an empty attribute.\n for (const key of URL_PROPS) {\n if (typeof merged[key] !== 'string') continue\n const url = allowed(merged[key] as string, key, node)\n if (url === undefined) delete merged[key]\n else merged[key] = url\n }\n // An element given its HTML has no children of its own.\n if (merged.innerHTML !== undefined) children = []\n }\n const Page = components[name]\n if (Page === undefined) return h(name, merged, children)\n return h(Page, merged === null ? { node } : { ...merged, node }, children)\n }\n\n /** A run of text-level nodes. */\n const inline = (nodes: Node[] | undefined): E[] => {\n const out: E[] = []\n if (nodes !== undefined) for (const node of nodes) one(node, out, false)\n return out\n }\n\n /** A run of blocks, a line break between each: before the first and after the last as well when `around`. */\n const blocks = (nodes: Node[] | undefined, around: boolean): E[] => {\n const out: E[] = []\n if (nodes === undefined) return out\n if (around) out.push(NEWLINE)\n for (let index = 0; index < nodes.length; index++) {\n const node = nodes[index] as Node\n // Text among blocks is what raw HTML left there, with its own line breaks.\n if (index > 0 && node.type !== 'text' && (nodes[index - 1] as Node).type !== 'text')\n out.push(NEWLINE)\n one(node, out, false)\n }\n if (around && nodes.length > 0) out.push(NEWLINE)\n return out\n }\n\n const item = (node: Node, loose: boolean): E => {\n const children = node.children ?? []\n const out: E[] = []\n const task = typeof node.checked === 'boolean'\n const box = task\n ? h(\n pageTag('input'),\n {\n type: 'checkbox',\n disabled: true,\n // A box with no name is announced as \"checkbox\" alone (WCAG 4.1.2).\n 'aria-label': (options.labels ?? DEFAULT_LABELS)[\n node.checked ? 'taskDone' : 'taskOpen'\n ],\n 'data-task-key': options.offset + (node.position?.start.offset ?? 0),\n ...(node.checked ? { checked: true } : null),\n },\n [],\n )\n : null\n const first = children[0]\n // A task's box opens the item's first paragraph, or stands as one when there is none.\n if (\n box !== null &&\n (first === undefined || first.type !== 'paragraph' || first.data?.hName !== undefined)\n ) {\n if (loose) out.push(NEWLINE, h(pageTag('p'), null, [box]))\n else out.push(box)\n }\n for (let index = 0; index < children.length; index++) {\n const child = children[index] as Node\n const paragraph = child.type === 'paragraph' && child.data?.hName === undefined\n if (loose || index !== 0 || !paragraph) out.push(NEWLINE)\n if (paragraph) {\n const words = inline(child.children)\n if (box !== null && index === 0) {\n if (words.length > 0) words.unshift(SPACE)\n words.unshift(box)\n }\n if (loose) out.push(element(child, 'p', null, words))\n else out.push(...words)\n } else one(child, out, false)\n }\n const last = children[children.length - 1]\n if (\n last !== undefined &&\n (loose || last.type !== 'paragraph' || last.data?.hName !== undefined)\n )\n out.push(NEWLINE)\n return element(node, 'li', task ? { className: 'task-list-item' } : null, out)\n }\n\n const footnotes = (node: Node): E => {\n const items: E[] = [NEWLINE]\n const definitions = node.children ?? []\n for (let at = 0; at < definitions.length; at++) {\n const definition = definitions[at] as Node\n const id = normalizeUri(definition.identifier ?? '')\n const back: E[] = []\n for (let nth = 1; nth <= (definition.count ?? 1); nth++) {\n const suffix = nth > 1 ? `-${nth}` : ''\n if (nth > 1) back.push(SPACE)\n back.push(\n h(\n pageTag('a'),\n {\n href: `#${ID_PREFIX}fnref-${id}${suffix}`,\n 'data-footnote-backref': '',\n 'aria-label': `Back to reference ${definition.index}${suffix}`,\n className: 'data-footnote-backref',\n },\n nth > 1 ? [text('↩'), h(pageTag('sup'), null, [text(String(nth))])] : [text('↩')],\n ),\n )\n }\n const content = definition.children ?? []\n const body: E[] = [NEWLINE]\n const tail = content[content.length - 1]\n for (let index = 0; index < content.length; index++) {\n const child = content[index] as Node\n if (index > 0) body.push(NEWLINE)\n if (child === tail && child.type === 'paragraph' && child.data?.hName === undefined)\n body.push(h(pageTag('p'), null, [...inline(child.children), SPACE, ...back]))\n else one(child, body, false)\n }\n if (tail === undefined || tail.type !== 'paragraph' || tail.data?.hName !== undefined)\n body.push(NEWLINE, ...back)\n body.push(NEWLINE)\n if (at > 0) items.push(NEWLINE)\n items.push(h(pageTag('li'), { id: `${ID_PREFIX}fn-${id}` }, body))\n }\n items.push(NEWLINE)\n return h(pageTag('section'), { 'data-footnotes': '', className: 'footnotes' }, [\n h(pageTag('h2'), { className: 'sr-only', id: `${ID_PREFIX}footnote-label` }, [\n text('Footnotes'),\n ]),\n NEWLINE,\n h(pageTag('ol'), null, items),\n NEWLINE,\n ])\n }\n\n const one = (node: Node, out: E[], _tight: boolean): void => {\n switch (node.type) {\n case 'text':\n out.push(text(node.value ?? ''))\n return\n case 'paragraph':\n out.push(element(node, 'p', null, inline(node.children)))\n return\n case 'heading': {\n const id = options.headingIds?.get(options.offset + (node.position?.start.offset ?? 0))\n out.push(\n element(\n node,\n `h${node.depth}`,\n id === undefined ? null : { id },\n inline(node.children),\n ),\n )\n return\n }\n case 'emphasis':\n out.push(element(node, 'em', null, inline(node.children)))\n return\n case 'strong':\n out.push(element(node, 'strong', null, inline(node.children)))\n return\n case 'delete':\n out.push(element(node, 'del', null, inline(node.children)))\n return\n case 'inlineCode':\n out.push(\n element(node, 'code', null, [text((node.value ?? '').replace(/\\r?\\n|\\r/g, ' '))]),\n )\n return\n case 'break':\n out.push(element(node, 'br', null, []), NEWLINE)\n return\n case 'link': {\n const url = node.url ?? ''\n const href = allowed(url, 'href', node)\n // A bare address the page refuses to link (`ftp://…`) stays the text it was written as.\n if (\n href === undefined &&\n node.children?.length === 1 &&\n node.children[0]?.value === url\n ) {\n out.push(text(url))\n return\n }\n // A refused URL leaves the link without an `href`: an empty one would link to the page.\n const props: Props = href === undefined ? {} : { href }\n if (node.title) props.title = node.title\n out.push(element(node, 'a', props, inline(node.children)))\n return\n }\n case 'image': {\n // A refused or empty source is left off: `src=\"\"` makes a browser fetch the page again.\n const src = allowed(node.url ?? '', 'src', node)\n const props: Props = src ? { src, alt: node.alt ?? '' } : { alt: node.alt ?? '' }\n if (node.title) props.title = node.title\n out.push(element(node, 'img', props, []))\n return\n }\n case 'thematicBreak':\n out.push(element(node, 'hr', null, []))\n return\n case 'blockquote':\n out.push(element(node, 'blockquote', null, blocks(node.children, true)))\n return\n case 'code': {\n const language = node.lang\n if (language === DIAGRAM_FENCE_LANGUAGE || language === TREE_FENCE_LANGUAGE) {\n const drawn = options.special(language, (node.value ?? '').replace(/\\n$/, ''))\n if (drawn !== undefined) {\n out.push(drawn)\n return\n }\n }\n const code = element(\n node,\n 'code',\n language ? { className: `language-${language}` } : null,\n [text(node.value ?? '')],\n )\n const Pre = components.pre\n out.push(Pre === undefined ? h('pre', null, [code]) : h(Pre, { node }, [code]))\n return\n }\n case 'list': {\n const children = node.children ?? []\n let loose = node.spread === true\n let tasks = false\n for (const child of children) {\n if (child.spread === true) loose = true\n if (typeof child.checked === 'boolean') tasks = true\n }\n const props: Props = {}\n if (node.ordered && typeof node.start === 'number' && node.start !== 1)\n props.start = node.start\n if (tasks) props.className = 'contains-task-list'\n const rows: E[] = [NEWLINE]\n for (let index = 0; index < children.length; index++) {\n if (index > 0) rows.push(NEWLINE)\n rows.push(item(children[index] as Node, loose))\n }\n if (children.length > 0) rows.push(NEWLINE)\n out.push(element(node, node.ordered ? 'ol' : 'ul', props, rows))\n return\n }\n case 'listItem':\n out.push(item(node, node.spread === true))\n return\n case 'table': {\n const rows = node.children ?? []\n const align = node.align ?? []\n const row = (from: Node, header: boolean): E =>\n element(\n from,\n 'tr',\n null,\n (from.children ?? []).map((cell, index) =>\n element(\n cell,\n header ? 'th' : 'td',\n align[index] ? { style: { textAlign: align[index] } } : null,\n inline(cell.children),\n ),\n ),\n )\n const parts: E[] = []\n const head = rows[0]\n if (head !== undefined) parts.push(h(pageTag('thead'), null, [row(head, true)]))\n if (rows.length > 1)\n parts.push(\n h(\n pageTag('tbody'),\n null,\n rows.slice(1).map((from) => row(from, false)),\n ),\n )\n out.push(element(node, 'table', null, parts))\n return\n }\n case 'footnoteReference': {\n const id = normalizeUri(node.identifier ?? '')\n const nth = (node.occurrence ?? 1) > 1 ? `-${node.occurrence}` : ''\n out.push(\n h(pageTag('sup'), null, [\n h(\n pageTag('a'),\n {\n href: `#${ID_PREFIX}fn-${id}`,\n id: `${ID_PREFIX}fnref-${id}${nth}`,\n 'data-footnote-ref': '',\n 'aria-describedby': `${ID_PREFIX}footnote-label`,\n },\n [text(String(node.index))],\n ),\n ]),\n )\n return\n }\n case 'footnotes':\n out.push(footnotes(node))\n return\n case 'html':\n // Raw HTML is drawn only once the HTML stage has made elements of it.\n return\n case 'root':\n out.push(...blocks(node.children, false))\n return\n default: {\n // An extension's node: drawn by the element it names for itself, or as its children.\n const children =\n node.children === undefined\n ? node.value === undefined\n ? []\n : [text(node.value)]\n : mixed(node.children)\n if (typeof node.data?.hName === 'string') out.push(element(node, 'div', null, children))\n else out.push(...children)\n }\n }\n }\n\n /** The children of an element the tree does not know: blocks or text, as they come. */\n const mixed = (nodes: Node[]): E[] => {\n const out: E[] = []\n for (const node of nodes) one(node, out, false)\n return out\n }\n\n const out: E[] = []\n one(root, out, false)\n return fragment(out)\n }\n"],"mappings":"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"}
@@ -50,6 +50,9 @@ export interface ViewHost {
50
50
  copy(text: string): Promise<boolean>;
51
51
  /** Save `text` as a file named after its fence's title, or its language. */
52
52
  download(text: string, title: string | undefined, language: string | undefined): void;
53
- /** The box of a table, each time it is drawn: a host that can measure makes a wide one scroll. */
53
+ /**
54
+ * The box of a table or the `<pre>` of a code block, each time it is drawn: a host that can
55
+ * measure makes a wide one a Tab stop.
56
+ */
54
57
  table?(box: unknown): void;
55
58
  }
@@ -174,7 +174,10 @@ function createView(options = {}) {
174
174
  },
175
175
  pre: ({ node: _node, ...props }, children) => {
176
176
  const { language, title, text } = readCodeBlock(vnode, children);
177
- return codeFrame(vnode, language, codeBar(vnode, language, title, buttons(text, title, language)), props, children);
177
+ return codeFrame(vnode, language, codeBar(vnode, language, title, buttons(text, title, language)), host?.table ? {
178
+ ...props,
179
+ ref: host.table
180
+ } : props, children);
178
181
  },
179
182
  code: (props, children) => code(vnode, props, children, highlight),
180
183
  ...own
@@ -194,6 +197,7 @@ function createView(options = {}) {
194
197
  components: using,
195
198
  urlTransform,
196
199
  special,
200
+ labels,
197
201
  offset: at,
198
202
  headingIds: ids
199
203
  });
@@ -219,7 +223,7 @@ function createView(options = {}) {
219
223
  return html === null ? text : vnode.element("span", { innerHTML: html }, []);
220
224
  });
221
225
  if (!known && body) pieces.push("\n");
222
- return codeFrame(vnode, language, codeBar(vnode, language, title, buttons(body, title, language)), null, [vnode.element("code", { className: className || void 0 }, pieces)]);
226
+ return codeFrame(vnode, language, codeBar(vnode, language, title, buttons(body, title, language)), host?.table ? { ref: host.table } : null, [vnode.element("code", { className: className || void 0 }, pieces)]);
223
227
  };
224
228
  const block = (open, closer, ids) => {
225
229
  const fence = growing && !ownCode ? growingFence(open, closer, memory) : null;
@@ -1 +1 @@
1
- {"version":3,"file":"view.js","names":["views.downloadButton","views.copyButton","views.tabs","views.fileTree","views.callout","views.table","views.readTabs","views.readCodeBlock","views.codeFrame","views.codeBar","views.code","views.diagramSource"],"sources":["../../src/render/view.ts"],"sourcesContent":["/**\n * A whole document as plain elements (`vnode.ts`): what `<Markdown>` draws with React, for the\n * targets that have no framework. The HTML text of one (`@localix-ai/flowdown/static`) and the page kept\n * up to date from one (`@localix-ai/flowdown/dom`) are both made here, so they hold the same elements.\n */\nimport { parseFileTree, type TreeRow } from '../core/file-tree-parse'\nimport { flowdownDialect } from '../core/remark-answer-blocks'\nimport { DIAGRAM_FENCE_LANGUAGE } from '../dialect'\nimport { createDocument, usesHtml, usesMath, type MarkdownBlock } from '../document'\nimport { createCutter, createFenceMemory, growingFence } from '../document/fence'\nimport { tableRows } from '../document/table'\nimport { parse } from '../engine'\nimport type { Extension, Node } from '../engine/types'\nimport { defaultUrlTransform, type UrlTransform } from '../engine/url'\nimport type { Props } from './factory'\nimport { mergeLabels, type LabelOverrides, type MarkdownLabels } from './labels'\nimport { createRenderer } from './tree'\nimport * as views from './views'\nimport { expand, vnode, type Drawn, type DrawnElement, type VComponent, type VNode } from './vnode'\n\n/** Your own elements for any tag (`a`, `img`, `pre`, `table`, …) or the dialect's. */\nexport type ViewComponents = Record<string, VComponent | undefined>\n\nexport interface ViewOptions {\n components?: ViewComponents\n /** Your own drawing of a ```mermaid fence; `growing` while the fence is still being written. */\n diagram?: (source: string, growing: boolean) => VNode\n /** Your own drawing of a ```tree fence. */\n tree?: (source: string) => VNode\n /** Your own syntax, after the dialect's (`Extension`). */\n extensions?: Extension[]\n /** Give headings ids (`setup`, `setup-1`) to link to. */\n headingIds?: boolean\n /** Raw HTML in the text, parsed and sanitized: `@localix-ai/flowdown/html`. Without it the tags are left out. */\n html?: Extension | null\n /** Typeset `$…$`: `@localix-ai/flowdown/math`. */\n math?: Extension | null\n /** Highlight fenced code: `@localix-ai/flowdown/highlight`, or your own. */\n highlight?: views.Highlighter | null\n /** The words the renderer adds (callout names, button labels), for a page in another language. */\n labels?: LabelOverrides\n /** Rewrite or refuse a link or image URL; by default only safe protocols are kept. */\n urlTransform?: UrlTransform | null\n className?: string\n /** Leaves the `flowdown` class off the root element, so the package's stylesheet does not apply. */\n unstyled?: boolean\n /** The caret after streaming text; on by default while streaming. */\n caret?: boolean\n /**\n * What the ids of a `[!TABS]` form's tabs and panels start with. Two documents on one page need\n * two prefixes; `@localix-ai/flowdown/dom` gives each of its own.\n */\n idPrefix?: string\n /** The page the view is drawn on: with one, its tabs, trees and buttons answer the reader. */\n host?: ViewHost\n}\n\n/**\n * What makes a view alive. A view drawn as HTML text has none, and its tabs, file trees and code\n * buttons are drawn and nothing more; given one, they carry listeners (`onClick`, `onKeyDown`)\n * that change what the view shows and ask the host to draw it again.\n */\nexport interface ViewHost {\n /** Something the reader did changed what the document shows: draw it again, now. */\n redraw(): void\n /** Put `text` on the clipboard; resolves whether it got there. */\n copy(text: string): Promise<boolean>\n /** Save `text` as a file named after its fence's title, or its language. */\n download(text: string, title: string | undefined, language: string | undefined): void\n /** The box of a table, each time it is drawn: a host that can measure makes a wide one scroll. */\n table?(box: unknown): void\n}\n\n/** What a listener is given, as far as the view reads it. */\ninterface KeyPress {\n key: string\n preventDefault(): void\n stopPropagation(): void\n}\n\n/** How long \"Copied\" stays before the button reads \"Copy\" again. */\nconst COPIED_MS = 1_600\n\nconst focusOn = (element: unknown): void =>\n (element as { focus?: () => void } | undefined)?.focus?.()\n\nexport interface View {\n /**\n * The document's root element for `text`; `streaming` while the reply is still arriving. What\n * a block drew is kept: a block that did not change gives the very same elements again, so a\n * target that compares by identity leaves them alone.\n */\n render(text: string, streaming?: boolean): DrawnElement\n /** Stops what a reader's action left running (a \"Copied\" that has yet to go back to \"Copy\"). */\n dispose(): void\n}\n\nconst draw = createRenderer(vnode)\n\nconst NOTHING: VComponent = () => []\nconst CHILDREN: VComponent = (_props, children) => children\n\n/** How many body rows one piece of a table holds: the same pieces `<Markdown>` draws. */\nconst ROWS = 16\n\nconst headingKeyOf = (own: ReadonlyMap<number, string> | null): string =>\n own ? [...own].map(([at, id]) => `${at}:${id}`).join(';') : ''\n\ninterface Kept {\n stages: string\n closer: string\n growing: boolean\n offset: number\n headingKey: string\n nodes: Drawn[]\n}\n\nexport function createView(options: ViewOptions = {}): View {\n const labels: MarkdownLabels = mergeLabels(options.labels)\n const highlight = options.highlight ?? null\n const own = options.components\n // What the document is read with. Math and raw HTML are passes a document without either does\n // not pay for, exactly as `<Markdown>` leaves them out: set for each `render`.\n let extensions: Extension[] = [flowdownDialect]\n let math: Extension | null = null\n const urlTransform = options.urlTransform ?? defaultUrlTransform\n const ownCode = Boolean(own?.pre || own?.code)\n const ownTable = Boolean(\n own?.table || own?.thead || own?.tbody || (options.extensions?.length ?? 0) > 0,\n )\n const prefix = options.idPrefix ?? 'fd-'\n const { diagram: pageDiagram, tree: pageTree } = options\n\n /** Where the block being drawn starts in the document, and whether it is the fence still growing. */\n let offset = 0\n let growing = false\n\n const host = options.host ?? null\n /** Blocks, by where they start, that a reader's action changed: drawn again at the next render. */\n const stale = new Set<number>()\n /** How many live parts the block being drawn has made: with the block's offset, a part's name. */\n let serial = 0\n const tabStates = new Map<string, { active: number; buttons: unknown[] }>()\n const treeStates = new Map<\n string,\n { collapsed: Set<string>; focused: string | null; elements: Map<string, unknown> }\n >()\n /** The code blocks that read \"Copied\", each with the timer that ends it. */\n const copied = new Map<string, ReturnType<typeof setTimeout>>()\n\n const changed = (at: number) => {\n stale.add(at)\n host!.redraw()\n }\n\n const buttons = (\n text: string,\n title: string | undefined,\n language: string | undefined,\n ): VNode[] => {\n if (!host) return [views.downloadButton(vnode, labels), views.copyButton(vnode, labels, false)]\n const at = offset\n const id = `${at}.${serial++}`\n const copy = () => {\n // When the clipboard refuses there is nothing to say: the code is still selectable.\n void host.copy(text).then((done) => {\n if (!done) return\n clearTimeout(copied.get(id))\n copied.set(\n id,\n setTimeout(() => {\n copied.delete(id)\n changed(at)\n }, COPIED_MS),\n )\n changed(at)\n })\n }\n return [\n views.downloadButton(vnode, labels, () => host.download(text, title, language)),\n views.copyButton(vnode, labels, copied.has(id), copy),\n ]\n }\n\n /** `[!TABS]` on a page: the WAI-ARIA tabs pattern, as `<Markdown>` has it. */\n const liveTabs = (list: Array<views.TabView<VNode>>, id: string): VNode => {\n const at = offset\n let state = tabStates.get(id)\n if (!state) tabStates.set(id, (state = { active: 0, buttons: [] }))\n const tab = state\n tab.buttons = []\n // A streamed form can lose a tab between renders only by being re-parsed; never point past it.\n const current = tab.active < list.length ? tab.active : 0\n const select = (index: number, focus: boolean) => {\n const next = (index + list.length) % list.length\n tab.active = next\n changed(at)\n if (focus) focusOn(tab.buttons[next])\n }\n return views.tabs(vnode, list, current, id, labels, (index) => ({\n ref: (element: unknown) => {\n tab.buttons[index] = element\n },\n onClick: () => select(index, false),\n onKeyDown: (event: KeyPress) => {\n const moves: Record<string, number> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: list.length - 1,\n }\n const target = moves[event.key]\n if (target === undefined) return\n event.preventDefault()\n select(target, true)\n },\n }))\n }\n\n /** A ```tree fence on a page: folders fold, and the arrow keys walk the rows (the tree pattern). */\n const liveTree = (rows: TreeRow[]): VNode => {\n const at = offset\n const id = `${at}.${serial++}`\n let state = treeStates.get(id)\n if (!state)\n treeStates.set(id, (state = { collapsed: new Set(), focused: null, elements: new Map() }))\n const tree = state\n tree.elements = new Map()\n const toggle = (path: string) => {\n if (!tree.collapsed.delete(path)) tree.collapsed.add(path)\n changed(at)\n }\n const focus = (row: TreeRow | undefined) => {\n if (row) focusOn(tree.elements.get(row.path))\n }\n return views.fileTree(\n vnode,\n {\n rows,\n collapsed: tree.collapsed,\n focused: tree.focused,\n on: (row, visible) => ({\n ref: (element: unknown) => {\n tree.elements.set(row.path, element)\n },\n onFocus: () => {\n if (tree.focused === row.path) return\n tree.focused = row.path\n changed(at)\n },\n onClick: (event: { currentTarget: unknown }) => {\n // Safari leaves a clicked row unfocused; the keys must carry on from it.\n focusOn(event.currentTarget)\n if (row.folder) toggle(row.path)\n },\n onKeyDown: (event: KeyPress) => {\n const index = visible.indexOf(row)\n const open = row.folder && !tree.collapsed.has(row.path)\n const parent = row.parent === null ? undefined : rows[row.parent]\n const actions: Record<string, () => void> = {\n ArrowDown: () => focus(visible[index + 1]),\n ArrowUp: () => focus(visible[index - 1]),\n Home: () => focus(visible[0]),\n End: () => focus(visible.at(-1)),\n ArrowRight: () => (open ? focus(visible[index + 1]) : row.folder && toggle(row.path)),\n ArrowLeft: () => (open ? toggle(row.path) : focus(parent)),\n Enter: () => row.folder && toggle(row.path),\n ' ': () => row.folder && toggle(row.path),\n }\n const action = actions[event.key]\n if (!action) return\n // Handled here, so not also by the page around the reply; the keys the tree leaves\n // (Escape, Tab) still reach it.\n event.preventDefault()\n event.stopPropagation()\n action()\n },\n }),\n },\n labels,\n )\n }\n\n const components: Record<string, VComponent | undefined> = {\n blockquote: (props, children) => views.callout(vnode, props, children, labels),\n table: (props, children) =>\n views.table(\n vnode,\n props,\n children,\n false,\n labels,\n host?.table ? { ref: host.table } : undefined,\n ),\n 'answer-tabs': (props, children) => {\n const list = views.readTabs(vnode, children)\n if (list.length === 0) return []\n const at = (props.node as Node | undefined)?.position?.start.offset ?? 0\n const id = `${prefix}${offset + at}`\n return host ? liveTabs(list, id) : views.tabs(vnode, list, 0, id, labels)\n },\n pre: ({ node: _node, ...props }, children) => {\n const { language, title, text } = views.readCodeBlock(vnode, children)\n return views.codeFrame(\n vnode,\n language,\n views.codeBar(vnode, language, title, buttons(text, title, language)),\n props,\n children,\n )\n },\n code: (props, children) => views.code(vnode, props, children, highlight),\n ...own,\n }\n\n const special = (language: string, source: string): VNode =>\n language === DIAGRAM_FENCE_LANGUAGE\n ? pageDiagram\n ? pageDiagram(source, growing)\n : views.diagramSource(vnode, source, null, false)\n : pageTree\n ? pageTree(source)\n : treeOf(source)\n\n const treeOf = (source: string): VNode => {\n const rows = parseFileTree(source)\n if (rows.length === 0) return []\n if (host) return liveTree(rows)\n return views.fileTree(vnode, { rows, collapsed: new Set(), focused: null }, labels)\n }\n\n const one = (\n source: string,\n at: number,\n ids: ReadonlyMap<number, string> | null,\n using: Record<string, VComponent | undefined>,\n ): VNode =>\n draw(parse(source, { extensions }), {\n components: using,\n urlTransform,\n special,\n offset: at,\n headingIds: ids,\n })\n\n // A table's own shell taken off: a piece yields its rows alone.\n const headParts = { ...components, table: CHILDREN, tbody: NOTHING }\n const bodyParts = { ...components, table: CHILDREN, thead: NOTHING, tbody: CHILDREN }\n\n const memory = createFenceMemory()\n const cut = createCutter()\n\n /** A code fence still being written, in the pieces it is highlighted by. */\n const growingCode = (language: string | undefined, title: string, body: string): VNode => {\n // The class a finished block gets when its language is one the highlighter knows.\n const known = Boolean(language && highlight && highlight('', language) !== null)\n const className = [language ? `language-${language}` : null, known ? 'fd-highlighted' : null]\n .filter(Boolean)\n .join(' ')\n const pieces: VNode[] = cut(body).parts.map((text) => {\n const html = known ? highlight!(text, language!) : null\n return html === null ? text : vnode.element('span', { innerHTML: html }, [])\n })\n // A finished block's text ends in a newline unless it is highlighted; so does this.\n if (!known && body) pieces.push('\\n')\n return views.codeFrame(\n vnode,\n language,\n views.codeBar(vnode, language, title, buttons(body, title, language)),\n null,\n [vnode.element('code', { className: className || undefined }, pieces)],\n )\n }\n\n const block = (open: string, closer: string, ids: ReadonlyMap<number, string> | null): VNode => {\n const fence = growing && !ownCode ? growingFence(open, closer, memory) : null\n if (\n fence &&\n fence.language !== DIAGRAM_FENCE_LANGUAGE &&\n fence.language !== 'tree' &&\n // A `math` fence is a formula where the page typesets math, and code where it does not.\n !(fence.language === 'math' && math)\n )\n return growingCode(fence.language, fence.title, fence.body)\n const source = closer ? open + closer : open\n // A table that is the whole block is drawn as its header and its rows, as `<Markdown>` does.\n const rows = ownTable ? null : tableRows(source)\n if (rows) {\n const pieces: VNode[] = []\n for (let at = 0; at < rows.rows.length; at += ROWS)\n pieces.push(one(rows.head + rows.rows.slice(at, at + ROWS).join('\\n'), 0, null, bodyParts))\n return vnode.element(components.table!, null, [\n one(rows.head, 0, null, headParts),\n vnode.element('tbody', null, pieces),\n ])\n }\n return one(source, offset, ids, components)\n }\n\n const doc = createDocument()\n /** What each block drew last: a block that did not change is not drawn again. */\n const drawn = new WeakMap<MarkdownBlock, Kept>()\n\n return {\n render(text, streaming = false) {\n const state = doc.update(text, {\n streaming,\n math: Boolean(options.math),\n headingIds: options.headingIds ?? false,\n })\n math = options.math && usesMath(state.blocks) ? options.math : null\n // Whether the document has HTML is decided for the whole of it, not per block: a tag still\n // being typed (`<div`) is its own block, and only the whole document says it is HTML.\n const html = options.html && usesHtml(state.blocks) ? options.html : null\n extensions = [\n flowdownDialect,\n ...(math ? [math] : []),\n ...(options.extensions ?? []),\n // Raw HTML is made into elements last, so every pass before it sees the text as written.\n ...(html ? [html] : []),\n ]\n const stages = `${math ? 'm' : ''}${html ? 'h' : ''}`\n const children: Drawn[] = []\n const last = state.blocks.length - 1\n state.blocks.forEach((item, index) => {\n const closer = index === last ? state.closer : ''\n const grows = index === last && state.growing\n const ids = state.headingIds?.[index] ?? null\n const headingKey = headingKeyOf(ids)\n let made = drawn.get(item)\n if (\n made === undefined ||\n stale.has(item.offset) ||\n made.stages !== stages ||\n made.closer !== closer ||\n made.growing !== grows ||\n made.offset !== item.offset ||\n made.headingKey !== headingKey\n ) {\n offset = item.offset\n growing = grows\n serial = 0\n const nodes = expand(block(item.source, closer, ids && ids.size > 0 ? ids : null))\n made = { stages, closer, growing: grows, offset: item.offset, headingKey, nodes }\n drawn.set(item, made)\n }\n // One parse puts a line break between top-level blocks; so does this.\n if (index > 0) children.push('\\n')\n for (const node of made.nodes) children.push(node)\n })\n stale.clear()\n const caret = options.caret ?? streaming\n const { className, unstyled } = options\n const props: Props = {\n className: unstyled ? className : className ? `flowdown ${className}` : 'flowdown',\n 'data-streaming': streaming || undefined,\n 'data-caret': (streaming && caret) || undefined,\n }\n return { tag: 'div', props, children }\n },\n dispose() {\n for (const timer of copied.values()) clearTimeout(timer)\n copied.clear()\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"view.js","names":["views.downloadButton","views.copyButton","views.tabs","views.fileTree","views.callout","views.table","views.readTabs","views.readCodeBlock","views.codeFrame","views.codeBar","views.code","views.diagramSource"],"sources":["../../src/render/view.ts"],"sourcesContent":["/**\n * A whole document as plain elements (`vnode.ts`): what `<Markdown>` draws with React, for the\n * targets that have no framework. The HTML text of one (`@localix-ai/flowdown/static`) and the page kept\n * up to date from one (`@localix-ai/flowdown/dom`) are both made here, so they hold the same elements.\n */\nimport { parseFileTree, type TreeRow } from '../core/file-tree-parse'\nimport { flowdownDialect } from '../core/remark-answer-blocks'\nimport { DIAGRAM_FENCE_LANGUAGE } from '../dialect'\nimport { createDocument, usesHtml, usesMath, type MarkdownBlock } from '../document'\nimport { createCutter, createFenceMemory, growingFence } from '../document/fence'\nimport { tableRows } from '../document/table'\nimport { parse } from '../engine'\nimport type { Extension, Node } from '../engine/types'\nimport { defaultUrlTransform, type UrlTransform } from '../engine/url'\nimport type { Props } from './factory'\nimport { mergeLabels, type LabelOverrides, type MarkdownLabels } from './labels'\nimport { createRenderer } from './tree'\nimport * as views from './views'\nimport { expand, vnode, type Drawn, type DrawnElement, type VComponent, type VNode } from './vnode'\n\n/** Your own elements for any tag (`a`, `img`, `pre`, `table`, …) or the dialect's. */\nexport type ViewComponents = Record<string, VComponent | undefined>\n\nexport interface ViewOptions {\n components?: ViewComponents\n /** Your own drawing of a ```mermaid fence; `growing` while the fence is still being written. */\n diagram?: (source: string, growing: boolean) => VNode\n /** Your own drawing of a ```tree fence. */\n tree?: (source: string) => VNode\n /** Your own syntax, after the dialect's (`Extension`). */\n extensions?: Extension[]\n /** Give headings ids (`setup`, `setup-1`) to link to. */\n headingIds?: boolean\n /** Raw HTML in the text, parsed and sanitized: `@localix-ai/flowdown/html`. Without it the tags are left out. */\n html?: Extension | null\n /** Typeset `$…$`: `@localix-ai/flowdown/math`. */\n math?: Extension | null\n /** Highlight fenced code: `@localix-ai/flowdown/highlight`, or your own. */\n highlight?: views.Highlighter | null\n /** The words the renderer adds (callout names, button labels), for a page in another language. */\n labels?: LabelOverrides\n /** Rewrite or refuse a link or image URL; by default only safe protocols are kept. */\n urlTransform?: UrlTransform | null\n className?: string\n /** Leaves the `flowdown` class off the root element, so the package's stylesheet does not apply. */\n unstyled?: boolean\n /** The caret after streaming text; on by default while streaming. */\n caret?: boolean\n /**\n * What the ids of a `[!TABS]` form's tabs and panels start with. Two documents on one page need\n * two prefixes; `@localix-ai/flowdown/dom` gives each of its own.\n */\n idPrefix?: string\n /** The page the view is drawn on: with one, its tabs, trees and buttons answer the reader. */\n host?: ViewHost\n}\n\n/**\n * What makes a view alive. A view drawn as HTML text has none, and its tabs, file trees and code\n * buttons are drawn and nothing more; given one, they carry listeners (`onClick`, `onKeyDown`)\n * that change what the view shows and ask the host to draw it again.\n */\nexport interface ViewHost {\n /** Something the reader did changed what the document shows: draw it again, now. */\n redraw(): void\n /** Put `text` on the clipboard; resolves whether it got there. */\n copy(text: string): Promise<boolean>\n /** Save `text` as a file named after its fence's title, or its language. */\n download(text: string, title: string | undefined, language: string | undefined): void\n /**\n * The box of a table or the `<pre>` of a code block, each time it is drawn: a host that can\n * measure makes a wide one a Tab stop.\n */\n table?(box: unknown): void\n}\n\n/** What a listener is given, as far as the view reads it. */\ninterface KeyPress {\n key: string\n preventDefault(): void\n stopPropagation(): void\n}\n\n/** How long \"Copied\" stays before the button reads \"Copy\" again. */\nconst COPIED_MS = 1_600\n\nconst focusOn = (element: unknown): void =>\n (element as { focus?: () => void } | undefined)?.focus?.()\n\nexport interface View {\n /**\n * The document's root element for `text`; `streaming` while the reply is still arriving. What\n * a block drew is kept: a block that did not change gives the very same elements again, so a\n * target that compares by identity leaves them alone.\n */\n render(text: string, streaming?: boolean): DrawnElement\n /** Stops what a reader's action left running (a \"Copied\" that has yet to go back to \"Copy\"). */\n dispose(): void\n}\n\nconst draw = createRenderer(vnode)\n\nconst NOTHING: VComponent = () => []\nconst CHILDREN: VComponent = (_props, children) => children\n\n/** How many body rows one piece of a table holds: the same pieces `<Markdown>` draws. */\nconst ROWS = 16\n\nconst headingKeyOf = (own: ReadonlyMap<number, string> | null): string =>\n own ? [...own].map(([at, id]) => `${at}:${id}`).join(';') : ''\n\ninterface Kept {\n stages: string\n closer: string\n growing: boolean\n offset: number\n headingKey: string\n nodes: Drawn[]\n}\n\nexport function createView(options: ViewOptions = {}): View {\n const labels: MarkdownLabels = mergeLabels(options.labels)\n const highlight = options.highlight ?? null\n const own = options.components\n // What the document is read with. Math and raw HTML are passes a document without either does\n // not pay for, exactly as `<Markdown>` leaves them out: set for each `render`.\n let extensions: Extension[] = [flowdownDialect]\n let math: Extension | null = null\n const urlTransform = options.urlTransform ?? defaultUrlTransform\n const ownCode = Boolean(own?.pre || own?.code)\n const ownTable = Boolean(\n own?.table || own?.thead || own?.tbody || (options.extensions?.length ?? 0) > 0,\n )\n const prefix = options.idPrefix ?? 'fd-'\n const { diagram: pageDiagram, tree: pageTree } = options\n\n /** Where the block being drawn starts in the document, and whether it is the fence still growing. */\n let offset = 0\n let growing = false\n\n const host = options.host ?? null\n /** Blocks, by where they start, that a reader's action changed: drawn again at the next render. */\n const stale = new Set<number>()\n /** How many live parts the block being drawn has made: with the block's offset, a part's name. */\n let serial = 0\n const tabStates = new Map<string, { active: number; buttons: unknown[] }>()\n const treeStates = new Map<\n string,\n { collapsed: Set<string>; focused: string | null; elements: Map<string, unknown> }\n >()\n /** The code blocks that read \"Copied\", each with the timer that ends it. */\n const copied = new Map<string, ReturnType<typeof setTimeout>>()\n\n const changed = (at: number) => {\n stale.add(at)\n host!.redraw()\n }\n\n const buttons = (\n text: string,\n title: string | undefined,\n language: string | undefined,\n ): VNode[] => {\n if (!host) return [views.downloadButton(vnode, labels), views.copyButton(vnode, labels, false)]\n const at = offset\n const id = `${at}.${serial++}`\n const copy = () => {\n // When the clipboard refuses there is nothing to say: the code is still selectable.\n void host.copy(text).then((done) => {\n if (!done) return\n clearTimeout(copied.get(id))\n copied.set(\n id,\n setTimeout(() => {\n copied.delete(id)\n changed(at)\n }, COPIED_MS),\n )\n changed(at)\n })\n }\n return [\n views.downloadButton(vnode, labels, () => host.download(text, title, language)),\n views.copyButton(vnode, labels, copied.has(id), copy),\n ]\n }\n\n /** `[!TABS]` on a page: the WAI-ARIA tabs pattern, as `<Markdown>` has it. */\n const liveTabs = (list: Array<views.TabView<VNode>>, id: string): VNode => {\n const at = offset\n let state = tabStates.get(id)\n if (!state) tabStates.set(id, (state = { active: 0, buttons: [] }))\n const tab = state\n tab.buttons = []\n // A streamed form can lose a tab between renders only by being re-parsed; never point past it.\n const current = tab.active < list.length ? tab.active : 0\n const select = (index: number, focus: boolean) => {\n const next = (index + list.length) % list.length\n tab.active = next\n changed(at)\n if (focus) focusOn(tab.buttons[next])\n }\n return views.tabs(vnode, list, current, id, labels, (index) => ({\n ref: (element: unknown) => {\n tab.buttons[index] = element\n },\n onClick: () => select(index, false),\n onKeyDown: (event: KeyPress) => {\n const moves: Record<string, number> = {\n ArrowRight: index + 1,\n ArrowLeft: index - 1,\n Home: 0,\n End: list.length - 1,\n }\n const target = moves[event.key]\n if (target === undefined) return\n event.preventDefault()\n select(target, true)\n },\n }))\n }\n\n /** A ```tree fence on a page: folders fold, and the arrow keys walk the rows (the tree pattern). */\n const liveTree = (rows: TreeRow[]): VNode => {\n const at = offset\n const id = `${at}.${serial++}`\n let state = treeStates.get(id)\n if (!state)\n treeStates.set(id, (state = { collapsed: new Set(), focused: null, elements: new Map() }))\n const tree = state\n tree.elements = new Map()\n const toggle = (path: string) => {\n if (!tree.collapsed.delete(path)) tree.collapsed.add(path)\n changed(at)\n }\n const focus = (row: TreeRow | undefined) => {\n if (row) focusOn(tree.elements.get(row.path))\n }\n return views.fileTree(\n vnode,\n {\n rows,\n collapsed: tree.collapsed,\n focused: tree.focused,\n on: (row, visible) => ({\n ref: (element: unknown) => {\n tree.elements.set(row.path, element)\n },\n onFocus: () => {\n if (tree.focused === row.path) return\n tree.focused = row.path\n changed(at)\n },\n onClick: (event: { currentTarget: unknown }) => {\n // Safari leaves a clicked row unfocused; the keys must carry on from it.\n focusOn(event.currentTarget)\n if (row.folder) toggle(row.path)\n },\n onKeyDown: (event: KeyPress) => {\n const index = visible.indexOf(row)\n const open = row.folder && !tree.collapsed.has(row.path)\n const parent = row.parent === null ? undefined : rows[row.parent]\n const actions: Record<string, () => void> = {\n ArrowDown: () => focus(visible[index + 1]),\n ArrowUp: () => focus(visible[index - 1]),\n Home: () => focus(visible[0]),\n End: () => focus(visible.at(-1)),\n ArrowRight: () => (open ? focus(visible[index + 1]) : row.folder && toggle(row.path)),\n ArrowLeft: () => (open ? toggle(row.path) : focus(parent)),\n Enter: () => row.folder && toggle(row.path),\n ' ': () => row.folder && toggle(row.path),\n }\n const action = actions[event.key]\n if (!action) return\n // Handled here, so not also by the page around the reply; the keys the tree leaves\n // (Escape, Tab) still reach it.\n event.preventDefault()\n event.stopPropagation()\n action()\n },\n }),\n },\n labels,\n )\n }\n\n const components: Record<string, VComponent | undefined> = {\n blockquote: (props, children) => views.callout(vnode, props, children, labels),\n table: (props, children) =>\n views.table(\n vnode,\n props,\n children,\n false,\n labels,\n host?.table ? { ref: host.table } : undefined,\n ),\n 'answer-tabs': (props, children) => {\n const list = views.readTabs(vnode, children)\n if (list.length === 0) return []\n const at = (props.node as Node | undefined)?.position?.start.offset ?? 0\n const id = `${prefix}${offset + at}`\n return host ? liveTabs(list, id) : views.tabs(vnode, list, 0, id, labels)\n },\n pre: ({ node: _node, ...props }, children) => {\n const { language, title, text } = views.readCodeBlock(vnode, children)\n return views.codeFrame(\n vnode,\n language,\n views.codeBar(vnode, language, title, buttons(text, title, language)),\n host?.table ? { ...props, ref: host.table } : props,\n children,\n )\n },\n code: (props, children) => views.code(vnode, props, children, highlight),\n ...own,\n }\n\n const special = (language: string, source: string): VNode =>\n language === DIAGRAM_FENCE_LANGUAGE\n ? pageDiagram\n ? pageDiagram(source, growing)\n : views.diagramSource(vnode, source, null, false)\n : pageTree\n ? pageTree(source)\n : treeOf(source)\n\n const treeOf = (source: string): VNode => {\n const rows = parseFileTree(source)\n if (rows.length === 0) return []\n if (host) return liveTree(rows)\n return views.fileTree(vnode, { rows, collapsed: new Set(), focused: null }, labels)\n }\n\n const one = (\n source: string,\n at: number,\n ids: ReadonlyMap<number, string> | null,\n using: Record<string, VComponent | undefined>,\n ): VNode =>\n draw(parse(source, { extensions }), {\n components: using,\n urlTransform,\n special,\n labels,\n offset: at,\n headingIds: ids,\n })\n\n // A table's own shell taken off: a piece yields its rows alone.\n const headParts = { ...components, table: CHILDREN, tbody: NOTHING }\n const bodyParts = { ...components, table: CHILDREN, thead: NOTHING, tbody: CHILDREN }\n\n const memory = createFenceMemory()\n const cut = createCutter()\n\n /** A code fence still being written, in the pieces it is highlighted by. */\n const growingCode = (language: string | undefined, title: string, body: string): VNode => {\n // The class a finished block gets when its language is one the highlighter knows.\n const known = Boolean(language && highlight && highlight('', language) !== null)\n const className = [language ? `language-${language}` : null, known ? 'fd-highlighted' : null]\n .filter(Boolean)\n .join(' ')\n const pieces: VNode[] = cut(body).parts.map((text) => {\n const html = known ? highlight!(text, language!) : null\n return html === null ? text : vnode.element('span', { innerHTML: html }, [])\n })\n // A finished block's text ends in a newline unless it is highlighted; so does this.\n if (!known && body) pieces.push('\\n')\n return views.codeFrame(\n vnode,\n language,\n views.codeBar(vnode, language, title, buttons(body, title, language)),\n host?.table ? { ref: host.table } : null,\n [vnode.element('code', { className: className || undefined }, pieces)],\n )\n }\n\n const block = (open: string, closer: string, ids: ReadonlyMap<number, string> | null): VNode => {\n const fence = growing && !ownCode ? growingFence(open, closer, memory) : null\n if (\n fence &&\n fence.language !== DIAGRAM_FENCE_LANGUAGE &&\n fence.language !== 'tree' &&\n // A `math` fence is a formula where the page typesets math, and code where it does not.\n !(fence.language === 'math' && math)\n )\n return growingCode(fence.language, fence.title, fence.body)\n const source = closer ? open + closer : open\n // A table that is the whole block is drawn as its header and its rows, as `<Markdown>` does.\n const rows = ownTable ? null : tableRows(source)\n if (rows) {\n const pieces: VNode[] = []\n for (let at = 0; at < rows.rows.length; at += ROWS)\n pieces.push(one(rows.head + rows.rows.slice(at, at + ROWS).join('\\n'), 0, null, bodyParts))\n return vnode.element(components.table!, null, [\n one(rows.head, 0, null, headParts),\n vnode.element('tbody', null, pieces),\n ])\n }\n return one(source, offset, ids, components)\n }\n\n const doc = createDocument()\n /** What each block drew last: a block that did not change is not drawn again. */\n const drawn = new WeakMap<MarkdownBlock, Kept>()\n\n return {\n render(text, streaming = false) {\n const state = doc.update(text, {\n streaming,\n math: Boolean(options.math),\n headingIds: options.headingIds ?? false,\n })\n math = options.math && usesMath(state.blocks) ? options.math : null\n // Whether the document has HTML is decided for the whole of it, not per block: a tag still\n // being typed (`<div`) is its own block, and only the whole document says it is HTML.\n const html = options.html && usesHtml(state.blocks) ? options.html : null\n extensions = [\n flowdownDialect,\n ...(math ? [math] : []),\n ...(options.extensions ?? []),\n // Raw HTML is made into elements last, so every pass before it sees the text as written.\n ...(html ? [html] : []),\n ]\n const stages = `${math ? 'm' : ''}${html ? 'h' : ''}`\n const children: Drawn[] = []\n const last = state.blocks.length - 1\n state.blocks.forEach((item, index) => {\n const closer = index === last ? state.closer : ''\n const grows = index === last && state.growing\n const ids = state.headingIds?.[index] ?? null\n const headingKey = headingKeyOf(ids)\n let made = drawn.get(item)\n if (\n made === undefined ||\n stale.has(item.offset) ||\n made.stages !== stages ||\n made.closer !== closer ||\n made.growing !== grows ||\n made.offset !== item.offset ||\n made.headingKey !== headingKey\n ) {\n offset = item.offset\n growing = grows\n serial = 0\n const nodes = expand(block(item.source, closer, ids && ids.size > 0 ? ids : null))\n made = { stages, closer, growing: grows, offset: item.offset, headingKey, nodes }\n drawn.set(item, made)\n }\n // One parse puts a line break between top-level blocks; so does this.\n if (index > 0) children.push('\\n')\n for (const node of made.nodes) children.push(node)\n })\n stale.clear()\n const caret = options.caret ?? streaming\n const { className, unstyled } = options\n const props: Props = {\n className: unstyled ? className : className ? `flowdown ${className}` : 'flowdown',\n 'data-streaming': streaming || undefined,\n 'data-caret': (streaming && caret) || undefined,\n }\n return { tag: 'div', props, children }\n },\n dispose() {\n for (const timer of copied.values()) clearTimeout(timer)\n copied.clear()\n },\n }\n}\n"],"mappings":"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"}
@@ -185,13 +185,13 @@ function tabs(f, list, current, base, labels, on) {
185
185
  }
186
186
  /**
187
187
  * A table in a box of its own that scrolls sideways: a wide comparison then scrolls inside the
188
- * reply instead of widening the page. A box that scrolls is a region the Tab key stops on, so the
188
+ * reply instead of widening the page. A box that scrolls is a named group the Tab key stops on, so the
189
189
  * arrow keys can scroll it (WCAG 2.1.1); a table that fits stays out of the Tab order.
190
190
  */
191
191
  const table = (f, { node: _node, ...props }, children, scrolls, labels, box) => f.element("div", {
192
192
  ...box,
193
193
  className: "fd-table",
194
- role: scrolls ? "region" : void 0,
194
+ role: scrolls ? "group" : void 0,
195
195
  "aria-label": scrolls ? labels.scrollableTable : void 0,
196
196
  tabIndex: scrolls ? 0 : void 0
197
197
  }, [f.element("table", props, children)]);
@@ -1 +1 @@
1
- {"version":3,"file":"views.js","names":[],"sources":["../../src/render/views.ts"],"sourcesContent":["/**\n * What the dialect's own elements are made of, written once for every target that has elements.\n *\n * A view is a function of a {@link Factory} and of what the element shows right now: which tab is\n * open, whether the code was just copied. It keeps no state and listens to nothing: a target that\n * can (React, the DOM) holds the state and hands in the listeners, and one that cannot (a server)\n * draws the element as it first appears.\n */\nimport type { TreeRow } from '../core/file-tree-parse'\nimport { CALLOUT_KINDS, type CalloutKind } from '../dialect'\nimport type { Factory, Props } from './factory'\nimport type { MarkdownLabels } from './labels'\n\n/**\n * The few glyphs the renderer draws, inline. A dependency on an icon library for eleven outlines\n * would cost every consumer an install, and a theme cannot restyle an icon font as simply as an\n * SVG that strokes with `currentColor`.\n *\n * Each glyph is one `<path>`: its strokes are subpaths of the same outline, and its circle or\n * rounded square is written out as arcs. A reply with a dozen code blocks and callouts draws\n * dozens of icons, and every stroke that is its own element is one more node on the page.\n *\n * Outlines from Lucide (https://lucide.dev), ISC License, Copyright (c) Lucide Contributors.\n */\nexport const OUTLINES = {\n info: 'M2 12a10 10 0 1 0 20 0 10 10 0 1 0-20 0zM12 16v-4M12 8h.01',\n tip: 'M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5M9 18h6M10 22h4',\n important: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2zM12 7v2M12 13h.01',\n warning:\n 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4M12 17h.01',\n caution:\n 'M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586zM12 8v4M12 16h.01',\n chevron: 'm9 18 6-6-6-6',\n file: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM14 2v4a2 2 0 0 0 2 2h4',\n folder:\n 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z',\n folderOpen:\n 'm6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2',\n copy: 'M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2zM4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2',\n check: 'M20 6 9 17l-5-5',\n download: 'M12 15V3M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5',\n} as const\n\n/** One glyph. `props` go on its `<svg>` after its own. */\nexport const glyph = <E, C>(f: Factory<E, C>, outline: string, props?: Props | null): E =>\n f.element(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.75,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n focusable: 'false',\n ...props,\n },\n [f.element('path', { d: outline }, [])],\n )\n\n/** The language a `<code>`'s class names: `language-ts` is `ts`. */\nexport function getLanguage(className?: string): string | undefined {\n return /language-([^\\s]+)/.exec(className ?? '')?.[1]\n}\n\n/** The text of some children, whatever elements it is in. */\nexport function textOf<E, C>(f: Factory<E, C>, children: E[]): string {\n let out = ''\n for (const child of f.read(children))\n out += typeof child === 'string' ? child : textOf(f, child.children)\n return out\n}\n\n// A Record over the dialect's kinds, so a kind added there fails the type check here.\nconst CALLOUT_OUTLINES: Record<CalloutKind, string> = {\n note: OUTLINES.info,\n tip: OUTLINES.tip,\n important: OUTLINES.important,\n warning: OUTLINES.warning,\n caution: OUTLINES.caution,\n}\n\nfunction calloutKind(value: unknown): CalloutKind | null {\n return typeof value === 'string' && (CALLOUT_KINDS as readonly string[]).includes(value)\n ? (value as CalloutKind)\n : null\n}\n\n/**\n * A quote, or, when the dialect marked it, a typed callout (`> [!WARNING]`). An `aside` with\n * `role=\"note\"` rather than a styled quote: a warning is not a quotation, and assistive tech should\n * announce it as the aside it is, its kind first.\n *\n * The kind is carried by the glyph alone, in its colour. Its name is spoken, not printed, and a\n * title is printed only when the author wrote one (`> [!TIP] Faster builds`).\n */\nexport function callout<E, C>(\n f: Factory<E, C>,\n {\n node: _node,\n 'data-callout': marked,\n 'data-callout-title': title,\n className,\n cite,\n ...quoteProps\n }: Props,\n children: E[],\n labels: MarkdownLabels,\n): E {\n const kind = calloutKind(marked)\n if (!kind) return f.element('blockquote', { className, cite, ...quoteProps }, children)\n return f.element(\n 'aside',\n {\n ...quoteProps,\n className: className ? `fd-callout ${className as string}` : 'fd-callout',\n 'data-callout': kind,\n role: 'note',\n 'aria-label': title || labels.callouts[kind],\n },\n [\n glyph(f, CALLOUT_OUTLINES[kind], { className: 'fd-callout-icon' }),\n f.element('div', { className: 'fd-callout-body' }, [\n ...(title\n ? [f.element('div', { className: 'fd-callout-title' }, [f.text(title as string)])]\n : []),\n ...children,\n ]),\n ],\n )\n}\n\n/**\n * Turns a fence's code into highlighted HTML, or returns null to leave it plain. The HTML goes into\n * the page as is, so a highlighter must escape the code it was given (highlight.js and Shiki do);\n * `@localix-ai/flowdown/highlight` is one.\n */\nexport type Highlighter = (code: string, language: string) => string | null\n\n/**\n * Code, inline or in a fence. A fenced block is highlighted when the page gave a highlighter and\n * the fence names a language it knows; otherwise its text is printed as is.\n */\nexport function code<E, C>(\n f: Factory<E, C>,\n { node: _node, className, ...props }: Props,\n children: E[],\n highlight: Highlighter | null,\n): E {\n const language = getLanguage(className as string | undefined)\n const html =\n language && highlight ? highlight(textOf(f, children).replace(/\\n$/, ''), language) : null\n if (html === null) return f.element('code', { className, ...props }, children)\n return f.element(\n 'code',\n {\n className: [className, 'fd-highlighted'].filter(Boolean).join(' '),\n ...props,\n innerHTML: html,\n },\n [],\n )\n}\n\n/** What a code block's bar and buttons need of a `<pre>`'s children: its one `<code>`. */\nexport function readCodeBlock<E, C>(\n f: Factory<E, C>,\n children: E[],\n): { language: string | undefined; title: string | undefined; text: string } {\n const items = f.read(children)\n const only = items.length === 1 && typeof items[0] !== 'string' ? items[0]! : null\n return {\n language: getLanguage(only?.props.className as string | undefined),\n title: only?.props['data-title'] as string | undefined,\n text: only ? textOf(f, only.children).replace(/\\n$/, '') : '',\n }\n}\n\n/** The copy button of a code block, before and after a copy. */\nexport const copyButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n copied: boolean,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-copy',\n onClick,\n 'aria-label': copied ? labels.copied : labels.copy,\n title: copied ? labels.copied : labels.copy,\n 'data-copied': copied || undefined,\n },\n [glyph(f, copied ? OUTLINES.check : OUTLINES.copy)],\n )\n\n/** Saves the code as a file: named as its fence's title, or `code.<ext>` from its language. */\nexport const downloadButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-download',\n onClick,\n 'aria-label': labels.download,\n title: labels.download,\n },\n [glyph(f, OUTLINES.download)],\n )\n\n/** The bar of a code block: the file its fence names, its language, and the two buttons. */\nexport const codeBar = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n title: string | undefined,\n buttons: E[],\n): E =>\n f.element('figcaption', { className: 'fd-code-bar' }, [\n ...(title ? [f.element('span', { className: 'fd-code-title' }, [f.text(title)])] : []),\n ...(language ? [f.element('span', { className: 'fd-code-language' }, [f.text(language)])] : []),\n ...buttons,\n ])\n\n/**\n * The frame of a code block: the bar, then the `<pre>`. A fence that names its file\n * (` ```ts title=\"src/app.ts\" `) shows the file first, the language after it; a fence with no\n * language shows none.\n */\nexport const codeFrame = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n bar: E,\n preProps: Props | null | undefined,\n children: E[],\n): E =>\n f.element('figure', { className: 'fd-code', 'data-language': language }, [\n bar,\n f.element('pre', { ...preProps }, children),\n ])\n\n/** One variant of a `[!TABS]` form: its label and what it shows. */\nexport interface TabView<E> {\n label: string | undefined\n children: E[]\n}\n\n/** The variants among some children: every element, whatever is between them. */\nexport function readTabs<E, C>(f: Factory<E, C>, children: E[]): Array<TabView<E>> {\n const list: Array<TabView<E>> = []\n for (const child of f.read(children))\n if (typeof child !== 'string')\n list.push({\n label: child.props['data-label'] as string | undefined,\n children: child.children,\n })\n return list\n}\n\n/**\n * `[!TABS]`: the variants of one answer (an install per OS, an example per language), of which\n * the reader needs one. Every panel stays in the document, `hidden`, so a search on the page, a\n * copy of the whole reply and a screen reader's panel navigation still reach the others.\n *\n * The WAI-ARIA tabs pattern: one Tab stop for the row, each tab names the panel it controls.\n * `on` gives a tab its listeners and its handle (`onClick`, `onKeyDown`, `ref`).\n */\nexport function tabs<E, C>(\n f: Factory<E, C>,\n list: Array<TabView<E>>,\n current: number,\n base: string,\n labels: MarkdownLabels,\n on?: (index: number) => Props,\n): E {\n const tabId = (index: number) => `${base}-tab-${index}`\n const panelId = (index: number) => `${base}-panel-${index}`\n return f.element('div', { className: 'fd-tabs' }, [\n f.element(\n 'div',\n { className: 'fd-tab-list', role: 'tablist', 'aria-label': labels.variants },\n list.map((tab, index) =>\n f.element(\n 'button',\n {\n key: index,\n type: 'button',\n role: 'tab',\n id: tabId(index),\n className: 'fd-tab',\n 'aria-selected': index === current,\n 'aria-controls': panelId(index),\n tabIndex: index === current ? 0 : -1,\n ...on?.(index),\n },\n [f.text(tab.label || String(index + 1))],\n ),\n ),\n ),\n ...list.map((tab, index) =>\n f.element(\n 'div',\n {\n key: index,\n role: 'tabpanel',\n id: panelId(index),\n 'aria-labelledby': tabId(index),\n className: 'fd-tab-panel',\n hidden: index !== current,\n },\n tab.children,\n ),\n ),\n ])\n}\n\n/**\n * A table in a box of its own that scrolls sideways: a wide comparison then scrolls inside the\n * reply instead of widening the page. A box that scrolls is a region the Tab key stops on, so the\n * arrow keys can scroll it (WCAG 2.1.1); a table that fits stays out of the Tab order.\n */\nexport const table = <E, C>(\n f: Factory<E, C>,\n { node: _node, ...props }: Props,\n children: E[],\n scrolls: boolean,\n labels: MarkdownLabels,\n box?: Props,\n): E =>\n f.element(\n 'div',\n {\n ...box,\n className: 'fd-table',\n role: scrolls ? 'region' : undefined,\n 'aria-label': scrolls ? labels.scrollableTable : undefined,\n tabIndex: scrolls ? 0 : undefined,\n },\n [f.element('table', props, children)],\n )\n\n/** What a ```tree fence shows right now: which folders are shut, which row the Tab key stops on. */\nexport interface TreeState<E> {\n rows: TreeRow[]\n collapsed: ReadonlySet<string>\n /** The path of the one row in the Tab order; the first row shown when null or hidden. */\n focused: string | null\n /** The glyph before a row, for a page that colours files by type. */\n icon?: (row: TreeRow, open: boolean) => E\n /** A row's listeners and its handle. */\n on?: (row: TreeRow, visible: TreeRow[]) => Props\n}\n\n/** The rows of a tree that no shut folder hides. */\nexport function visibleRows(rows: TreeRow[], collapsed: ReadonlySet<string>): TreeRow[] {\n const hidden = (row: TreeRow): boolean => {\n for (let parent = row.parent; parent !== null; parent = rows[parent]!.parent)\n if (collapsed.has(rows[parent]!.path)) return true\n return false\n }\n return rows.filter((row) => !hidden(row))\n}\n\n/**\n * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. The\n * tree is one Tab stop walked with the arrow keys (the WAI-ARIA tree pattern).\n */\nexport function fileTree<E, C>(\n f: Factory<E, C>,\n { rows, collapsed, focused, icon, on }: TreeState<E>,\n labels: MarkdownLabels,\n): E {\n const visible = visibleRows(rows, collapsed)\n // One Tab stop: the row last focused, or the first while it is still shown.\n const rover = visible.some((row) => row.path === focused) ? focused : visible[0]?.path\n return f.element(\n 'div',\n { role: 'tree', 'aria-label': labels.fileTree, className: 'fd-tree' },\n visible.map((row) => {\n const open = !collapsed.has(row.path)\n return f.element(\n 'div',\n {\n key: row.path,\n role: 'treeitem',\n 'aria-level': row.depth + 1,\n 'aria-selected': false,\n ...(row.folder ? { 'aria-expanded': open } : {}),\n tabIndex: row.path === rover ? 0 : -1,\n className: 'fd-tree-row',\n 'data-folder': row.folder || undefined,\n style: { '--fd-tree-depth': row.depth },\n ...on?.(row, visible),\n },\n [\n ...(row.folder\n ? [\n glyph(f, OUTLINES.chevron, {\n className: 'fd-tree-chevron',\n 'data-open': open || undefined,\n }),\n ]\n : []),\n icon\n ? icon(row, open)\n : glyph(\n f,\n row.folder ? (open ? OUTLINES.folderOpen : OUTLINES.folder) : OUTLINES.file,\n { className: 'fd-tree-icon' },\n ),\n f.element('span', { className: 'fd-tree-name', title: row.name }, [\n f.text(row.name.replace(/\\/$/, '')),\n ]),\n ...(row.comment\n ? [\n f.element('span', { className: 'fd-tree-comment', title: row.comment }, [\n f.text(row.comment),\n ]),\n ]\n : []),\n ],\n )\n }),\n )\n}\n\n/**\n * A ```mermaid fence shown as the source it is: before it is drawn, where nothing draws it, or\n * when it could not be drawn, with a line saying why.\n */\nexport const diagramSource = <E, C>(\n f: Factory<E, C>,\n source: string,\n note: string | null,\n failed: boolean,\n figure?: Props,\n): E =>\n f.element(\n 'figure',\n { ...figure, className: 'fd-diagram', 'data-state': failed ? 'error' : 'source' },\n [\n f.element('pre', null, [\n f.element('code', { className: 'language-mermaid' }, [f.text(source)]),\n ]),\n ...(note\n ? [\n f.element('figcaption', { className: 'fd-diagram-note', role: 'status' }, [\n f.text(note),\n ]),\n ]\n : []),\n ],\n )\n"],"mappings":"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"}
1
+ {"version":3,"file":"views.js","names":[],"sources":["../../src/render/views.ts"],"sourcesContent":["/**\n * What the dialect's own elements are made of, written once for every target that has elements.\n *\n * A view is a function of a {@link Factory} and of what the element shows right now: which tab is\n * open, whether the code was just copied. It keeps no state and listens to nothing: a target that\n * can (React, the DOM) holds the state and hands in the listeners, and one that cannot (a server)\n * draws the element as it first appears.\n */\nimport type { TreeRow } from '../core/file-tree-parse'\nimport { CALLOUT_KINDS, type CalloutKind } from '../dialect'\nimport type { Factory, Props } from './factory'\nimport type { MarkdownLabels } from './labels'\n\n/**\n * The few glyphs the renderer draws, inline. A dependency on an icon library for eleven outlines\n * would cost every consumer an install, and a theme cannot restyle an icon font as simply as an\n * SVG that strokes with `currentColor`.\n *\n * Each glyph is one `<path>`: its strokes are subpaths of the same outline, and its circle or\n * rounded square is written out as arcs. A reply with a dozen code blocks and callouts draws\n * dozens of icons, and every stroke that is its own element is one more node on the page.\n *\n * Outlines from Lucide (https://lucide.dev), ISC License, Copyright (c) Lucide Contributors.\n */\nexport const OUTLINES = {\n info: 'M2 12a10 10 0 1 0 20 0 10 10 0 1 0-20 0zM12 16v-4M12 8h.01',\n tip: 'M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5M9 18h6M10 22h4',\n important: 'M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2zM12 7v2M12 13h.01',\n warning:\n 'm21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3M12 9v4M12 17h.01',\n caution:\n 'M2.586 16.726A2 2 0 0 1 2 15.312V8.688a2 2 0 0 1 .586-1.414l4.688-4.688A2 2 0 0 1 8.688 2h6.624a2 2 0 0 1 1.414.586l4.688 4.688A2 2 0 0 1 22 8.688v6.624a2 2 0 0 1-.586 1.414l-4.688 4.688a2 2 0 0 1-1.414.586H8.688a2 2 0 0 1-1.414-.586zM12 8v4M12 16h.01',\n chevron: 'm9 18 6-6-6-6',\n file: 'M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM14 2v4a2 2 0 0 0 2 2h4',\n folder:\n 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z',\n folderOpen:\n 'm6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2',\n copy: 'M10 8h10a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H10a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2zM4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2',\n check: 'M20 6 9 17l-5-5',\n download: 'M12 15V3M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5',\n} as const\n\n/** One glyph. `props` go on its `<svg>` after its own. */\nexport const glyph = <E, C>(f: Factory<E, C>, outline: string, props?: Props | null): E =>\n f.element(\n 'svg',\n {\n xmlns: 'http://www.w3.org/2000/svg',\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.75,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n 'aria-hidden': true,\n focusable: 'false',\n ...props,\n },\n [f.element('path', { d: outline }, [])],\n )\n\n/** The language a `<code>`'s class names: `language-ts` is `ts`. */\nexport function getLanguage(className?: string): string | undefined {\n return /language-([^\\s]+)/.exec(className ?? '')?.[1]\n}\n\n/** The text of some children, whatever elements it is in. */\nexport function textOf<E, C>(f: Factory<E, C>, children: E[]): string {\n let out = ''\n for (const child of f.read(children))\n out += typeof child === 'string' ? child : textOf(f, child.children)\n return out\n}\n\n// A Record over the dialect's kinds, so a kind added there fails the type check here.\nconst CALLOUT_OUTLINES: Record<CalloutKind, string> = {\n note: OUTLINES.info,\n tip: OUTLINES.tip,\n important: OUTLINES.important,\n warning: OUTLINES.warning,\n caution: OUTLINES.caution,\n}\n\nfunction calloutKind(value: unknown): CalloutKind | null {\n return typeof value === 'string' && (CALLOUT_KINDS as readonly string[]).includes(value)\n ? (value as CalloutKind)\n : null\n}\n\n/**\n * A quote, or, when the dialect marked it, a typed callout (`> [!WARNING]`). An `aside` with\n * `role=\"note\"` rather than a styled quote: a warning is not a quotation, and assistive tech should\n * announce it as the aside it is, its kind first.\n *\n * The kind is carried by the glyph alone, in its colour. Its name is spoken, not printed, and a\n * title is printed only when the author wrote one (`> [!TIP] Faster builds`).\n */\nexport function callout<E, C>(\n f: Factory<E, C>,\n {\n node: _node,\n 'data-callout': marked,\n 'data-callout-title': title,\n className,\n cite,\n ...quoteProps\n }: Props,\n children: E[],\n labels: MarkdownLabels,\n): E {\n const kind = calloutKind(marked)\n if (!kind) return f.element('blockquote', { className, cite, ...quoteProps }, children)\n return f.element(\n 'aside',\n {\n ...quoteProps,\n className: className ? `fd-callout ${className as string}` : 'fd-callout',\n 'data-callout': kind,\n role: 'note',\n 'aria-label': title || labels.callouts[kind],\n },\n [\n glyph(f, CALLOUT_OUTLINES[kind], { className: 'fd-callout-icon' }),\n f.element('div', { className: 'fd-callout-body' }, [\n ...(title\n ? [f.element('div', { className: 'fd-callout-title' }, [f.text(title as string)])]\n : []),\n ...children,\n ]),\n ],\n )\n}\n\n/**\n * Turns a fence's code into highlighted HTML, or returns null to leave it plain. The HTML goes into\n * the page as is, so a highlighter must escape the code it was given (highlight.js and Shiki do);\n * `@localix-ai/flowdown/highlight` is one.\n */\nexport type Highlighter = (code: string, language: string) => string | null\n\n/**\n * Code, inline or in a fence. A fenced block is highlighted when the page gave a highlighter and\n * the fence names a language it knows; otherwise its text is printed as is.\n */\nexport function code<E, C>(\n f: Factory<E, C>,\n { node: _node, className, ...props }: Props,\n children: E[],\n highlight: Highlighter | null,\n): E {\n const language = getLanguage(className as string | undefined)\n const html =\n language && highlight ? highlight(textOf(f, children).replace(/\\n$/, ''), language) : null\n if (html === null) return f.element('code', { className, ...props }, children)\n return f.element(\n 'code',\n {\n className: [className, 'fd-highlighted'].filter(Boolean).join(' '),\n ...props,\n innerHTML: html,\n },\n [],\n )\n}\n\n/** What a code block's bar and buttons need of a `<pre>`'s children: its one `<code>`. */\nexport function readCodeBlock<E, C>(\n f: Factory<E, C>,\n children: E[],\n): { language: string | undefined; title: string | undefined; text: string } {\n const items = f.read(children)\n const only = items.length === 1 && typeof items[0] !== 'string' ? items[0]! : null\n return {\n language: getLanguage(only?.props.className as string | undefined),\n title: only?.props['data-title'] as string | undefined,\n text: only ? textOf(f, only.children).replace(/\\n$/, '') : '',\n }\n}\n\n/** The copy button of a code block, before and after a copy. */\nexport const copyButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n copied: boolean,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-copy',\n onClick,\n 'aria-label': copied ? labels.copied : labels.copy,\n title: copied ? labels.copied : labels.copy,\n 'data-copied': copied || undefined,\n },\n [glyph(f, copied ? OUTLINES.check : OUTLINES.copy)],\n )\n\n/** Saves the code as a file: named as its fence's title, or `code.<ext>` from its language. */\nexport const downloadButton = <E, C>(\n f: Factory<E, C>,\n labels: MarkdownLabels,\n onClick?: () => void,\n): E =>\n f.element(\n 'button',\n {\n type: 'button',\n className: 'fd-code-download',\n onClick,\n 'aria-label': labels.download,\n title: labels.download,\n },\n [glyph(f, OUTLINES.download)],\n )\n\n/** The bar of a code block: the file its fence names, its language, and the two buttons. */\nexport const codeBar = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n title: string | undefined,\n buttons: E[],\n): E =>\n f.element('figcaption', { className: 'fd-code-bar' }, [\n ...(title ? [f.element('span', { className: 'fd-code-title' }, [f.text(title)])] : []),\n ...(language ? [f.element('span', { className: 'fd-code-language' }, [f.text(language)])] : []),\n ...buttons,\n ])\n\n/**\n * The frame of a code block: the bar, then the `<pre>`. A fence that names its file\n * (` ```ts title=\"src/app.ts\" `) shows the file first, the language after it; a fence with no\n * language shows none.\n */\nexport const codeFrame = <E, C>(\n f: Factory<E, C>,\n language: string | undefined,\n bar: E,\n preProps: Props | null | undefined,\n children: E[],\n): E =>\n f.element('figure', { className: 'fd-code', 'data-language': language }, [\n bar,\n f.element('pre', { ...preProps }, children),\n ])\n\n/** One variant of a `[!TABS]` form: its label and what it shows. */\nexport interface TabView<E> {\n label: string | undefined\n children: E[]\n}\n\n/** The variants among some children: every element, whatever is between them. */\nexport function readTabs<E, C>(f: Factory<E, C>, children: E[]): Array<TabView<E>> {\n const list: Array<TabView<E>> = []\n for (const child of f.read(children))\n if (typeof child !== 'string')\n list.push({\n label: child.props['data-label'] as string | undefined,\n children: child.children,\n })\n return list\n}\n\n/**\n * `[!TABS]`: the variants of one answer (an install per OS, an example per language), of which\n * the reader needs one. Every panel stays in the document, `hidden`, so a search on the page, a\n * copy of the whole reply and a screen reader's panel navigation still reach the others.\n *\n * The WAI-ARIA tabs pattern: one Tab stop for the row, each tab names the panel it controls.\n * `on` gives a tab its listeners and its handle (`onClick`, `onKeyDown`, `ref`).\n */\nexport function tabs<E, C>(\n f: Factory<E, C>,\n list: Array<TabView<E>>,\n current: number,\n base: string,\n labels: MarkdownLabels,\n on?: (index: number) => Props,\n): E {\n const tabId = (index: number) => `${base}-tab-${index}`\n const panelId = (index: number) => `${base}-panel-${index}`\n return f.element('div', { className: 'fd-tabs' }, [\n f.element(\n 'div',\n { className: 'fd-tab-list', role: 'tablist', 'aria-label': labels.variants },\n list.map((tab, index) =>\n f.element(\n 'button',\n {\n key: index,\n type: 'button',\n role: 'tab',\n id: tabId(index),\n className: 'fd-tab',\n 'aria-selected': index === current,\n 'aria-controls': panelId(index),\n tabIndex: index === current ? 0 : -1,\n ...on?.(index),\n },\n [f.text(tab.label || String(index + 1))],\n ),\n ),\n ),\n ...list.map((tab, index) =>\n f.element(\n 'div',\n {\n key: index,\n role: 'tabpanel',\n id: panelId(index),\n 'aria-labelledby': tabId(index),\n className: 'fd-tab-panel',\n hidden: index !== current,\n },\n tab.children,\n ),\n ),\n ])\n}\n\n/**\n * A table in a box of its own that scrolls sideways: a wide comparison then scrolls inside the\n * reply instead of widening the page. A box that scrolls is a named group the Tab key stops on, so the\n * arrow keys can scroll it (WCAG 2.1.1); a table that fits stays out of the Tab order.\n */\nexport const table = <E, C>(\n f: Factory<E, C>,\n { node: _node, ...props }: Props,\n children: E[],\n scrolls: boolean,\n labels: MarkdownLabels,\n box?: Props,\n): E =>\n f.element(\n 'div',\n {\n ...box,\n className: 'fd-table',\n role: scrolls ? 'group' : undefined,\n 'aria-label': scrolls ? labels.scrollableTable : undefined,\n tabIndex: scrolls ? 0 : undefined,\n },\n [f.element('table', props, children)],\n )\n\n/** What a ```tree fence shows right now: which folders are shut, which row the Tab key stops on. */\nexport interface TreeState<E> {\n rows: TreeRow[]\n collapsed: ReadonlySet<string>\n /** The path of the one row in the Tab order; the first row shown when null or hidden. */\n focused: string | null\n /** The glyph before a row, for a page that colours files by type. */\n icon?: (row: TreeRow, open: boolean) => E\n /** A row's listeners and its handle. */\n on?: (row: TreeRow, visible: TreeRow[]) => Props\n}\n\n/** The rows of a tree that no shut folder hides. */\nexport function visibleRows(rows: TreeRow[], collapsed: ReadonlySet<string>): TreeRow[] {\n const hidden = (row: TreeRow): boolean => {\n for (let parent = row.parent; parent !== null; parent = rows[parent]!.parent)\n if (collapsed.has(rows[parent]!.path)) return true\n return false\n }\n return rows.filter((row) => !hidden(row))\n}\n\n/**\n * A ```tree fence: a directory layout drawn as a tree instead of box characters in monospace. The\n * tree is one Tab stop walked with the arrow keys (the WAI-ARIA tree pattern).\n */\nexport function fileTree<E, C>(\n f: Factory<E, C>,\n { rows, collapsed, focused, icon, on }: TreeState<E>,\n labels: MarkdownLabels,\n): E {\n const visible = visibleRows(rows, collapsed)\n // One Tab stop: the row last focused, or the first while it is still shown.\n const rover = visible.some((row) => row.path === focused) ? focused : visible[0]?.path\n return f.element(\n 'div',\n { role: 'tree', 'aria-label': labels.fileTree, className: 'fd-tree' },\n visible.map((row) => {\n const open = !collapsed.has(row.path)\n return f.element(\n 'div',\n {\n key: row.path,\n role: 'treeitem',\n 'aria-level': row.depth + 1,\n 'aria-selected': false,\n ...(row.folder ? { 'aria-expanded': open } : {}),\n tabIndex: row.path === rover ? 0 : -1,\n className: 'fd-tree-row',\n 'data-folder': row.folder || undefined,\n style: { '--fd-tree-depth': row.depth },\n ...on?.(row, visible),\n },\n [\n ...(row.folder\n ? [\n glyph(f, OUTLINES.chevron, {\n className: 'fd-tree-chevron',\n 'data-open': open || undefined,\n }),\n ]\n : []),\n icon\n ? icon(row, open)\n : glyph(\n f,\n row.folder ? (open ? OUTLINES.folderOpen : OUTLINES.folder) : OUTLINES.file,\n { className: 'fd-tree-icon' },\n ),\n f.element('span', { className: 'fd-tree-name', title: row.name }, [\n f.text(row.name.replace(/\\/$/, '')),\n ]),\n ...(row.comment\n ? [\n f.element('span', { className: 'fd-tree-comment', title: row.comment }, [\n f.text(row.comment),\n ]),\n ]\n : []),\n ],\n )\n }),\n )\n}\n\n/**\n * A ```mermaid fence shown as the source it is: before it is drawn, where nothing draws it, or\n * when it could not be drawn, with a line saying why.\n */\nexport const diagramSource = <E, C>(\n f: Factory<E, C>,\n source: string,\n note: string | null,\n failed: boolean,\n figure?: Props,\n): E =>\n f.element(\n 'figure',\n { ...figure, className: 'fd-diagram', 'data-state': failed ? 'error' : 'source' },\n [\n f.element('pre', null, [\n f.element('code', { className: 'language-mermaid' }, [f.text(source)]),\n ]),\n ...(note\n ? [\n f.element('figcaption', { className: 'fd-diagram-note', role: 'status' }, [\n f.text(note),\n ]),\n ]\n : []),\n ],\n )\n"],"mappings":"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"}
package/dist/styles.css CHANGED
@@ -18,7 +18,7 @@
18
18
  /* Text, from the body copy to the quietest annotation. */
19
19
  --fd-fg: light-dark(#1f2328, #e6edf3);
20
20
  --fd-fg-soft: light-dark(#4b535d, #b1bac4);
21
- --fd-fg-faint: light-dark(#6e7781, #8b949e);
21
+ --fd-fg-faint: light-dark(#656d76, #8b949e);
22
22
  /* The ground the Markdown sits on: diagrams mix their fills over it. Set it to your page's. */
23
23
  --fd-bg: light-dark(#ffffff, #0d1117);
24
24
  /* What shows through --fd-bg when it is translucent (a glass panel): the opaque colour under it.
@@ -70,6 +70,17 @@
70
70
  --fd-ease: cubic-bezier(0.2, 0, 0, 1);
71
71
  }
72
72
 
73
+ /*
74
+ * A bundler that builds for browsers older than `light-dark()` (Vite 8 does, with Lightning CSS)
75
+ * rewrites each colour above as `var(--lightningcss-light, …) var(--lightningcss-dark, …)` and
76
+ * sets that pair only where the CSS it reads declares `color-scheme`. On a page that declares
77
+ * none, both fallbacks applied and every colour was invalid. This makes light the default there,
78
+ * as it is for `light-dark()` itself; any `color-scheme` the bundler does read still wins.
79
+ */
80
+ :where(:root) {
81
+ --lightningcss-dark: ;
82
+ }
83
+
73
84
  :where(.dark, [data-theme='dark']) :where(.flowdown),
74
85
  :where(.flowdown[data-theme='dark']) {
75
86
  color-scheme: dark;