@humanspeak/svelte-markdown 0.8.8 → 0.8.10

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/README.md CHANGED
@@ -122,6 +122,166 @@ import type {
122
122
  } from '@humanspeak/svelte-markdown'
123
123
  ```
124
124
 
125
+ ## Exports for programmatic overrides
126
+
127
+ You can import renderer maps and helper keys to selectively override behavior.
128
+
129
+ ```ts
130
+ import SvelteMarkdown, {
131
+ // Maps
132
+ defaultRenderers, // markdown renderer map
133
+ Html, // HTML renderer map
134
+
135
+ // Keys
136
+ rendererKeys, // markdown renderer keys (excludes 'html')
137
+ htmlRendererKeys, // HTML renderer tag names
138
+
139
+ // Utility components
140
+ Unsupported, // markdown-level unsupported fallback
141
+ UnsupportedHTML // HTML-level unsupported fallback
142
+ } from '@humanspeak/svelte-markdown'
143
+
144
+ // Example: override a subset
145
+ const customRenderers = {
146
+ ...defaultRenderers,
147
+ link: CustomLink,
148
+ html: {
149
+ ...Html,
150
+ span: CustomSpan
151
+ }
152
+ }
153
+
154
+ // Optional: iterate keys when building overrides dynamically
155
+ for (const key of rendererKeys) {
156
+ // if (key === 'paragraph') customRenderers.paragraph = MyParagraph
157
+ }
158
+ for (const tag of htmlRendererKeys) {
159
+ // if (tag === 'div') customRenderers.html.div = MyDiv
160
+ }
161
+ ```
162
+
163
+ Notes
164
+
165
+ - `rendererKeys` intentionally excludes `html`. Use `htmlRendererKeys` for HTML tag overrides.
166
+ - `Unsupported` and `UnsupportedHTML` are available if you want a pass-through fallback strategy.
167
+
168
+ ## Helper utilities for allow/deny strategies
169
+
170
+ These helpers make it easy to either allow only a subset or exclude only a subset of renderers without writing huge maps by hand.
171
+
172
+ - **HTML helpers**
173
+ - `buildUnsupportedHTML()`: returns a map where every HTML tag uses `UnsupportedHTML`.
174
+ - `allowHtmlOnly(allowed)`: enable only the provided tags; others use `UnsupportedHTML`.
175
+ - Accepts tag names like `'strong'` or tuples like `['div', MyDiv]` to plug in custom components.
176
+ - `excludeHtmlOnly(excluded, overrides?)`: disable only the listed tags (mapped to `UnsupportedHTML`), with optional overrides for non-excluded tags using tuples.
177
+ - **Markdown helpers (non-HTML)**
178
+ - `buildUnsupportedRenderers()`: returns a map where all markdown renderers (except `html`) use `Unsupported`.
179
+ - `allowRenderersOnly(allowed)`: enable only the provided markdown renderer keys; others use `Unsupported`.
180
+ - Accepts keys like `'paragraph'` or tuples like `['paragraph', MyParagraph]` to plug in custom components.
181
+ - `excludeRenderersOnly(excluded, overrides?)`: disable only the listed markdown renderer keys, with optional overrides for non-excluded keys using tuples.
182
+
183
+ ### HTML helpers in context
184
+
185
+ The HTML helpers return an `HtmlRenderers` map to be used inside the `html` key of the overall `renderers` map. They do not replace the entire `renderers` object by themselves.
186
+
187
+ Basic: keep markdown defaults, allow only a few HTML tags (others become `UnsupportedHTML`):
188
+
189
+ ```ts
190
+ import SvelteMarkdown, { defaultRenderers, allowHtmlOnly } from '@humanspeak/svelte-markdown'
191
+
192
+ const renderers = {
193
+ ...defaultRenderers, // keep markdown defaults
194
+ html: allowHtmlOnly(['strong', 'em', 'a']) // restrict HTML
195
+ }
196
+ ```
197
+
198
+ Allow a custom component for one tag while allowing others with defaults:
199
+
200
+ ```ts
201
+ import SvelteMarkdown, { defaultRenderers, allowHtmlOnly } from '@humanspeak/svelte-markdown'
202
+
203
+ const renderers = {
204
+ ...defaultRenderers,
205
+ html: allowHtmlOnly([['div', MyDiv], 'a'])
206
+ }
207
+ ```
208
+
209
+ Exclude just a few HTML tags; keep all other HTML tags as defaults:
210
+
211
+ ```ts
212
+ import SvelteMarkdown, { defaultRenderers, excludeHtmlOnly } from '@humanspeak/svelte-markdown'
213
+
214
+ const renderers = {
215
+ ...defaultRenderers,
216
+ html: excludeHtmlOnly(['span', 'iframe'])
217
+ }
218
+
219
+ // Or exclude 'span', but override 'a' to CustomA
220
+ const renderersWithOverride = {
221
+ ...defaultRenderers,
222
+ html: excludeHtmlOnly(['span'], [['a', CustomA]])
223
+ }
224
+ ```
225
+
226
+ Disable all HTML quickly (markdown defaults unchanged):
227
+
228
+ ```ts
229
+ import SvelteMarkdown, { defaultRenderers, buildUnsupportedHTML } from '@humanspeak/svelte-markdown'
230
+
231
+ const renderers = {
232
+ ...defaultRenderers,
233
+ html: buildUnsupportedHTML()
234
+ }
235
+ ```
236
+
237
+ ### Markdown-only (non-HTML) scenarios
238
+
239
+ Allow only paragraph and link with defaults, disable others:
240
+
241
+ ```ts
242
+ import { allowRenderersOnly } from '@humanspeak/svelte-markdown'
243
+
244
+ const md = allowRenderersOnly(['paragraph', 'link'])
245
+ ```
246
+
247
+ Exclude just link; keep others as defaults:
248
+
249
+ ```ts
250
+ import { excludeRenderersOnly } from '@humanspeak/svelte-markdown'
251
+
252
+ const md = excludeRenderersOnly(['link'])
253
+ ```
254
+
255
+ Disable all markdown renderers (except `html`) quickly:
256
+
257
+ ```ts
258
+ import { buildUnsupportedRenderers } from '@humanspeak/svelte-markdown'
259
+
260
+ const md = buildUnsupportedRenderers()
261
+ ```
262
+
263
+ ### Combine HTML and Markdown helpers
264
+
265
+ You can combine both maps in `renderers` for `SvelteMarkdown`.
266
+
267
+ ```svelte
268
+ <script lang="ts">
269
+ import SvelteMarkdown, { allowRenderersOnly, allowHtmlOnly } from '@humanspeak/svelte-markdown'
270
+
271
+ const renderers = {
272
+ // Only allow a minimal markdown set
273
+ ...allowRenderersOnly(['paragraph', 'link']),
274
+
275
+ // Configure HTML separately (only strong/em/a)
276
+ html: allowHtmlOnly(['strong', 'em', 'a'])
277
+ }
278
+
279
+ const source = `# Title\n\nThis has <strong>HTML</strong> and [a link](https://example.com).`
280
+ </script>
281
+
282
+ <SvelteMarkdown {source} {renderers} />
283
+ ```
284
+
125
285
  ## Custom Renderer Example
126
286
 
127
287
  Here's a complete example of a custom renderer with TypeScript support:
@@ -109,44 +109,13 @@
109
109
  {#each row ?? [] as cells, i (i)}
110
110
  {@const { align: _align, ...cellRest } = rest}
111
111
  <renderers.tablecell
112
+ {...cellRest}
112
113
  header={false}
113
114
  align={(rest.align as string[])[i]}
114
- {...cellRest}
115
115
  >
116
- {#if cells.tokens?.[0]?.type === 'html'}
117
- {@const token = cells.tokens[0] as Token & {
118
- tag: string
119
- tokens?: Token[]
120
- }}
121
- {@const { tag, ...localRest } = token}
122
- {@const htmlTag = tag as keyof typeof Html}
123
- {#if renderers.html && htmlTag in renderers.html}
124
- {@const HtmlComponent =
125
- renderers.html[
126
- htmlTag as keyof typeof renderers.html
127
- ]}
128
- {#if HtmlComponent}
129
- <HtmlComponent {...token}>
130
- {#if token.tokens?.length}
131
- <Parser
132
- tokens={token.tokens}
133
- {renderers}
134
- {...Object.fromEntries(
135
- Object.entries(
136
- localRest
137
- ).filter(
138
- ([key]) =>
139
- key !== 'attributes'
140
- )
141
- )}
142
- />
143
- {/if}
144
- </HtmlComponent>
145
- {/if}
146
- {/if}
147
- {:else}
148
- <Parser tokens={cells.tokens} {renderers} />
149
- {/if}
116
+ {#each cells.tokens ?? [] as cellToken, index (index)}
117
+ <Parser {...cellRest} {...cellToken} {renderers} />
118
+ {/each}
150
119
  </renderers.tablecell>
151
120
  {/each}
152
121
  </renderers.tablerow>
@@ -158,7 +127,8 @@
158
127
  {:else if type === 'list' && renderers.list}
159
128
  {#if ordered}
160
129
  <renderers.list {ordered} {...rest}>
161
- {@const { items, ...parserRest }: {items: Props[]} = rest}
130
+ {@const { items: _items, ...parserRest } = rest}
131
+ {@const items = (_items as Props[] | undefined) ?? []}
162
132
  {#each items as item, index (index)}
163
133
  {@const OrderedListComponent = renderers.orderedlistitem || renderers.listitem}
164
134
  {#if OrderedListComponent}
@@ -170,7 +140,8 @@
170
140
  </renderers.list>
171
141
  {:else}
172
142
  <renderers.list {ordered} {...rest}>
173
- {@const { items, ...parserRest }: {items: Props[]} = rest}
143
+ {@const { items: _items, ...parserRest } = rest}
144
+ {@const items = (_items as Props[] | undefined) ?? []}
174
145
  {#each items as item, index (index)}
175
146
  {@const UnorderedListComponent =
176
147
  renderers.unorderedlistitem || renderers.listitem}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,12 @@
1
- import type { HtmlRenderers } from './renderers/html/index.js';
2
- import type { RendererComponent, Renderers, Token, TokensList } from './utils/markdown-parser.js';
1
+ import { type HtmlRenderers } from './renderers/html/index.js';
3
2
  import SvelteMarkdown from './SvelteMarkdown.svelte';
4
3
  import type { SvelteMarkdownOptions, SvelteMarkdownProps } from './types.js';
4
+ import { defaultRenderers, type RendererComponent, type Renderers, type Token, type TokensList } from './utils/markdown-parser.js';
5
5
  export default SvelteMarkdown;
6
+ export { default as Html, UnsupportedHTML } from './renderers/html/index.js';
7
+ export { Unsupported } from './renderers/index.js';
8
+ export { allowHtmlOnly, buildUnsupportedHTML, excludeHtmlOnly } from './utils/unsupportedHtmlRenderers.js';
9
+ export { allowRenderersOnly, buildUnsupportedRenderers, excludeRenderersOnly } from './utils/unsupportedRenderers.js';
10
+ export { defaultRenderers };
11
+ export { htmlRendererKeysInternal as htmlRendererKeys, rendererKeysInternal as rendererKeys } from './utils/rendererKeys.js';
6
12
  export type { HtmlRenderers, RendererComponent, Renderers, SvelteMarkdownOptions, SvelteMarkdownProps, Token, TokensList };
package/dist/index.js CHANGED
@@ -1,2 +1,12 @@
1
+ // Reexport your entry components here
2
+ import {} from './renderers/html/index.js';
1
3
  import SvelteMarkdown from './SvelteMarkdown.svelte';
4
+ import { defaultRenderers } from './utils/markdown-parser.js';
2
5
  export default SvelteMarkdown;
6
+ export { default as Html, UnsupportedHTML } from './renderers/html/index.js';
7
+ export { Unsupported } from './renderers/index.js';
8
+ export { allowHtmlOnly, buildUnsupportedHTML, excludeHtmlOnly } from './utils/unsupportedHtmlRenderers.js';
9
+ export { allowRenderersOnly, buildUnsupportedRenderers, excludeRenderersOnly } from './utils/unsupportedRenderers.js';
10
+ export { defaultRenderers };
11
+ // Canonical key lists (public API names)
12
+ export { htmlRendererKeysInternal as htmlRendererKeys, rendererKeysInternal as rendererKeys } from './utils/rendererKeys.js';
@@ -0,0 +1,5 @@
1
+ <script lang="ts">
2
+ const { raw } = $props()
3
+ </script>
4
+
5
+ {raw}
@@ -0,0 +1,5 @@
1
+ declare const Unsupported: import("svelte").Component<{
2
+ raw: any;
3
+ }, {}, "">;
4
+ type Unsupported = ReturnType<typeof Unsupported>;
5
+ export default Unsupported;
@@ -0,0 +1,9 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ attributes?: Record<string, any> // eslint-disable-line @typescript-eslint/no-explicit-any
4
+ }
5
+
6
+ const { attributes = {} }: Props = $props()
7
+ </script>
8
+
9
+ <br {...attributes} />
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ attributes?: Record<string, any>;
3
+ }
4
+ declare const Br: import("svelte").Component<Props, {}, "">;
5
+ type Br = ReturnType<typeof Br>;
6
+ export default Br;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ const {
5
+ children,
6
+ tag,
7
+ attributes
8
+ }: { children?: Snippet; tag: string; attributes?: Record<string, string> } = $props()
9
+
10
+ const attributesString =
11
+ Object.entries(attributes || {})
12
+ .map(([key, value]) => `${key}="${value}"`)
13
+ .join(' ') || ''
14
+ </script>
15
+
16
+ &lt;{tag}{attributesString ? ` ${attributesString}` : ''}&gt;{@render children?.()}&lt;/{tag}&gt;
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ children?: Snippet;
4
+ tag: string;
5
+ attributes?: Record<string, string>;
6
+ };
7
+ declare const UnsupportedHtml: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type UnsupportedHtml = ReturnType<typeof UnsupportedHtml>;
9
+ export default UnsupportedHtml;
@@ -1,6 +1,8 @@
1
1
  import type { Component } from 'svelte';
2
+ import UnsupportedHTML from './_UnsupportedHTML.svelte';
2
3
  export interface HtmlRenderers {
3
4
  [key: string]: Component<any, any, any> | null;
4
5
  }
5
6
  export declare const Html: HtmlRenderers;
6
7
  export default Html;
8
+ export { UnsupportedHTML };
@@ -8,6 +8,7 @@ import B from './B.svelte';
8
8
  import Bdi from './Bdi.svelte';
9
9
  import Bdo from './Bdo.svelte';
10
10
  import Blockquote from './Blockquote.svelte';
11
+ import Br from './Br.svelte';
11
12
  import Button from './Button.svelte';
12
13
  import Canvas from './Canvas.svelte';
13
14
  import Cite from './Cite.svelte';
@@ -80,6 +81,7 @@ import Track from './Track.svelte';
80
81
  import U from './U.svelte';
81
82
  import Ul from './Ul.svelte';
82
83
  import Var from './Var.svelte';
84
+ import UnsupportedHTML from './_UnsupportedHTML.svelte';
83
85
  export const Html = {
84
86
  a: A,
85
87
  abbr: Abbr,
@@ -91,6 +93,7 @@ export const Html = {
91
93
  bdi: Bdi,
92
94
  bdo: Bdo,
93
95
  blockquote: Blockquote,
96
+ br: Br,
94
97
  button: Button,
95
98
  canvas: Canvas,
96
99
  cite: Cite,
@@ -165,3 +168,4 @@ export const Html = {
165
168
  var: Var
166
169
  };
167
170
  export default Html;
171
+ export { UnsupportedHTML };
@@ -1,3 +1,4 @@
1
+ export { default as Unsupported } from './_Unsupported.svelte';
1
2
  export { default as Blockquote } from './Blockquote.svelte';
2
3
  export { default as Br } from './Br.svelte';
3
4
  export { default as Code } from './Code.svelte';
@@ -1,3 +1,4 @@
1
+ export { default as Unsupported } from './_Unsupported.svelte';
1
2
  export { default as Blockquote } from './Blockquote.svelte';
2
3
  export { default as Br } from './Br.svelte';
3
4
  export { default as Code } from './Code.svelte';
@@ -0,0 +1,6 @@
1
+ import Html from '../renderers/html/index.js';
2
+ import { type Renderers } from './markdown-parser.js';
3
+ export type RendererKey = Exclude<keyof Renderers, 'html'>;
4
+ export declare const rendererKeysInternal: RendererKey[];
5
+ export type HtmlKey = keyof typeof Html;
6
+ export declare const htmlRendererKeysInternal: HtmlKey[];
@@ -0,0 +1,4 @@
1
+ import Html from '../renderers/html/index.js';
2
+ import { defaultRenderers } from './markdown-parser.js';
3
+ export const rendererKeysInternal = Object.keys(defaultRenderers).filter((k) => k !== 'html');
4
+ export const htmlRendererKeysInternal = Object.keys(Html);
@@ -94,6 +94,7 @@ export declare const containsMultipleTags: (html: string) => boolean;
94
94
  *
95
95
  * Key features:
96
96
  * - Breaks down complex HTML structures into atomic tokens
97
+ * - Formats self-closing tags with proper syntax (e.g., <br> -> <br/>)
97
98
  * - Maintains attribute information
98
99
  * - Preserves proper nesting relationships
99
100
  * - Handles malformed HTML gracefully
@@ -12,6 +12,11 @@ import * as htmlparser2 from 'htmlparser2';
12
12
  */
13
13
  const HTML_TAG_PATTERN = /<\/?([a-zA-Z][a-zA-Z0-9-]{0,})(?:\s+[^>]*)?>/;
14
14
  const htmlTagRegex = new RegExp(HTML_TAG_PATTERN);
15
+ /**
16
+ * Regex pattern for self-closing HTML tags.
17
+ * @const {RegExp}
18
+ */
19
+ const SELF_CLOSING_TAGS = /^(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)$/i;
15
20
  /**
16
21
  * Analyzes a string to determine if it contains an HTML tag and its characteristics.
17
22
  *
@@ -37,6 +42,33 @@ export const isHtmlOpenTag = (raw) => {
37
42
  return null;
38
43
  return { tag: match[1], isOpening: !raw.startsWith('</') };
39
44
  };
45
+ /**
46
+ * Formats individual HTML tokens to ensure self-closing tags are properly formatted.
47
+ * This handles cases like <br> -> <br/> without affecting the token structure.
48
+ *
49
+ * @param {Token} token - HTML token to format
50
+ * @returns {Token} Formatted token with proper self-closing syntax
51
+ */
52
+ const formatSelfClosingHtmlToken = (token) => {
53
+ // Extract tag name from raw HTML
54
+ const tagMatch = token.raw.match(/<\/?([a-zA-Z][a-zA-Z0-9-]*)/i);
55
+ if (!tagMatch)
56
+ return token;
57
+ const tagName = tagMatch[1];
58
+ if (!SELF_CLOSING_TAGS.test(tagName))
59
+ return token;
60
+ // If it's a self-closing tag and doesn't already end with />, format it properly
61
+ if (!token.raw.endsWith('/>')) {
62
+ const formattedRaw = token.raw.replace(/\s*>$/, '/>');
63
+ return {
64
+ ...token,
65
+ raw: formattedRaw,
66
+ tag: tagName,
67
+ attributes: extractAttributes(token.raw)
68
+ };
69
+ }
70
+ return token;
71
+ };
40
72
  /**
41
73
  * Parses HTML attributes from a tag string into a structured object.
42
74
  * Handles both single and double quoted attributes.
@@ -117,7 +149,6 @@ export const extractAttributes = (raw) => {
117
149
  export const parseHtmlBlock = (html) => {
118
150
  const tokens = [];
119
151
  let currentText = '';
120
- const selfClosingTags = /^(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)$/i;
121
152
  const openTags = [];
122
153
  const parser = new htmlparser2.Parser({
123
154
  onopentag: (name, attributes) => {
@@ -129,8 +160,7 @@ export const parseHtmlBlock = (html) => {
129
160
  });
130
161
  currentText = '';
131
162
  }
132
- openTags.push(name);
133
- if (selfClosingTags.test(name)) {
163
+ if (SELF_CLOSING_TAGS.test(name)) {
134
164
  tokens.push({
135
165
  type: 'html',
136
166
  raw: `<${name}${Object.entries(attributes)
@@ -141,6 +171,7 @@ export const parseHtmlBlock = (html) => {
141
171
  });
