@dbcdk/react-components 0.0.208 → 0.0.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/client.cjs +10 -0
  2. package/dist/client.d.ts +4 -0
  3. package/dist/client.js +2 -0
  4. package/dist/components/forms/markdown-editor/MarkdownEditor.cjs +475 -0
  5. package/dist/components/forms/markdown-editor/MarkdownEditor.d.ts +68 -0
  6. package/dist/components/forms/markdown-editor/MarkdownEditor.js +468 -0
  7. package/dist/components/forms/markdown-editor/MarkdownEditor.module.css +202 -0
  8. package/dist/components/forms/markdown-editor/MarkdownViewer.cjs +29 -0
  9. package/dist/components/forms/markdown-editor/MarkdownViewer.d.ts +27 -0
  10. package/dist/components/forms/markdown-editor/MarkdownViewer.js +23 -0
  11. package/dist/components/forms/markdown-editor/MarkdownViewer.module.css +97 -0
  12. package/dist/components/forms/markdown-editor/markdown.utils.cjs +325 -0
  13. package/dist/components/forms/markdown-editor/markdown.utils.d.ts +66 -0
  14. package/dist/components/forms/markdown-editor/markdown.utils.js +310 -0
  15. package/dist/components/listbox/Listbox.cjs +2 -2
  16. package/dist/components/listbox/Listbox.d.ts +2 -2
  17. package/dist/components/listbox/Listbox.js +2 -2
  18. package/dist/components/meta-bar/MetaBar.cjs +2 -2
  19. package/dist/components/meta-bar/MetaBar.d.ts +2 -1
  20. package/dist/components/meta-bar/MetaBar.js +2 -2
  21. package/dist/components/meta-bar/MetaBar.module.css +7 -0
  22. package/dist/components/overlay/side-panel/SidePanel.cjs +80 -2
  23. package/dist/components/overlay/side-panel/SidePanel.js +80 -2
  24. package/dist/components/overlay/tooltip/TooltipProvider.cjs +10 -1
  25. package/dist/components/overlay/tooltip/TooltipProvider.js +11 -2
  26. package/dist/components/overlay/tooltip/useTooltipTrigger.cjs +9 -2
  27. package/dist/components/overlay/tooltip/useTooltipTrigger.js +9 -2
  28. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.cjs +1 -1
  29. package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +1 -1
  30. package/dist/components/split-pane/SplitPane.cjs +22 -7
  31. package/dist/components/split-pane/SplitPane.d.ts +5 -2
  32. package/dist/components/split-pane/SplitPane.js +22 -7
  33. package/dist/components/split-pane/SplitPane.module.css +9 -0
  34. package/dist/components/split-pane/provider/SplitPaneContext.cjs +5 -2
  35. package/dist/components/split-pane/provider/SplitPaneContext.d.ts +3 -1
  36. package/dist/components/split-pane/provider/SplitPaneContext.js +5 -2
  37. package/dist/components/tabs/Tabs.cjs +83 -69
  38. package/dist/components/tabs/Tabs.d.ts +5 -1
  39. package/dist/components/tabs/Tabs.js +83 -69
  40. package/dist/components/tabs/Tabs.module.css +39 -0
  41. package/dist/components/tree-view/TreeNodeRow.cjs +70 -30
  42. package/dist/components/tree-view/TreeNodeRow.d.ts +6 -1
  43. package/dist/components/tree-view/TreeNodeRow.js +70 -30
  44. package/dist/components/tree-view/TreeView.cjs +107 -19
  45. package/dist/components/tree-view/TreeView.d.ts +1 -1
  46. package/dist/components/tree-view/TreeView.js +107 -19
  47. package/dist/components/tree-view/TreeView.module.css +47 -3
  48. package/dist/components/tree-view/types.d.ts +6 -0
  49. package/dist/index.cjs +213 -208
  50. package/dist/index.css +2 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +1 -0
  53. package/dist/styles/css-helper-classes/typography.css +4 -0
  54. package/package.json +4 -1
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+ import React from 'react';
3
+ export interface MarkdownViewerProps {
4
+ markdown: string;
5
+ emptyMessage?: ReactNode;
6
+ /**
7
+ * Added to every heading level, so Markdown headings fit into the surrounding page outline,
8
+ * e.g. `1` renders `#` as `<h2>`. Levels stop at `<h6>`.
9
+ */
10
+ headingOffset?: number;
11
+ /**
12
+ * Open links in a new tab, with `rel="noopener noreferrer"` and visually hidden text announcing
13
+ * it. `mailto:` and `tel:` links always open normally.
14
+ */
15
+ openLinksInNewTab?: boolean;
16
+ /** Visually hidden text added to links that open in a new tab. */
17
+ newTabLabel?: string;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Renders Markdown the same way as `new MarkdownIt()`, as React elements without raw HTML.
22
+ * Images render as their alt text, and links open in a new tab unless `openLinksInNewTab` is false.
23
+ */
24
+ export declare function MarkdownViewer({ markdown, emptyMessage, headingOffset, openLinksInNewTab, newTabLabel, className, }: MarkdownViewerProps): React.ReactElement;
25
+ export declare namespace MarkdownViewer {
26
+ var displayName: string;
27
+ }
@@ -0,0 +1,23 @@
1
+ import { jsx } from 'react/jsx-runtime';
2
+ import { renderMarkdown } from './markdown.utils';
3
+ import styles from './MarkdownViewer.module.css';
4
+
5
+ function MarkdownViewer({
6
+ markdown,
7
+ emptyMessage,
8
+ headingOffset,
9
+ openLinksInNewTab = true,
10
+ newTabLabel = "(\xE5bner i en ny fane)",
11
+ className
12
+ }) {
13
+ const isEmpty = markdown.trim().length === 0;
14
+ return /* @__PURE__ */ jsx("div", { className: [styles.viewer, className].filter(Boolean).join(" "), children: isEmpty ? emptyMessage !== void 0 ? /* @__PURE__ */ jsx("p", { className: styles.emptyMessage, children: emptyMessage }) : null : renderMarkdown(markdown, {
15
+ headingOffset,
16
+ openLinksInNewTab,
17
+ // The space is hidden too, so links don't end in a visible underlined gap.
18
+ newTabHint: /* @__PURE__ */ jsx("span", { className: styles.visuallyHidden, children: ` ${newTabLabel}` })
19
+ }) });
20
+ }
21
+ MarkdownViewer.displayName = "MarkdownViewer";
22
+
23
+ export { MarkdownViewer };
@@ -0,0 +1,97 @@
1
+ .viewer {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--spacing-md);
5
+ min-width: 0;
6
+ color: var(--color-fg-default);
7
+ overflow-wrap: anywhere;
8
+ }
9
+
10
+ .viewer > *,
11
+ .viewer li > *,
12
+ .viewer blockquote > * {
13
+ margin: 0;
14
+ }
15
+
16
+ .viewer ul,
17
+ .viewer ol {
18
+ padding-inline-start: var(--spacing-lg);
19
+ }
20
+
21
+ .viewer li + li,
22
+ .viewer li > * + * {
23
+ margin-block-start: var(--spacing-xxs);
24
+ }
25
+
26
+ .viewer blockquote {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: var(--spacing-xs);
30
+ padding-inline-start: var(--spacing-md);
31
+ border-inline-start: var(--border-width-thick) solid var(--color-border-default);
32
+ color: var(--color-fg-muted);
33
+ }
34
+
35
+ .viewer code {
36
+ padding: 0 var(--spacing-xxs);
37
+ border-radius: var(--border-radius-sm);
38
+ font-family: var(--font-family-mono);
39
+ font-size: 0.9em;
40
+ background: var(--color-bg-surface-subtle);
41
+ }
42
+
43
+ .viewer pre {
44
+ overflow-x: auto;
45
+ padding: var(--spacing-sm);
46
+ border-radius: var(--border-radius-default);
47
+ background: var(--color-bg-surface-subtle);
48
+ }
49
+
50
+ .viewer pre code {
51
+ padding: 0;
52
+ background: transparent;
53
+ }
54
+
55
+ .viewer hr {
56
+ width: 100%;
57
+ border: 0;
58
+ border-block-start: var(--border-width-thin) solid var(--color-border-default);
59
+ }
60
+
61
+ /* As wide as its content, scrolling instead of overflowing the viewer. */
62
+ .viewer table {
63
+ display: block;
64
+ width: max-content;
65
+ max-width: 100%;
66
+ overflow-x: auto;
67
+ border-collapse: collapse;
68
+ }
69
+
70
+ .viewer th,
71
+ .viewer td {
72
+ padding: var(--spacing-xxs) var(--spacing-xs);
73
+ border: var(--border-width-thin) solid var(--color-border-default);
74
+ }
75
+
76
+ .emptyMessage {
77
+ color: var(--color-fg-subtle);
78
+ font-style: italic;
79
+ }
80
+
81
+ .viewer a {
82
+ color: var(--color-link);
83
+ text-decoration: underline;
84
+ }
85
+
86
+ .viewer a:hover {
87
+ color: var(--color-link-hover);
88
+ }
89
+
90
+ .visuallyHidden {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ overflow: hidden;
95
+ clip-path: inset(50%);
96
+ white-space: nowrap;
97
+ }
@@ -0,0 +1,325 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var MarkdownIt = require('markdown-it');
5
+ var React = require('react');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var MarkdownIt__default = /*#__PURE__*/_interopDefault(MarkdownIt);
10
+ var React__default = /*#__PURE__*/_interopDefault(React);
11
+
12
+ const ESCAPE_PATTERN = /\\[!-/:-@[-`{-~]/;
13
+ const LINK_PATTERN = /\[((?:\\[^\n]|[^\]\\\n])+)\]\(((?:[^()\s]|\([^()\s]*\))+)\)/;
14
+ const escapeRegExp = (text) => text.replace(/[.*+?^$()|[\]\\{}]/g, "\\$&");
15
+ const linkPattern = (urlPlaceholder) => new RegExp(LINK_PATTERN.source.replace(/\)\\\)$/, `|${escapeRegExp(urlPlaceholder)})\\)`));
16
+ const LINK_PLACEHOLDER_TEXT = "linktekst";
17
+ const LINK_PLACEHOLDER_URL = "inds\xE6t url";
18
+ const parser = new MarkdownIt__default.default();
19
+ parser.validateLink = (url) => getSafeHref(url) !== void 0;
20
+ function getSafeHref(url) {
21
+ if ([...url].some((character) => character < " " || character === "\x7F")) return void 0;
22
+ const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(url);
23
+ return !hasScheme || /^(https?|mailto|tel):/i.test(url) ? url : void 0;
24
+ }
25
+ function findLinkAt(value, selectionStart, selectionEnd, urlPlaceholder = LINK_PLACEHOLDER_URL) {
26
+ const pattern = new RegExp(`${ESCAPE_PATTERN.source}|${linkPattern(urlPlaceholder).source}`, "g");
27
+ let match;
28
+ while ((match = pattern.exec(value)) !== null) {
29
+ if (match.index > selectionStart) return void 0;
30
+ const end = match.index + match[0].length;
31
+ if (match[1] !== void 0 && selectionEnd <= end) {
32
+ return { start: match.index, end, text: match[1] };
33
+ }
34
+ }
35
+ return void 0;
36
+ }
37
+ function escapeMarkdown(value) {
38
+ return value.replace(/[!-/:-@[-`{-~]/g, "\\$&");
39
+ }
40
+ function unescapeMarkdown(value) {
41
+ return value.replace(/\\([!-/:-@[-`{-~])/g, "$1");
42
+ }
43
+ const INLINE_SYNTAX = /\\(?=[!-/:-@[-`{-~]|$)|[`*_[<~|]|&(?=#?[a-z0-9]+;)/gi;
44
+ const BLOCK_SYNTAX = /^(\s*)(#{1,6}(?=\s|$)|>|[-+=](?=\s|$)|[-=]+\s*$)|^(\s*\d+)([.)])(?=\s|$)/;
45
+ const INDENT_ENTITIES = { " ": "&nbsp;", " ": "&#9;" };
46
+ const ENCODED_INDENT = /^(?:&nbsp;|&#9;)+/gm;
47
+ function escapePlainLine(line) {
48
+ if (!line.trim()) return line;
49
+ const indent = /^[ \t]*/.exec(line)[0];
50
+ const escaped = line.slice(indent.length).replace(INLINE_SYNTAX, "\\$&");
51
+ if (indent) return `${indent.replace(/[ \t]/g, (space) => INDENT_ENTITIES[space])}${escaped}`;
52
+ return escaped.replace(
53
+ BLOCK_SYNTAX,
54
+ (_, indent2, marker, number, delimiter) => marker === void 0 ? `${number}\\${delimiter}` : `${indent2}\\${marker}`
55
+ );
56
+ }
57
+ function plainTextToMarkdown(text) {
58
+ const lines = text.split("\n");
59
+ return lines.map((line, index) => {
60
+ const escaped = escapePlainLine(line);
61
+ return index < lines.length - 1 && line.trim() && lines[index + 1].trim() ? `${escaped}\\` : escaped;
62
+ }).join("\n");
63
+ }
64
+ function limitPlainText(previous, next, maxLength) {
65
+ if (maxLength === void 0 || next.length < previous.length || plainTextToMarkdown(next).length <= maxLength) {
66
+ return { text: next, caret: -1 };
67
+ }
68
+ let start = 0;
69
+ while (start < previous.length && start < next.length && previous[start] === next[start]) start++;
70
+ let end = 0;
71
+ while (end < previous.length - start && end < next.length - start && previous[previous.length - 1 - end] === next[next.length - 1 - end]) {
72
+ end++;
73
+ }
74
+ const inserted = next.slice(start, next.length - end);
75
+ const withInserted = (length) => `${next.slice(0, start)}${inserted.slice(0, length)}${next.slice(next.length - end)}`;
76
+ let low = 0;
77
+ let high = inserted.length;
78
+ while (low < high) {
79
+ const middle = Math.ceil((low + high) / 2);
80
+ if (plainTextToMarkdown(withInserted(middle)).length <= maxLength) low = middle;
81
+ else high = middle - 1;
82
+ }
83
+ const text = withInserted(low);
84
+ return text === previous ? void 0 : { text, caret: start + low };
85
+ }
86
+ function markdownToPlainText(markdown) {
87
+ return markdown.replace(ENCODED_INDENT, (indent) => indent.replace(/&nbsp;/g, " ").replace(/&#9;/g, " ")).replace(/\\([!-/:-@[-`{-~\n])/g, "$1");
88
+ }
89
+ function isPlainMarkdown(markdown) {
90
+ const normalize = (html) => html.replace(/<br>\n/g, "\n").replace(/(^|>|\n)[\u00a0\t]+/g, "$1");
91
+ return normalize(parser.render(markdown)) === normalize(parser.render(plainTextToMarkdown(markdownToPlainText(markdown))));
92
+ }
93
+ const SELECTION_SYNTAX = /\\[!-/:-@[-`{-~]|[`*_[<~|]|&(?=#?[a-z0-9]+;)/gi;
94
+ function escapeSyntax(text, startsLine) {
95
+ return text.split("\n").map((line, index) => {
96
+ const escaped = line.replace(
97
+ SELECTION_SYNTAX,
98
+ (part) => part.length === 2 ? part : `\\${part}`
99
+ );
100
+ if (index === 0 && !startsLine) return escaped;
101
+ return escaped.replace(
102
+ BLOCK_SYNTAX,
103
+ (_, indent, marker, number, delimiter) => marker === void 0 ? `${number}\\${delimiter}` : `${indent}\\${marker}`
104
+ );
105
+ }).join("\n");
106
+ }
107
+ function isSelectionEscaped(value, selectionStart, selectionEnd) {
108
+ const selectedText = value.slice(selectionStart, selectionEnd);
109
+ const startsLine = selectionStart === 0 || value[selectionStart - 1] === "\n";
110
+ return ESCAPE_PATTERN.test(selectedText) && escapeSyntax(selectedText, startsLine) === selectedText;
111
+ }
112
+ function isEscaped(value, index) {
113
+ let backslashes = 0;
114
+ while (index > 0 && value[--index] === "\\") backslashes++;
115
+ return backslashes % 2 === 1;
116
+ }
117
+ function formatMarkdownSelection(value, selectionStart, selectionEnd, format) {
118
+ var _a, _b;
119
+ if (format.type === "link") {
120
+ const textPlaceholder = (_a = format.textPlaceholder) != null ? _a : LINK_PLACEHOLDER_TEXT;
121
+ const urlPlaceholder = (_b = format.urlPlaceholder) != null ? _b : LINK_PLACEHOLDER_URL;
122
+ const link = findLinkAt(value, selectionStart, selectionEnd, urlPlaceholder);
123
+ if (link) {
124
+ return {
125
+ value: `${value.slice(0, link.start)}${link.text}${value.slice(link.end)}`,
126
+ selectionStart: link.start,
127
+ selectionEnd: link.start + link.text.length
128
+ };
129
+ }
130
+ const selectedText = value.slice(selectionStart, selectionEnd);
131
+ const textStart = selectionStart + 1;
132
+ if (/^(https?:\/\/|mailto:)\S+$/i.test(selectedText)) {
133
+ return {
134
+ value: `${value.slice(0, selectionStart)}[${textPlaceholder}](${selectedText})${value.slice(selectionEnd)}`,
135
+ selectionStart: textStart,
136
+ selectionEnd: textStart + textPlaceholder.length
137
+ };
138
+ }
139
+ const text = selectedText || textPlaceholder;
140
+ const urlStart = textStart + text.length + 2;
141
+ return {
142
+ value: `${value.slice(0, selectionStart)}[${text}](${urlPlaceholder})${value.slice(selectionEnd)}`,
143
+ selectionStart: selectedText ? urlStart : textStart,
144
+ selectionEnd: selectedText ? urlStart + urlPlaceholder.length : textStart + text.length
145
+ };
146
+ }
147
+ if (format.type === "escape") {
148
+ const selectedText = value.slice(selectionStart, selectionEnd);
149
+ const startsLine = selectionStart === 0 || value[selectionStart - 1] === "\n";
150
+ const replacement2 = isSelectionEscaped(value, selectionStart, selectionEnd) ? unescapeMarkdown(selectedText) : escapeSyntax(selectedText, startsLine);
151
+ return {
152
+ value: `${value.slice(0, selectionStart)}${replacement2}${value.slice(selectionEnd)}`,
153
+ selectionStart,
154
+ selectionEnd: selectionStart + replacement2.length
155
+ };
156
+ }
157
+ if (format.type === "wrap") {
158
+ const selectedText = value.slice(selectionStart, selectionEnd);
159
+ const markerStart = selectionStart - format.marker.length;
160
+ const isAlreadyWrapped = isMarkdownFormatActive(value, selectionStart, selectionEnd, format);
161
+ if (isAlreadyWrapped) {
162
+ return {
163
+ value: `${value.slice(0, markerStart)}${selectedText}${value.slice(selectionEnd + format.marker.length)}`,
164
+ selectionStart: markerStart,
165
+ selectionEnd: markerStart + selectedText.length
166
+ };
167
+ }
168
+ const replacement2 = `${format.marker}${selectedText}${format.marker}`;
169
+ const innerStart = selectionStart + format.marker.length;
170
+ return {
171
+ value: `${value.slice(0, selectionStart)}${replacement2}${value.slice(selectionEnd)}`,
172
+ selectionStart: innerStart,
173
+ selectionEnd: innerStart + selectedText.length
174
+ };
175
+ }
176
+ const previousNewline = selectionStart > 0 ? value.lastIndexOf("\n", selectionStart - 1) : -1;
177
+ const lineStart = previousNewline + 1;
178
+ const selectedEnd = selectionEnd > selectionStart && value[selectionEnd - 1] === "\n" ? selectionEnd - 1 : selectionEnd;
179
+ const lineEndIndex = value.indexOf("\n", selectedEnd);
180
+ const lineEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
181
+ const selectedLines = value.slice(lineStart, lineEnd).split("\n");
182
+ const removePrefix = format.toggle && isMarkdownFormatActive(value, selectionStart, selectionEnd, format);
183
+ const formattedLines = selectedLines.map((line) => {
184
+ if (removePrefix) return line.slice(format.prefix.length);
185
+ if (format.prefix.startsWith("#")) return `${format.prefix}${line.replace(/^#{1,6}\s+/, "")}`;
186
+ return `${format.prefix}${line}`;
187
+ });
188
+ const replacement = formattedLines.join("\n");
189
+ return {
190
+ value: `${value.slice(0, lineStart)}${replacement}${value.slice(lineEnd)}`,
191
+ selectionStart: lineStart,
192
+ selectionEnd: lineStart + replacement.length
193
+ };
194
+ }
195
+ function isMarkdownFormatActive(value, selectionStart, selectionEnd, format) {
196
+ if (format.type === "link") {
197
+ return findLinkAt(value, selectionStart, selectionEnd, format.urlPlaceholder) !== void 0;
198
+ }
199
+ if (format.type === "escape") return isSelectionEscaped(value, selectionStart, selectionEnd);
200
+ if (format.type === "wrap") {
201
+ const character = format.marker[0];
202
+ let start = selectionStart;
203
+ let end = selectionEnd;
204
+ while (start > 0 && value[start - 1] === character && !isEscaped(value, start - 1)) start--;
205
+ while (end < value.length && value[end] === character && !isEscaped(value, end)) end++;
206
+ const markerCount = Math.min(selectionStart - start, end - selectionEnd);
207
+ return format.marker.length === 1 ? markerCount % 2 === 1 : markerCount >= 2;
208
+ }
209
+ const previousNewline = selectionStart > 0 ? value.lastIndexOf("\n", selectionStart - 1) : -1;
210
+ const lineStart = previousNewline + 1;
211
+ const selectedEnd = selectionEnd > selectionStart && value[selectionEnd - 1] === "\n" ? selectionEnd - 1 : selectionEnd;
212
+ const lineEndIndex = value.indexOf("\n", selectedEnd);
213
+ const lineEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
214
+ const lines = value.slice(lineStart, lineEnd).split("\n");
215
+ if (format.prefix.startsWith("#")) {
216
+ return lines.every((line) => {
217
+ var _a;
218
+ return ((_a = /^(#{1,6}\s+)/.exec(line)) == null ? void 0 : _a[0]) === format.prefix;
219
+ });
220
+ }
221
+ return lines.every((line) => line.startsWith(format.prefix));
222
+ }
223
+ function getAttr(token, name) {
224
+ const value = token.attrGet(name);
225
+ return value === null ? void 0 : String(value);
226
+ }
227
+ function toStyle(style) {
228
+ var _a;
229
+ const align = style && ((_a = /^text-align:\s*(left|right|center)$/.exec(style)) == null ? void 0 : _a[1]);
230
+ return align ? { textAlign: align } : void 0;
231
+ }
232
+ function renderLeaf(token, key, options) {
233
+ var _a, _b;
234
+ switch (token.type) {
235
+ case "text":
236
+ return token.content;
237
+ case "softbreak":
238
+ return "\n";
239
+ case "hardbreak":
240
+ return /* @__PURE__ */ jsxRuntime.jsx("br", {}, key);
241
+ case "code_inline":
242
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { children: token.content }, key);
243
+ case "code_block":
244
+ return /* @__PURE__ */ jsxRuntime.jsx("pre", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: token.content }) }, key);
245
+ case "fence": {
246
+ const language = token.info.trim().split(/\s+/)[0];
247
+ return /* @__PURE__ */ jsxRuntime.jsx("pre", { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: language ? `language-${language}` : void 0, children: token.content }) }, key);
248
+ }
249
+ case "hr":
250
+ return /* @__PURE__ */ jsxRuntime.jsx("hr", {}, key);
251
+ case "image":
252
+ return parser.renderer.renderInlineAsText((_a = token.children) != null ? _a : [], parser.options, {});
253
+ case "inline":
254
+ return renderTokens((_b = token.children) != null ? _b : [], options);
255
+ default:
256
+ return token.content || null;
257
+ }
258
+ }
259
+ function renderElement(open, children, key, options) {
260
+ var _a;
261
+ if (open.hidden) return /* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children }, key);
262
+ if (/^h[1-6]$/.test(open.tag)) {
263
+ const level = Math.min(6, Math.max(1, Number(open.tag[1]) + ((_a = options.headingOffset) != null ? _a : 0)));
264
+ return React__default.default.createElement(`h${level}`, { key }, children);
265
+ }
266
+ if (open.tag === "a") {
267
+ const href = getAttr(open, "href");
268
+ const newTab = options.openLinksInNewTab && !/^(mailto|tel):/i.test(href != null ? href : "");
269
+ return /* @__PURE__ */ jsxRuntime.jsxs(
270
+ "a",
271
+ {
272
+ href,
273
+ title: getAttr(open, "title"),
274
+ target: newTab ? "_blank" : void 0,
275
+ rel: newTab ? "noopener noreferrer" : void 0,
276
+ children: [
277
+ children,
278
+ newTab && options.newTabHint
279
+ ]
280
+ },
281
+ key
282
+ );
283
+ }
284
+ if (open.tag === "ol") {
285
+ const start = getAttr(open, "start");
286
+ return /* @__PURE__ */ jsxRuntime.jsx("ol", { start: start ? Number(start) : void 0, children }, key);
287
+ }
288
+ if (open.tag === "th" || open.tag === "td") {
289
+ return React__default.default.createElement(open.tag, { key, style: toStyle(getAttr(open, "style")) }, children);
290
+ }
291
+ return React__default.default.createElement(open.tag, { key }, children);
292
+ }
293
+ function renderTokens(tokens, options) {
294
+ const root = [];
295
+ const stack = [];
296
+ tokens.forEach((token, index) => {
297
+ const siblings = stack.length ? stack[stack.length - 1].children : root;
298
+ if (token.nesting === 1) {
299
+ stack.push({ open: token, children: [] });
300
+ } else if (token.nesting === -1) {
301
+ const element = stack.pop();
302
+ if (!element) return;
303
+ const parent = stack.length ? stack[stack.length - 1].children : root;
304
+ parent.push(renderElement(element.open, element.children, index, options));
305
+ } else {
306
+ const leaf = renderLeaf(token, index, options);
307
+ if (Array.isArray(leaf)) siblings.push(/* @__PURE__ */ jsxRuntime.jsx(React__default.default.Fragment, { children: leaf }, index));
308
+ else if (leaf !== null) siblings.push(leaf);
309
+ }
310
+ });
311
+ return root;
312
+ }
313
+ function renderMarkdown(value, options = {}) {
314
+ return renderTokens(parser.parse(value, {}), options);
315
+ }
316
+
317
+ exports.escapeMarkdown = escapeMarkdown;
318
+ exports.formatMarkdownSelection = formatMarkdownSelection;
319
+ exports.getSafeHref = getSafeHref;
320
+ exports.isMarkdownFormatActive = isMarkdownFormatActive;
321
+ exports.isPlainMarkdown = isPlainMarkdown;
322
+ exports.limitPlainText = limitPlainText;
323
+ exports.markdownToPlainText = markdownToPlainText;
324
+ exports.plainTextToMarkdown = plainTextToMarkdown;
325
+ exports.renderMarkdown = renderMarkdown;
@@ -0,0 +1,66 @@
1
+ import type { ReactNode } from 'react';
2
+ export type MarkdownFormat = {
3
+ type: 'wrap';
4
+ marker: '**' | '*' | '__' | '_';
5
+ } | {
6
+ type: 'line';
7
+ prefix: string;
8
+ toggle?: boolean;
9
+ } | {
10
+ type: 'escape';
11
+ } | {
12
+ type: 'link';
13
+ /** Link text inserted when nothing is selected. */
14
+ textPlaceholder?: string;
15
+ /**
16
+ * Target inserted for the user to replace. It must contain a space, so an unfinished link is
17
+ * never rendered as a link, here or by markdown-it.
18
+ */
19
+ urlPlaceholder?: string;
20
+ };
21
+ export interface FormattedSelection {
22
+ value: string;
23
+ selectionStart: number;
24
+ selectionEnd: number;
25
+ }
26
+ /** Only allow web, mail and phone links, plus relative urls - never `javascript:` and friends. */
27
+ export declare function getSafeHref(url: string): string | undefined;
28
+ /** Escape ASCII punctuation, as supported by CommonMark and markdown-it. */
29
+ export declare function escapeMarkdown(value: string): string;
30
+ /**
31
+ * Markdown that renders as exactly `text`: only characters that would otherwise become syntax
32
+ * are escaped, and line breaks inside a paragraph become hard breaks (a trailing backslash), so
33
+ * they aren't joined into one line.
34
+ */
35
+ export declare function plainTextToMarkdown(text: string): string;
36
+ /**
37
+ * Keeps a plain text edit within `maxLength` characters of stored Markdown, like a native
38
+ * `maxlength`: inserted text is cut to what fits, and deletions are always allowed. Returns the
39
+ * text to keep and where the caret goes, or undefined when nothing of the edit fits.
40
+ */
41
+ export declare function limitPlainText(previous: string, next: string, maxLength: number | undefined): {
42
+ text: string;
43
+ caret: number;
44
+ } | undefined;
45
+ /** The text a user would type to get `markdown` from `plainTextToMarkdown`. */
46
+ export declare function markdownToPlainText(markdown: string): string;
47
+ /**
48
+ * Whether `markdown` has no formatting, so editing it as plain text loses nothing: it renders the
49
+ * same once converted, apart from line breaks and indentation, which plain text keeps as typed.
50
+ */
51
+ export declare function isPlainMarkdown(markdown: string): boolean;
52
+ export declare function formatMarkdownSelection(value: string, selectionStart: number, selectionEnd: number, format: MarkdownFormat): FormattedSelection;
53
+ export declare function isMarkdownFormatActive(value: string, selectionStart: number, selectionEnd: number, format: MarkdownFormat): boolean;
54
+ export interface RenderMarkdownOptions {
55
+ /** Added to every heading level, e.g. `1` renders `#` as `<h2>`. Levels stop at `<h6>`. */
56
+ headingOffset?: number;
57
+ /** Open links in a new tab, except `mailto:` and `tel:` links. */
58
+ openLinksInNewTab?: boolean;
59
+ /** Added inside links that open in a new tab, e.g. visually hidden text announcing it. */
60
+ newTabHint?: ReactNode;
61
+ }
62
+ /**
63
+ * Renders Markdown as React elements, exactly as `new MarkdownIt()` would render it as HTML,
64
+ * except that images render as their alt text and links can open in a new tab.
65
+ */
66
+ export declare function renderMarkdown(value: string, options?: RenderMarkdownOptions): ReactNode;