@dbcdk/react-components 0.0.182 → 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.
Files changed (52) hide show
  1. package/dist/client.cjs +21 -0
  2. package/dist/client.d.ts +3 -0
  3. package/dist/client.js +3 -0
  4. package/dist/components/clear-button/ClearButton.cjs +7 -2
  5. package/dist/components/clear-button/ClearButton.d.ts +8 -1
  6. package/dist/components/clear-button/ClearButton.js +7 -2
  7. package/dist/components/clear-button/ClearButton.module.css +12 -0
  8. package/dist/components/forms/select/Select.cjs +2 -1
  9. package/dist/components/forms/select/Select.js +2 -1
  10. package/dist/components/json-viewer/JsonViewer.cjs +3 -2
  11. package/dist/components/json-viewer/JsonViewer.d.ts +1 -1
  12. package/dist/components/json-viewer/JsonViewer.js +3 -2
  13. package/dist/components/json-viewer/JsonViewer.module.css +7 -0
  14. package/dist/components/json-viewer/types.d.ts +6 -0
  15. package/dist/components/line-viewer/LineViewer.cjs +174 -0
  16. package/dist/components/line-viewer/LineViewer.d.ts +20 -0
  17. package/dist/components/line-viewer/LineViewer.js +168 -0
  18. package/dist/components/line-viewer/LineViewer.module.css +112 -0
  19. package/dist/components/line-viewer/tokenizeLineFormat.cjs +52 -0
  20. package/dist/components/line-viewer/tokenizeLineFormat.d.ts +14 -0
  21. package/dist/components/line-viewer/tokenizeLineFormat.js +50 -0
  22. package/dist/components/tree-view/TreeNodeRow.cjs +99 -0
  23. package/dist/components/tree-view/TreeNodeRow.d.ts +18 -0
  24. package/dist/components/tree-view/TreeNodeRow.js +93 -0
  25. package/dist/components/tree-view/TreeView.cjs +93 -0
  26. package/dist/components/tree-view/TreeView.d.ts +20 -0
  27. package/dist/components/tree-view/TreeView.js +79 -0
  28. package/dist/components/tree-view/TreeView.module.css +141 -0
  29. package/dist/components/tree-view/types.cjs +2 -0
  30. package/dist/components/tree-view/types.d.ts +44 -0
  31. package/dist/components/tree-view/types.js +1 -0
  32. package/dist/components/tree-view/useTreeExpansion.cjs +47 -0
  33. package/dist/components/tree-view/useTreeExpansion.d.ts +17 -0
  34. package/dist/components/tree-view/useTreeExpansion.js +45 -0
  35. package/dist/components/tree-view/utils.cjs +32 -0
  36. package/dist/components/tree-view/utils.d.ts +16 -0
  37. package/dist/components/tree-view/utils.js +29 -0
  38. package/dist/components/xml-viewer/XmlViewer.cjs +175 -0
  39. package/dist/components/xml-viewer/XmlViewer.d.ts +20 -0
  40. package/dist/components/xml-viewer/XmlViewer.js +169 -0
  41. package/dist/components/xml-viewer/XmlViewer.module.css +109 -0
  42. package/dist/components/xml-viewer/tokenizeXml.cjs +50 -0
  43. package/dist/components/xml-viewer/tokenizeXml.d.ts +12 -0
  44. package/dist/components/xml-viewer/tokenizeXml.js +48 -0
  45. package/dist/index.cjs +7 -0
  46. package/dist/index.css +3 -0
  47. package/dist/index.d.ts +1 -0
  48. package/dist/index.js +1 -0
  49. package/dist/utils/date/formatDate.cjs +29 -17
  50. package/dist/utils/date/formatDate.d.ts +7 -0
  51. package/dist/utils/date/formatDate.js +29 -17
  52. package/package.json +1 -1
