@dbcdk/react-components 0.0.193 → 0.0.195
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/components/json-viewer/JsonViewer.cjs +10 -8
- package/dist/components/json-viewer/JsonViewer.d.ts +1 -1
- package/dist/components/json-viewer/JsonViewer.js +10 -8
- package/dist/components/json-viewer/JsonViewer.module.css +5 -0
- package/dist/components/json-viewer/types.d.ts +2 -0
- package/dist/components/line-viewer/LineViewer.cjs +2 -3
- package/dist/components/line-viewer/LineViewer.js +2 -3
- package/dist/components/line-viewer/tokenizeLineFormat.cjs +37 -17
- package/dist/components/line-viewer/tokenizeLineFormat.d.ts +5 -4
- package/dist/components/line-viewer/tokenizeLineFormat.js +37 -17
- package/dist/components/xml-viewer/XmlViewer.cjs +11 -5
- package/dist/components/xml-viewer/XmlViewer.d.ts +7 -1
- package/dist/components/xml-viewer/XmlViewer.js +11 -5
- package/dist/components/xml-viewer/XmlViewer.module.css +10 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@ var JsonNode = require('./JsonNode');
|
|
|
11
11
|
var styles = require('./JsonViewer.module.css');
|
|
12
12
|
var useClipboardStatus = require('./useClipboardStatus');
|
|
13
13
|
var utils = require('./utils');
|
|
14
|
+
var clipboard_utils = require('../../utils/clipboard.utils');
|
|
14
15
|
var Button = require('../button/Button');
|
|
15
16
|
var Icon = require('../icon/Icon');
|
|
16
17
|
|
|
@@ -31,6 +32,7 @@ function JsonViewer({
|
|
|
31
32
|
className,
|
|
32
33
|
maxHeight,
|
|
33
34
|
appearance = "dark",
|
|
35
|
+
deleted = false,
|
|
34
36
|
storageKey,
|
|
35
37
|
borderless = false
|
|
36
38
|
}) {
|
|
@@ -77,13 +79,13 @@ function JsonViewer({
|
|
|
77
79
|
);
|
|
78
80
|
const hasResults = normalizedQuery ? matches.size > 0 : true;
|
|
79
81
|
const rawJson = react.useMemo(() => JSON.stringify(safeValue, null, 2), [safeValue]);
|
|
82
|
+
const contentClassName = [
|
|
83
|
+
viewMode === "raw" ? styles__default.default.rawPane : styles__default.default.treePane,
|
|
84
|
+
deleted ? styles__default.default.deleted : ""
|
|
85
|
+
].filter(Boolean).join(" ");
|
|
80
86
|
const handleCopy = async (nodeId, rawValue) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
setCopiedId(nodeId);
|
|
84
|
-
} catch {
|
|
85
|
-
setCopiedId(null);
|
|
86
|
-
}
|
|
87
|
+
const success = await clipboard_utils.copyToClipboard(rawValue);
|
|
88
|
+
setCopiedId(success ? nodeId : null);
|
|
87
89
|
};
|
|
88
90
|
const handleCopyAll = () => handleCopy(COPY_ALL_ID, rawJson);
|
|
89
91
|
const handleExpandAll = () => {
|
|
@@ -243,14 +245,14 @@ function JsonViewer({
|
|
|
243
245
|
viewMode === "raw" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
244
246
|
"pre",
|
|
245
247
|
{
|
|
246
|
-
className:
|
|
248
|
+
className: contentClassName,
|
|
247
249
|
style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
|
|
248
250
|
children: rawJson
|
|
249
251
|
}
|
|
250
252
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
251
253
|
"div",
|
|
252
254
|
{
|
|
253
|
-
className:
|
|
255
|
+
className: contentClassName,
|
|
254
256
|
role: "tree",
|
|
255
257
|
"aria-label": "JSON-visning",
|
|
256
258
|
style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
import type { JsonViewerProps } from './types';
|
|
3
3
|
export type { JsonViewerProps, JsonViewerViewMode } from './types';
|
|
4
|
-
export declare function JsonViewer({ value, defaultExpandedDepth, expandAll, defaultViewMode, searchDebounceMs, helperText, searchPlaceholder, emptySearchText, className, maxHeight, appearance, storageKey, borderless, }: JsonViewerProps): JSX.Element;
|
|
4
|
+
export declare function JsonViewer({ value, defaultExpandedDepth, expandAll, defaultViewMode, searchDebounceMs, helperText, searchPlaceholder, emptySearchText, className, maxHeight, appearance, deleted, storageKey, borderless, }: JsonViewerProps): JSX.Element;
|
|
@@ -9,6 +9,7 @@ import { JsonNode } from './JsonNode';
|
|
|
9
9
|
import styles from './JsonViewer.module.css';
|
|
10
10
|
import { useClipboardStatus } from './useClipboardStatus';
|
|
11
11
|
import { normalizeSearch, isJsonValue, collectSearchMatches, collectExpandableNodeIds, collectInitiallyExpandedNodeIds } from './utils';
|
|
12
|
+
import { copyToClipboard } from '../../utils/clipboard.utils';
|
|
12
13
|
import { Button } from '../button/Button';
|
|
13
14
|
import { Icon } from '../icon/Icon';
|
|
14
15
|
|
|
@@ -25,6 +26,7 @@ function JsonViewer({
|
|
|
25
26
|
className,
|
|
26
27
|
maxHeight,
|
|
27
28
|
appearance = "dark",
|
|
29
|
+
deleted = false,
|
|
28
30
|
storageKey,
|
|
29
31
|
borderless = false
|
|
30
32
|
}) {
|
|
@@ -71,13 +73,13 @@ function JsonViewer({
|
|
|
71
73
|
);
|
|
72
74
|
const hasResults = normalizedQuery ? matches.size > 0 : true;
|
|
73
75
|
const rawJson = useMemo(() => JSON.stringify(safeValue, null, 2), [safeValue]);
|
|
76
|
+
const contentClassName = [
|
|
77
|
+
viewMode === "raw" ? styles.rawPane : styles.treePane,
|
|
78
|
+
deleted ? styles.deleted : ""
|
|
79
|
+
].filter(Boolean).join(" ");
|
|
74
80
|
const handleCopy = async (nodeId, rawValue) => {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
setCopiedId(nodeId);
|
|
78
|
-
} catch {
|
|
79
|
-
setCopiedId(null);
|
|
80
|
-
}
|
|
81
|
+
const success = await copyToClipboard(rawValue);
|
|
82
|
+
setCopiedId(success ? nodeId : null);
|
|
81
83
|
};
|
|
82
84
|
const handleCopyAll = () => handleCopy(COPY_ALL_ID, rawJson);
|
|
83
85
|
const handleExpandAll = () => {
|
|
@@ -237,14 +239,14 @@ function JsonViewer({
|
|
|
237
239
|
viewMode === "raw" ? /* @__PURE__ */ jsx(
|
|
238
240
|
"pre",
|
|
239
241
|
{
|
|
240
|
-
className:
|
|
242
|
+
className: contentClassName,
|
|
241
243
|
style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
|
|
242
244
|
children: rawJson
|
|
243
245
|
}
|
|
244
246
|
) : /* @__PURE__ */ jsx(
|
|
245
247
|
"div",
|
|
246
248
|
{
|
|
247
|
-
className:
|
|
249
|
+
className: contentClassName,
|
|
248
250
|
role: "tree",
|
|
249
251
|
"aria-label": "JSON-visning",
|
|
250
252
|
style: maxHeight !== void 0 ? { maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight } : void 0,
|
|
@@ -22,6 +22,8 @@ export interface JsonViewerProps {
|
|
|
22
22
|
maxHeight?: number | string;
|
|
23
23
|
/** Initial appearance. The user can toggle it via the kebab menu; defaults to 'dark'. */
|
|
24
24
|
appearance?: JsonViewerAppearance;
|
|
25
|
+
/** Displays all JSON content in the theme's error color, for deleted or invalid content. */
|
|
26
|
+
deleted?: boolean;
|
|
25
27
|
/**
|
|
26
28
|
* When set, the user's appearance choice is persisted to localStorage
|
|
27
29
|
* under `${storageKey}:appearance`.
|
|
@@ -9,6 +9,7 @@ var Popover = require('../../components/popover/Popover');
|
|
|
9
9
|
var usePersistentState = require('../../hooks/usePersistentState');
|
|
10
10
|
var styles = require('./LineViewer.module.css');
|
|
11
11
|
var tokenizeLineFormat = require('./tokenizeLineFormat');
|
|
12
|
+
var clipboard_utils = require('../../utils/clipboard.utils');
|
|
12
13
|
var Button = require('../button/Button');
|
|
13
14
|
var Icon = require('../icon/Icon');
|
|
14
15
|
|
|
@@ -53,9 +54,7 @@ function LineViewer({
|
|
|
53
54
|
validate: (stored) => typeof stored === "boolean"
|
|
54
55
|
});
|
|
55
56
|
const handleCopy = () => {
|
|
56
|
-
|
|
57
|
-
(_a = navigator.clipboard) == null ? void 0 : _a.writeText(lines).catch(() => {
|
|
58
|
-
});
|
|
57
|
+
void clipboard_utils.copyToClipboard(lines);
|
|
59
58
|
};
|
|
60
59
|
const handleDoubleClick = () => {
|
|
61
60
|
const el = codeRef.current;
|
|
@@ -7,6 +7,7 @@ import { Popover } from '../../components/popover/Popover';
|
|
|
7
7
|
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
8
8
|
import styles from './LineViewer.module.css';
|
|
9
9
|
import { tokenizeLineFormat } from './tokenizeLineFormat';
|
|
10
|
+
import { copyToClipboard } from '../../utils/clipboard.utils';
|
|
10
11
|
import { Button } from '../button/Button';
|
|
11
12
|
import { Icon } from '../icon/Icon';
|
|
12
13
|
|
|
@@ -47,9 +48,7 @@ function LineViewer({
|
|
|
47
48
|
validate: (stored) => typeof stored === "boolean"
|
|
48
49
|
});
|
|
49
50
|
const handleCopy = () => {
|
|
50
|
-
|
|
51
|
-
(_a = navigator.clipboard) == null ? void 0 : _a.writeText(lines).catch(() => {
|
|
52
|
-
});
|
|
51
|
+
void copyToClipboard(lines);
|
|
53
52
|
};
|
|
54
53
|
const handleDoubleClick = () => {
|
|
55
54
|
const el = codeRef.current;
|
|
@@ -1,34 +1,51 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const FIELD_LINE_PATTERN = /^(\
|
|
3
|
+
const FIELD_LINE_PATTERN = /^(\d{3})(\s+)(?:((?!\*)\S+)(\s+))?(\*[\s\S]*)$/;
|
|
4
4
|
const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
|
|
5
|
+
function tokenizeSubfields(text) {
|
|
6
|
+
const tokens = [];
|
|
7
|
+
SUBFIELD_PATTERN.lastIndex = 0;
|
|
8
|
+
let match;
|
|
9
|
+
let lastIndex = 0;
|
|
10
|
+
while (match = SUBFIELD_PATTERN.exec(text)) {
|
|
11
|
+
if (match.index > lastIndex) {
|
|
12
|
+
tokens.push({ type: "text", value: text.slice(lastIndex, match.index) });
|
|
13
|
+
}
|
|
14
|
+
tokens.push({ type: "subfieldMarker", value: "*" });
|
|
15
|
+
tokens.push({ type: "subfieldCode", value: match[1] });
|
|
16
|
+
tokens.push({ type: "value", value: match[2] });
|
|
17
|
+
lastIndex = SUBFIELD_PATTERN.lastIndex;
|
|
18
|
+
}
|
|
19
|
+
if (lastIndex < text.length) {
|
|
20
|
+
tokens.push({ type: "text", value: text.slice(lastIndex) });
|
|
21
|
+
}
|
|
22
|
+
return tokens;
|
|
23
|
+
}
|
|
24
|
+
function tokenizeContinuationLine(line, continuesValue) {
|
|
25
|
+
const tokens = tokenizeSubfields(line);
|
|
26
|
+
if (!continuesValue) return tokens;
|
|
27
|
+
return tokens.map((token) => token.type === "text" ? { ...token, type: "value" } : token);
|
|
28
|
+
}
|
|
5
29
|
function tokenizeFieldLine(line) {
|
|
6
30
|
const match = FIELD_LINE_PATTERN.exec(line);
|
|
7
|
-
if (!match) return
|
|
31
|
+
if (!match) return tokenizeContinuationLine(line, false);
|
|
8
32
|
const [, tag, gap1, indicator, gap2, rest] = match;
|
|
9
33
|
const tokens = [
|
|
10
34
|
{ type: "tag", value: tag },
|
|
11
|
-
{ type: "whitespace", value: gap1 }
|
|
12
|
-
{ type: "indicator", value: indicator },
|
|
13
|
-
{ type: "whitespace", value: gap2 }
|
|
35
|
+
{ type: "whitespace", value: gap1 }
|
|
14
36
|
];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
while (match2 = SUBFIELD_PATTERN.exec(rest)) {
|
|
19
|
-
tokens.push({ type: "subfieldMarker", value: "*" });
|
|
20
|
-
tokens.push({ type: "subfieldCode", value: match2[1] });
|
|
21
|
-
tokens.push({ type: "value", value: match2[2] });
|
|
22
|
-
lastIndex = SUBFIELD_PATTERN.lastIndex;
|
|
23
|
-
}
|
|
24
|
-
if (lastIndex < rest.length) {
|
|
25
|
-
tokens.push({ type: "text", value: rest.slice(lastIndex) });
|
|
37
|
+
if (indicator !== void 0) {
|
|
38
|
+
tokens.push({ type: "indicator", value: indicator });
|
|
39
|
+
tokens.push({ type: "whitespace", value: gap2 });
|
|
26
40
|
}
|
|
41
|
+
tokens.push(...tokenizeSubfields(rest));
|
|
27
42
|
return tokens;
|
|
28
43
|
}
|
|
29
44
|
function tokenizeLineFormat(source) {
|
|
45
|
+
var _a;
|
|
30
46
|
const tokens = [];
|
|
31
47
|
const chunks = source.split(/(\n)/);
|
|
48
|
+
let continuesValue = false;
|
|
32
49
|
for (const chunk of chunks) {
|
|
33
50
|
if (chunk === "") continue;
|
|
34
51
|
if (chunk === "\n") {
|
|
@@ -42,9 +59,12 @@ function tokenizeLineFormat(source) {
|
|
|
42
59
|
if (leading) tokens.push({ type: "text", value: leading });
|
|
43
60
|
tokens.push({ type: "terminator", value: "$" });
|
|
44
61
|
if (trailing) tokens.push({ type: "text", value: trailing });
|
|
62
|
+
continuesValue = false;
|
|
45
63
|
continue;
|
|
46
64
|
}
|
|
47
|
-
|
|
65
|
+
const lineTokens = FIELD_LINE_PATTERN.test(chunk) ? tokenizeFieldLine(chunk) : tokenizeContinuationLine(chunk, continuesValue);
|
|
66
|
+
tokens.push(...lineTokens);
|
|
67
|
+
continuesValue = ((_a = lineTokens[lineTokens.length - 1]) == null ? void 0 : _a.type) === "value";
|
|
48
68
|
}
|
|
49
69
|
return tokens;
|
|
50
70
|
}
|
|
@@ -4,11 +4,12 @@ export interface LineToken {
|
|
|
4
4
|
value: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Tokenizes a MARC-style "line format" record (field tag,
|
|
8
|
-
* `*code value` subfields, terminated by a lone `$`) for syntax
|
|
7
|
+
* Tokenizes a MARC-style "line format" record (field tag, optional
|
|
8
|
+
* indicators, `*code value` subfields, terminated by a lone `$`) for syntax
|
|
9
9
|
* highlighting without imposing a strict grammar. Preserves the source
|
|
10
10
|
* exactly - concatenating every token's `value` reproduces the input - so a
|
|
11
|
-
* wrapped continuation
|
|
12
|
-
* text instead of breaking the
|
|
11
|
+
* wrapped continuation after a subfield value retains value styling, while a
|
|
12
|
+
* standalone malformed line displays as plain text instead of breaking the
|
|
13
|
+
* tokenizer.
|
|
13
14
|
*/
|
|
14
15
|
export declare function tokenizeLineFormat(source: string): LineToken[];
|
|
@@ -1,32 +1,49 @@
|
|
|
1
|
-
const FIELD_LINE_PATTERN = /^(\
|
|
1
|
+
const FIELD_LINE_PATTERN = /^(\d{3})(\s+)(?:((?!\*)\S+)(\s+))?(\*[\s\S]*)$/;
|
|
2
2
|
const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
|
|
3
|
+
function tokenizeSubfields(text) {
|
|
4
|
+
const tokens = [];
|
|
5
|
+
SUBFIELD_PATTERN.lastIndex = 0;
|
|
6
|
+
let match;
|
|
7
|
+
let lastIndex = 0;
|
|
8
|
+
while (match = SUBFIELD_PATTERN.exec(text)) {
|
|
9
|
+
if (match.index > lastIndex) {
|
|
10
|
+
tokens.push({ type: "text", value: text.slice(lastIndex, match.index) });
|
|
11
|
+
}
|
|
12
|
+
tokens.push({ type: "subfieldMarker", value: "*" });
|
|
13
|
+
tokens.push({ type: "subfieldCode", value: match[1] });
|
|
14
|
+
tokens.push({ type: "value", value: match[2] });
|
|
15
|
+
lastIndex = SUBFIELD_PATTERN.lastIndex;
|
|
16
|
+
}
|
|
17
|
+
if (lastIndex < text.length) {
|
|
18
|
+
tokens.push({ type: "text", value: text.slice(lastIndex) });
|
|
19
|
+
}
|
|
20
|
+
return tokens;
|
|
21
|
+
}
|
|
22
|
+
function tokenizeContinuationLine(line, continuesValue) {
|
|
23
|
+
const tokens = tokenizeSubfields(line);
|
|
24
|
+
if (!continuesValue) return tokens;
|
|
25
|
+
return tokens.map((token) => token.type === "text" ? { ...token, type: "value" } : token);
|
|
26
|
+
}
|
|
3
27
|
function tokenizeFieldLine(line) {
|
|
4
28
|
const match = FIELD_LINE_PATTERN.exec(line);
|
|
5
|
-
if (!match) return
|
|
29
|
+
if (!match) return tokenizeContinuationLine(line, false);
|
|
6
30
|
const [, tag, gap1, indicator, gap2, rest] = match;
|
|
7
31
|
const tokens = [
|
|
8
32
|
{ type: "tag", value: tag },
|
|
9
|
-
{ type: "whitespace", value: gap1 }
|
|
10
|
-
{ type: "indicator", value: indicator },
|
|
11
|
-
{ type: "whitespace", value: gap2 }
|
|
33
|
+
{ type: "whitespace", value: gap1 }
|
|
12
34
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
while (match2 = SUBFIELD_PATTERN.exec(rest)) {
|
|
17
|
-
tokens.push({ type: "subfieldMarker", value: "*" });
|
|
18
|
-
tokens.push({ type: "subfieldCode", value: match2[1] });
|
|
19
|
-
tokens.push({ type: "value", value: match2[2] });
|
|
20
|
-
lastIndex = SUBFIELD_PATTERN.lastIndex;
|
|
21
|
-
}
|
|
22
|
-
if (lastIndex < rest.length) {
|
|
23
|
-
tokens.push({ type: "text", value: rest.slice(lastIndex) });
|
|
35
|
+
if (indicator !== void 0) {
|
|
36
|
+
tokens.push({ type: "indicator", value: indicator });
|
|
37
|
+
tokens.push({ type: "whitespace", value: gap2 });
|
|
24
38
|
}
|
|
39
|
+
tokens.push(...tokenizeSubfields(rest));
|
|
25
40
|
return tokens;
|
|
26
41
|
}
|
|
27
42
|
function tokenizeLineFormat(source) {
|
|
43
|
+
var _a;
|
|
28
44
|
const tokens = [];
|
|
29
45
|
const chunks = source.split(/(\n)/);
|
|
46
|
+
let continuesValue = false;
|
|
30
47
|
for (const chunk of chunks) {
|
|
31
48
|
if (chunk === "") continue;
|
|
32
49
|
if (chunk === "\n") {
|
|
@@ -40,9 +57,12 @@ function tokenizeLineFormat(source) {
|
|
|
40
57
|
if (leading) tokens.push({ type: "text", value: leading });
|
|
41
58
|
tokens.push({ type: "terminator", value: "$" });
|
|
42
59
|
if (trailing) tokens.push({ type: "text", value: trailing });
|
|
60
|
+
continuesValue = false;
|
|
43
61
|
continue;
|
|
44
62
|
}
|
|
45
|
-
|
|
63
|
+
const lineTokens = FIELD_LINE_PATTERN.test(chunk) ? tokenizeFieldLine(chunk) : tokenizeContinuationLine(chunk, continuesValue);
|
|
64
|
+
tokens.push(...lineTokens);
|
|
65
|
+
continuesValue = ((_a = lineTokens[lineTokens.length - 1]) == null ? void 0 : _a.type) === "value";
|
|
46
66
|
}
|
|
47
67
|
return tokens;
|
|
48
68
|
}
|
|
@@ -9,6 +9,7 @@ var Popover = require('../../components/popover/Popover');
|
|
|
9
9
|
var usePersistentState = require('../../hooks/usePersistentState');
|
|
10
10
|
var tokenizeXml = require('./tokenizeXml');
|
|
11
11
|
var styles = require('./XmlViewer.module.css');
|
|
12
|
+
var clipboard_utils = require('../../utils/clipboard.utils');
|
|
12
13
|
var Button = require('../button/Button');
|
|
13
14
|
var Icon = require('../icon/Icon');
|
|
14
15
|
|
|
@@ -31,6 +32,8 @@ function XmlViewer({
|
|
|
31
32
|
xml,
|
|
32
33
|
size = "md",
|
|
33
34
|
appearance = "dark",
|
|
35
|
+
defaultViewMode = "highlighted",
|
|
36
|
+
deleted = false,
|
|
34
37
|
storageKey,
|
|
35
38
|
borderless = false
|
|
36
39
|
}) {
|
|
@@ -42,7 +45,7 @@ function XmlViewer({
|
|
|
42
45
|
validate: (stored) => stored === "dark" || stored === "light"
|
|
43
46
|
});
|
|
44
47
|
const { value: viewMode, setValue: setViewMode } = usePersistentState.usePersistentState({
|
|
45
|
-
initialValue:
|
|
48
|
+
initialValue: defaultViewMode,
|
|
46
49
|
storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
|
|
47
50
|
validate: (stored) => stored === "highlighted" || stored === "raw"
|
|
48
51
|
});
|
|
@@ -52,9 +55,7 @@ function XmlViewer({
|
|
|
52
55
|
validate: (stored) => typeof stored === "boolean"
|
|
53
56
|
});
|
|
54
57
|
const handleCopy = () => {
|
|
55
|
-
|
|
56
|
-
(_a = navigator.clipboard) == null ? void 0 : _a.writeText(xml).catch(() => {
|
|
57
|
-
});
|
|
58
|
+
void clipboard_utils.copyToClipboard(xml);
|
|
58
59
|
};
|
|
59
60
|
const handleDoubleClick = () => {
|
|
60
61
|
const el = codeRef.current;
|
|
@@ -159,7 +160,12 @@ function XmlViewer({
|
|
|
159
160
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
160
161
|
"pre",
|
|
161
162
|
{
|
|
162
|
-
className: [
|
|
163
|
+
className: [
|
|
164
|
+
styles__default.default.codePane,
|
|
165
|
+
styles__default.default[size],
|
|
166
|
+
wrap ? styles__default.default.wrap : styles__default.default.noWrap,
|
|
167
|
+
deleted ? styles__default.default.deleted : ""
|
|
168
|
+
].filter(Boolean).join(" "),
|
|
163
169
|
tabIndex: 0,
|
|
164
170
|
onDoubleClick: handleDoubleClick,
|
|
165
171
|
children: /* @__PURE__ */ jsxRuntime.jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { JSX } from 'react';
|
|
2
2
|
export type XmlViewerAppearance = 'dark' | 'light';
|
|
3
|
+
type ViewMode = 'highlighted' | 'raw';
|
|
3
4
|
export interface XmlViewerProps {
|
|
4
5
|
xml: string;
|
|
5
6
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
6
7
|
/** Initial appearance. The user can toggle it via the kebab menu; defaults to 'dark'. */
|
|
7
8
|
appearance?: XmlViewerAppearance;
|
|
9
|
+
/** Initial view mode. The user can toggle it via the kebab menu; defaults to 'highlighted'. */
|
|
10
|
+
defaultViewMode?: ViewMode;
|
|
11
|
+
/** Displays all XML text in the theme's error color, for deleted or invalid content. */
|
|
12
|
+
deleted?: boolean;
|
|
8
13
|
/**
|
|
9
14
|
* When set, the user's appearance/view-mode/wrap choices are persisted to
|
|
10
15
|
* localStorage under `${storageKey}:appearance` etc.
|
|
@@ -17,4 +22,5 @@ export interface XmlViewerProps {
|
|
|
17
22
|
*/
|
|
18
23
|
borderless?: boolean;
|
|
19
24
|
}
|
|
20
|
-
export declare function XmlViewer({ xml, size, appearance, storageKey, borderless, }: XmlViewerProps): JSX.Element;
|
|
25
|
+
export declare function XmlViewer({ xml, size, appearance, defaultViewMode, deleted, storageKey, borderless, }: XmlViewerProps): JSX.Element;
|
|
26
|
+
export {};
|
|
@@ -7,6 +7,7 @@ import { Popover } from '../../components/popover/Popover';
|
|
|
7
7
|
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
8
8
|
import { tokenizeXml } from './tokenizeXml';
|
|
9
9
|
import styles from './XmlViewer.module.css';
|
|
10
|
+
import { copyToClipboard } from '../../utils/clipboard.utils';
|
|
10
11
|
import { Button } from '../button/Button';
|
|
11
12
|
import { Icon } from '../icon/Icon';
|
|
12
13
|
|
|
@@ -25,6 +26,8 @@ function XmlViewer({
|
|
|
25
26
|
xml,
|
|
26
27
|
size = "md",
|
|
27
28
|
appearance = "dark",
|
|
29
|
+
defaultViewMode = "highlighted",
|
|
30
|
+
deleted = false,
|
|
28
31
|
storageKey,
|
|
29
32
|
borderless = false
|
|
30
33
|
}) {
|
|
@@ -36,7 +39,7 @@ function XmlViewer({
|
|
|
36
39
|
validate: (stored) => stored === "dark" || stored === "light"
|
|
37
40
|
});
|
|
38
41
|
const { value: viewMode, setValue: setViewMode } = usePersistentState({
|
|
39
|
-
initialValue:
|
|
42
|
+
initialValue: defaultViewMode,
|
|
40
43
|
storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
|
|
41
44
|
validate: (stored) => stored === "highlighted" || stored === "raw"
|
|
42
45
|
});
|
|
@@ -46,9 +49,7 @@ function XmlViewer({
|
|
|
46
49
|
validate: (stored) => typeof stored === "boolean"
|
|
47
50
|
});
|
|
48
51
|
const handleCopy = () => {
|
|
49
|
-
|
|
50
|
-
(_a = navigator.clipboard) == null ? void 0 : _a.writeText(xml).catch(() => {
|
|
51
|
-
});
|
|
52
|
+
void copyToClipboard(xml);
|
|
52
53
|
};
|
|
53
54
|
const handleDoubleClick = () => {
|
|
54
55
|
const el = codeRef.current;
|
|
@@ -153,7 +154,12 @@ function XmlViewer({
|
|
|
153
154
|
/* @__PURE__ */ jsx(
|
|
154
155
|
"pre",
|
|
155
156
|
{
|
|
156
|
-
className: [
|
|
157
|
+
className: [
|
|
158
|
+
styles.codePane,
|
|
159
|
+
styles[size],
|
|
160
|
+
wrap ? styles.wrap : styles.noWrap,
|
|
161
|
+
deleted ? styles.deleted : ""
|
|
162
|
+
].filter(Boolean).join(" "),
|
|
157
163
|
tabIndex: 0,
|
|
158
164
|
onDoubleClick: handleDoubleClick,
|
|
159
165
|
children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? xml : tokens.map((token, index) => {
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
--xml-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
|
|
7
7
|
--xml-attr-name: color-mix(in oklab, var(--color-fg-inverse) 50%, var(--color-syntax-accent) 50%);
|
|
8
|
-
--xml-attr-value: color-mix(
|
|
8
|
+
--xml-attr-value: color-mix(
|
|
9
|
+
in oklab,
|
|
10
|
+
var(--color-fg-inverse) 20%,
|
|
11
|
+
var(--color-syntax-string) 80%
|
|
12
|
+
);
|
|
9
13
|
--xml-comment: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
|
|
10
14
|
--xml-punctuation: color-mix(in oklab, var(--color-fg-inverse) 60%, transparent);
|
|
11
15
|
|
|
@@ -53,6 +57,11 @@
|
|
|
53
57
|
line-height: 1.35;
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
.deleted code,
|
|
61
|
+
.deleted code * {
|
|
62
|
+
color: var(--color-status-error);
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
.wrap {
|
|
57
66
|
white-space: pre-wrap;
|
|
58
67
|
overflow-wrap: break-word;
|