duckfn-docs-kit 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The CodeMirror 6 editor behind the kit's editable content: the code view of a
3
+ * runnable SQL block (`<dfk-sql>`) and the diagram-source dialog of a
4
+ * `<dfk-mermaid>`. One implementation, two languages — the SQL block asks for
5
+ * highlighting, the diagram editor takes the plain-text default.
6
+ *
7
+ * Every CodeMirror module arrives through a dynamic `import()` inside
8
+ * {@link mountCodeEditor}: a page full of blocks pays nothing for the editor on
9
+ * its critical path, and Docusaurus' Node prerender never evaluates any of it.
10
+ *
11
+ * This is browser-only code: `document` is touched only through the container
12
+ * the caller hands over, and everything else is behind the `import()`s.
13
+ */
14
+ export interface CodeEditor {
15
+ getValue(): string;
16
+ setValue(value: string): void;
17
+ /** Soft-wraps long lines, or stops wrapping them (the block's wrap toggle). */
18
+ setWrap(wrapped: boolean): void;
19
+ destroy(): void;
20
+ }
21
+ export interface CodeEditorOptions {
22
+ /**
23
+ * Highlight the document as SQL. Omitted, the editor is plain text — which is
24
+ * what a mermaid diagram gets: there is no first-party CodeMirror language for
25
+ * it, and a docs reader is editing prose-shaped source, not writing SQL.
26
+ */
27
+ language?: 'sql';
28
+ }
29
+ export declare function mountCodeEditor(container: HTMLElement, value: string, onChange: (value: string) => void, options?: CodeEditorOptions): Promise<CodeEditor>;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import type { HTMLAttributes } from 'react';
2
+ import { DfkFeatures } from './home/DfkFeatures';
3
+ import { DfkHero } from './home/DfkHero';
4
+ import { DfkMermaid } from './mermaid/DfkMermaid';
5
+ import { DfkNextSteps } from './home/DfkNextSteps';
6
+ import { DfkSql } from './sql/DfkSql';
2
7
  /**
3
- * Browser entry for duckfn-docs-kit: the home-page custom elements and the
8
+ * Browser entry for duckfn-docs-kit: the custom elements it registers and the
4
9
  * value types their `set*` methods accept.
5
10
  *
6
11
  * The components are retained-mode (build once, then mutate held nodes) and
@@ -12,17 +17,16 @@ import type { HTMLAttributes } from 'react';
12
17
  * web component, which `registerDfkElements()` registers as a side effect, so
13
18
  * this package ships no icon data.
14
19
  *
15
- * `TocToggle` and the remark plugin keep their own subpaths
16
- * (`duckfn-docs-kit/toc-toggle/TocToggle`, `duckfn-docs-kit/remark`) instead of
20
+ * `TocToggle` and the remark plugins keep their own subpaths
21
+ * (`duckfn-docs-kit/toc-toggle/TocToggle`, `duckfn-docs-kit/remark`,
22
+ * `duckfn-docs-kit/sql/remark`, `duckfn-docs-kit/mermaid/remark`) instead of
17
23
  * being merged here: a Docusaurus config file must never pull browser code into
18
24
  * Node, and a site that only wants the TOC collapse button should not pay for
19
25
  * the bundled `iconify-icon`.
20
26
  */
21
- export { DfkFeatures } from './home/DfkFeatures';
22
- export { DfkHero } from './home/DfkHero';
23
- export { DfkNextSteps } from './home/DfkNextSteps';
24
- export { DfkSql } from './sql/DfkSql';
27
+ export { DfkFeatures, DfkHero, DfkMermaid, DfkNextSteps, DfkSql };
25
28
  export { registerDfkElements } from './register';
29
+ export type { DfkMermaidConfig, DfkMermaidConfigInput } from './mermaid/config';
26
30
  export type { RunnableSqlConfig } from './sql/remark';
27
31
  export type { FeatureItem, HeroAction, HeroBadge, HeroLink, NextStepItem, } from './types';