@@ -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 };
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+ 'use strict';
3
+
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+ var react = require('react');
6
+ var TreeNodeRow = require('./TreeNodeRow');
7
+ var styles = require('./TreeView.module.css');
8
+ var useTreeExpansion = require('./useTreeExpansion');
9
+ var utils = require('./utils');
10
+
11
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+
13
+ var styles__default = /*#__PURE__*/_interopDefault(styles);
14
+
15
+ function TreeView({
16
+ data,
17
+ loadChildren,
18
+ defaultExpandedIds,
19
+ expandedIds,
20
+ onExpandedIdsChange,
21
+ selectedId,
22
+ onSelectNode,
23
+ renderLabel,
24
+ emptyMessage = "Intet at vise.",
25
+ loadErrorMessage = () => "Der skete en fejl under indl\xE6sning.",
26
+ className,
27
+ ...rest
28
+ }) {
29
+ const { isExpanded, toggle, expand } = useTreeExpansion.useTreeExpansion(
30
+ defaultExpandedIds,
31
+ expandedIds,
32
+ onExpandedIdsChange
33
+ );
34
+ const [resolvedChildren, setResolvedChildren] = react.useState({});
35
+ const [loadingIds, setLoadingIds] = react.useState(/* @__PURE__ */ new Set());
36
+ const [errorIds, setErrorIds] = react.useState({});
37
+ const requestLoad = react.useCallback(
38
+ (node) => {
39
+ if (!loadChildren || loadingIds.has(node.id)) return;
40
+ setLoadingIds((prev) => new Set(prev).add(node.id));
41
+ setErrorIds((prev) => {
42
+ if (!(node.id in prev)) return prev;
43
+ const next = { ...prev };
44
+ delete next[node.id];
45
+ return next;
46
+ });
47
+ loadChildren(node).then((children) => {
48
+ setResolvedChildren((prev) => ({ ...prev, [node.id]: children }));
49
+ expand(node.id);
50
+ }).catch((err) => {
51
+ setErrorIds((prev) => ({ ...prev, [node.id]: loadErrorMessage(err, node) }));
52
+ }).finally(() => {
53
+ setLoadingIds((prev) => {
54
+ const next = new Set(prev);
55
+ next.delete(node.id);
56
+ return next;
57
+ });
58
+ });
59
+ },
60
+ [loadChildren, loadingIds, expand, loadErrorMessage]
61
+ );
62
+ if (data.length === 0) {
63
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: [styles__default.default.emptyState, className].filter(Boolean).join(" "), ...rest, children: emptyMessage });
64
+ }
65
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tree", className: [styles__default.default.tree, className].filter(Boolean).join(" "), ...rest, children: data.map((node) => /* @__PURE__ */ jsxRuntime.jsx(
66
+ TreeNodeRow.TreeNodeRow,
67
+ {
68
+ node,
69
+ depth: 0,
70
+ isExpanded,
71
+ toggle,
72
+ resolvedChildren,
73
+ loadingIds,
74
+ errorIds,
75
+ requestLoad,
76
+ loadChildren,
77
+ selectedId,
78
+ onSelectNode,
79
+ renderLabel
80
+ },
81
+ node.id
82
+ )) });
83
+ }
84
+
85
+ Object.defineProperty(exports, "collectExpandableIds", {
86
+ enumerable: true,
87
+ get: function () { return utils.collectExpandableIds; }
88
+ });
89
+ Object.defineProperty(exports, "findAncestorIds", {
90
+ enumerable: true,
91
+ get: function () { return utils.findAncestorIds; }
92
+ });
93
+ exports.TreeView = TreeView;
@@ -0,0 +1,20 @@
1
+ import type { JSX } from 'react';
2
+ import type { TreeViewProps } from './types';
3
+ export type { LoadTreeChildren, TreeNode, TreeViewProps } from './types';
4
+ export { collectExpandableIds, findAncestorIds } from './utils';
5
+ /**
6
+ * A single component for both shapes of tree data:
7
+ * - Eager: every node's full subtree is already in `data` via `children`.
8
+ * No `loadChildren` needed - toggling a node only ever flips its
9
+ * expanded/collapsed state.
10
+ * - Lazy: `data` only has to contain what you already know; pass
11
+ * `loadChildren` and a node whose `children` is undefined gets fetched
12
+ * the first time it's expanded (see TreeNode's docs on `hasChildren` for
13
+ * how a not-yet-fetched node still shows a toggle).
14
+ *
15
+ * Expand/collapse state follows the same model as the Sidebar: a single
16
+ * `Set<string>` of expanded ids is the source of truth (see
17
+ * useTreeExpansion), and it can be left uncontrolled or owned by the
18
+ * caller via `expandedIds`/`onExpandedIdsChange`.
19
+ */
20
+ export declare function TreeView<T = unknown>({ data, loadChildren, defaultExpandedIds, expandedIds, onExpandedIdsChange, selectedId, onSelectNode, renderLabel, emptyMessage, loadErrorMessage, className, ...rest }: TreeViewProps<T>): JSX.Element;
@@ -0,0 +1,79 @@
1
+ 'use client';
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import { useState, useCallback } from 'react';
4
+ import { TreeNodeRow } from './TreeNodeRow';
5
+ import styles from './TreeView.module.css';
6
+ import { useTreeExpansion } from './useTreeExpansion';
7
+ export { collectExpandableIds, findAncestorIds } from './utils';
8
+
9
+ function TreeView({
10
+ data,
11
+ loadChildren,
12
+ defaultExpandedIds,
13
+ expandedIds,
14
+ onExpandedIdsChange,
15
+ selectedId,
16
+ onSelectNode,
17
+ renderLabel,
18
+ emptyMessage = "Intet at vise.",
19
+ loadErrorMessage = () => "Der skete en fejl under indl\xE6sning.",
20
+ className,
21
+ ...rest
22
+ }) {
23
+ const { isExpanded, toggle, expand } = useTreeExpansion(
24
+ defaultExpandedIds,
25
+ expandedIds,
26
+ onExpandedIdsChange
27
+ );
28
+ const [resolvedChildren, setResolvedChildren] = useState({});
29
+ const [loadingIds, setLoadingIds] = useState(/* @__PURE__ */ new Set());
30
+ const [errorIds, setErrorIds] = useState({});
31
+ const requestLoad = useCallback(
32
+ (node) => {
33
+ if (!loadChildren || loadingIds.has(node.id)) return;
34
+ setLoadingIds((prev) => new Set(prev).add(node.id));
35
+ setErrorIds((prev) => {
36
+ if (!(node.id in prev)) return prev;
37
+ const next = { ...prev };
38
+ delete next[node.id];
39
+ return next;
40
+ });
41
+ loadChildren(node).then((children) => {
42
+ setResolvedChildren((prev) => ({ ...prev, [node.id]: children }));
43
+ expand(node.id);
44
+ }).catch((err) => {
45
+ setErrorIds((prev) => ({ ...prev, [node.id]: loadErrorMessage(err, node) }));
46
+ }).finally(() => {
47
+ setLoadingIds((prev) => {
48
+ const next = new Set(prev);
49
+ next.delete(node.id);
50
+ return next;
51
+ });
52
+ });
53
+ },
54
+ [loadChildren, loadingIds, expand, loadErrorMessage]
55
+ );
56
+ if (data.length === 0) {
57
+ return /* @__PURE__ */ jsx("div", { className: [styles.emptyState, className].filter(Boolean).join(" "), ...rest, children: emptyMessage });
58
+ }
59
+ return /* @__PURE__ */ jsx("div", { role: "tree", className: [styles.tree, className].filter(Boolean).join(" "), ...rest, children: data.map((node) => /* @__PURE__ */ jsx(
60
+ TreeNodeRow,
61
+ {
62
+ node,
63
+ depth: 0,
64
+ isExpanded,
65
+ toggle,
66
+ resolvedChildren,
67
+ loadingIds,
68
+ errorIds,
69
+ requestLoad,
70
+ loadChildren,
71
+ selectedId,
72
+ onSelectNode,
73
+ renderLabel
74
+ },
75
+ node.id
76
+ )) });
77
+ }
78
+
79
+ export { TreeView };