@mirrordown/core 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,87 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+ <sub>2026-10-28</sub>
5
+
6
+ - *(minor)*
7
+ Backslash-escaped delimiters are now literal in `remd-mark`, `remd-ins`, `remd-del`, `remd-kbd`, `remd-spoiler`, `remd-sub` and `remd-sup`, as they already were in the `mdit-*` plugins: `\==not marked==` renders as `==not marked==` instead of a `<mark>`, while an escaped backslash (`\==marked==`) still allows the markup. These plugins now depend on the new `@mirrordown/core` package, which holds logic shared across Mirrordown's engines.
8
+ - *(minor)*
9
+ `mdit-github-alerts` and `remd-github-alerts` now share their alert keywords, default titles, icons, option resolution and (for remark and Sätteri) the marker-line rule through `@mirrordown/core`, instead of keeping duplicate copies. Their output and public exports are unchanged.
10
+ - *(minor)*
11
+ `remd-mark` now runs its `==mark==` scan through `createDelimitedTransform` and `MARK_DELIMITERS` from `@mirrordown/core`, which the Sätteri plugin shares, and drops its `unist-util-find-*` and `unist-builder` dependencies. Its output is unchanged.
12
+ - *(minor)*
13
+ `~subscript~` and `^superscript^` now parse the same way in markdown-it, remark and Sätteri: a single `~` or `^` is a delimiter run with emphasis-style flanking, its content is inline markup (spaces, emphasis, links, code and each other), and crossing runs resolve leftmost-first.
14
+
15
+ - `mdit-sub` and `mdit-sup` used to keep the content as plain text and reject any space inside it. They now parse it as inline markup, so `~*a* b~` renders emphasis and `^a~b~c^` nests.
16
+ - `remd-sub` and `remd-sup` are now micromark syntax extensions sharing one resolver from `@mirrordown/core` (`scriptSyntax`), instead of text scans run one after another. Crossing runs such as `^a~b^c~` no longer depend on which plugin was registered first, `~~a~~` without GFM is no longer turned into empty subscripts, and the packages drop their `unist-util-*` dependencies.
17
+ - The sub guide now shows `remark-gfm` with `singleTilde: false`: GFM's single-tilde strikethrough, which Astro enables by default, otherwise claims `~text~` first.
18
+ - *(minor)*
19
+ `==mark==`, `++ins++` and `--del--` now pair delimiters the same way in every engine. `@mirrordown/core`'s `createDelimitedTransform` ports markdown-it's delimiter algorithm (the one markdown-it-mark and markdown-it-ins use), and the remark and Sätteri plugins run on it:
20
+
21
+ - A closer pairs with the nearest opener: `==a ==b==` highlights only `b`, as markdown-it does.
22
+ - Spans nest: `==a ==b== c==` is a highlight inside a highlight.
23
+ - The extra marker of an odd-length run stays outside the span (`===a===` → `=<mark>a</mark>=`); `mdit-del` compared against `+` instead of `-` here, so its marker ended up inside.
24
+ - `remd-del` no longer mishandles runs such as `--a --- b--` or `--+a+--`, which came from patterns copied from the ins plugin.
25
+
26
+ New Sätteri plugins `@mirrordown/sat-ins` and `@mirrordown/sat-del`, also included in `@mirrordown/preset-satteri`. The `del` guide notes that smart punctuation, on by default in Astro, turns `--` into an en dash before the plugin runs.
27
+ - *(minor)*
28
+ `[[key]]` now scans the same way in every engine. `@mirrordown/core`'s new `createKbdTransform` ports markdown-it-kbd's tokenizer, and `remd-kbd` and the new `@mirrordown/sat-kbd` run on it:
29
+
30
+ - Keys nest: `[[a [[b]] c]]`.
31
+ - A single `]` stays inside a key: `[[a]b]]`.
32
+ - A line ending before the closing `]]` cancels the key even when formatting sits in between.
33
+ - `[[[a]]` keeps the extra `[` outside the key.
34
+
35
+ `remd-kbd` drops its `unist-util-find-*` and `unist-builder` dependencies. One difference is inherent to the engines and documented in the guide: markdown-it reads `[[*a]]*` as a key, while remark and Sätteri have already read the emphasis.
36
+
37
+ `@mirrordown/sat-kbd` is included in `@mirrordown/preset-satteri`.
38
+ - *(minor)*
39
+ `||spoiler||` now pairs delimiters with markdown-it's rules in every engine: `remd-spoiler` runs on `@mirrordown/core`'s delimiter scan (which gains the `|` marker), so a closer pairs with the nearest opener, spoilers nest, and an odd-length run's extra `|` stays outside; it drops its `unist-util-find-*` and `unist-builder` dependencies.
40
+
41
+ New `@mirrordown/sat-spoiler` renders the same JavaScript-free click-to-reveal markup, ships the same stylesheet, and is included in `@mirrordown/preset-satteri`.
42
+ - *(minor)*
43
+ Sätteri ports of the hast plugins: `@mirrordown/sat-squeeze-paragraphs`, `sat-unwrap-images`, `sat-sectionize`, `sat-lightbox` and `sat-autolink-headings`, all included in `@mirrordown/preset-satteri` (whose stylesheet now also imports the lightbox and autolink-headings styles). Their output is identical to the markdown-it and rehype plugins'. `sat-autolink-headings` takes the same options as `remd-autolink-headings`, including the hast-node ones (`content`, `group`, `properties`, `headingProperties`, `test`).
44
+
45
+ The rules these engines must compute identically move into `@mirrordown/core` (`isEmptyParagraph`, `isImageParagraph`, `sectionize`, `headingRank`, and the lightbox's marker, ids and markup), which the rehype plugins now use too; their output is unchanged, and squeeze-paragraphs and unwrap-images drop `hast-util-whitespace`.
46
+ - *(minor)*
47
+ New `@mirrordown/sat-abbr`: abbreviations (`*[term]: definition`) for Sätteri, with the same output as `remd-abbr` and `mdit-abbr`, and included in `@mirrordown/preset-satteri`. The definition and term rules move into `@mirrordown/core` (`parseAbbreviations`, `createAbbreviationScan`), which `remd-abbr` now uses too; its output is unchanged.
48
+ - *(minor)*
49
+ New `@mirrordown/sat-steps`: numbered step sequences (`@1. Title` with `>` bodies) for Sätteri, with the same output as `remd-steps` and `mdit-steps`, and included in `@mirrordown/preset-satteri` along with its stylesheet. Step recognition moves into `@mirrordown/core` (`processSteps`), which `remd-steps` now uses too; its output is unchanged.
50
+ - *(minor)*
51
+ Tab labels behave the same in every engine:
52
+
53
+ - `remd-tabs` keeps a tab's label in the tree, as a `tabsLabel` node that is the first child of each `tabsItem` (the `label` field is gone), so remark plugins that run after it also apply to the label, and a panel's `aria-label` is the label's final text.
54
+ - `data-tabs-group` hashes each label's plain text without a trailing attrs block, as the tabs specification describes. `mdit-tabs` hashed the raw markdown, so labels with links, images, entities or escapes got different ids in markdown-it and remark; labels with a trailing `{…}` change id in both.
55
+ - `mdit-tabs` escapes a panel's `aria-label` once: `npm & yarn` rendered as `aria-label="npm &amp;amp; yarn"`.
56
+ - `mdit-tabs` nests its panel tokens inside the tabs tokens, so `mdit-sectionize` no longer opens a `<section>` around a heading in a tab panel that closed outside the panel.
57
+ - *(minor)*
58
+ `@mirrordown/sat-steps` writes back only the parts of the tree that step recognition changed, through core's new `editableTree`, so every node it leaves alone stays Sätteri's own and keeps its source position for the plugins after it (tabs reads positions to end a group at a blank line). It also recognizes a step header directly after a list marker (`- @1. Step`), as the remark and markdown-it plugins do.
59
+ - *(minor)*
60
+ A step header written right after nested content (`> > Quoted.` then `@2. Next`) starts the next step in every engine. CommonMark lazily continues the nested paragraph with it, so the remark and Sätteri plugins rendered the header as text inside the previous step; they now read the source's `>` markers as markdown-it does, recovering the header and the blockquote structure of the lines after it. `processSteps` takes the document's source for this.
61
+ - *(minor)*
62
+ Tab groups nested in step bodies and other containers work in every engine:
63
+
64
+ - Block ids (the radio `name` and input ids) number the document's tab groups in the order they open, combined with the group id, instead of hashing a start line that nested groups don't reliably have. Two groups with the same labels in different steps shared radio names, so the first loaded with no panel open and the second's tabs switched the first. Ids of the first group on a page are unchanged; later groups' ids change. Core exports `tabsBlockId`, and `TabsListNode` no longer carries a `blockId`.
65
+ - `mdit-tabs` keeps a group together across blank lines inside a container (a step body, tab panel, blockquote or list item) when another entry follows, as `remd-tabs` does: the empty `>` lines between entries are the container's own. At the top level a blank line still ends a group.
66
+ - *(minor)*
67
+ New `@mirrordown/sat-inline-svg`: inlines referenced local `.svg` images as `<svg>` elements in Sätteri, resolving them against the `fileURL` compile option, with the same output as `mdit-inline-svg` and `remd-inline-svg`. The size limits, deduplication and markup move into `@mirrordown/core` (`inlineSvgMarkup`, `svgSpriteId`), which the markdown-it and rehype plugins now use too; their output is unchanged. Like the other presets, `@mirrordown/preset-satteri` leaves it out because it reads files from disk.
68
+ - *(minor)*
69
+ A tab header written right after nested tab content (`> > Option C body.` then `% Code`) starts a tab in the outer group in every engine. CommonMark lazily continues the nested paragraph with it, so the remark and Sätteri plugins put it in the inner group (the tabs guide's own "Default open tab" demo showed `Code` beside `Option C`); they now read the source's `>` markers as markdown-it does, keeping the header at the depth it was written with and the blockquote structure of the lines after it. `processTabs` takes the document's source for this; the marker handling is shared with steps.
70
+ - *(minor)*
71
+ A tab header that skips a level no longer loses content in the remark and Sätteri plugins, which dropped it and its body; they now render it as `mdit-tabs` does. In a tab's explicit `> %` body it stays in that panel as text, after any group nested there; in a group of plain `>` bodies it ends the group, and it and the lines after it follow the group as ordinary content, with the blockquotes their `>` markers give. A panel's content now also renders in document order in both plugins, rather than its body before its nested groups.
72
+ - *(patch)*
73
+ `@mirrordown/core` declares `"sideEffects": false`, so bundlers drop the parts a plugin doesn't import (the escape helpers, the sub/sup micromark syntax) without having to prove them unused.
74
+ - *(patch)*
75
+ Paragraphs in tab panels and step bodies keep what they had when recognition splits them around a header:
76
+
77
+ - A hard break (`line\` then `more`) stays a `<br>` in `remd-tabs`, `remd-steps`, `sat-tabs` and `sat-steps`; it became a plain line break, unlike in `mdit-tabs` and `mdit-steps`.
78
+ - Text split around a header keeps its source position, and the lines left together are one text node again, as remark gives them.
79
+ - In `sat-tabs` and `sat-steps`, a paragraph keeps the attributes Sätteri read from its end. A header right after a panel's last paragraph continues that paragraph in CommonMark, so Sätteri saw the paragraph's attribute block mid-paragraph; with Sätteri's `attributes` feature on, the plugins now place that block once the header is split off, where Sätteri would have.
80
+ - `mdit-tabs` and `mdit-steps` give the panel's opening token its body as `meta.attrsSource`, since the tokens inside index the body's lines, so `mdit-attrs` can read a line's indentation there.
81
+ - *(patch)*
82
+ Sätteri 0.11 reads attribute blocks and definition lists the way Mirrordown's other engines do, so the Sätteri suite now covers every feature:
83
+
84
+ - `preset-satteri` has `attrs` and `definitionList` options, on by default like the other presets, which turn on Sätteri's own `attributes` and `definitionList` features. With them, the preset renders every shared fixture as `preset-remark` and `preset-markdown-it` do.
85
+ - The `sat-*` packages accept Sätteri 0.11 as well as 0.10 (`^0.10.5 || ^0.11.0`); Astro's Sätteri integration is still on 0.10, where the plugins work as before.
86
+ - An attribute block ending a tab or step header (`% Tab {.tab}`, `@1. Step {.step}`) now goes on the item in `sat-tabs` and `sat-steps`, as in the other engines: Sätteri read it off the header's paragraph before recognition, and the item didn't get it.
87
+ - `sat-ruby` parses a ruby's base and reading with the document's features, which Sätteri 0.11.3 and later tell plugins; its `features` option is only needed with an older Sätteri.
package/README.md CHANGED
@@ -1,9 +1,13 @@
1
1
  # @mirrordown/core
2
2
 
3
- 🐣 This package name has been claimed with [fledgling](https://github.com/dmno-dev/fledgling) for [https://github.com/mirrordown/mirrordown](https://github.com/mirrordown/mirrordown), and a real release will land here soon.
3
+ > Part of [Mirrordown](https://github.com/mirrordown/mirrordown) — a suite of markdown syntax extensions for the unified and markdown-it ecosystems.
4
4
 
5
- This is a placeholder — there is nothing to install yet.
5
+ Shared, engine-agnostic logic used by Mirrordown's plugins, so the markdown-it (`mdit-*`), remark/rehype (`remd-*`) and Sätteri plugins for a feature behave identically. It has no dependencies and is installed automatically with the plugins that need it; you don't need to depend on it directly.
6
6
 
7
- ---
7
+ ## Escapes
8
8
 
9
- Want to claim a name of your own? Run `npx fledgling add <name> --new --yes` — it reserves the name on npm and can set up token-less (OIDC trusted) publishing for when you are ready.
9
+ Parsers resolve backslash escapes before any post-parse transform runs, so a plugin that scans text for delimiters would see `\==not marked==` as `==not marked==`. These helpers recover which characters of a text node were escaped, by aligning the node's value with its source, and mask them while the plugin scans:
10
+
11
+ - `escapedIndices(value, source, start, end)` — indices of `value` whose character was backslash-escaped in `source[start..end]`.
12
+ - `maskEscaped(value, indices)` / `unmaskEscaped(value)` — replace those characters with stand-ins no delimiter matches, and restore them.
13
+ - `maskTreeEscapes(tree, source)` — mask every positioned `text` node of a unist tree; returns a function that restores them.
@@ -0,0 +1,329 @@
1
+ import { BlockContent, DefinitionContent, PhrasingContent, Root, RootContent } from "mdast";
2
+ import { Properties } from "hast";
3
+ /**
4
+ * Indices of `value` whose character was backslash-escaped in `source`, where
5
+ * `start`–`end` is the source range the text came from. Characters the parser
6
+ * dropped (backslashes, container prefixes such as `> `, trailing spaces) are
7
+ * skipped, and a character reference stands for the character it decodes to.
8
+ */
9
+ export declare const escapedIndices: (value: string, source: string, start: number, end: number) => Set<number>;
10
+ /** Replace the characters at `escaped` with stand-ins no delimiter matches. */
11
+ export declare const maskEscaped: (value: string, escaped: Set<number>) => string;
12
+ /** Restore characters replaced by {@link maskEscaped}. */
13
+ export declare const unmaskEscaped: (value: string) => string;
14
+ /** The shape of a unist text node this module reads and writes. */
15
+ interface TextLikeNode {
16
+ type: string;
17
+ value?: unknown;
18
+ position?: {
19
+ start: {
20
+ offset?: number | undefined;
21
+ };
22
+ end: {
23
+ offset?: number | undefined;
24
+ };
25
+ } | undefined;
26
+ children?: TextLikeNode[] | undefined;
27
+ }
28
+ /**
29
+ * Mask escaped characters in every positioned `text` node of a unist tree, so
30
+ * a delimiter scan skips them. Returns a function that restores them in every
31
+ * node afterwards, including nodes the scan created from masked text.
32
+ */
33
+ export declare const maskTreeEscapes: (tree: TextLikeNode, source: string) => (() => void);
34
+ /** A recognized alert keyword (e.g. `note`, `warning`, `caution`). */
35
+ type AlertType = "note" | "tip" | "important" | "warning" | "caution" | "abstract" | "info" | "todo" | "success" | "question" | "failure" | "danger" | "bug" | "example" | "quote";
36
+ /** Maps every recognized alert keyword (lowercase) to its canonical {@link AlertType}. */
37
+ export declare const ALERT_ALIASES: Record<string, AlertType>;
38
+ /** Default display title for each {@link AlertType}. */
39
+ export declare const DEFAULT_TITLE: Record<AlertType, string>;
40
+ /** Options shared by the GitHub alerts plugins for every engine. */
41
+ interface AlertOptions {
42
+ /**
43
+ * Extra alert types beyond the built-in set. Keys are lowercase keywords
44
+ * (including any aliases you want), values are the canonical type string
45
+ * that appears in the data-alert attribute and CSS class.
46
+ */
47
+ types?: Record<string, string>;
48
+ /** Default display titles, keyed by canonical type. */
49
+ titles?: Partial<Record<AlertType, string>>;
50
+ /** Set false to require exact case on the [!TYPE] keyword. Default: true. */
51
+ matchCaseInsensitive?: boolean;
52
+ /** Set false to suppress the SVG icon in the title. Default: true. */
53
+ icons?: boolean;
54
+ /** CSS class applied to the alert container. Default: "markdown-alert". */
55
+ containerClass?: string;
56
+ }
57
+ /** Inline SVG icon markup for each {@link AlertType}. */
58
+ export declare const ICONS: Record<AlertType, string>;
59
+ /** How an alert keyword resolves under a plugin's options. */
60
+ interface AlertResolver {
61
+ /** The canonical type for a `[!KEYWORD]`, or `undefined` if it is not an alert. */
62
+ resolve: (keyword: string) => string | undefined;
63
+ /** The title shown when the alert has no custom title. */
64
+ titleFor: (type: string) => string;
65
+ /** The icon SVG for the title, or `""` when icons are off. */
66
+ iconFor: (type: string) => string;
67
+ /** The alert container's CSS class. */
68
+ containerClass: string;
69
+ }
70
+ /** Resolve alert keywords, titles and icons the same way in every engine. */
71
+ export declare const createAlertResolver: (options?: AlertOptions) => AlertResolver;
72
+ /** The shape of an mdast phrasing node this module reads and builds. */
73
+ interface PhrasingLike {
74
+ type: string;
75
+ value?: string;
76
+ alt?: string | null | undefined;
77
+ children?: readonly PhrasingLike[];
78
+ }
79
+ /** A paragraph that opens with an alert marker, split into its parts. */
80
+ interface AlertParagraph<N extends PhrasingLike> {
81
+ /** The `[!KEYWORD]` text, before alias resolution. */
82
+ keyword: string;
83
+ /** `+` (open) or `-` (closed) for a foldable alert, else `undefined`. */
84
+ fold: `+` | `-` | undefined;
85
+ /** The first line after the marker: the custom title, formatting included. */
86
+ titleNodes: N[];
87
+ /** The title's plain text (text, inline code and image alt), for `aria-label`. */
88
+ titleText: string;
89
+ /** Everything after the first line break (soft or hard): the paragraph's body. */
90
+ bodyNodes: N[];
91
+ }
92
+ /** Plain text of inline content: text, inline code and image alt count; raw HTML does not. */
93
+ export declare const plainText: (nodes: readonly PhrasingLike[]) => string;
94
+ /**
95
+ * Split an alert's first paragraph (an mdast `paragraph`'s children) into its marker, title and body, or return `null` when it does not open with a `[!KEYWORD]` marker in plain text. The first line is the marker plus an optional title; everything after the first line break — a newline inside a text node (soft) or a `break` node (hard) — is the body. Nothing is dropped. Text nodes are copied; other nodes are reused as they are.
96
+ */
97
+ export declare const splitAlertParagraph: <N extends PhrasingLike>(children: readonly N[]) => AlertParagraph<N> | null;
98
+ /** How a scanned span becomes nodes. */
99
+ interface SpanNodes<N extends PhrasingLike> {
100
+ /** A text node with the given value. */
101
+ text: (value: string) => N;
102
+ /** The node a span's content is wrapped in (e.g. an mdast `mark`). */
103
+ wrap: (children: N[]) => N;
104
+ }
105
+ /** A node's source range, as unist positions carry it. */
106
+ interface Positioned {
107
+ position?: {
108
+ start: {
109
+ offset?: number | undefined;
110
+ };
111
+ end: {
112
+ offset?: number | undefined;
113
+ };
114
+ } | undefined;
115
+ }
116
+ /** A scan over one parent's children, and the nodes it created. */
117
+ interface SpanTransform<N extends PhrasingLike> {
118
+ /** Scan `children`, the children of a node of type `parentType`. Returns `children` itself when no span forms. */
119
+ (children: readonly N[], parentType?: string): readonly N[];
120
+ /** Whether `node` was built by this transform (a wrapper or a text node). */
121
+ built: (node: object) => boolean;
122
+ /**
123
+ * The transform for engines whose text values have escapes resolved and whose nodes are read-only, such as Sätteri: escaped characters are masked by aligning each positioned text node with `source`, using copies, and restored in the result. Returns the new children, or `undefined` when no span forms.
124
+ */
125
+ withSource: (children: ReadonlyArray<N & Positioned>, parentType: string, source: string) => N[] | undefined;
126
+ }
127
+ /** A marker whose doubled form delimits a span: `==`, `++`, `--` or `||`. */
128
+ type DelimiterMarker = `=` | `+` | `-` | `|`;
129
+ /** Build the delimiter scan for one marker: `=` (mark), `+` (ins), `-` (del) or `|` (spoiler). */
130
+ export declare const createDelimitedTransform: <N extends PhrasingLike>(marker: DelimiterMarker, nodes: SpanNodes<N>) => SpanTransform<N>;
131
+ /** Build the `[[key]]` scan. */
132
+ export declare const createKbdTransform: <N extends PhrasingLike>(nodes: SpanNodes<N>) => SpanTransform<N>;
133
+ /** The marker characters, and the micromark token types each produces. */
134
+ declare const SCRIPTS: {
135
+ readonly "~": {
136
+ readonly code: 126;
137
+ readonly type: "subscript";
138
+ };
139
+ readonly "^": {
140
+ readonly code: 94;
141
+ readonly type: "superscript";
142
+ };
143
+ };
144
+ type Marker = keyof typeof SCRIPTS;
145
+ /** The shape of a micromark syntax extension this module builds. */
146
+ interface ScriptSyntax {
147
+ text: Record<number, unknown>;
148
+ insideSpan: {
149
+ null: unknown[];
150
+ };
151
+ }
152
+ /**
153
+ * The micromark syntax extension for one of the two markers: `~` (subscript, `subscript` tokens) or `^` (superscript, `superscript` tokens). The constructs of both markers resolve together.
154
+ */
155
+ export declare const scriptSyntax: (marker: Marker) => ScriptSyntax;
156
+ /** The shape of a hast node this module reads and builds. */
157
+ interface HastLike {
158
+ type: string;
159
+ tagName?: string;
160
+ value?: string;
161
+ properties?: Record<string, unknown>;
162
+ children?: readonly HastLike[];
163
+ }
164
+ /** A hast element as this module builds it; its properties fit hast's `Properties`. */
165
+ interface HastElementLike<C> {
166
+ type: `element`;
167
+ tagName: string;
168
+ properties: Record<string, string | string[]>;
169
+ children: C[];
170
+ }
171
+ /** A `<p>` with no children, or only whitespace text: squeeze-paragraphs removes it. */
172
+ export declare const isEmptyParagraph: (node: HastLike) => boolean;
173
+ /**
174
+ * A `<p>` whose content is only images (each possibly inside a link or a lightbox button) and whitespace: unwrap-images replaces it with its children.
175
+ */
176
+ export declare const isImageParagraph: (node: HastLike) => boolean;
177
+ /** The rank of an `h1`–`h6` element, or `undefined`. */
178
+ export declare const headingRank: (node: HastLike) => number | undefined;
179
+ /**
180
+ * Wrap heading-delimited runs of `nodes` into `<section data-depth>` elements, nesting deeper headings inside shallower ones. Content before the first heading stays unwrapped.
181
+ */
182
+ export declare const sectionize: <N extends HastLike>(nodes: readonly N[], section: (depth: number, children: N[]) => N, floor?: number) => N[];
183
+ /**
184
+ * The lightbox opt-in: an image directly after a text run that ends in a `!` at a boundary (start of the run or after whitespace), which is what `!![alt](src)` leaves behind.
185
+ */
186
+ export declare const LIGHTBOX_MARKER: RegExp;
187
+ /**
188
+ * A short, stable lightbox id from an image src (FNV-1a, base36): the same image always gets the same id, so repeated references share one dialog.
189
+ */
190
+ export declare const lightboxId: (src: string) => string;
191
+ /** The button that replaces an opted-in image and opens its dialog. */
192
+ export declare const lightboxTrigger: <C>(id: string, image: C) => HastElementLike<C>;
193
+ /** The modal dialog, appended at the document end, showing the image full size. */
194
+ export declare const lightboxDialog: <C>(id: string, image: C) => HastElementLike<HastElementLike<C>>;
195
+ /**
196
+ * The `[term, title]` pairs a paragraph's text defines, when every line of it is a `*[term]: title` definition; otherwise `undefined` (the paragraph is ordinary text).
197
+ */
198
+ export declare const parseAbbreviations: (text: string) => Array<[string, string]> | undefined;
199
+ /** A piece of text: plain, or a defined term with its title. */
200
+ type AbbreviationPart = {
201
+ text: string;
202
+ } | {
203
+ term: string;
204
+ title: string;
205
+ };
206
+ /**
207
+ * Build the scan for a document's abbreviations. Terms match on word boundaries, longest first; the scan returns the pieces of a text value, or `undefined` when it holds no term.
208
+ */
209
+ export declare const createAbbreviationScan: (abbreviations: ReadonlyMap<string, string>) => ((value: string) => AbbreviationPart[] | undefined);
210
+ /** A list of steps at one depth, rendered as an `<ol>`. */
211
+ interface StepsListNode {
212
+ type: "stepsList";
213
+ depth: number;
214
+ containerClass?: string;
215
+ children: StepsItemNode[];
216
+ data?: {
217
+ attrsRole?: string;
218
+ hProperties?: Properties;
219
+ };
220
+ }
221
+ /** A step's title, rendered as its title `<p>` unless it is empty. */
222
+ interface StepsTitleNode {
223
+ type: "stepsTitle";
224
+ children: PhrasingContent[];
225
+ }
226
+ /** One step: its title (always the first child, so transforms that walk children reach it), body and nested steps, rendered as an `<li>`. */
227
+ interface StepsItemNode {
228
+ type: "stepsItem";
229
+ depth: number;
230
+ number: number;
231
+ children: Array<StepsTitleNode | BlockContent | DefinitionContent | StepsListNode>;
232
+ data?: {
233
+ attrsRole?: string;
234
+ attrsTitle?: PhrasingContent[];
235
+ hProperties?: Properties;
236
+ };
237
+ }
238
+ export declare const processStepsInChildren: (children: Array<BlockContent | DefinitionContent | Root["children"][number]>, containerClass: string, sourceLines?: readonly string[]) => void;
239
+ /**
240
+ * Turn every step sequence in `root` into `stepsList` nodes, at the top level and inside any container (blockquotes, list items, tab panels…), walking into the lists it builds so nested sequences are found too. With the document's `source`, a step header that CommonMark lazily continued into a nested paragraph is recognized too, as markdown-it reads it.
241
+ */
242
+ export declare const processSteps: (root: Root, containerClass: string, source?: string) => void;
243
+ /** A group of tabs at one depth, rendered as the tabs container. */
244
+ interface TabsListNode {
245
+ type: "tabsList";
246
+ depth: number;
247
+ containerClass?: string;
248
+ groupId: string;
249
+ children: TabsItemNode[];
250
+ data?: {
251
+ attrsRole?: string;
252
+ hProperties?: Properties;
253
+ };
254
+ }
255
+ /** A tab's label, rendered as its `<label>`. */
256
+ interface TabsLabelNode {
257
+ type: "tabsLabel";
258
+ children: PhrasingContent[];
259
+ }
260
+ /** One tab: its label (always the first child, so transforms that walk children reach it), panel body and nested tabs. */
261
+ interface TabsItemNode {
262
+ type: "tabsItem";
263
+ depth: number;
264
+ labelText: string;
265
+ open: boolean;
266
+ children: Array<TabsLabelNode | BlockContent | DefinitionContent | TabsListNode>;
267
+ data?: {
268
+ attrsRole?: string;
269
+ attrsTitle?: PhrasingContent[];
270
+ attrsItemTitle?: boolean;
271
+ hProperties?: Properties;
272
+ };
273
+ }
274
+ /** Strip a trailing attrs block like `{.class #id}` from plain label text, so attrs-consumed markers don't leak into `aria-label`. */
275
+ export declare const stripAttrs: (text: string) => string;
276
+ /**
277
+ * A group's block id, unique on the page even when label sets match: its group id with the group's index among the document's tab groups, counted in the order they open (each nested group after the group around it), so every engine counts the same groups the same way.
278
+ */
279
+ export declare const tabsBlockId: (index: number, groupId: string) => string;
280
+ /** The plain text of a tab label (text and inline code; other markup contributes its text, images nothing), for its group id and `aria-label`. */
281
+ export declare const tabLabelText: (nodes: PhrasingContent[]) => string;
282
+ export declare const processTabsInChildren: (children: Array<BlockContent | DefinitionContent | RootContent>, containerClass: string, checkBlankLines?: boolean, sourceLines?: readonly string[]) => void;
283
+ /**
284
+ * Recognize every tab group in the tree: at the root, then inside every container (step bodies, list items, other tabs' panels). Blank-line checks only apply at the root: blank lines inside a blockquote leave position gaps that would wrongly end a group once its content has been lifted out of the blockquote. With the document's `source`, a tab header that CommonMark lazily continued into a nested panel is placed by its `>` markers, as markdown-it reads it.
285
+ */
286
+ export declare const processTabs: (root: Root, containerClass: string, source?: string) => void;
287
+ /** The part of a node the copy reads: its children, if it has any. */
288
+ interface TreeNode {
289
+ type: string;
290
+ children?: readonly TreeNode[];
291
+ data?: unknown;
292
+ }
293
+ /** A copy of a tree, and how to write the changes made to it back. */
294
+ interface EditableTree<N extends TreeNode> {
295
+ /** A plain, mutable copy of the tree, for recognition to rewrite. */
296
+ tree: N;
297
+ /**
298
+ * Write a node of the (rewritten) copy back: a node recognition left alone becomes the engine's own node again, so it keeps what the engine tracks for it (such as its source position, which later plugins read), and `setChildren` is called only where a children list changed, `setData` only where its `data` was replaced. `build` converts a node recognition created, writing back any copied nodes inside it.
299
+ */
300
+ writeBack: (node: N) => N;
301
+ }
302
+ export declare const editableTree: <N extends TreeNode>(root: N, build: (node: N, writeBack: (node: N) => N) => N, setChildren: (node: N, children: N[]) => void, setData?: (node: N, data: unknown) => void) => EditableTree<N>;
303
+ /** The properties an attribute block written after text gives the text's paragraph, as the parser reads them, or `undefined` if it isn't one. */
304
+ type ReadAttributes = (block: string) => Properties | undefined;
305
+ /**
306
+ * Places the attribute block now ending each paragraph recognition rebuilt (one a header was cut from), as the parser places one: a block on a line of its own goes on the block it's written after, at the level its `>` markers and indentation put it (the paragraph, its list item in a tight list, or the outermost blockquote or list item it's written outside of, a list item giving its list); a block after text goes on the paragraph, or its list item from the item's first paragraph.
307
+ */
308
+ export declare const placeCutAttributes: (root: Root, sourceLines: readonly string[], read: ReadAttributes) => void;
309
+ /** The limits and mode that decide how an SVG's occurrences are inlined. */
310
+ interface InlineSvgLimits {
311
+ /** Skip inlining SVGs larger than this many characters. */
312
+ maxImageSize: number;
313
+ /** Skip inlining an SVG referenced more than this many times. */
314
+ maxOccurrences: number;
315
+ /** Skip inlining an SVG whose occurrences add up to more than this many characters. */
316
+ maxTotalSize: number;
317
+ /** Reuse a repeated SVG through a hidden `<symbol>` and `<use>`. */
318
+ deduplication: boolean;
319
+ }
320
+ /**
321
+ * The id of a deduplicated SVG's sprite: a hash of its path relative to the document, with `/` separators, so the same document renders the same ids on any machine or OS (an absolute path would leak the build machine into the HTML).
322
+ */
323
+ export declare const svgSpriteId: (relativePath: string) => string;
324
+ /**
325
+ * The markup for every occurrence of one SVG file, given each occurrence's alt text in document order: the SVG itself, or, with deduplication and more than one occurrence, a hidden sprite on the first and a `<use>` of it on each. `null` when the limits leave the images as they are. `relativePath` is the file's path relative to the document, with `/` separators, for the sprite id.
326
+ */
327
+ export declare const inlineSvgMarkup: (content: string, alts: readonly string[], relativePath: string, limits: InlineSvgLimits) => string[] | null;
328
+ export type { AbbreviationPart, AlertOptions, AlertParagraph, AlertResolver, AlertType, DelimiterMarker, EditableTree, HastElementLike, HastLike, InlineSvgLimits, PhrasingLike, Positioned, ReadAttributes, ScriptSyntax, SpanNodes, SpanTransform, StepsItemNode, StepsListNode, StepsTitleNode, TabsItemNode, TabsLabelNode, TabsListNode, TextLikeNode, TreeNode };
329
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/escapes.ts","../src/github-alerts/types.ts","../src/github-alerts/icons.ts","../src/github-alerts/index.ts","../src/spans.ts","../src/delimited.ts","../src/kbd.ts","../src/scripts.ts","../src/hast.ts","../src/abbr.ts","../src/steps.ts","../src/tabs.ts","../src/editable.ts","../src/cut-attributes.ts","../src/inline-svg.ts"],"mappings":"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"}