@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.
- package/dist/client.cjs +10 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +2 -0
- package/dist/components/forms/markdown-editor/MarkdownEditor.cjs +475 -0
- package/dist/components/forms/markdown-editor/MarkdownEditor.d.ts +68 -0
- package/dist/components/forms/markdown-editor/MarkdownEditor.js +468 -0
- package/dist/components/forms/markdown-editor/MarkdownEditor.module.css +202 -0
- package/dist/components/forms/markdown-editor/MarkdownViewer.cjs +29 -0
- package/dist/components/forms/markdown-editor/MarkdownViewer.d.ts +27 -0
- package/dist/components/forms/markdown-editor/MarkdownViewer.js +23 -0
- package/dist/components/forms/markdown-editor/MarkdownViewer.module.css +97 -0
- package/dist/components/forms/markdown-editor/markdown.utils.cjs +325 -0
- package/dist/components/forms/markdown-editor/markdown.utils.d.ts +66 -0
- package/dist/components/forms/markdown-editor/markdown.utils.js +310 -0
- package/dist/components/listbox/Listbox.cjs +2 -2
- package/dist/components/listbox/Listbox.d.ts +2 -2
- package/dist/components/listbox/Listbox.js +2 -2
- package/dist/components/meta-bar/MetaBar.cjs +2 -2
- package/dist/components/meta-bar/MetaBar.d.ts +2 -1
- package/dist/components/meta-bar/MetaBar.js +2 -2
- package/dist/components/meta-bar/MetaBar.module.css +7 -0
- package/dist/components/overlay/side-panel/SidePanel.cjs +80 -2
- package/dist/components/overlay/side-panel/SidePanel.js +80 -2
- package/dist/components/overlay/tooltip/TooltipProvider.cjs +10 -1
- package/dist/components/overlay/tooltip/TooltipProvider.js +11 -2
- package/dist/components/overlay/tooltip/useTooltipTrigger.cjs +9 -2
- package/dist/components/overlay/tooltip/useTooltipTrigger.js +9 -2
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.cjs +1 -1
- package/dist/components/sidebar/components/sidebar-container/SidebarContainer.js +1 -1
- package/dist/components/split-pane/SplitPane.cjs +22 -7
- package/dist/components/split-pane/SplitPane.d.ts +5 -2
- package/dist/components/split-pane/SplitPane.js +22 -7
- package/dist/components/split-pane/SplitPane.module.css +9 -0
- package/dist/components/split-pane/provider/SplitPaneContext.cjs +5 -2
- package/dist/components/split-pane/provider/SplitPaneContext.d.ts +3 -1
- package/dist/components/split-pane/provider/SplitPaneContext.js +5 -2
- package/dist/components/tabs/Tabs.cjs +83 -69
- package/dist/components/tabs/Tabs.d.ts +5 -1
- package/dist/components/tabs/Tabs.js +83 -69
- package/dist/components/tabs/Tabs.module.css +39 -0
- package/dist/components/tree-view/TreeNodeRow.cjs +70 -30
- package/dist/components/tree-view/TreeNodeRow.d.ts +6 -1
- package/dist/components/tree-view/TreeNodeRow.js +70 -30
- package/dist/components/tree-view/TreeView.cjs +107 -19
- package/dist/components/tree-view/TreeView.d.ts +1 -1
- package/dist/components/tree-view/TreeView.js +107 -19
- package/dist/components/tree-view/TreeView.module.css +47 -3
- package/dist/components/tree-view/types.d.ts +6 -0
- package/dist/index.cjs +213 -208
- package/dist/index.css +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/styles/css-helper-classes/typography.css +4 -0
- package/package.json +4 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import MarkdownIt from 'markdown-it';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
|
|
5
|
+
const ESCAPE_PATTERN = /\\[!-/:-@[-`{-~]/;
|
|
6
|
+
const LINK_PATTERN = /\[((?:\\[^\n]|[^\]\\\n])+)\]\(((?:[^()\s]|\([^()\s]*\))+)\)/;
|
|
7
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^$()|[\]\\{}]/g, "\\$&");
|
|
8
|
+
const linkPattern = (urlPlaceholder) => new RegExp(LINK_PATTERN.source.replace(/\)\\\)$/, `|${escapeRegExp(urlPlaceholder)})\\)`));
|
|
9
|
+
const LINK_PLACEHOLDER_TEXT = "linktekst";
|
|
10
|
+
const LINK_PLACEHOLDER_URL = "inds\xE6t url";
|
|
11
|
+
const parser = new MarkdownIt();
|
|
12
|
+
parser.validateLink = (url) => getSafeHref(url) !== void 0;
|
|
13
|
+
function getSafeHref(url) {
|
|
14
|
+
if ([...url].some((character) => character < " " || character === "\x7F")) return void 0;
|
|
15
|
+
const hasScheme = /^[a-z][a-z0-9+.-]*:/i.test(url);
|
|
16
|
+
return !hasScheme || /^(https?|mailto|tel):/i.test(url) ? url : void 0;
|
|
17
|
+
}
|
|
18
|
+
function findLinkAt(value, selectionStart, selectionEnd, urlPlaceholder = LINK_PLACEHOLDER_URL) {
|
|
19
|
+
const pattern = new RegExp(`${ESCAPE_PATTERN.source}|${linkPattern(urlPlaceholder).source}`, "g");
|
|
20
|
+
let match;
|
|
21
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
22
|
+
if (match.index > selectionStart) return void 0;
|
|
23
|
+
const end = match.index + match[0].length;
|
|
24
|
+
if (match[1] !== void 0 && selectionEnd <= end) {
|
|
25
|
+
return { start: match.index, end, text: match[1] };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
function escapeMarkdown(value) {
|
|
31
|
+
return value.replace(/[!-/:-@[-`{-~]/g, "\\$&");
|
|
32
|
+
}
|
|
33
|
+
function unescapeMarkdown(value) {
|
|
34
|
+
return value.replace(/\\([!-/:-@[-`{-~])/g, "$1");
|
|
35
|
+
}
|
|
36
|
+
const INLINE_SYNTAX = /\\(?=[!-/:-@[-`{-~]|$)|[`*_[<~|]|&(?=#?[a-z0-9]+;)/gi;
|
|
37
|
+
const BLOCK_SYNTAX = /^(\s*)(#{1,6}(?=\s|$)|>|[-+=](?=\s|$)|[-=]+\s*$)|^(\s*\d+)([.)])(?=\s|$)/;
|
|
38
|
+
const INDENT_ENTITIES = { " ": " ", " ": "	" };
|
|
39
|
+
const ENCODED_INDENT = /^(?: |	)+/gm;
|
|
40
|
+
function escapePlainLine(line) {
|
|
41
|
+
if (!line.trim()) return line;
|
|
42
|
+
const indent = /^[ \t]*/.exec(line)[0];
|
|
43
|
+
const escaped = line.slice(indent.length).replace(INLINE_SYNTAX, "\\$&");
|
|
44
|
+
if (indent) return `${indent.replace(/[ \t]/g, (space) => INDENT_ENTITIES[space])}${escaped}`;
|
|
45
|
+
return escaped.replace(
|
|
46
|
+
BLOCK_SYNTAX,
|
|
47
|
+
(_, indent2, marker, number, delimiter) => marker === void 0 ? `${number}\\${delimiter}` : `${indent2}\\${marker}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
function plainTextToMarkdown(text) {
|
|
51
|
+
const lines = text.split("\n");
|
|
52
|
+
return lines.map((line, index) => {
|
|
53
|
+
const escaped = escapePlainLine(line);
|
|
54
|
+
return index < lines.length - 1 && line.trim() && lines[index + 1].trim() ? `${escaped}\\` : escaped;
|
|
55
|
+
}).join("\n");
|
|
56
|
+
}
|
|
57
|
+
function limitPlainText(previous, next, maxLength) {
|
|
58
|
+
if (maxLength === void 0 || next.length < previous.length || plainTextToMarkdown(next).length <= maxLength) {
|
|
59
|
+
return { text: next, caret: -1 };
|
|
60
|
+
}
|
|
61
|
+
let start = 0;
|
|
62
|
+
while (start < previous.length && start < next.length && previous[start] === next[start]) start++;
|
|
63
|
+
let end = 0;
|
|
64
|
+
while (end < previous.length - start && end < next.length - start && previous[previous.length - 1 - end] === next[next.length - 1 - end]) {
|
|
65
|
+
end++;
|
|
66
|
+
}
|
|
67
|
+
const inserted = next.slice(start, next.length - end);
|
|
68
|
+
const withInserted = (length) => `${next.slice(0, start)}${inserted.slice(0, length)}${next.slice(next.length - end)}`;
|
|
69
|
+
let low = 0;
|
|
70
|
+
let high = inserted.length;
|
|
71
|
+
while (low < high) {
|
|
72
|
+
const middle = Math.ceil((low + high) / 2);
|
|
73
|
+
if (plainTextToMarkdown(withInserted(middle)).length <= maxLength) low = middle;
|
|
74
|
+
else high = middle - 1;
|
|
75
|
+
}
|
|
76
|
+
const text = withInserted(low);
|
|
77
|
+
return text === previous ? void 0 : { text, caret: start + low };
|
|
78
|
+
}
|
|
79
|
+
function markdownToPlainText(markdown) {
|
|
80
|
+
return markdown.replace(ENCODED_INDENT, (indent) => indent.replace(/ /g, " ").replace(/	/g, " ")).replace(/\\([!-/:-@[-`{-~\n])/g, "$1");
|
|
81
|
+
}
|
|
82
|
+
function isPlainMarkdown(markdown) {
|
|
83
|
+
const normalize = (html) => html.replace(/<br>\n/g, "\n").replace(/(^|>|\n)[\u00a0\t]+/g, "$1");
|
|
84
|
+
return normalize(parser.render(markdown)) === normalize(parser.render(plainTextToMarkdown(markdownToPlainText(markdown))));
|
|
85
|
+
}
|
|
86
|
+
const SELECTION_SYNTAX = /\\[!-/:-@[-`{-~]|[`*_[<~|]|&(?=#?[a-z0-9]+;)/gi;
|
|
87
|
+
function escapeSyntax(text, startsLine) {
|
|
88
|
+
return text.split("\n").map((line, index) => {
|
|
89
|
+
const escaped = line.replace(
|
|
90
|
+
SELECTION_SYNTAX,
|
|
91
|
+
(part) => part.length === 2 ? part : `\\${part}`
|
|
92
|
+
);
|
|
93
|
+
if (index === 0 && !startsLine) return escaped;
|
|
94
|
+
return escaped.replace(
|
|
95
|
+
BLOCK_SYNTAX,
|
|
96
|
+
(_, indent, marker, number, delimiter) => marker === void 0 ? `${number}\\${delimiter}` : `${indent}\\${marker}`
|
|
97
|
+
);
|
|
98
|
+
}).join("\n");
|
|
99
|
+
}
|
|
100
|
+
function isSelectionEscaped(value, selectionStart, selectionEnd) {
|
|
101
|
+
const selectedText = value.slice(selectionStart, selectionEnd);
|
|
102
|
+
const startsLine = selectionStart === 0 || value[selectionStart - 1] === "\n";
|
|
103
|
+
return ESCAPE_PATTERN.test(selectedText) && escapeSyntax(selectedText, startsLine) === selectedText;
|
|
104
|
+
}
|
|
105
|
+
function isEscaped(value, index) {
|
|
106
|
+
let backslashes = 0;
|
|
107
|
+
while (index > 0 && value[--index] === "\\") backslashes++;
|
|
108
|
+
return backslashes % 2 === 1;
|
|
109
|
+
}
|
|
110
|
+
function formatMarkdownSelection(value, selectionStart, selectionEnd, format) {
|
|
111
|
+
var _a, _b;
|
|
112
|
+
if (format.type === "link") {
|
|
113
|
+
const textPlaceholder = (_a = format.textPlaceholder) != null ? _a : LINK_PLACEHOLDER_TEXT;
|
|
114
|
+
const urlPlaceholder = (_b = format.urlPlaceholder) != null ? _b : LINK_PLACEHOLDER_URL;
|
|
115
|
+
const link = findLinkAt(value, selectionStart, selectionEnd, urlPlaceholder);
|
|
116
|
+
if (link) {
|
|
117
|
+
return {
|
|
118
|
+
value: `${value.slice(0, link.start)}${link.text}${value.slice(link.end)}`,
|
|
119
|
+
selectionStart: link.start,
|
|
120
|
+
selectionEnd: link.start + link.text.length
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const selectedText = value.slice(selectionStart, selectionEnd);
|
|
124
|
+
const textStart = selectionStart + 1;
|
|
125
|
+
if (/^(https?:\/\/|mailto:)\S+$/i.test(selectedText)) {
|
|
126
|
+
return {
|
|
127
|
+
value: `${value.slice(0, selectionStart)}[${textPlaceholder}](${selectedText})${value.slice(selectionEnd)}`,
|
|
128
|
+
selectionStart: textStart,
|
|
129
|
+
selectionEnd: textStart + textPlaceholder.length
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const text = selectedText || textPlaceholder;
|
|
133
|
+
const urlStart = textStart + text.length + 2;
|
|
134
|
+
return {
|
|
135
|
+
value: `${value.slice(0, selectionStart)}[${text}](${urlPlaceholder})${value.slice(selectionEnd)}`,
|
|
136
|
+
selectionStart: selectedText ? urlStart : textStart,
|
|
137
|
+
selectionEnd: selectedText ? urlStart + urlPlaceholder.length : textStart + text.length
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (format.type === "escape") {
|
|
141
|
+
const selectedText = value.slice(selectionStart, selectionEnd);
|
|
142
|
+
const startsLine = selectionStart === 0 || value[selectionStart - 1] === "\n";
|
|
143
|
+
const replacement2 = isSelectionEscaped(value, selectionStart, selectionEnd) ? unescapeMarkdown(selectedText) : escapeSyntax(selectedText, startsLine);
|
|
144
|
+
return {
|
|
145
|
+
value: `${value.slice(0, selectionStart)}${replacement2}${value.slice(selectionEnd)}`,
|
|
146
|
+
selectionStart,
|
|
147
|
+
selectionEnd: selectionStart + replacement2.length
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (format.type === "wrap") {
|
|
151
|
+
const selectedText = value.slice(selectionStart, selectionEnd);
|
|
152
|
+
const markerStart = selectionStart - format.marker.length;
|
|
153
|
+
const isAlreadyWrapped = isMarkdownFormatActive(value, selectionStart, selectionEnd, format);
|
|
154
|
+
if (isAlreadyWrapped) {
|
|
155
|
+
return {
|
|
156
|
+
value: `${value.slice(0, markerStart)}${selectedText}${value.slice(selectionEnd + format.marker.length)}`,
|
|
157
|
+
selectionStart: markerStart,
|
|
158
|
+
selectionEnd: markerStart + selectedText.length
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const replacement2 = `${format.marker}${selectedText}${format.marker}`;
|
|
162
|
+
const innerStart = selectionStart + format.marker.length;
|
|
163
|
+
return {
|
|
164
|
+
value: `${value.slice(0, selectionStart)}${replacement2}${value.slice(selectionEnd)}`,
|
|
165
|
+
selectionStart: innerStart,
|
|
166
|
+
selectionEnd: innerStart + selectedText.length
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const previousNewline = selectionStart > 0 ? value.lastIndexOf("\n", selectionStart - 1) : -1;
|
|
170
|
+
const lineStart = previousNewline + 1;
|
|
171
|
+
const selectedEnd = selectionEnd > selectionStart && value[selectionEnd - 1] === "\n" ? selectionEnd - 1 : selectionEnd;
|
|
172
|
+
const lineEndIndex = value.indexOf("\n", selectedEnd);
|
|
173
|
+
const lineEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
|
|
174
|
+
const selectedLines = value.slice(lineStart, lineEnd).split("\n");
|
|
175
|
+
const removePrefix = format.toggle && isMarkdownFormatActive(value, selectionStart, selectionEnd, format);
|
|
176
|
+
const formattedLines = selectedLines.map((line) => {
|
|
177
|
+
if (removePrefix) return line.slice(format.prefix.length);
|
|
178
|
+
if (format.prefix.startsWith("#")) return `${format.prefix}${line.replace(/^#{1,6}\s+/, "")}`;
|
|
179
|
+
return `${format.prefix}${line}`;
|
|
180
|
+
});
|
|
181
|
+
const replacement = formattedLines.join("\n");
|
|
182
|
+
return {
|
|
183
|
+
value: `${value.slice(0, lineStart)}${replacement}${value.slice(lineEnd)}`,
|
|
184
|
+
selectionStart: lineStart,
|
|
185
|
+
selectionEnd: lineStart + replacement.length
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function isMarkdownFormatActive(value, selectionStart, selectionEnd, format) {
|
|
189
|
+
if (format.type === "link") {
|
|
190
|
+
return findLinkAt(value, selectionStart, selectionEnd, format.urlPlaceholder) !== void 0;
|
|
191
|
+
}
|
|
192
|
+
if (format.type === "escape") return isSelectionEscaped(value, selectionStart, selectionEnd);
|
|
193
|
+
if (format.type === "wrap") {
|
|
194
|
+
const character = format.marker[0];
|
|
195
|
+
let start = selectionStart;
|
|
196
|
+
let end = selectionEnd;
|
|
197
|
+
while (start > 0 && value[start - 1] === character && !isEscaped(value, start - 1)) start--;
|
|
198
|
+
while (end < value.length && value[end] === character && !isEscaped(value, end)) end++;
|
|
199
|
+
const markerCount = Math.min(selectionStart - start, end - selectionEnd);
|
|
200
|
+
return format.marker.length === 1 ? markerCount % 2 === 1 : markerCount >= 2;
|
|
201
|
+
}
|
|
202
|
+
const previousNewline = selectionStart > 0 ? value.lastIndexOf("\n", selectionStart - 1) : -1;
|
|
203
|
+
const lineStart = previousNewline + 1;
|
|
204
|
+
const selectedEnd = selectionEnd > selectionStart && value[selectionEnd - 1] === "\n" ? selectionEnd - 1 : selectionEnd;
|
|
205
|
+
const lineEndIndex = value.indexOf("\n", selectedEnd);
|
|
206
|
+
const lineEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
|
|
207
|
+
const lines = value.slice(lineStart, lineEnd).split("\n");
|
|
208
|
+
if (format.prefix.startsWith("#")) {
|
|
209
|
+
return lines.every((line) => {
|
|
210
|
+
var _a;
|
|
211
|
+
return ((_a = /^(#{1,6}\s+)/.exec(line)) == null ? void 0 : _a[0]) === format.prefix;
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return lines.every((line) => line.startsWith(format.prefix));
|
|
215
|
+
}
|
|
216
|
+
function getAttr(token, name) {
|
|
217
|
+
const value = token.attrGet(name);
|
|
218
|
+
return value === null ? void 0 : String(value);
|
|
219
|
+
}
|
|
220
|
+
function toStyle(style) {
|
|
221
|
+
var _a;
|
|
222
|
+
const align = style && ((_a = /^text-align:\s*(left|right|center)$/.exec(style)) == null ? void 0 : _a[1]);
|
|
223
|
+
return align ? { textAlign: align } : void 0;
|
|
224
|
+
}
|
|
225
|
+
function renderLeaf(token, key, options) {
|
|
226
|
+
var _a, _b;
|
|
227
|
+
switch (token.type) {
|
|
228
|
+
case "text":
|
|
229
|
+
return token.content;
|
|
230
|
+
case "softbreak":
|
|
231
|
+
return "\n";
|
|
232
|
+
case "hardbreak":
|
|
233
|
+
return /* @__PURE__ */ jsx("br", {}, key);
|
|
234
|
+
case "code_inline":
|
|
235
|
+
return /* @__PURE__ */ jsx("code", { children: token.content }, key);
|
|
236
|
+
case "code_block":
|
|
237
|
+
return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: token.content }) }, key);
|
|
238
|
+
case "fence": {
|
|
239
|
+
const language = token.info.trim().split(/\s+/)[0];
|
|
240
|
+
return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children: token.content }) }, key);
|
|
241
|
+
}
|
|
242
|
+
case "hr":
|
|
243
|
+
return /* @__PURE__ */ jsx("hr", {}, key);
|
|
244
|
+
case "image":
|
|
245
|
+
return parser.renderer.renderInlineAsText((_a = token.children) != null ? _a : [], parser.options, {});
|
|
246
|
+
case "inline":
|
|
247
|
+
return renderTokens((_b = token.children) != null ? _b : [], options);
|
|
248
|
+
default:
|
|
249
|
+
return token.content || null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function renderElement(open, children, key, options) {
|
|
253
|
+
var _a;
|
|
254
|
+
if (open.hidden) return /* @__PURE__ */ jsx(React.Fragment, { children }, key);
|
|
255
|
+
if (/^h[1-6]$/.test(open.tag)) {
|
|
256
|
+
const level = Math.min(6, Math.max(1, Number(open.tag[1]) + ((_a = options.headingOffset) != null ? _a : 0)));
|
|
257
|
+
return React.createElement(`h${level}`, { key }, children);
|
|
258
|
+
}
|
|
259
|
+
if (open.tag === "a") {
|
|
260
|
+
const href = getAttr(open, "href");
|
|
261
|
+
const newTab = options.openLinksInNewTab && !/^(mailto|tel):/i.test(href != null ? href : "");
|
|
262
|
+
return /* @__PURE__ */ jsxs(
|
|
263
|
+
"a",
|
|
264
|
+
{
|
|
265
|
+
href,
|
|
266
|
+
title: getAttr(open, "title"),
|
|
267
|
+
target: newTab ? "_blank" : void 0,
|
|
268
|
+
rel: newTab ? "noopener noreferrer" : void 0,
|
|
269
|
+
children: [
|
|
270
|
+
children,
|
|
271
|
+
newTab && options.newTabHint
|
|
272
|
+
]
|
|
273
|
+
},
|
|
274
|
+
key
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (open.tag === "ol") {
|
|
278
|
+
const start = getAttr(open, "start");
|
|
279
|
+
return /* @__PURE__ */ jsx("ol", { start: start ? Number(start) : void 0, children }, key);
|
|
280
|
+
}
|
|
281
|
+
if (open.tag === "th" || open.tag === "td") {
|
|
282
|
+
return React.createElement(open.tag, { key, style: toStyle(getAttr(open, "style")) }, children);
|
|
283
|
+
}
|
|
284
|
+
return React.createElement(open.tag, { key }, children);
|
|
285
|
+
}
|
|
286
|
+
function renderTokens(tokens, options) {
|
|
287
|
+
const root = [];
|
|
288
|
+
const stack = [];
|
|
289
|
+
tokens.forEach((token, index) => {
|
|
290
|
+
const siblings = stack.length ? stack[stack.length - 1].children : root;
|
|
291
|
+
if (token.nesting === 1) {
|
|
292
|
+
stack.push({ open: token, children: [] });
|
|
293
|
+
} else if (token.nesting === -1) {
|
|
294
|
+
const element = stack.pop();
|
|
295
|
+
if (!element) return;
|
|
296
|
+
const parent = stack.length ? stack[stack.length - 1].children : root;
|
|
297
|
+
parent.push(renderElement(element.open, element.children, index, options));
|
|
298
|
+
} else {
|
|
299
|
+
const leaf = renderLeaf(token, index, options);
|
|
300
|
+
if (Array.isArray(leaf)) siblings.push(/* @__PURE__ */ jsx(React.Fragment, { children: leaf }, index));
|
|
301
|
+
else if (leaf !== null) siblings.push(leaf);
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return root;
|
|
305
|
+
}
|
|
306
|
+
function renderMarkdown(value, options = {}) {
|
|
307
|
+
return renderTokens(parser.parse(value, {}), options);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export { escapeMarkdown, formatMarkdownSelection, getSafeHref, isMarkdownFormatActive, isPlainMarkdown, limitPlainText, markdownToPlainText, plainTextToMarkdown, renderMarkdown };
|
|
@@ -111,9 +111,9 @@ function Listbox({
|
|
|
111
111
|
].filter(Boolean).join(" "),
|
|
112
112
|
disabled: option.disabled,
|
|
113
113
|
onFocus: () => setActiveValue(option.value),
|
|
114
|
-
onClick: () => {
|
|
114
|
+
onClick: (event) => {
|
|
115
115
|
setActiveValue(option.value);
|
|
116
|
-
onOptionSelect(option.value);
|
|
116
|
+
onOptionSelect(option.value, event);
|
|
117
117
|
},
|
|
118
118
|
children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles__default.default.optionContent, children: [
|
|
119
119
|
option.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.icon, "aria-hidden": "true", children: option.icon }) : null,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type HTMLAttributes, type ReactNode } from 'react';
|
|
1
|
+
import { type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';
|
|
2
2
|
export type ListboxOption<T extends string | number = string> = {
|
|
3
3
|
value: T;
|
|
4
4
|
label: ReactNode;
|
|
@@ -15,7 +15,7 @@ export type ListboxProps<T extends string | number = string> = Omit<HTMLAttribut
|
|
|
15
15
|
selectedValues: readonly T[];
|
|
16
16
|
selectionMode?: 'single' | 'multiple';
|
|
17
17
|
autoFocusValue?: T;
|
|
18
|
-
onOptionSelect: (value: T) => void;
|
|
18
|
+
onOptionSelect: (value: T, event: MouseEvent<HTMLButtonElement>) => void;
|
|
19
19
|
onActiveChange?: (value: T) => void;
|
|
20
20
|
renderAction?: (option: ListboxOption<T>, selected: boolean) => ReactNode;
|
|
21
21
|
};
|
|
@@ -104,9 +104,9 @@ function Listbox({
|
|
|
104
104
|
].filter(Boolean).join(" "),
|
|
105
105
|
disabled: option.disabled,
|
|
106
106
|
onFocus: () => setActiveValue(option.value),
|
|
107
|
-
onClick: () => {
|
|
107
|
+
onClick: (event) => {
|
|
108
108
|
setActiveValue(option.value);
|
|
109
|
-
onOptionSelect(option.value);
|
|
109
|
+
onOptionSelect(option.value, event);
|
|
110
110
|
},
|
|
111
111
|
children: /* @__PURE__ */ jsxs("span", { className: styles.optionContent, children: [
|
|
112
112
|
option.icon ? /* @__PURE__ */ jsx("span", { className: styles.icon, "aria-hidden": "true", children: option.icon }) : null,
|
|
@@ -8,12 +8,12 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
|
|
9
9
|
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
10
10
|
|
|
11
|
-
function MetaBar({ items, separator = "\u2022" }) {
|
|
11
|
+
function MetaBar({ items, separator = "\u2022", size = "sm" }) {
|
|
12
12
|
const visibleItems = items.filter(
|
|
13
13
|
(item) => item.value !== null && item.value !== void 0 && item.value !== ""
|
|
14
14
|
);
|
|
15
15
|
if (visibleItems.length === 0) return null;
|
|
16
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles__default.default.meta
|
|
16
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${styles__default.default.meta} ${styles__default.default[size]}`, children: visibleItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
|
|
17
17
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: item.muted === false ? styles__default.default.itemDefault : styles__default.default.itemMuted, children: [
|
|
18
18
|
item.label && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
19
19
|
item.label,
|
|
@@ -6,7 +6,8 @@ export interface MetaItem {
|
|
|
6
6
|
}
|
|
7
7
|
interface MetaBarProps {
|
|
8
8
|
items: MetaItem[];
|
|
9
|
+
size?: 'sm' | 'md';
|
|
9
10
|
separator?: ReactNode;
|
|
10
11
|
}
|
|
11
|
-
export declare function MetaBar({ items, separator }: MetaBarProps): JSX.Element | null;
|
|
12
|
+
export declare function MetaBar({ items, separator, size }: MetaBarProps): JSX.Element | null;
|
|
12
13
|
export {};
|
|
@@ -2,12 +2,12 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
2
2
|
import { Fragment } from 'react';
|
|
3
3
|
import styles from './MetaBar.module.css';
|
|
4
4
|
|
|
5
|
-
function MetaBar({ items, separator = "\u2022" }) {
|
|
5
|
+
function MetaBar({ items, separator = "\u2022", size = "sm" }) {
|
|
6
6
|
const visibleItems = items.filter(
|
|
7
7
|
(item) => item.value !== null && item.value !== void 0 && item.value !== ""
|
|
8
8
|
);
|
|
9
9
|
if (visibleItems.length === 0) return null;
|
|
10
|
-
return /* @__PURE__ */ jsx("div", { className: styles.meta
|
|
10
|
+
return /* @__PURE__ */ jsx("div", { className: `${styles.meta} ${styles[size]}`, children: visibleItems.map((item, index) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
11
11
|
/* @__PURE__ */ jsxs("span", { className: item.muted === false ? styles.itemDefault : styles.itemMuted, children: [
|
|
12
12
|
item.label && /* @__PURE__ */ jsxs("span", { children: [
|
|
13
13
|
item.label,
|
|
@@ -13,6 +13,36 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
13
13
|
|
|
14
14
|
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
15
15
|
|
|
16
|
+
function getFocusable(container) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const selector = [
|
|
19
|
+
'a[href]:not([aria-disabled="true"])',
|
|
20
|
+
"button:not([disabled])",
|
|
21
|
+
"input:not([disabled])",
|
|
22
|
+
"select:not([disabled])",
|
|
23
|
+
"textarea:not([disabled])",
|
|
24
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
25
|
+
].join(",");
|
|
26
|
+
const focusables = [];
|
|
27
|
+
const addFocusableContent = (root) => {
|
|
28
|
+
const candidates = root.querySelectorAll(selector);
|
|
29
|
+
for (const element of candidates) {
|
|
30
|
+
if (element.hasAttribute("hidden") || element.getAttribute("aria-hidden") === "true" || element.getAttribute("aria-disabled") === "true") {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (!focusables.includes(element)) focusables.push(element);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
addFocusableContent(container);
|
|
37
|
+
for (const trigger of container.querySelectorAll("[aria-controls]")) {
|
|
38
|
+
const controlledIds = (_b = (_a = trigger.getAttribute("aria-controls")) == null ? void 0 : _a.split(/\s+/)) != null ? _b : [];
|
|
39
|
+
for (const controlledId of controlledIds) {
|
|
40
|
+
const controlledContent = document.getElementById(controlledId);
|
|
41
|
+
if (controlledContent) addFocusableContent(controlledContent);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return focusables;
|
|
45
|
+
}
|
|
16
46
|
function SidePanel({
|
|
17
47
|
isOpen,
|
|
18
48
|
onClose,
|
|
@@ -36,23 +66,70 @@ function SidePanel({
|
|
|
36
66
|
const [shouldRender, setShouldRender] = react.useState(isOpen);
|
|
37
67
|
const [isActive, setIsActive] = react.useState(false);
|
|
38
68
|
const panelRef = react.useRef(null);
|
|
69
|
+
const returnFocusRef = react.useRef(null);
|
|
70
|
+
const wasOpenRef = react.useRef(false);
|
|
39
71
|
react.useEffect(() => setMounted(true), []);
|
|
40
72
|
react.useEffect(() => {
|
|
41
73
|
if (isOpen) setShouldRender(true);
|
|
42
74
|
}, [isOpen]);
|
|
43
75
|
react.useEffect(() => {
|
|
44
|
-
if (!isOpen) return;
|
|
76
|
+
if (!isOpen || !shouldRender || !mounted) return;
|
|
77
|
+
if (!wasOpenRef.current) {
|
|
78
|
+
const activeElement = document.activeElement;
|
|
79
|
+
returnFocusRef.current = activeElement instanceof HTMLElement ? activeElement : null;
|
|
80
|
+
wasOpenRef.current = true;
|
|
81
|
+
}
|
|
82
|
+
const focusInitial = () => {
|
|
83
|
+
var _a;
|
|
84
|
+
const panel = panelRef.current;
|
|
85
|
+
if (!panel) return;
|
|
86
|
+
const closeButton = panel.querySelector(
|
|
87
|
+
'[data-cy="side-panel-close-button"]'
|
|
88
|
+
);
|
|
89
|
+
const focusables = getFocusable(panel);
|
|
90
|
+
((_a = closeButton != null ? closeButton : focusables[0]) != null ? _a : panel).focus();
|
|
91
|
+
};
|
|
92
|
+
focusInitial();
|
|
93
|
+
const frame = requestAnimationFrame(focusInitial);
|
|
45
94
|
const handleKeyDown = (e) => {
|
|
46
95
|
if (e.key === "Escape") {
|
|
47
96
|
e.stopPropagation();
|
|
48
97
|
onClose();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (e.key !== "Tab") return;
|
|
101
|
+
const panel = panelRef.current;
|
|
102
|
+
if (!panel) return;
|
|
103
|
+
const focusables = getFocusable(panel);
|
|
104
|
+
if (focusables.length === 0) {
|
|
105
|
+
e.preventDefault();
|
|
106
|
+
panel.focus();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const first = focusables[0];
|
|
110
|
+
const last = focusables[focusables.length - 1];
|
|
111
|
+
const active = document.activeElement;
|
|
112
|
+
if (e.shiftKey && (active === first || !panel.contains(active))) {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
last.focus();
|
|
115
|
+
} else if (!e.shiftKey && (active === last || !panel.contains(active))) {
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
first.focus();
|
|
49
118
|
}
|
|
50
119
|
};
|
|
51
120
|
document.addEventListener("keydown", handleKeyDown);
|
|
52
121
|
return () => {
|
|
122
|
+
cancelAnimationFrame(frame);
|
|
53
123
|
document.removeEventListener("keydown", handleKeyDown);
|
|
54
124
|
};
|
|
55
|
-
}, [isOpen, onClose]);
|
|
125
|
+
}, [isOpen, mounted, onClose, shouldRender]);
|
|
126
|
+
react.useEffect(() => {
|
|
127
|
+
var _a;
|
|
128
|
+
if (isOpen || !wasOpenRef.current) return;
|
|
129
|
+
wasOpenRef.current = false;
|
|
130
|
+
(_a = returnFocusRef.current) == null ? void 0 : _a.focus();
|
|
131
|
+
returnFocusRef.current = null;
|
|
132
|
+
}, [isOpen]);
|
|
56
133
|
react.useEffect(() => {
|
|
57
134
|
if (!shouldRender) return;
|
|
58
135
|
if (!isOpen) {
|
|
@@ -103,6 +180,7 @@ function SidePanel({
|
|
|
103
180
|
"data-cy": "details-panel",
|
|
104
181
|
role: "dialog",
|
|
105
182
|
"aria-modal": "true",
|
|
183
|
+
tabIndex: -1,
|
|
106
184
|
children: [
|
|
107
185
|
hasDetails ? /* @__PURE__ */ jsxRuntime.jsxs("aside", { className: styles__default.default.detailsCol, "data-cy": "details-panel-details", children: [
|
|
108
186
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.detailsHeader, children: [
|
|
@@ -7,6 +7,36 @@ import { Headline } from '../../../components/headline/Headline';
|
|
|
7
7
|
import { MOTION_MS } from '../../../styles/animation';
|
|
8
8
|
import styles from './SidePanel.module.css';
|
|
9
9
|
|
|
10
|
+
function getFocusable(container) {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
const selector = [
|
|
13
|
+
'a[href]:not([aria-disabled="true"])',
|
|
14
|
+
"button:not([disabled])",
|
|
15
|
+
"input:not([disabled])",
|
|
16
|
+
"select:not([disabled])",
|
|
17
|
+
"textarea:not([disabled])",
|
|
18
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
19
|
+
].join(",");
|
|
20
|
+
const focusables = [];
|
|
21
|
+
const addFocusableContent = (root) => {
|
|
22
|
+
const candidates = root.querySelectorAll(selector);
|
|
23
|
+
for (const element of candidates) {
|
|
24
|
+
if (element.hasAttribute("hidden") || element.getAttribute("aria-hidden") === "true" || element.getAttribute("aria-disabled") === "true") {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (!focusables.includes(element)) focusables.push(element);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
addFocusableContent(container);
|
|
31
|
+
for (const trigger of container.querySelectorAll("[aria-controls]")) {
|
|
32
|
+
const controlledIds = (_b = (_a = trigger.getAttribute("aria-controls")) == null ? void 0 : _a.split(/\s+/)) != null ? _b : [];
|
|
33
|
+
for (const controlledId of controlledIds) {
|
|
34
|
+
const controlledContent = document.getElementById(controlledId);
|
|
35
|
+
if (controlledContent) addFocusableContent(controlledContent);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return focusables;
|
|
39
|
+
}
|
|
10
40
|
function SidePanel({
|
|
11
41
|
isOpen,
|
|
12
42
|
onClose,
|
|
@@ -30,23 +60,70 @@ function SidePanel({
|
|
|
30
60
|
const [shouldRender, setShouldRender] = useState(isOpen);
|
|
31
61
|
const [isActive, setIsActive] = useState(false);
|
|
32
62
|
const panelRef = useRef(null);
|
|
63
|
+
const returnFocusRef = useRef(null);
|
|
64
|
+
const wasOpenRef = useRef(false);
|
|
33
65
|
useEffect(() => setMounted(true), []);
|
|
34
66
|
useEffect(() => {
|
|
35
67
|
if (isOpen) setShouldRender(true);
|
|
36
68
|
}, [isOpen]);
|
|
37
69
|
useEffect(() => {
|
|
38
|
-
if (!isOpen) return;
|
|
70
|
+
if (!isOpen || !shouldRender || !mounted) return;
|
|
71
|
+
if (!wasOpenRef.current) {
|
|
72
|
+
const activeElement = document.activeElement;
|
|
73
|
+
returnFocusRef.current = activeElement instanceof HTMLElement ? activeElement : null;
|
|
74
|
+
wasOpenRef.current = true;
|
|
75
|
+
}
|
|
76
|
+
const focusInitial = () => {
|
|
77
|
+
var _a;
|
|
78
|
+
const panel = panelRef.current;
|
|
79
|
+
if (!panel) return;
|
|
80
|
+
const closeButton = panel.querySelector(
|
|
81
|
+
'[data-cy="side-panel-close-button"]'
|
|
82
|
+
);
|
|
83
|
+
const focusables = getFocusable(panel);
|
|
84
|
+
((_a = closeButton != null ? closeButton : focusables[0]) != null ? _a : panel).focus();
|
|
85
|
+
};
|
|
86
|
+
focusInitial();
|
|
87
|
+
const frame = requestAnimationFrame(focusInitial);
|
|
39
88
|
const handleKeyDown = (e) => {
|
|
40
89
|
if (e.key === "Escape") {
|
|
41
90
|
e.stopPropagation();
|
|
42
91
|
onClose();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (e.key !== "Tab") return;
|
|
95
|
+
const panel = panelRef.current;
|
|
96
|
+
if (!panel) return;
|
|
97
|
+
const focusables = getFocusable(panel);
|
|
98
|
+
if (focusables.length === 0) {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
panel.focus();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const first = focusables[0];
|
|
104
|
+
const last = focusables[focusables.length - 1];
|
|
105
|
+
const active = document.activeElement;
|
|
106
|
+
if (e.shiftKey && (active === first || !panel.contains(active))) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
last.focus();
|
|
109
|
+
} else if (!e.shiftKey && (active === last || !panel.contains(active))) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
first.focus();
|
|
43
112
|
}
|
|
44
113
|
};
|
|
45
114
|
document.addEventListener("keydown", handleKeyDown);
|
|
46
115
|
return () => {
|
|
116
|
+
cancelAnimationFrame(frame);
|
|
47
117
|
document.removeEventListener("keydown", handleKeyDown);
|
|
48
118
|
};
|
|
49
|
-
}, [isOpen, onClose]);
|
|
119
|
+
}, [isOpen, mounted, onClose, shouldRender]);
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
var _a;
|
|
122
|
+
if (isOpen || !wasOpenRef.current) return;
|
|
123
|
+
wasOpenRef.current = false;
|
|
124
|
+
(_a = returnFocusRef.current) == null ? void 0 : _a.focus();
|
|
125
|
+
returnFocusRef.current = null;
|
|
126
|
+
}, [isOpen]);
|
|
50
127
|
useEffect(() => {
|
|
51
128
|
if (!shouldRender) return;
|
|
52
129
|
if (!isOpen) {
|
|
@@ -97,6 +174,7 @@ function SidePanel({
|
|
|
97
174
|
"data-cy": "details-panel",
|
|
98
175
|
role: "dialog",
|
|
99
176
|
"aria-modal": "true",
|
|
177
|
+
tabIndex: -1,
|
|
100
178
|
children: [
|
|
101
179
|
hasDetails ? /* @__PURE__ */ jsxs("aside", { className: styles.detailsCol, "data-cy": "details-panel-details", children: [
|
|
102
180
|
/* @__PURE__ */ jsxs("div", { className: styles.detailsHeader, children: [
|