@dbcdk/react-components 0.0.181 → 0.0.183
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 +21 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.js +3 -0
- package/dist/components/clear-button/ClearButton.cjs +35 -12
- package/dist/components/clear-button/ClearButton.d.ts +17 -1
- package/dist/components/clear-button/ClearButton.js +35 -12
- package/dist/components/clear-button/ClearButton.module.css +12 -0
- package/dist/components/forms/multi-select/MultiSelect.cjs +2 -1
- package/dist/components/forms/multi-select/MultiSelect.js +2 -1
- package/dist/components/forms/select/Select.cjs +3 -1
- package/dist/components/forms/select/Select.js +3 -1
- package/dist/components/interval-select/IntervalSelect.cjs +2 -1
- package/dist/components/interval-select/IntervalSelect.js +2 -1
- package/dist/components/json-viewer/JsonViewer.cjs +3 -2
- package/dist/components/json-viewer/JsonViewer.d.ts +1 -1
- package/dist/components/json-viewer/JsonViewer.js +3 -2
- package/dist/components/json-viewer/JsonViewer.module.css +7 -0
- package/dist/components/json-viewer/types.d.ts +6 -0
- package/dist/components/line-viewer/LineViewer.cjs +174 -0
- package/dist/components/line-viewer/LineViewer.d.ts +20 -0
- package/dist/components/line-viewer/LineViewer.js +168 -0
- package/dist/components/line-viewer/LineViewer.module.css +112 -0
- package/dist/components/line-viewer/tokenizeLineFormat.cjs +52 -0
- package/dist/components/line-viewer/tokenizeLineFormat.d.ts +14 -0
- package/dist/components/line-viewer/tokenizeLineFormat.js +50 -0
- package/dist/components/tree-view/TreeNodeRow.cjs +99 -0
- package/dist/components/tree-view/TreeNodeRow.d.ts +18 -0
- package/dist/components/tree-view/TreeNodeRow.js +93 -0
- package/dist/components/tree-view/TreeView.cjs +93 -0
- package/dist/components/tree-view/TreeView.d.ts +20 -0
- package/dist/components/tree-view/TreeView.js +79 -0
- package/dist/components/tree-view/TreeView.module.css +141 -0
- package/dist/components/tree-view/types.cjs +2 -0
- package/dist/components/tree-view/types.d.ts +44 -0
- package/dist/components/tree-view/types.js +1 -0
- package/dist/components/tree-view/useTreeExpansion.cjs +47 -0
- package/dist/components/tree-view/useTreeExpansion.d.ts +17 -0
- package/dist/components/tree-view/useTreeExpansion.js +45 -0
- package/dist/components/tree-view/utils.cjs +32 -0
- package/dist/components/tree-view/utils.d.ts +16 -0
- package/dist/components/tree-view/utils.js +29 -0
- package/dist/components/xml-viewer/XmlViewer.cjs +175 -0
- package/dist/components/xml-viewer/XmlViewer.d.ts +20 -0
- package/dist/components/xml-viewer/XmlViewer.js +169 -0
- package/dist/components/xml-viewer/XmlViewer.module.css +109 -0
- package/dist/components/xml-viewer/tokenizeXml.cjs +50 -0
- package/dist/components/xml-viewer/tokenizeXml.d.ts +12 -0
- package/dist/components/xml-viewer/tokenizeXml.js +48 -0
- package/dist/index.cjs +7 -0
- package/dist/index.css +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/utils/date/formatDate.cjs +29 -17
- package/dist/utils/date/formatDate.d.ts +7 -0
- package/dist/utils/date/formatDate.js +29 -17
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { TextWrap, ListTree, Code2, Sun, Moon, ClipboardCopy, MoreVertical } from 'lucide-react';
|
|
4
|
+
import { useMemo, useRef } from 'react';
|
|
5
|
+
import { Menu } from '../../components/menu/Menu';
|
|
6
|
+
import { Popover } from '../../components/popover/Popover';
|
|
7
|
+
import { usePersistentState } from '../../hooks/usePersistentState';
|
|
8
|
+
import styles from './LineViewer.module.css';
|
|
9
|
+
import { tokenizeLineFormat } from './tokenizeLineFormat';
|
|
10
|
+
import { Button } from '../button/Button';
|
|
11
|
+
|
|
12
|
+
const TOKEN_CLASS = {
|
|
13
|
+
tag: styles.tag,
|
|
14
|
+
indicator: styles.indicator,
|
|
15
|
+
subfieldMarker: styles.subfieldMarker,
|
|
16
|
+
subfieldCode: styles.subfieldCode,
|
|
17
|
+
value: styles.value,
|
|
18
|
+
terminator: styles.terminator,
|
|
19
|
+
text: styles.text,
|
|
20
|
+
whitespace: void 0
|
|
21
|
+
};
|
|
22
|
+
function LineViewer({
|
|
23
|
+
lines,
|
|
24
|
+
size = "md",
|
|
25
|
+
appearance = "dark",
|
|
26
|
+
storageKey,
|
|
27
|
+
borderless = false
|
|
28
|
+
}) {
|
|
29
|
+
const tokens = useMemo(() => tokenizeLineFormat(lines), [lines]);
|
|
30
|
+
const codeRef = useRef(null);
|
|
31
|
+
const { value: currentAppearance, setValue: setCurrentAppearance } = usePersistentState({
|
|
32
|
+
initialValue: appearance,
|
|
33
|
+
storageKey: storageKey ? `${storageKey}:appearance` : void 0,
|
|
34
|
+
validate: (stored) => stored === "dark" || stored === "light"
|
|
35
|
+
});
|
|
36
|
+
const { value: viewMode, setValue: setViewMode } = usePersistentState({
|
|
37
|
+
initialValue: "highlighted",
|
|
38
|
+
storageKey: storageKey ? `${storageKey}:viewMode` : void 0,
|
|
39
|
+
validate: (stored) => stored === "highlighted" || stored === "raw"
|
|
40
|
+
});
|
|
41
|
+
const { value: wrap, setValue: setWrap } = usePersistentState({
|
|
42
|
+
initialValue: true,
|
|
43
|
+
storageKey: storageKey ? `${storageKey}:wrap` : void 0,
|
|
44
|
+
validate: (stored) => typeof stored === "boolean"
|
|
45
|
+
});
|
|
46
|
+
const handleCopy = () => {
|
|
47
|
+
var _a;
|
|
48
|
+
(_a = navigator.clipboard) == null ? void 0 : _a.writeText(lines).catch(() => {
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
const handleDoubleClick = () => {
|
|
52
|
+
const el = codeRef.current;
|
|
53
|
+
const selection = typeof window !== "undefined" ? window.getSelection() : null;
|
|
54
|
+
if (!el || !selection) return;
|
|
55
|
+
const range = document.createRange();
|
|
56
|
+
range.selectNodeContents(el);
|
|
57
|
+
selection.removeAllRanges();
|
|
58
|
+
selection.addRange(range);
|
|
59
|
+
};
|
|
60
|
+
return /* @__PURE__ */ jsxs(
|
|
61
|
+
"section",
|
|
62
|
+
{
|
|
63
|
+
className: [styles.viewer, borderless ? styles.borderless : ""].filter(Boolean).join(" "),
|
|
64
|
+
"data-appearance": currentAppearance,
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
"div",
|
|
68
|
+
{
|
|
69
|
+
className: styles.toolbar,
|
|
70
|
+
"data-surface": currentAppearance === "dark" ? "inverse" : void 0,
|
|
71
|
+
children: /* @__PURE__ */ jsx(
|
|
72
|
+
Popover,
|
|
73
|
+
{
|
|
74
|
+
matchTriggerWidth: false,
|
|
75
|
+
minWidth: "200px",
|
|
76
|
+
trigger: (toggle, _icon, isOpen) => /* @__PURE__ */ jsx(
|
|
77
|
+
Button,
|
|
78
|
+
{
|
|
79
|
+
variant: "inline",
|
|
80
|
+
"aria-label": "Flere handlinger",
|
|
81
|
+
"aria-haspopup": "menu",
|
|
82
|
+
"aria-expanded": isOpen ? true : false,
|
|
83
|
+
onClick: toggle,
|
|
84
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { "aria-hidden": "true" })
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
children: (close) => /* @__PURE__ */ jsxs(Menu, { children: [
|
|
88
|
+
/* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
|
|
89
|
+
"button",
|
|
90
|
+
{
|
|
91
|
+
type: "button",
|
|
92
|
+
onClick: () => {
|
|
93
|
+
setWrap((value) => !value);
|
|
94
|
+
close();
|
|
95
|
+
},
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ jsx(TextWrap, { "aria-hidden": "true" }),
|
|
98
|
+
/* @__PURE__ */ jsx("span", { children: wrap ? "Ombryd ikke tekst" : "Ombryd tekst" })
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
) }),
|
|
102
|
+
/* @__PURE__ */ jsx(Menu.Separator, {}),
|
|
103
|
+
/* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: () => {
|
|
108
|
+
setViewMode((mode) => mode === "raw" ? "highlighted" : "raw");
|
|
109
|
+
close();
|
|
110
|
+
},
|
|
111
|
+
children: [
|
|
112
|
+
viewMode === "raw" ? /* @__PURE__ */ jsx(ListTree, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Code2, { "aria-hidden": "true" }),
|
|
113
|
+
/* @__PURE__ */ jsx("span", { children: viewMode === "raw" ? "Vis med farver" : "Vis raw" })
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
) }),
|
|
117
|
+
/* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
|
|
118
|
+
"button",
|
|
119
|
+
{
|
|
120
|
+
type: "button",
|
|
121
|
+
onClick: () => {
|
|
122
|
+
setCurrentAppearance((value) => value === "dark" ? "light" : "dark");
|
|
123
|
+
close();
|
|
124
|
+
},
|
|
125
|
+
children: [
|
|
126
|
+
currentAppearance === "dark" ? /* @__PURE__ */ jsx(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(Moon, { "aria-hidden": "true" }),
|
|
127
|
+
/* @__PURE__ */ jsx("span", { children: currentAppearance === "dark" ? "Lys visning" : "M\xF8rk visning" })
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
) }),
|
|
131
|
+
/* @__PURE__ */ jsx(Menu.Separator, {}),
|
|
132
|
+
/* @__PURE__ */ jsx(Menu.Item, { children: /* @__PURE__ */ jsxs(
|
|
133
|
+
"button",
|
|
134
|
+
{
|
|
135
|
+
type: "button",
|
|
136
|
+
onClick: () => {
|
|
137
|
+
handleCopy();
|
|
138
|
+
close();
|
|
139
|
+
},
|
|
140
|
+
children: [
|
|
141
|
+
/* @__PURE__ */ jsx(ClipboardCopy, { "aria-hidden": "true" }),
|
|
142
|
+
/* @__PURE__ */ jsx("span", { children: "Kopi\xE9r" })
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
) })
|
|
146
|
+
] })
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
/* @__PURE__ */ jsx(
|
|
152
|
+
"pre",
|
|
153
|
+
{
|
|
154
|
+
className: [styles.codePane, styles[size], wrap ? styles.wrap : styles.noWrap].join(" "),
|
|
155
|
+
tabIndex: 0,
|
|
156
|
+
onDoubleClick: handleDoubleClick,
|
|
157
|
+
children: /* @__PURE__ */ jsx("code", { ref: codeRef, children: viewMode === "raw" ? lines : tokens.map((token, index) => {
|
|
158
|
+
const tokenClassName = TOKEN_CLASS[token.type];
|
|
159
|
+
return tokenClassName ? /* @__PURE__ */ jsx("span", { className: tokenClassName, children: token.value }, index) : token.value;
|
|
160
|
+
}) })
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { LineViewer };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
.viewer {
|
|
2
|
+
--line-pane-bg: var(--color-bg-inverse);
|
|
3
|
+
--line-pane-border: color-mix(in oklab, var(--color-fg-inverse) 12%, transparent);
|
|
4
|
+
--line-pane-text: var(--color-fg-inverse);
|
|
5
|
+
|
|
6
|
+
--line-tag: color-mix(in oklab, var(--color-fg-inverse) 66%, var(--color-syntax-accent) 34%);
|
|
7
|
+
--line-indicator: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
|
|
8
|
+
--line-subfield-code: color-mix(in oklab, var(--color-fg-inverse) 30%, var(--color-syntax-number) 70%);
|
|
9
|
+
--line-value: color-mix(in oklab, var(--color-fg-inverse) 20%, var(--color-syntax-string) 80%);
|
|
10
|
+
--line-terminator: color-mix(in oklab, var(--color-fg-inverse) 30%, var(--color-syntax-boolean) 70%);
|
|
11
|
+
--line-punctuation: color-mix(in oklab, var(--color-fg-inverse) 60%, transparent);
|
|
12
|
+
--line-continuation: color-mix(in oklab, var(--color-fg-inverse) 46%, transparent);
|
|
13
|
+
|
|
14
|
+
position: relative;
|
|
15
|
+
background: var(--line-pane-bg);
|
|
16
|
+
border: var(--border-width-thin) solid var(--line-pane-border);
|
|
17
|
+
border-radius: var(--border-radius-default);
|
|
18
|
+
box-shadow: var(--shadow-md);
|
|
19
|
+
font-family: var(--font-family-mono);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.viewer[data-appearance='light'] {
|
|
23
|
+
--line-pane-bg: var(--color-bg-surface);
|
|
24
|
+
--line-pane-border: var(--color-border-subtle);
|
|
25
|
+
--line-pane-text: var(--color-fg-default);
|
|
26
|
+
|
|
27
|
+
--line-tag: var(--color-link);
|
|
28
|
+
--line-indicator: var(--color-fg-subtle);
|
|
29
|
+
--line-subfield-code: var(--color-status-warning-fg);
|
|
30
|
+
--line-value: var(--color-status-success-fg);
|
|
31
|
+
--line-terminator: var(--color-accent-fg);
|
|
32
|
+
--line-punctuation: var(--color-fg-subtle);
|
|
33
|
+
--line-continuation: var(--color-fg-muted);
|
|
34
|
+
|
|
35
|
+
box-shadow: var(--shadow-xs);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.viewer.borderless {
|
|
39
|
+
border: none;
|
|
40
|
+
border-radius: 0;
|
|
41
|
+
box-shadow: none;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.toolbar {
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: var(--spacing-xs);
|
|
47
|
+
right: var(--spacing-xs);
|
|
48
|
+
z-index: 1;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.codePane {
|
|
52
|
+
margin: 0;
|
|
53
|
+
padding: var(--spacing-sm);
|
|
54
|
+
overflow: auto;
|
|
55
|
+
color: var(--line-pane-text);
|
|
56
|
+
font-family: inherit;
|
|
57
|
+
line-height: 1.35;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.wrap {
|
|
61
|
+
white-space: pre-wrap;
|
|
62
|
+
overflow-wrap: break-word;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.noWrap {
|
|
66
|
+
white-space: pre;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.xs,
|
|
70
|
+
.sm {
|
|
71
|
+
font-size: var(--font-size-xs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.md {
|
|
75
|
+
font-size: var(--font-size-sm);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.lg {
|
|
79
|
+
font-size: var(--font-size-base);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.tag {
|
|
83
|
+
color: var(--line-tag);
|
|
84
|
+
font-weight: var(--font-weight-medium);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.indicator {
|
|
88
|
+
color: var(--line-indicator);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.subfieldMarker {
|
|
92
|
+
color: var(--line-punctuation);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.subfieldCode {
|
|
96
|
+
color: var(--line-subfield-code);
|
|
97
|
+
font-weight: var(--font-weight-medium);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.value {
|
|
101
|
+
color: var(--line-value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.terminator {
|
|
105
|
+
color: var(--line-terminator);
|
|
106
|
+
font-weight: var(--font-weight-medium);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.text {
|
|
110
|
+
color: var(--line-continuation);
|
|
111
|
+
font-style: italic;
|
|
112
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const FIELD_LINE_PATTERN = /^(\S+)(\s+)(\S+)(\s+)(\*[\s\S]*)$/;
|
|
4
|
+
const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
|
|
5
|
+
function tokenizeFieldLine(line) {
|
|
6
|
+
const match = FIELD_LINE_PATTERN.exec(line);
|
|
7
|
+
if (!match) return [{ type: "text", value: line }];
|
|
8
|
+
const [, tag, gap1, indicator, gap2, rest] = match;
|
|
9
|
+
const tokens = [
|
|
10
|
+
{ type: "tag", value: tag },
|
|
11
|
+
{ type: "whitespace", value: gap1 },
|
|
12
|
+
{ type: "indicator", value: indicator },
|
|
13
|
+
{ type: "whitespace", value: gap2 }
|
|
14
|
+
];
|
|
15
|
+
SUBFIELD_PATTERN.lastIndex = 0;
|
|
16
|
+
let match2;
|
|
17
|
+
let lastIndex = 0;
|
|
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) });
|
|
26
|
+
}
|
|
27
|
+
return tokens;
|
|
28
|
+
}
|
|
29
|
+
function tokenizeLineFormat(source) {
|
|
30
|
+
const tokens = [];
|
|
31
|
+
const chunks = source.split(/(\n)/);
|
|
32
|
+
for (const chunk of chunks) {
|
|
33
|
+
if (chunk === "") continue;
|
|
34
|
+
if (chunk === "\n") {
|
|
35
|
+
tokens.push({ type: "whitespace", value: "\n" });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (chunk.trim() === "$") {
|
|
39
|
+
const dollarIndex = chunk.indexOf("$");
|
|
40
|
+
const leading = chunk.slice(0, dollarIndex);
|
|
41
|
+
const trailing = chunk.slice(dollarIndex + 1);
|
|
42
|
+
if (leading) tokens.push({ type: "text", value: leading });
|
|
43
|
+
tokens.push({ type: "terminator", value: "$" });
|
|
44
|
+
if (trailing) tokens.push({ type: "text", value: trailing });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
tokens.push(...tokenizeFieldLine(chunk));
|
|
48
|
+
}
|
|
49
|
+
return tokens;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
exports.tokenizeLineFormat = tokenizeLineFormat;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type LineTokenType = 'tag' | 'indicator' | 'subfieldMarker' | 'subfieldCode' | 'value' | 'terminator' | 'text' | 'whitespace';
|
|
2
|
+
export interface LineToken {
|
|
3
|
+
type: LineTokenType;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Tokenizes a MARC-style "line format" record (field tag, indicators,
|
|
8
|
+
* `*code value` subfields, terminated by a lone `$`) for syntax
|
|
9
|
+
* highlighting without imposing a strict grammar. Preserves the source
|
|
10
|
+
* exactly - concatenating every token's `value` reproduces the input - so a
|
|
11
|
+
* wrapped continuation line or malformed field still displays as plain
|
|
12
|
+
* text instead of breaking the tokenizer.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tokenizeLineFormat(source: string): LineToken[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const FIELD_LINE_PATTERN = /^(\S+)(\s+)(\S+)(\s+)(\*[\s\S]*)$/;
|
|
2
|
+
const SUBFIELD_PATTERN = /\*(\S)([^*]*)/g;
|
|
3
|
+
function tokenizeFieldLine(line) {
|
|
4
|
+
const match = FIELD_LINE_PATTERN.exec(line);
|
|
5
|
+
if (!match) return [{ type: "text", value: line }];
|
|
6
|
+
const [, tag, gap1, indicator, gap2, rest] = match;
|
|
7
|
+
const tokens = [
|
|
8
|
+
{ type: "tag", value: tag },
|
|
9
|
+
{ type: "whitespace", value: gap1 },
|
|
10
|
+
{ type: "indicator", value: indicator },
|
|
11
|
+
{ type: "whitespace", value: gap2 }
|
|
12
|
+
];
|
|
13
|
+
SUBFIELD_PATTERN.lastIndex = 0;
|
|
14
|
+
let match2;
|
|
15
|
+
let lastIndex = 0;
|
|
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) });
|
|
24
|
+
}
|
|
25
|
+
return tokens;
|
|
26
|
+
}
|
|
27
|
+
function tokenizeLineFormat(source) {
|
|
28
|
+
const tokens = [];
|
|
29
|
+
const chunks = source.split(/(\n)/);
|
|
30
|
+
for (const chunk of chunks) {
|
|
31
|
+
if (chunk === "") continue;
|
|
32
|
+
if (chunk === "\n") {
|
|
33
|
+
tokens.push({ type: "whitespace", value: "\n" });
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (chunk.trim() === "$") {
|
|
37
|
+
const dollarIndex = chunk.indexOf("$");
|
|
38
|
+
const leading = chunk.slice(0, dollarIndex);
|
|
39
|
+
const trailing = chunk.slice(dollarIndex + 1);
|
|
40
|
+
if (leading) tokens.push({ type: "text", value: leading });
|
|
41
|
+
tokens.push({ type: "terminator", value: "$" });
|
|
42
|
+
if (trailing) tokens.push({ type: "text", value: trailing });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
tokens.push(...tokenizeFieldLine(chunk));
|
|
46
|
+
}
|
|
47
|
+
return tokens;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { tokenizeLineFormat };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var lucideReact = require('lucide-react');
|
|
6
|
+
var react = require('react');
|
|
7
|
+
var styles = require('./TreeView.module.css');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var styles__default = /*#__PURE__*/_interopDefault(styles);
|
|
12
|
+
|
|
13
|
+
function TreeNodeRow({
|
|
14
|
+
node,
|
|
15
|
+
depth,
|
|
16
|
+
isExpanded,
|
|
17
|
+
toggle,
|
|
18
|
+
resolvedChildren,
|
|
19
|
+
loadingIds,
|
|
20
|
+
errorIds,
|
|
21
|
+
requestLoad,
|
|
22
|
+
loadChildren,
|
|
23
|
+
selectedId,
|
|
24
|
+
onSelectNode,
|
|
25
|
+
renderLabel
|
|
26
|
+
}) {
|
|
27
|
+
var _a;
|
|
28
|
+
const children = (_a = node.children) != null ? _a : resolvedChildren[node.id];
|
|
29
|
+
const isResolved = children !== void 0;
|
|
30
|
+
const expandable = isResolved ? children.length > 0 : Boolean(loadChildren) && node.hasChildren !== false;
|
|
31
|
+
const expanded = isExpanded(node.id);
|
|
32
|
+
const loading = loadingIds.has(node.id);
|
|
33
|
+
const error = errorIds[node.id];
|
|
34
|
+
const selected = selectedId !== void 0 && selectedId === node.id;
|
|
35
|
+
react.useEffect(() => {
|
|
36
|
+
if (expanded && children === void 0 && !loading && !error && loadChildren) {
|
|
37
|
+
requestLoad(node);
|
|
38
|
+
}
|
|
39
|
+
}, [expanded, children, loading, error]);
|
|
40
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
role: "treeitem",
|
|
44
|
+
"aria-expanded": expandable ? expanded : void 0,
|
|
45
|
+
"aria-selected": selected,
|
|
46
|
+
style: { ["--tree-depth"]: depth },
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.row, "data-selected": selected || void 0, children: [
|
|
49
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
50
|
+
"button",
|
|
51
|
+
{
|
|
52
|
+
type: "button",
|
|
53
|
+
className: styles__default.default.toggle,
|
|
54
|
+
onClick: () => expandable && toggle(node.id),
|
|
55
|
+
disabled: !expandable,
|
|
56
|
+
"aria-hidden": !expandable,
|
|
57
|
+
tabIndex: expandable ? 0 : -1,
|
|
58
|
+
children: loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: `spin ${styles__default.default.spinner}`, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderCircle, { "aria-hidden": "true" }) }) : expandable ? (
|
|
59
|
+
// A resolved node's chevron means "this has children"; an
|
|
60
|
+
// unresolved lazy node's dimmer chevron means "might have
|
|
61
|
+
// children, click to check" - we genuinely don't know yet,
|
|
62
|
+
// and shouldn't visually assert otherwise.
|
|
63
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: isResolved ? void 0 : styles__default.default.toggleUnresolved, children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { "aria-hidden": "true" }) })
|
|
64
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.leafSpacer, "aria-hidden": "true" })
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: styles__default.default.labelButton, onClick: () => onSelectNode == null ? void 0 : onSelectNode(node), children: [
|
|
68
|
+
node.icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.icon, "aria-hidden": "true", children: node.icon }) : null,
|
|
69
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles__default.default.label, children: renderLabel ? renderLabel(node) : node.label })
|
|
70
|
+
] })
|
|
71
|
+
] }),
|
|
72
|
+
error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles__default.default.errorRow, children: [
|
|
73
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: error }),
|
|
74
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: styles__default.default.retryButton, onClick: () => requestLoad(node), children: "Pr\xF8v igen" })
|
|
75
|
+
] }) : null,
|
|
76
|
+
expanded && children && children.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "group", className: styles__default.default.children, children: children.map((child) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
77
|
+
TreeNodeRow,
|
|
78
|
+
{
|
|
79
|
+
node: child,
|
|
80
|
+
depth: depth + 1,
|
|
81
|
+
isExpanded,
|
|
82
|
+
toggle,
|
|
83
|
+
resolvedChildren,
|
|
84
|
+
loadingIds,
|
|
85
|
+
errorIds,
|
|
86
|
+
requestLoad,
|
|
87
|
+
loadChildren,
|
|
88
|
+
selectedId,
|
|
89
|
+
onSelectNode,
|
|
90
|
+
renderLabel
|
|
91
|
+
},
|
|
92
|
+
child.id
|
|
93
|
+
)) }) : null
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
exports.TreeNodeRow = TreeNodeRow;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JSX, ReactNode } from 'react';
|
|
2
|
+
import type { LoadTreeChildren, TreeNode } from './types';
|
|
3
|
+
type TreeNodeRowProps<T> = {
|
|
4
|
+
node: TreeNode<T>;
|
|
5
|
+
depth: number;
|
|
6
|
+
isExpanded: (id: string) => boolean;
|
|
7
|
+
toggle: (id: string) => void;
|
|
8
|
+
resolvedChildren: Record<string, TreeNode<T>[]>;
|
|
9
|
+
loadingIds: Set<string>;
|
|
10
|
+
errorIds: Record<string, ReactNode>;
|
|
11
|
+
requestLoad: (node: TreeNode<T>) => void;
|
|
12
|
+
loadChildren?: LoadTreeChildren<T>;
|
|
13
|
+
selectedId?: string;
|
|
14
|
+
onSelectNode?: (node: TreeNode<T>) => void;
|
|
15
|
+
renderLabel?: (node: TreeNode<T>) => ReactNode;
|
|
16
|
+
};
|
|
17
|
+
export declare function TreeNodeRow<T>({ node, depth, isExpanded, toggle, resolvedChildren, loadingIds, errorIds, requestLoad, loadChildren, selectedId, onSelectNode, renderLabel, }: TreeNodeRowProps<T>): JSX.Element;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { LoaderCircle, ChevronDown, ChevronRight } from 'lucide-react';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
import styles from './TreeView.module.css';
|
|
6
|
+
|
|
7
|
+
function TreeNodeRow({
|
|
8
|
+
node,
|
|
9
|
+
depth,
|
|
10
|
+
isExpanded,
|
|
11
|
+
toggle,
|
|
12
|
+
resolvedChildren,
|
|
13
|
+
loadingIds,
|
|
14
|
+
errorIds,
|
|
15
|
+
requestLoad,
|
|
16
|
+
loadChildren,
|
|
17
|
+
selectedId,
|
|
18
|
+
onSelectNode,
|
|
19
|
+
renderLabel
|
|
20
|
+
}) {
|
|
21
|
+
var _a;
|
|
22
|
+
const children = (_a = node.children) != null ? _a : resolvedChildren[node.id];
|
|
23
|
+
const isResolved = children !== void 0;
|
|
24
|
+
const expandable = isResolved ? children.length > 0 : Boolean(loadChildren) && node.hasChildren !== false;
|
|
25
|
+
const expanded = isExpanded(node.id);
|
|
26
|
+
const loading = loadingIds.has(node.id);
|
|
27
|
+
const error = errorIds[node.id];
|
|
28
|
+
const selected = selectedId !== void 0 && selectedId === node.id;
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (expanded && children === void 0 && !loading && !error && loadChildren) {
|
|
31
|
+
requestLoad(node);
|
|
32
|
+
}
|
|
33
|
+
}, [expanded, children, loading, error]);
|
|
34
|
+
return /* @__PURE__ */ jsxs(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
role: "treeitem",
|
|
38
|
+
"aria-expanded": expandable ? expanded : void 0,
|
|
39
|
+
"aria-selected": selected,
|
|
40
|
+
style: { ["--tree-depth"]: depth },
|
|
41
|
+
children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: styles.row, "data-selected": selected || void 0, children: [
|
|
43
|
+
/* @__PURE__ */ jsx(
|
|
44
|
+
"button",
|
|
45
|
+
{
|
|
46
|
+
type: "button",
|
|
47
|
+
className: styles.toggle,
|
|
48
|
+
onClick: () => expandable && toggle(node.id),
|
|
49
|
+
disabled: !expandable,
|
|
50
|
+
"aria-hidden": !expandable,
|
|
51
|
+
tabIndex: expandable ? 0 : -1,
|
|
52
|
+
children: loading ? /* @__PURE__ */ jsx("span", { className: `spin ${styles.spinner}`, children: /* @__PURE__ */ jsx(LoaderCircle, { "aria-hidden": "true" }) }) : expandable ? (
|
|
53
|
+
// A resolved node's chevron means "this has children"; an
|
|
54
|
+
// unresolved lazy node's dimmer chevron means "might have
|
|
55
|
+
// children, click to check" - we genuinely don't know yet,
|
|
56
|
+
// and shouldn't visually assert otherwise.
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: isResolved ? void 0 : styles.toggleUnresolved, children: expanded ? /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" }) })
|
|
58
|
+
) : /* @__PURE__ */ jsx("span", { className: styles.leafSpacer, "aria-hidden": "true" })
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
/* @__PURE__ */ jsxs("button", { type: "button", className: styles.labelButton, onClick: () => onSelectNode == null ? void 0 : onSelectNode(node), children: [
|
|
62
|
+
node.icon ? /* @__PURE__ */ jsx("span", { className: styles.icon, "aria-hidden": "true", children: node.icon }) : null,
|
|
63
|
+
/* @__PURE__ */ jsx("span", { className: styles.label, children: renderLabel ? renderLabel(node) : node.label })
|
|
64
|
+
] })
|
|
65
|
+
] }),
|
|
66
|
+
error ? /* @__PURE__ */ jsxs("div", { className: styles.errorRow, children: [
|
|
67
|
+
/* @__PURE__ */ jsx("span", { children: error }),
|
|
68
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: styles.retryButton, onClick: () => requestLoad(node), children: "Pr\xF8v igen" })
|
|
69
|
+
] }) : null,
|
|
70
|
+
expanded && children && children.length > 0 ? /* @__PURE__ */ jsx("div", { role: "group", className: styles.children, children: children.map((child) => /* @__PURE__ */ jsx(
|
|
71
|
+
TreeNodeRow,
|
|
72
|
+
{
|
|
73
|
+
node: child,
|
|
74
|
+
depth: depth + 1,
|
|
75
|
+
isExpanded,
|
|
76
|
+
toggle,
|
|
77
|
+
resolvedChildren,
|
|
78
|
+
loadingIds,
|
|
79
|
+
errorIds,
|
|
80
|
+
requestLoad,
|
|
81
|
+
loadChildren,
|
|
82
|
+
selectedId,
|
|
83
|
+
onSelectNode,
|
|
84
|
+
renderLabel
|
|
85
|
+
},
|
|
86
|
+
child.id
|
|
87
|
+
)) }) : null
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export { TreeNodeRow };
|