28
32
  /**
@@ -38,6 +42,22 @@ export type { FeatureItem, HeroAction, HeroBadge, HeroLink, NextStepItem, } from
38
42
  * from the declaration file.
39
43
  */
40
44
  export type DfkElementProps = HTMLAttributes<HTMLElement>;
45
+ /**
46
+ * The kit's tags in the DOM's own tag map, so `document.createElement('dfk-sql')`
47
+ * (and the kit's `el()` helper) is typed as the class it upgrades to. Without
48
+ * this, every place that builds a `dfk-*` element from scratch — `sql/renderers.ts`
49
+ * building a `<dfk-mermaid>` for a `mermaid` result, `docs/src/pages/index.tsx`
50
+ * mounting the home elements — would have to cast the result.
51
+ */
52
+ declare global {
53
+ interface HTMLElementTagNameMap {
54
+ 'dfk-hero': DfkHero;
55
+ 'dfk-features': DfkFeatures;
56
+ 'dfk-next-steps': DfkNextSteps;
57
+ 'dfk-sql': DfkSql;
58
+ 'dfk-mermaid': DfkMermaid;
59
+ }
60
+ }
41
61
  declare module 'react' {
42
62
  namespace JSX {
43
63
  interface IntrinsicElements {
@@ -45,6 +65,7 @@ declare module 'react' {
45
65
  'dfk-features': DfkElementProps;
46
66
  'dfk-next-steps': DfkElementProps;
47
67
  'dfk-sql': DfkElementProps;
68
+ 'dfk-mermaid': DfkElementProps;
48
69
  }
49
70
  }
50
71
  }
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, r, t as i } from "./register-CALCwFBv.js";
2
- export { e as DfkFeatures, t as DfkHero, r as DfkNextSteps, n as DfkSql, i as registerDfkElements };
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "./register-Dev_kc3Z.js";
2
+ export { r as DfkFeatures, e as DfkHero, t as DfkMermaid, i as DfkNextSteps, n as DfkSql, a as registerDfkElements };
@@ -0,0 +1,7 @@
1
+ import { HTMLElementBase } from '../dom';
2
+ export declare class DfkMermaid extends HTMLElementBase {
3
+ #private;
4
+ constructor();
5
+ connectedCallback(): void;
6
+ disconnectedCallback(): void;
7
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The mermaid look and palette, shared by the Node-side remark plugin (which
3
+ * stamps it onto every `<dfk-mermaid>` it emits) and the browser element (whose
4
+ * fallback when no config travels with the element).
5
+ *
6
+ * Pure data with no imports, like `sql/runtimeConfig.ts`: both sides have to
7
+ * agree on the shape, and neither may drag the other into its bundle — the
8
+ * remark plugin runs in Docusaurus' Node build, the element in the browser.
9
+ *
10
+ * Why the config travels per element instead of living in the component: the
11
+ * palette is the one part of a diagram that is *site-specific*. Passing it
12
+ * through the site's `docusaurus.config.ts` (as an option of `remarkMermaid`)
13
+ * keeps that choice where the rest of the site's theme is decided, and keeps a
14
+ * downstream docs site from having to fork the kit to change two colour names.
15
+ */
16
+ /** The two colour modes a diagram is rendered for. */
17
+ export type MermaidColorMode = 'light' | 'dark';
18
+ /** A partial config, as a site writes it. */
19
+ export interface DfkMermaidConfigInput {
20
+ /** Mermaid theme name per colour mode; missing sides fall back to the default. */
21
+ theme?: Partial<Record<MermaidColorMode, string>>;
22
+ /** Extra mermaid options, spread into `initialize()` after `theme`. */
23
+ options?: Record<string, unknown>;
24
+ }
25
+ /** A resolved config: both themes present, options ready to spread. */
26
+ export interface DfkMermaidConfig {
27
+ theme: Record<MermaidColorMode, string>;
28
+ options: Record<string, unknown>;
29
+ }
30
+ /**
31
+ * The kit's default: the `neo` look (mermaid's flatter, rounder chrome) with the
32
+ * redux palette — `redux-color` in light mode, `redux-dark-color` in dark.
33
+ *
34
+ * `look` has no per-mode counterpart in mermaid, so it belongs in `options`;
35
+ * `theme` is the per-mode one. Both are easy to get wrong *silently*: mermaid
36
+ * ignores an unrecognised value and falls back, so a change is verified in a
37
+ * browser, not by a build.
38
+ */
39
+ export declare const DEFAULT_MERMAID_CONFIG: DfkMermaidConfig;
40
+ /** Merges a site's overrides over {@link DEFAULT_MERMAID_CONFIG}. */
41
+ export declare function resolveMermaidConfig(input?: DfkMermaidConfigInput | null): DfkMermaidConfig;
42
+ /**
43
+ * Parses the `config` attribute the remark plugin writes. Anything unreadable
44
+ * falls back to the default rather than failing a diagram, and only the two
45
+ * known keys are read — the attribute is content, not a channel for arbitrary
46
+ * configuration.
47
+ */
48
+ export declare function parseMermaidConfig(raw: string | null | undefined): DfkMermaidConfig;
@@ -0,0 +1,40 @@
1
+ import type { Plugin } from 'unified';
2
+ import type { DfkMermaidConfigInput } from './config';
3
+ /**
4
+ * Turns a ```mermaid fence into a `<dfk-mermaid>` custom element, so the docs
5
+ * site renders diagrams through this kit instead of through
6
+ * `@docusaurus/theme-mermaid`.
7
+ *
8
+ * That theme's React component cannot avoid the two upstream defects
9
+ * `./render.ts` documents — it colours by `useColorMode()`, which lags behind on
10
+ * the first client render, so a dark-mode first load paints a light diagram and
11
+ * then a dark one (the flash, and occasionally an empty SVG), and mermaid's
12
+ * mutable singleton renders two diagrams at once. Doing the render in a custom
13
+ * element instead puts both fixes in one place that every duckfn-family docs site
14
+ * shares, and lets the runnable-SQL `mermaid` output reuse the same renderer.
15
+ *
16
+ * The source travels as the `source` attribute and the palette as the `config`
17
+ * one (see `./config`): React 19 reconciles string props onto a custom element as
18
+ * attributes, so both survive prerendering and hydration. The element has no
19
+ * children — the diagram is built in its shadow root, so there is no prerendered
20
+ * markup to hide (contrast `sql/remark.ts`, whose code node is the fallback the
21
+ * editor replaces).
22
+ *
23
+ * This is Node-side build code: it must not touch `window` / `document`, and it
24
+ * must not import any browser module (type-only imports are fine).
25
+ */
26
+ /** The custom element the plugin emits; must match `register.ts`. */
27
+ export declare const DFK_MERMAID_TAG = "dfk-mermaid";
28
+ export interface RemarkMermaidOptions {
29
+ /**
30
+ * Overrides for the kit's default look and palette, merged by the element (see
31
+ * `resolveMermaidConfig`). This is where a site picks its mermaid colours — the
32
+ * one part of a diagram that is site-specific — so no docs site has to fork the
33
+ * kit to change two theme names.
34
+ *
35
+ * Omitted, no `config` attribute is written at all and every element falls back
36
+ * to the kit's default (`DEFAULT_MERMAID_CONFIG` in `./config`).
37
+ */
38
+ config?: DfkMermaidConfigInput;
39
+ }
40
+ export declare const remarkMermaid: Plugin<[RemarkMermaidOptions?]>;
@@ -0,0 +1,32 @@
1
+ //#region src/mermaid/remark.ts
2
+ var e = "dfk-mermaid", t = (e = {}) => (t) => {
3
+ let r = e.config === void 0 ? null : JSON.stringify(e.config), i = (e) => {
4
+ if (typeof e != "object" || !e) return;
5
+ let t = e;
6
+ Array.isArray(t.children) && (t.children = t.children.map((e) => {
7
+ if (typeof e != "object" || !e) return e;
8
+ let t = e;
9
+ return t.type === "code" && t.lang === "mermaid" ? n(t, r) : (i(t), t);
10
+ }));
11
+ };
12
+ i(t);
13
+ };
14
+ function n(t, n) {
15
+ let r = [{
16
+ type: "mdxJsxAttribute",
17
+ name: "source",
18
+ value: String(t.value ?? "")
19
+ }];
20
+ return n !== null && r.push({
21
+ type: "mdxJsxAttribute",
22
+ name: "config",
23
+ value: n
24
+ }), {
25
+ type: "mdxJsxFlowElement",
26
+ name: e,
27
+ attributes: r,
28
+ children: []
29
+ };
30
+ }
31
+ //#endregion
32
+ export { e as DFK_MERMAID_TAG, t as remarkMermaid };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The one place the kit talks to mermaid: the load, the render queue, the config
3
+ * the renderer is initialised with, and the two document-level facts a diagram
4
+ * needs (which colour mode the page is in, and how a rendered SVG gets into a
5
+ * shadow tree).
6
+ *
7
+ * Two upstream problems are solved here, and neither can be fixed from a site's
8
+ * `docusaurus.config.ts` or from the diagram source:
9
+ *
10
+ * 1. **The flash on a dark-mode first load, and the blank diagram.** Docusaurus'
11
+ * `useColorMode()` deliberately lags behind on the first client render (its
12
+ * state is initialised in an effect, to avoid hydration mismatches), so a
13
+ * theme-aware renderer paints once with the light palette and then again with
14
+ * the dark one: the light one lands on screen briefly (the flash), and the two
15
+ * overlap inside mermaid's mutable singleton, which can resolve with an empty
16
+ * SVG (an empty container, no error — facebook/docusaurus#8357). This module
17
+ * reads `<html data-theme>` instead: the attribute the inline script in
18
+ * `<head>` writes *before* the first paint, and the one the page CSS keys off.
19
+ * Nothing is rendered until it is known, so each diagram is rendered exactly
20
+ * once per mode, in the mode the page is really in.
21
+ * 2. **Concurrent renders.** Mermaid is a mutable singleton that cannot render
22
+ * two diagrams at once (`mermaid.initialize()` sets one global config, and the
23
+ * renderer mutates it as it goes): docusaurus#8357 asks to "render them
24
+ * sequentially one after the other", pointing at mermaid-js/mermaid#3577.
25
+ * Every render therefore goes through a single queue.
26
+ *
27
+ * This is browser-only code: `mermaid` and every DOM API are reached lazily, so
28
+ * Docusaurus' Node prerender can import the module graph without evaluating any
29
+ * of it.
30
+ */
31
+ import type { DfkMermaidConfig, MermaidColorMode } from './config';
32
+ /** How a diagram is rendered: its source, its config, and its colour mode. */
33
+ export interface MermaidRenderRequest {
34
+ source: string;
35
+ config: DfkMermaidConfig;
36
+ colorMode: MermaidColorMode;
37
+ }
38
+ /** A rendered diagram: the SVG markup, plus mermaid's bind hook for click handlers. */
39
+ export interface MermaidRenderOutput {
40
+ svg: string;
41
+ bind?(container: Element): void;
42
+ }
43
+ /**
44
+ * Renders one diagram, through the page-wide queue. Each render re-initialises
45
+ * mermaid with this diagram's config first: mermaid has two config levels and the
46
+ * site-wide one can only be set through `initialize()`.
47
+ */
48
+ export declare function renderMermaid({ source, config, colorMode, }: MermaidRenderRequest): Promise<MermaidRenderOutput>;
49
+ /** The colour mode the page is actually in, read from the pre-paint attribute. */
50
+ export declare function documentColorMode(): MermaidColorMode;
51
+ /**
52
+ * Calls `listener` whenever the page's colour mode changes, and returns the
53
+ * unsubscribe. The `<head>` script only *writes* the attribute on load, so a
54
+ * mode switch made later has to be observed — that is how a diagram follows the
55
+ * theme toggle.
56
+ */
57
+ export declare function watchColorMode(listener: () => void): () => void;
58
+ /**
59
+ * Turns mermaid's SVG markup into a node a shadow tree can host.
60
+ *
61
+ * Mermaid serialises through `innerHTML`, so its output is *HTML*, not strict
62
+ * XML: an HTML label can carry a bare `<br>`, which `image/svg+xml` would reject.
63
+ * Parsing as `text/html` and taking the `<svg>` element gives the right
64
+ * namespace for free (the HTML parser enters foreign content for `<svg>`) and
65
+ * keeps markup out of `innerHTML`, which the kit's conventions rule out.
66
+ *
67
+ * Returns `null` when there is no `<svg>` to be found, so the caller can show an
68
+ * error instead of an empty box.
69
+ */
70
+ export declare function parseMermaidSvg(owner: Document, svg: string): SVGElement | null;
71
+ /** The pan/zoom library, loaded once on first use (see `DfkMermaid`). */
72
+ export declare function loadPanzoom(): Promise<typeof import('@panzoom/panzoom')['default']>;
73
+ /**
74
+ * Serialises a rendered diagram into standalone SVG markup.
75
+ *
76
+ * Mermaid hands its SVG back as an **HTML** string (it stringifies a detached
77
+ * element through `innerHTML`), and HTML serialisation writes a void element
78
+ * without a closing slash: a `<br/>` the author put inside a label comes back as
79
+ * `<br>`. That is fine where it lands — the page's HTML parser reads it, and
80
+ * `parseMermaidSvg()` parses it the same way — but it is fatal for a *file*: a
81
+ * browser opening an `.svg` parses XML, and `<br>` inside a `<p>` there is
82
+ * `Opening and ending tag mismatch: br … and p`, with the drawing cut off at the
83
+ * first error.
84
+ *
85
+ * Re-serialising the live node with `XMLSerializer` fixes both halves at once:
86
+ * XML serialisation closes every element, and it emits the namespace declarations
87
+ * a standalone document needs (`xmlns` on the `<svg>`, and one on any
88
+ * `foreignObject` subtree, whose XHTML content inherits its namespace from the
89
+ * page rather than carrying it as an attribute).
90
+ *
91
+ * This is why the element keeps the rendered `<svg>` node for the download
92
+ * instead of mermaid's own string: only the node can be re-serialised.
93
+ */
94
+ export declare function serializeMermaidSvg(svg: SVGElement): string;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The one parsed stylesheet shared by every `<dfk-mermaid>` shadow root.
3
+ * Created lazily: `CSSStyleSheet` does not exist during Docusaurus' Node
4
+ * prerender, and only the browser ever calls this.
5
+ */
6
+ export declare function mermaidStyles(): CSSStyleSheet;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * What a downloaded diagram is called.
3
+ *
4
+ * The name is derived from the page, not from the diagram: a diagram has no name
5
+ * of its own, and the reader downloading one is after "the diagram from that
6
+ * section", not `mermaid-diagram.svg`.
7
+ *
8
+ * Three sources, most specific first, each falling back to the next:
9
+ *
10
+ * 1. **The diagram's own title** — mermaid's frontmatter
11
+ * (`---\ntitle: …\n---`, which mermaid itself draws above the diagram).
12
+ * 2. **The nearest heading above it** — the section the diagram belongs to, which
13
+ * on a docs page is what a reader would call it.
14
+ * 3. **The document title** — the browser tab's title, for a diagram that sits
15
+ * above every heading on its page.
16
+ *
17
+ * Nothing found → {@link DEFAULT_DIAGRAM_FILE}.
18
+ *
19
+ * This is browser-only code.
20
+ */
21
+ /** Used when nothing on the page says anything about this diagram. */
22
+ export declare const DEFAULT_DIAGRAM_FILE = "mermaid-diagram.svg";
23
+ export declare function diagramFileName(source: string, element: Element): string;