@vectojs/markdown 0.18.1 → 0.19.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.
@@ -2,7 +2,8 @@ import { Entity, type DevtoolsDescriptor, IRenderer } from '@vectojs/core';
2
2
  import { type Token } from 'marked';
3
3
  import { type StreamController, type StreamControllerOptions } from './StreamController';
4
4
  export { isMathJaxReady, MathBlock, preloadMathJax } from './markdown-math';
5
- export { CodeBlock, codeAtlas, codeAtlasStats } from './markdown-code';
5
+ export { CodeBlock, codeAtlas, codeAtlasStats, highlightedLanguages } from './markdown-code';
6
+ export type { CodeBlockOptions } from './markdown-code';
6
7
  import { type MarkdownThemePresetName } from './markdown-presets';
7
8
  import { type MarkdownTheme } from './theme';
8
9
  export type { MarkdownTheme } from './theme';
@@ -11,6 +12,7 @@ export { isPresetName, PRESET_THEMES, resolvePresetTheme } from './markdown-pres
11
12
  export { registerFencedBlockRenderer, unregisterFencedBlockRenderer, hasFencedBlockRenderer, isFencedBlockRendererReady, ensureFencedBlockRenderer, renderFencedBlock, } from './markdown-fenced-registry';
12
13
  export type { FencedBlockRenderer, FencedBlockRendererSpec, FencedBlockRenderOptions, } from './markdown-fenced-registry';
13
14
  import { Stack, UIComponent } from '@vectojs/ui';