142
172
  }
143
173
  else {
174
+ openTags.push(name);
144
175
  tokens.push({
145
176
  type: 'html',
146
177
  raw: `<${name}${Object.entries(attributes)
@@ -165,7 +196,7 @@ export const parseHtmlBlock = (html) => {
165
196
  }
166
197
  // Only add closing tag if we found its opening tag
167
198
  // and it's not a self-closing tag
168
- if (openTags.includes(name) && !selfClosingTags.test(name)) {
199
+ if (openTags.includes(name) && !SELF_CLOSING_TAGS.test(name)) {
169
200
  if (html.includes(`</${name}>`)) {
170
201
  tokens.push({
171
202
  type: 'html',
@@ -219,6 +250,7 @@ export const containsMultipleTags = (html) => {
219
250
  *
220
251
  * Key features:
221
252
  * - Breaks down complex HTML structures into atomic tokens
253
+ * - Formats self-closing tags with proper syntax (e.g., <br> -> <br/>)
222
254
  * - Maintains attribute information
223
255
  * - Preserves proper nesting relationships
224
256
  * - Handles malformed HTML gracefully
@@ -251,6 +283,7 @@ export const shrinkHtmlTokens = (tokens) => {
251
283
  else if (token.type === 'table') {
252
284
  // Process header cells
253
285
  if (token.header) {
286
+ // @ts-expect-error: expected any
254
287
  token.header = token.header.map((cell) => ({
255
288
  ...cell,
256
289
  tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
@@ -258,7 +291,10 @@ export const shrinkHtmlTokens = (tokens) => {
258
291
  }
259
292
  // Process row cells
260
293
  if (token.rows) {
261
- token.rows = token.rows.map((row) => row.map((cell) => ({
294
+ // @ts-expect-error: expected any
295
+ token.rows = token.rows.map((row) =>
296
+ // @ts-expect-error: expected any
297
+ row.map((cell) => ({
262
298
  ...cell,
263
299
  tokens: cell.tokens ? shrinkHtmlTokens(cell.tokens) : []
264
300
  })));
@@ -269,6 +305,11 @@ export const shrinkHtmlTokens = (tokens) => {
269
305
  // Parse HTML with multiple tags into separate tokens
270
306
  result.push(...parseHtmlBlock(token.raw));
271
307
  }
308
+ else if (token.type === 'html') {
309
+ // Format self-closing tags properly (e.g., <br> -> <br/>)
310
+ const formattedToken = formatSelfClosingHtmlToken(token);
311
+ result.push(formattedToken);
312
+ }
272
313
  else {
273
314
  result.push(token);
274
315
  }
@@ -0,0 +1,62 @@
1
+ import { type HtmlRenderers } from '../renderers/html/index.js';
2
+ import { type HtmlKey } from './rendererKeys.js';
3
+ import type { Component } from 'svelte';
4
+ /**
5
+ * Builds a map of HTML renderers where every known HTML tag is mapped to `UnsupportedHTML`.
6
+ * This is useful when you want to disable all built‑in HTML rendering and provide
7
+ * explicit allow-lists or a minimal subset afterwards.
8
+ *
9
+ * @function buildUnsupportedHTML
10
+ * @returns {HtmlRenderers} A map containing all tags set to `UnsupportedHTML`.
11
+ * @example
12
+ * import { buildUnsupportedHTML } from '@humanspeak/svelte-markdown'
13
+ * const renderers = {
14
+ * html: buildUnsupportedHTML()
15
+ * }
16
+ */
17
+ export declare const buildUnsupportedHTML: () => HtmlRenderers;
18
+ /**
19
+ * Produces an HTML renderer map that allows only the specified tags.
20
+ * All non‑listed tags are set to `UnsupportedHTML`.
21
+ *
22
+ * Each entry can be either a tag name (to use the library’s default component for that tag),
23
+ * or a tuple `[tag, component]` to specify a custom component for that tag.
24
+ *
25
+ * @function allowHtmlOnly
26
+ * @param {Array<keyof HtmlRenderers | [keyof HtmlRenderers, Component | null]>} allowed
27
+ * Tag names to allow, or tuples specifying a custom component per allowed tag.
28
+ * Any tag not listed will be mapped to `UnsupportedHTML`.
29
+ * @returns {HtmlRenderers} A renderer map with only the provided tags enabled.
30
+ * @example
31
+ * // Only allow strong/em/a with default components; everything else UnsupportedHTML
32
+ * const html = allowHtmlOnly(['strong', 'em', 'a'])
33
+ *
34
+ * @example
35
+ * // Allow a custom component for div while allowing the default for a
36
+ * const html = allowHtmlOnly([['div', MyDiv], 'a'])
37
+ */
38
+ export declare const allowHtmlOnly: (allowed: Array<HtmlKey | [HtmlKey, Component | null]>) => HtmlRenderers;
39
+ /**
40
+ * Produces an HTML renderer map that excludes only the specified tags.
41
+ * Excluded tags are mapped to `UnsupportedHTML`, while all other tags use the
42
+ * library’s default components. Optionally, you can override specific non‑excluded
43
+ * tags with custom components using `[tag, component]` tuples.
44
+ *
45
+ * Exclusions take precedence over overrides. If a tag is listed in `excluded`, an
46
+ * override for the same tag will be ignored.
47
+ *
48
+ * @function excludeHtmlOnly
49
+ * @param {Array<keyof HtmlRenderers>} excluded
50
+ * Tag names to exclude (set to `UnsupportedHTML`).
51
+ * @param {Array<[keyof HtmlRenderers, Component | null]>} [overrides]
52
+ * Optional tuples mapping non‑excluded tags to custom components.
53
+ * @returns {HtmlRenderers} A renderer map with only the provided tags excluded.
54
+ * @example
55
+ * // Disable just span and div, keep others as defaults
56
+ * const html = excludeHtmlOnly(['span', 'div'])
57
+ *
58
+ * @example
59
+ * // Disable span; override 'a' to CustomA component
60
+ * const html = excludeHtmlOnly(['span'], [['a', CustomA]])
61
+ */
62
+ export declare const excludeHtmlOnly: (excluded: HtmlKey[], overrides?: Array<[HtmlKey, Component | null]>) => HtmlRenderers;
@@ -0,0 +1,99 @@
1
+ import Html, { UnsupportedHTML } from '../renderers/html/index.js';
2
+ import { htmlRendererKeysInternal } from './rendererKeys.js';
3
+ /**
4
+ * Builds a map of HTML renderers where every known HTML tag is mapped to `UnsupportedHTML`.
5
+ * This is useful when you want to disable all built‑in HTML rendering and provide
6
+ * explicit allow-lists or a minimal subset afterwards.
7
+ *
8
+ * @function buildUnsupportedHTML
9
+ * @returns {HtmlRenderers} A map containing all tags set to `UnsupportedHTML`.
10
+ * @example
11
+ * import { buildUnsupportedHTML } from '@humanspeak/svelte-markdown'
12
+ * const renderers = {
13
+ * html: buildUnsupportedHTML()
14
+ * }
15
+ */
16
+ export const buildUnsupportedHTML = () => {
17
+ const result = {};
18
+ for (const key of htmlRendererKeysInternal) {
19
+ result[key] = UnsupportedHTML;
20
+ }
21
+ return result;
22
+ };
23
+ /**
24
+ * Produces an HTML renderer map that allows only the specified tags.
25
+ * All non‑listed tags are set to `UnsupportedHTML`.
26
+ *
27
+ * Each entry can be either a tag name (to use the library’s default component for that tag),
28
+ * or a tuple `[tag, component]` to specify a custom component for that tag.
29
+ *
30
+ * @function allowHtmlOnly
31
+ * @param {Array<keyof HtmlRenderers | [keyof HtmlRenderers, Component | null]>} allowed
32
+ * Tag names to allow, or tuples specifying a custom component per allowed tag.
33
+ * Any tag not listed will be mapped to `UnsupportedHTML`.
34
+ * @returns {HtmlRenderers} A renderer map with only the provided tags enabled.
35
+ * @example
36
+ * // Only allow strong/em/a with default components; everything else UnsupportedHTML
37
+ * const html = allowHtmlOnly(['strong', 'em', 'a'])
38
+ *
39
+ * @example
40
+ * // Allow a custom component for div while allowing the default for a
41
+ * const html = allowHtmlOnly([['div', MyDiv], 'a'])
42
+ */
43
+ export const allowHtmlOnly = (allowed) => {
44
+ const result = buildUnsupportedHTML();
45
+ for (const entry of allowed) {
46
+ if (Array.isArray(entry)) {
47
+ const [tag, component] = entry;
48
+ // Only set if the tag exists in our Html map
49
+ if (tag in Html)
50
+ result[tag] = component;
51
+ }
52
+ else {
53
+ const tag = entry;
54
+ if (tag in Html)
55
+ result[tag] = Html[tag];
56
+ }
57
+ }
58
+ return result;
59
+ };
60
+ /**
61
+ * Produces an HTML renderer map that excludes only the specified tags.
62
+ * Excluded tags are mapped to `UnsupportedHTML`, while all other tags use the
63
+ * library’s default components. Optionally, you can override specific non‑excluded
64
+ * tags with custom components using `[tag, component]` tuples.
65
+ *
66
+ * Exclusions take precedence over overrides. If a tag is listed in `excluded`, an
67
+ * override for the same tag will be ignored.
68
+ *
69
+ * @function excludeHtmlOnly
70
+ * @param {Array<keyof HtmlRenderers>} excluded
71
+ * Tag names to exclude (set to `UnsupportedHTML`).
72
+ * @param {Array<[keyof HtmlRenderers, Component | null]>} [overrides]
73
+ * Optional tuples mapping non‑excluded tags to custom components.
74
+ * @returns {HtmlRenderers} A renderer map with only the provided tags excluded.
75
+ * @example
76
+ * // Disable just span and div, keep others as defaults
77
+ * const html = excludeHtmlOnly(['span', 'div'])
78
+ *
79
+ * @example
80
+ * // Disable span; override 'a' to CustomA component
81
+ * const html = excludeHtmlOnly(['span'], [['a', CustomA]])
82
+ */
83
+ export const excludeHtmlOnly = (excluded, overrides) => {
84
+ const result = { ...Html };
85
+ for (const tag of excluded) {
86
+ if (tag in Html)
87
+ result[tag] = UnsupportedHTML;
88
+ }
89
+ if (overrides) {
90
+ for (const [tag, component] of overrides) {
91
+ // Exclusions take precedence; do not override excluded tags
92
+ if (excluded.includes(tag))
93
+ continue;
94
+ if (tag in Html)
95
+ result[tag] = component;
96
+ }
97
+ }
98
+ return result;
99
+ };
@@ -0,0 +1,57 @@
1
+ import { type RendererComponent, type Renderers } from './markdown-parser.js';
2
+ import { type RendererKey } from './rendererKeys.js';
3
+ /**
4
+ * Builds a map where every markdown renderer (excluding the special `html` map)
5
+ * is set to the `Unsupported` component.
6
+ *
7
+ * @function buildUnsupportedRenderers
8
+ * @returns {Partial<Renderers>} A map with all non‑HTML renderers set to `Unsupported`.
9
+ * @example
10
+ * import { buildUnsupportedRenderers } from '@humanspeak/svelte-markdown'
11
+ * const renderers = {
12
+ * ...buildUnsupportedRenderers(),
13
+ * html: {} // customize HTML separately
14
+ * }
15
+ */
16
+ export declare const buildUnsupportedRenderers: () => Partial<Renderers>;
17
+ /**
18
+ * Produces a renderer map that allows only the specified markdown renderers (excluding `html`).
19
+ * All non‑listed renderer keys are set to `Unsupported`.
20
+ * Each entry can be either a renderer key (to use the library’s default component),
21
+ * or a tuple `[key, component]` to specify a custom component for that key.
22
+ *
23
+ * @function allowRenderersOnly
24
+ * @param {Array<RendererKey | [RendererKey, RendererComponent]>} allowed
25
+ * Renderer keys to allow, or tuples for custom component overrides.
26
+ * @returns {Partial<Renderers>} A renderer map with only the provided keys enabled.
27
+ * @example
28
+ * // Allow only paragraph and link with defaults
29
+ * const renderers = allowRenderersOnly(['paragraph', 'link'])
30
+ *
31
+ * @example
32
+ * // Allow paragraph with a custom component
33
+ * const renderers = allowRenderersOnly([['paragraph', MyParagraph]])
34
+ */
35
+ export declare const allowRenderersOnly: (allowed: Array<RendererKey | [RendererKey, RendererComponent]>) => Partial<Renderers>;
36
+ /**
37
+ * Produces a renderer map that excludes only the specified markdown renderer keys (excluding `html`).
38
+ * Excluded keys are mapped to `Unsupported`, while all other keys use the library’s default components.
39
+ * Optionally override specific non‑excluded keys with custom components via `[key, component]` tuples.
40
+ *
41
+ * Exclusions take precedence over overrides.
42
+ *
43
+ * @function excludeRenderersOnly
44
+ * @param {Array<RendererKey>} excluded
45
+ * Renderer keys to exclude (set to `Unsupported`).
46
+ * @param {Array<[RendererKey, RendererComponent]>} [overrides]
47
+ * Optional tuples mapping non‑excluded keys to custom components.
48
+ * @returns {Partial<Renderers>} A renderer map with only the provided keys excluded.
49
+ * @example
50
+ * // Disable just paragraph and link, keep others as defaults
51
+ * const renderers = excludeRenderersOnly(['paragraph', 'link'])
52
+ *
53
+ * @example
54
+ * // Disable link; override paragraph to a custom component
55
+ * const renderers = excludeRenderersOnly(['link'], [['paragraph', MyParagraph]])
56
+ */
57
+ export declare const excludeRenderersOnly: (excluded: RendererKey[], overrides?: Array<[RendererKey, RendererComponent]>) => Partial<Renderers>;
@@ -0,0 +1,98 @@
1
+ import { Unsupported } from '../renderers/index.js';
2
+ import { defaultRenderers } from './markdown-parser.js';
3
+ import { rendererKeysInternal } from './rendererKeys.js';
4
+ const allRendererKeys = rendererKeysInternal;
5
+ /**
6
+ * Builds a map where every markdown renderer (excluding the special `html` map)
7
+ * is set to the `Unsupported` component.
8
+ *
9
+ * @function buildUnsupportedRenderers
10
+ * @returns {Partial<Renderers>} A map with all non‑HTML renderers set to `Unsupported`.
11
+ * @example
12
+ * import { buildUnsupportedRenderers } from '@humanspeak/svelte-markdown'
13
+ * const renderers = {
14
+ * ...buildUnsupportedRenderers(),
15
+ * html: {} // customize HTML separately
16
+ * }
17
+ */
18
+ export const buildUnsupportedRenderers = () => {
19
+ const result = {};
20
+ for (const key of allRendererKeys) {
21
+ result[key] = Unsupported;
22
+ }
23
+ return result;
24
+ };
25
+ /**
26
+ * Produces a renderer map that allows only the specified markdown renderers (excluding `html`).
27
+ * All non‑listed renderer keys are set to `Unsupported`.
28
+ * Each entry can be either a renderer key (to use the library’s default component),
29
+ * or a tuple `[key, component]` to specify a custom component for that key.
30
+ *
31
+ * @function allowRenderersOnly
32
+ * @param {Array<RendererKey | [RendererKey, RendererComponent]>} allowed
33
+ * Renderer keys to allow, or tuples for custom component overrides.
34
+ * @returns {Partial<Renderers>} A renderer map with only the provided keys enabled.
35
+ * @example
36
+ * // Allow only paragraph and link with defaults
37
+ * const renderers = allowRenderersOnly(['paragraph', 'link'])
38
+ *
39
+ * @example
40
+ * // Allow paragraph with a custom component
41
+ * const renderers = allowRenderersOnly([['paragraph', MyParagraph]])
42
+ */
43
+ export const allowRenderersOnly = (allowed) => {
44
+ const result = buildUnsupportedRenderers();
45
+ for (const entry of allowed) {
46
+ if (Array.isArray(entry)) {
47
+ const [key, component] = entry;
48
+ if (allRendererKeys.includes(key))
49
+ result[key] = component;
50
+ }
51
+ else {
52
+ const key = entry;
53
+ if (allRendererKeys.includes(key))
54
+ result[key] = defaultRenderers[key];
55
+ }
56
+ }
57
+ return result;
58
+ };
59
+ /**
60
+ * Produces a renderer map that excludes only the specified markdown renderer keys (excluding `html`).
61
+ * Excluded keys are mapped to `Unsupported`, while all other keys use the library’s default components.
62
+ * Optionally override specific non‑excluded keys with custom components via `[key, component]` tuples.
63
+ *
64
+ * Exclusions take precedence over overrides.
65
+ *
66
+ * @function excludeRenderersOnly
67
+ * @param {Array<RendererKey>} excluded
68
+ * Renderer keys to exclude (set to `Unsupported`).
69
+ * @param {Array<[RendererKey, RendererComponent]>} [overrides]
70
+ * Optional tuples mapping non‑excluded keys to custom components.
71
+ * @returns {Partial<Renderers>} A renderer map with only the provided keys excluded.
72
+ * @example
73
+ * // Disable just paragraph and link, keep others as defaults
74
+ * const renderers = excludeRenderersOnly(['paragraph', 'link'])
75
+ *
76
+ * @example
77
+ * // Disable link; override paragraph to a custom component
78
+ * const renderers = excludeRenderersOnly(['link'], [['paragraph', MyParagraph]])
79
+ */
80
+ export const excludeRenderersOnly = (excluded, overrides) => {
81
+ const result = {};
82
+ for (const key of allRendererKeys) {
83
+ result[key] = defaultRenderers[key];
84
+ }
85
+ for (const key of excluded) {
86
+ if (allRendererKeys.includes(key))
87
+ result[key] = Unsupported;
88
+ }
89
+ if (overrides) {
90
+ for (const [key, component] of overrides) {
91
+ if (excluded.includes(key))
92
+ continue;
93
+ if (allRendererKeys.includes(key))
94
+ result[key] = component;
95
+ }
96
+ }
97
+ return result;
98
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-markdown",
3
- "version": "0.8.8",
3
+ "version": "0.8.10",
4
4
  "description": "A powerful, customizable markdown renderer for Svelte with TypeScript support",
5
5
  "keywords": [
6
6
  "svelte",
@@ -58,6 +58,7 @@
58
58
  "lint": "prettier --check . && eslint .",
59
59
  "lint:fix": "npm run format && eslint . --fix",
60
60
  "package": "svelte-kit sync && svelte-package && publint",
61
+ "prepare": "husky",
61
62
  "prepublishOnly": "npm run package",
62
63
  "preview": "vite preview",
63
64
  "test": "vitest run --coverage",
@@ -77,47 +78,48 @@
77
78
  "dependencies": {
78
79
  "github-slugger": "^2.0.0",
79
80
  "htmlparser2": "^10.0.0",
80
- "marked": "^16.0.0"
81
+ "marked": "^16.1.2"
81
82
  },
82
83
  "devDependencies": {
83
- "@eslint/compat": "^1.3.1",
84
- "@eslint/js": "^9.31.0",
85
- "@playwright/test": "^1.54.1",
86
- "@sveltejs/adapter-auto": "^6.0.1",
87
- "@sveltejs/kit": "^2.22.5",
88
- "@sveltejs/package": "^2.3.12",
89
- "@sveltejs/vite-plugin-svelte": "^6.0.0",
90
- "@testing-library/jest-dom": "^6.6.3",
84
+ "@eslint/compat": "^1.3.2",
85
+ "@eslint/js": "^9.33.0",
86
+ "@playwright/test": "^1.54.2",
87
+ "@sveltejs/adapter-auto": "^6.0.2",
88
+ "@sveltejs/kit": "^2.27.3",
89
+ "@sveltejs/package": "^2.4.1",
90
+ "@sveltejs/vite-plugin-svelte": "^6.1.1",
91
+ "@testing-library/jest-dom": "^6.6.4",
91
92
  "@testing-library/svelte": "^5.2.8",
92
93
  "@testing-library/user-event": "^14.6.1",
93
- "@types/node": "^24.0.13",
94
- "@typescript-eslint/eslint-plugin": "^8.36.0",
95
- "@typescript-eslint/parser": "^8.36.0",
94
+ "@types/node": "^24.2.1",
95
+ "@typescript-eslint/eslint-plugin": "^8.39.0",
96
+ "@typescript-eslint/parser": "^8.39.0",
96
97
  "@vitest/coverage-v8": "^3.2.4",
97
- "eslint": "^9.31.0",
98
- "eslint-config-prettier": "^10.1.5",
98
+ "eslint": "^9.33.0",
99
+ "eslint-config-prettier": "^10.1.8",
99
100
  "eslint-plugin-import": "^2.32.0",
100
- "eslint-plugin-svelte": "^3.10.1",
101
+ "eslint-plugin-svelte": "^3.11.0",
101
102
  "eslint-plugin-unused-imports": "^4.1.4",
102
103
  "globals": "^16.3.0",
104
+ "husky": "^9.1.7",
103
105
  "jsdom": "^26.1.0",
104
106
  "prettier": "^3.6.2",
105
- "prettier-plugin-organize-imports": "^4.1.0",
107
+ "prettier-plugin-organize-imports": "^4.2.0",
106
108
  "prettier-plugin-svelte": "^3.4.0",
107
109
  "prettier-plugin-tailwindcss": "^0.6.14",
108
110
  "publint": "^0.3.12",
109
- "svelte": "^5.35.6",
110
- "svelte-check": "^4.2.2",
111
- "typescript": "^5.8.3",
112
- "typescript-eslint": "^8.36.0",
113
- "vite": "^7.0.4",
111
+ "svelte": "^5.38.0",
112
+ "svelte-check": "^4.3.1",
113
+ "typescript": "^5.9.2",
114
+ "typescript-eslint": "^8.39.0",
115
+ "vite": "^7.1.1",
114
116
  "vitest": "^3.2.4"
115
117
  },
116
118
  "peerDependencies": {
117
119
  "svelte": "^5.0.0"
118
120
  },
119
121
  "volta": {
120
- "node": "22.17.0"
122
+ "node": "22.17.1"
121
123
  },
122
124
  "publishConfig": {
123
125
  "access": "public"