15
+ import { type BlockAffordanceConfig, type ResolvedBlockAffordanceConfig } from './blockAffordances';
14
16
  export interface MarkdownOptions {
15
17
  maxWidth?: number;
16
18
  /**
@@ -37,6 +39,33 @@ export interface MarkdownOptions {
37
39
  * offered one.
38
40
  */
39
41
  blockAffordances?: boolean;
42
+ /**
43
+ * Which affordance controls a block gets, and what they are called.
44
+ *
45
+ * Defaults to copy + download on both code blocks and tables, i.e. exactly what
46
+ * `blockAffordances: true` produced before this existed. Set it to narrow the
47
+ * set (`{ download: false }` for copy-only blocks) or to relabel a control for a
48
+ * non-English document — the labels are user-visible text and are also what a
49
+ * screen reader announces, so they cannot stay hardcoded English.
50
+ *
51
+ * Separate from {@link blockAffordances} rather than folded into it, because the
52
+ * two answer different questions: whether a reader is offered controls at all,
53
+ * and which ones. Keeping the boolean means the common case stays a boolean.
54
+ */
55
+ affordances?: BlockAffordanceConfig;
56
+ /**
57
+ * Show the fence's language in a header band at the top-left of each code
58
+ * block. Default `false`.
59
+ *
60
+ * Worth turning on for a document that mixes languages, where the label tells a
61
+ * reader what they are looking at. A single-language page gets the same word
62
+ * repeated down its length, which is why it is opt-in.
63
+ *
64
+ * It also reserves vertical space at the top of the block, which is what stops
65
+ * {@link blockAffordances} controls from overlapping the first line of code —
66
+ * so a document using both wants this on.
67
+ */
68
+ showCodeLanguage?: boolean;
40
69
  /**
41
70
  * Writes text to the clipboard for the copy controls.
42
71
  *
@@ -88,20 +117,62 @@ export declare class Markdown extends UIComponent {
88
117
  * affordance.
89
118
  */
90
119
  blockAffordances: boolean;
120
+ /**
121
+ * Which controls a block carries and what they are called, with defaults
122
+ * applied.
123
+ *
124
+ * Resolved once in the constructor rather than per block: the defaults are
125
+ * fixed, and re-deriving them for every code fence in a long document would
126
+ * repeat the same six `??` fallbacks for no benefit.
127
+ */
128
+ affordanceConfig: ResolvedBlockAffordanceConfig;
129
+ /**
130
+ * Whether code blocks show their language in a header band.
131
+ *
132
+ * Read when a block entity is built, exactly like {@link blockAffordances}, so
133
+ * it affects blocks rendered from here on rather than retroactively.
134
+ */
135
+ showCodeLanguage: boolean;
91
136
  /** Clipboard writer used by the copy controls. */
92
137
  writeClipboard: (text: string) => void;
93
138
  /** File saver used by the download controls. */
94
139
  saveFile: (filename: string, content: string, mimeType: string) => void;
95
140
  private activeBlockMetrics;
96
141
  /**
97
- * Called after a streamed append has re-laid-out the document.
142
+ * Called after this entity's own `width`/`height` changed because the document
143
+ * was re-laid-out. **This is the hook to wire up when a host has to move or
144
+ * resize anything positioned below the document.**
145
+ *
146
+ * Fires from three paths, all of which republish `width`/`height` from
147
+ * `content`:
148
+ *
149
+ * - a streamed append (`updateTokens`),
150
+ * - a width change ({@link setMaxWidth}),
151
+ * - a paragraph image whose decoded bitmap corrected the guessed aspect ratio
152
+ * (`reflowAfterImageResize`) — the guess is a flat 16:10, so this one fires
153
+ * on essentially every document containing an image, and a host that misses
154
+ * it leaves every block below the image overlapping it.
155
+ *
156
+ * It does **not** fire from `setContent()`, which replaces the whole document
157
+ * and is a call the host already made, so this is a re-layout signal rather
158
+ * than a complete size signal.
159
+ *
160
+ * A `VirtualList` needs none of this to track a streaming row: it re-reads
161
+ * `height` on every mounted row each frame, so it sees this entity grow without
162
+ * being told. Prefer that where it applies. Reach for this callback when the
163
+ * host owns absolute positions of its own — a page that stacks navigation, a
164
+ * footer and a scroll height under the document has to recompute them here.
98
165
  *
99
- * Not required for a `VirtualList` to track a streaming row's height: the list
100
- * re-reads `height` on every mounted row each frame, so it sees this entity grow
101
- * without being told. Prefer that over wiring this up it fires from the append
102
- * path only, **not** from `setContent()`, so it is not a complete size signal.
166
+ * There is no `onHeightChanged`. That name has been assigned by real callers
167
+ * through an `as unknown as` cast, which compiles, silences the type error and
168
+ * then never fires; if a layout callback appears dead, check the name first.
103
169
  */
104
170
  onLayoutUpdated?: () => void;
171
+ /**
172
+ * Latch for the miswired-hook warning, so a streaming document warns once
173
+ * rather than on every chunk.
174
+ */
175
+ private hasWarnedLayoutHookName;
105
176
  /**
106
177
  * The document's BODY text — everything after any front matter block.
107
178
  *
@@ -559,9 +630,9 @@ export declare class Markdown extends UIComponent {
559
630
  * `BlockAffordanceButton` does in its constructor.
560
631
  */
561
632
  private withBlockAffordances;
562
- /** Copy and download controls for one fenced code block. */
633
+ /** Copy and download controls for one fenced code block, per {@link affordanceConfig}. */
563
634
  private codeBlockAffordances;
564
- /** Copy (as Markdown) and download (as CSV) controls for one table. */
635
+ /** Copy (as Markdown) and download (as CSV) controls for one table, per {@link affordanceConfig}. */
565
636
  private tableAffordances;
566
637
  /**
567
638
  * Button styling for the affordances, derived from the document theme.
@@ -596,6 +667,23 @@ export declare class Markdown extends UIComponent {
596
667
  * level sees a freshly-sized child before it positions anything.
597
668
  */
598
669
  private reflowAfterImageResize;
670
+ /**
671
+ * Publish a completed re-layout to the host.
672
+ *
673
+ * Every path that republishes `width`/`height` from `content` ends here rather
674
+ * than calling {@link onLayoutUpdated} directly, so the misuse check below is
675
+ * reached however the re-layout was triggered.
676
+ *
677
+ * The check exists because the failure it catches is silent and was found in
678
+ * production, not in review. A host wired its reflow to `onHeightChanged` — a
679
+ * name this class has never had — through an `as unknown as` cast. The cast
680
+ * satisfied the compiler, the callback never fired, and every post containing
681
+ * an image stayed laid out against the guessed 16:10 aspect ratio with a stale
682
+ * document scroll height. Nothing in the type system, the tests or the console
683
+ * said anything. A property that is *only ever assigned* has no read site to
684
+ * fail, so the one place that can notice is the moment we would have called it.
685
+ */
686
+ private notifyLayoutUpdated;
599
687
  /**
600
688
  * Re-derive one `MarkdownContainer`'s cached box from its children.
601
689
  *
@@ -75,6 +75,66 @@ export declare function defaultWriteClipboard(text: string): void;
75
75
  * {@link tableToCsv} instead — see there for why.
76
76
  */
77
77
  export declare function defaultSaveFile(filename: string, content: string, mimeType: string): void;
78
+ /**
79
+ * Which take-away controls a block carries, and what they are called.
80
+ *
81
+ * Two separate axes, deliberately. WHICH controls appear is an interaction and
82
+ * accessibility decision — each one is a focus stop in every code block of the
83
+ * document, so a reader who only ever copies should be able to drop the download
84
+ * without also having to restate its label. WHAT they are called is a
85
+ * localization decision, and a consumer rendering a Chinese document needs the
86
+ * labels in Chinese regardless of which controls they kept.
87
+ *
88
+ * Every field is optional and every default matches the labels these controls
89
+ * shipped with, so an existing `blockAffordances: true` caller is unaffected.
90
+ */
91
+ export interface BlockAffordanceConfig {
92
+ /**
93
+ * Show the copy-to-clipboard control. Default `true`.
94
+ *
95
+ * Copy rather than download is the one kept by default when a caller disables
96
+ * the other: it is the action a reader takes on a code snippet, and it needs no
97
+ * filesystem.
98
+ */
99
+ copy?: boolean;
100
+ /** Show the download-as-file control. Default `true`. */
101
+ download?: boolean;
102
+ /**
103
+ * Labels, for localization or for a house style that says "Copy" rather than
104
+ * "Copy code".
105
+ *
106
+ * The success labels are separate strings rather than derived, because no
107
+ * derivation survives translation: "Copied" is not a suffix or a tense rule
108
+ * that holds across languages.
109
+ */
110
+ labels?: {
111
+ /** Resting label of the code-block copy control. Default `'Copy code'`. */
112
+ copyCode?: string;
113
+ /** Resting label of the code-block download control. Default `'Download code'`. */
114
+ downloadCode?: string;
115
+ /** Resting label of the table copy control. Default `'Copy table'`. */
116
+ copyTable?: string;
117
+ /** Resting label of the table download control. Default `'Download table'`. */
118
+ downloadTable?: string;
119
+ /** Confirmation shown after a successful copy. Default `'Copied'`. */
120
+ copied?: string;
121
+ /** Confirmation shown after a successful download. Default `'Saved'`. */
122
+ saved?: string;
123
+ };
124
+ }
125
+ /** Resolved {@link BlockAffordanceConfig}, with every default applied. */
126
+ export interface ResolvedBlockAffordanceConfig {
127
+ copy: boolean;
128
+ download: boolean;
129
+ labels: Required<NonNullable<BlockAffordanceConfig['labels']>>;
130
+ }
131
+ /**
132
+ * Apply {@link BlockAffordanceConfig} defaults.
133
+ *
134
+ * Exported so a consumer building controls by hand resolves them the same way the
135
+ * document does, rather than re-deriving a second set of defaults that can drift.
136
+ */
137
+ export declare function resolveBlockAffordanceConfig(config?: BlockAffordanceConfig): ResolvedBlockAffordanceConfig;
78
138
  /**
79
139
  * A copy or download control drawn in a block's top-right corner.
80
140
  *
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './Markdown';
2
- export { BlockAffordanceButton, BlockWithAffordances, escapeCsvField, escapeMarkdownTableCell, extensionForLanguage, mimeForLanguage, tableContentOf, tableToCsv, tableToMarkdown, } from './blockAffordances';
3
- export type { TableAlign, TableContent } from './blockAffordances';
2
+ export { BlockAffordanceButton, BlockWithAffordances, escapeCsvField, escapeMarkdownTableCell, extensionForLanguage, mimeForLanguage, resolveBlockAffordanceConfig, tableContentOf, tableToCsv, tableToMarkdown, } from './blockAffordances';
3
+ export type { BlockAffordanceConfig, ResolvedBlockAffordanceConfig, TableAlign, TableContent, } from './blockAffordances';
4
4
  export { footnoteMarker } from './markdown-footnote';
5
5
  export type { FootnoteDefToken, FootnoteRefToken } from './markdown-footnote';
6
6
  export { parseFrontMatterFields, scanFrontMatter } from './frontMatter';